From b695b5852b49dae619626b4ec657f6bbfe62ada2 Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Thu, 15 Jun 2023 11:18:34 -0700 Subject: [PATCH] sprot: Plumbing to get the cert chain out of the attest task. This enables humility to get the cert chain from the RoT attest task by way of the SP hiffy interface. This allows us to get the cert chain from the RoT on production systems where the RoT is configured to disable the debug port. --- Cargo.lock | 3 + app/lpc55xpresso/app-sprot.toml | 6 +- app/oxide-rot-1/app-dev.toml | 6 +- app/oxide-rot-1/app.toml | 4 +- app/rot-carrier/app.toml | 4 +- drv/lpc55-sprot-server/Cargo.toml | 1 + drv/lpc55-sprot-server/src/handler.rs | 77 ++++++++++++++++++++--- drv/sprot-api/Cargo.toml | 1 + drv/sprot-api/src/error.rs | 21 +++++++ drv/sprot-api/src/lib.rs | 22 ++++++- drv/stm32h7-sprot-server/src/main.rs | 91 ++++++++++++++++++++++++++- idl/sprot.idol | 39 +++++++++++- task/attest-api/Cargo.toml | 1 + 13 files changed, 252 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c408e0a93..40fe16942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,7 @@ name = "attest-api" version = "0.1.0" dependencies = [ "derive-idol-err", + "gateway-messages", "hubpack", "idol", "idol-runtime", @@ -1105,6 +1106,7 @@ dependencies = [ name = "drv-lpc55-sprot-server" version = "0.1.0" dependencies = [ + "attest-api", "build-lpc55pins", "build-util", "crc", @@ -1519,6 +1521,7 @@ dependencies = [ name = "drv-sprot-api" version = "0.1.0" dependencies = [ + "attest-api", "crc", "derive-idol-err", "derive_more", diff --git a/app/lpc55xpresso/app-sprot.toml b/app/lpc55xpresso/app-sprot.toml index 2bacbbfae..9171588d2 100644 --- a/app/lpc55xpresso/app-sprot.toml +++ b/app/lpc55xpresso/app-sprot.toml @@ -183,14 +183,14 @@ task-slots = ["swd"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 33056, ram = 32768} +max-sizes = {flash = 44000, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true notifications = ["spi-irq"] interrupts = {"flexcomm8.hs_spi" = "spi-irq"} stacksize = 16384 -task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper"] +task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper", "attest"] [tasks.sprot.config] pins = [ @@ -211,7 +211,7 @@ pins = [ [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 12256, ram = 16384} +max-sizes = {flash = 12512, ram = 16384} stacksize = 9000 start = true extern-regions = ["dice_alias", "dice_certs"] diff --git a/app/oxide-rot-1/app-dev.toml b/app/oxide-rot-1/app-dev.toml index adc936672..3110552d6 100644 --- a/app/oxide-rot-1/app-dev.toml +++ b/app/oxide-rot-1/app-dev.toml @@ -77,14 +77,14 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 34000, ram = 32768} +max-sizes = {flash = 44000, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true notifications = ["spi-irq"] interrupts = {"flexcomm8.hs_spi" = "spi-irq"} stacksize = 16384 -task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper"] +task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper", "attest"] [tasks.sprot.config] pins = [ @@ -158,7 +158,7 @@ binary_path = "../../target/gimlet-c/dist/default/final.bin" [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 12256, ram = 16384} +max-sizes = {flash = 12512, ram = 16384} stacksize = 9000 start = true extern-regions = ["dice_alias", "dice_certs"] diff --git a/app/oxide-rot-1/app.toml b/app/oxide-rot-1/app.toml index 9850eb6e3..74101edaf 100644 --- a/app/oxide-rot-1/app.toml +++ b/app/oxide-rot-1/app.toml @@ -68,14 +68,14 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 34000, ram = 32768} +max-sizes = {flash = 44000, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true notifications = ["spi-irq"] interrupts = {"flexcomm8.hs_spi" = "spi-irq"} stacksize = 16384 -task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper"] +task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper", "attest"] [tasks.sprot.config] pins = [ diff --git a/app/rot-carrier/app.toml b/app/rot-carrier/app.toml index f553b34ee..bfda9eb08 100644 --- a/app/rot-carrier/app.toml +++ b/app/rot-carrier/app.toml @@ -108,14 +108,14 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 34000, ram = 32768} +max-sizes = {flash = 44000, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true notifications = ["spi-irq"] interrupts = {"flexcomm8.hs_spi" = "spi-irq"} stacksize = 16384 -task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper"] +task-slots = ["gpio_driver", "syscon_driver", "update_server", "dumper", "attest"] [tasks.sprot.config] pins = [ diff --git a/drv/lpc55-sprot-server/Cargo.toml b/drv/lpc55-sprot-server/Cargo.toml index dbd69d5d3..4799334f6 100644 --- a/drv/lpc55-sprot-server/Cargo.toml +++ b/drv/lpc55-sprot-server/Cargo.toml @@ -15,6 +15,7 @@ sprockets-rot = { workspace = true } static_assertions = { workspace = true } zerocopy = { workspace = true } +attest-api = { path = "../../task/attest-api" } drv-lpc55-gpio-api = { path = "../lpc55-gpio-api" } drv-lpc55-spi = { path = "../lpc55-spi" } drv-lpc55-syscon-api = { path = "../lpc55-syscon-api" } diff --git a/drv/lpc55-sprot-server/src/handler.rs b/drv/lpc55-sprot-server/src/handler.rs index a9bba46d2..32450bdb9 100644 --- a/drv/lpc55-sprot-server/src/handler.rs +++ b/drv/lpc55-sprot-server/src/handler.rs @@ -3,13 +3,14 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::Trace; +use attest_api::Attest; use crc::{Crc, CRC_32_CKSUM}; use drv_lpc55_update_api::{SlotId, Update}; use drv_sprot_api::{ - CabooseReq, CabooseRsp, DumpReq, DumpRsp, ReqBody, Request, Response, - RotIoStats, RotState, RotStatus, RspBody, SprocketsError, SprotError, - SprotProtocolError, UpdateReq, UpdateRsp, CURRENT_VERSION, MIN_VERSION, - REQUEST_BUF_SIZE, RESPONSE_BUF_SIZE, + AttestReq, AttestRsp, CabooseReq, CabooseRsp, DumpReq, DumpRsp, ReqBody, + Request, Response, RotIoStats, RotState, RotStatus, RspBody, + SprocketsError, SprotError, SprotProtocolError, UpdateReq, UpdateRsp, + CURRENT_VERSION, MIN_VERSION, REQUEST_BUF_SIZE, RESPONSE_BUF_SIZE, }; use dumper_api::Dumper; use lpc55_romapi::bootrom; @@ -23,6 +24,8 @@ task_slot!(UPDATE_SERVER, update_server); task_slot!(DUMPER, dumper); +task_slot!(ATTEST, attest); + pub const CRC32: Crc = Crc::::new(&CRC_32_CKSUM); /// State that is set once at the start of the driver @@ -42,12 +45,14 @@ pub struct StartupState { /// Marker for data which should be copied after the packet is encoded pub enum TrailingData { Caboose { slot: SlotId, start: u32, size: u32 }, + Attest { index: u32, offset: u32, size: u32 }, } pub struct Handler { sprocket: RotSprocket, update: Update, startup_state: StartupState, + attest: Attest, } impl Handler { @@ -60,6 +65,7 @@ impl Handler { max_request_size: REQUEST_BUF_SIZE.try_into().unwrap_lite(), max_response_size: RESPONSE_BUF_SIZE.try_into().unwrap_lite(), }, + attest: Attest::from(ATTEST.get_task_id()), } } @@ -90,7 +96,7 @@ impl Handler { // In certain cases, handling the request has left us with trailing data // that needs to be packed into the remaining packet space. - let size = match trailer { + match trailer { Some(TrailingData::Caboose { slot, start, @@ -121,10 +127,33 @@ impl Handler { } } } + Some(TrailingData::Attest { + index, + offset, + size, + }) => { + let size: usize = usize::try_from(size).unwrap_lite(); + if size > drv_sprot_api::MAX_BLOB_SIZE { + Response::pack( + &Err(SprotError::Protocol( + SprotProtocolError::BadMessageLength, + )), + tx_buf, + ) + } else { + match Response::pack_with_cb(&rsp_body, tx_buf, |buf| { + self.attest + .cert(index, offset, &mut buf[..size]) + .map_err(|e| RspBody::Attest(Err(e.into())))?; + Ok(size) + }) { + Ok(size) => size, + Err(e) => Response::pack(&Ok(e), tx_buf), + } + } + } _ => Response::pack(&rsp_body, tx_buf), - }; - - size + } } pub fn handle_request( @@ -232,6 +261,38 @@ impl Handler { let boot_info = self.update.rot_boot_info()?; Ok((RspBody::Update(boot_info.into()), None)) } + ReqBody::Attest(AttestReq::Cert { + index, + offset, + size, + }) => { + // This command returns a variable amount of data that belongs + // in the trailing data region of the response. We return a + // marker struct with the data necessary retrieve this data so + // the work can be done elsewhere. + Ok(( + RspBody::Attest(Ok(AttestRsp::Cert)), + Some(TrailingData::Attest { + index, + offset, + size, + }), + )) + } + ReqBody::Attest(AttestReq::CertChainLen) => { + let rsp = match self.attest.cert_chain_len() { + Ok(v) => Ok(AttestRsp::CertChainLen(v)), + Err(e) => Err(e), + }; + Ok((RspBody::Attest(rsp), None)) + } + ReqBody::Attest(AttestReq::CertLen(i)) => { + let rsp = match self.attest.cert_len(i) { + Ok(v) => Ok(AttestRsp::CertLen(v)), + Err(e) => Err(e), + }; + Ok((RspBody::Attest(rsp), None)) + } } } } diff --git a/drv/sprot-api/Cargo.toml b/drv/sprot-api/Cargo.toml index 6cfd99f31..d224b2c29 100644 --- a/drv/sprot-api/Cargo.toml +++ b/drv/sprot-api/Cargo.toml @@ -17,6 +17,7 @@ static_assertions.workspace = true tlvc.workspace = true zerocopy.workspace = true +attest-api = { path = "../../task/attest-api" } derive-idol-err = { path = "../../lib/derive-idol-err" } drv-caboose = { path = "../../drv/caboose" } drv-lpc55-update-api = { path = "../../drv/lpc55-update-api" } diff --git a/drv/sprot-api/src/error.rs b/drv/sprot-api/src/error.rs index 2c6ddc17e..579dcbf72 100644 --- a/drv/sprot-api/src/error.rs +++ b/drv/sprot-api/src/error.rs @@ -4,6 +4,7 @@ //! Errors for the sprot API +use attest_api::AttestError; use derive_more::From; use drv_caboose::CabooseError; use drv_lpc55_update_api::RawCabooseError; @@ -209,3 +210,23 @@ impl From for CabooseOrSprotError { } } } + +#[derive(Copy, Clone, Debug, From, Deserialize, Serialize, SerializedSize)] +pub enum AttestOrSprotError { + Sprot(SprotError), + Attest(AttestError), +} + +impl From for RequestError { + fn from(err: SprotError) -> Self { + AttestOrSprotError::from(err).into() + } +} + +impl From + for Result> +{ + fn from(err: AttestOrSprotError) -> Self { + Err(RequestError::Runtime(err)) + } +} diff --git a/drv/sprot-api/src/lib.rs b/drv/sprot-api/src/lib.rs index b283f36e7..385f09c8d 100644 --- a/drv/sprot-api/src/lib.rs +++ b/drv/sprot-api/src/lib.rs @@ -9,11 +9,12 @@ extern crate memoffset; mod error; +use attest_api::AttestError; use drv_caboose::CabooseError; use dumper_api::DumperError; pub use error::{ - CabooseOrSprotError, DumpOrSprotError, RawCabooseOrSprotError, - SprocketsError, SprotError, SprotProtocolError, + AttestOrSprotError, CabooseOrSprotError, DumpOrSprotError, + RawCabooseOrSprotError, SprocketsError, SprotError, SprotProtocolError, }; use crc::{Crc, CRC_16_XMODEM}; @@ -328,6 +329,7 @@ pub enum ReqBody { Dump(DumpReq), // Added in sprot protocol version 3 Caboose(CabooseReq), + Attest(AttestReq), } /// Instruct the RoT to take a dump of the SP via SWD @@ -363,6 +365,13 @@ pub enum CabooseReq { Read { slot: SlotId, start: u32, size: u32 }, } +#[derive(Clone, Serialize, Deserialize, SerializedSize)] +pub enum AttestReq { + CertChainLen, + CertLen(u32), + Cert { index: u32, offset: u32, size: u32 }, +} + /// A response used for RoT updates #[derive(Clone, Serialize, Deserialize, SerializedSize, From)] pub enum UpdateRsp { @@ -380,6 +389,13 @@ pub enum CabooseRsp { Read, } +#[derive(Clone, Serialize, Deserialize, SerializedSize)] +pub enum AttestRsp { + CertChainLen(u32), + CertLen(u32), + Cert, +} + /// The body of a sprot response. /// /// See [`Msg`] for details about versioning and message evolution. @@ -404,6 +420,8 @@ pub enum RspBody { // Added in sprot protocol version 3 Caboose(Result), + + Attest(Result), } /// A response from the Dumper diff --git a/drv/stm32h7-sprot-server/src/main.rs b/drv/stm32h7-sprot-server/src/main.rs index 9ecdda974..0c90cd6a0 100644 --- a/drv/stm32h7-sprot-server/src/main.rs +++ b/drv/stm32h7-sprot-server/src/main.rs @@ -796,13 +796,98 @@ impl idl::InOrderSpRotImpl for ServerImpl { Err(e) => Err(RawCabooseOrSprotError::Sprot(e).into()), } } + + fn cert( + &mut self, + _: &userlib::RecvMessage, + index: u32, + offset: u32, + data: idol_runtime::Leased, + ) -> Result<(), idol_runtime::RequestError> { + let body = ReqBody::Attest(AttestReq::Cert { + index, + offset, + size: data.len() as u32, + }); + let tx_size = Request::pack(&body, &mut self.tx_buf); + let rsp = self + .do_send_recv_retries(tx_size, DUMP_TIMEOUT, DEFAULT_ATTEMPTS) + .map_err(AttestOrSprotError::Sprot)?; + + match rsp.body { + Ok(RspBody::Attest(Ok(AttestRsp::Cert))) => { + // Copy from the trailing data into the lease + if rsp.blob.len() < data.len() { + return Err(idol_runtime::RequestError::Fail( + idol_runtime::ClientError::BadLease, + )); + } + data.write_range(0..data.len(), &rsp.blob[..data.len()]) + .map_err(|()| { + idol_runtime::RequestError::Fail( + idol_runtime::ClientError::WentAway, + ) + })?; + Ok(()) + } + Ok(RspBody::Attest(Err(e))) => { + Err(AttestOrSprotError::Attest(e).into()) + } + Ok(RspBody::Attest(_)) | Ok(_) => Err(AttestOrSprotError::Sprot( + SprotError::Protocol(SprotProtocolError::UnexpectedResponse), + ) + .into()), + Err(e) => Err(AttestOrSprotError::Sprot(e).into()), + } + } + + fn cert_chain_len( + &mut self, + _: &userlib::RecvMessage, + ) -> Result> { + let body = ReqBody::Attest(AttestReq::CertChainLen); + let tx_size = Request::pack(&body, &mut self.tx_buf); + let rsp = self.do_send_recv_retries(tx_size, TIMEOUT_QUICK, 1)?; + match rsp.body { + Ok(RspBody::Attest(Ok(AttestRsp::CertChainLen(s)))) => Ok(s), + Ok(RspBody::Attest(Err(e))) => { + Err(AttestOrSprotError::Attest(e).into()) + } + Ok(RspBody::Attest(_)) | Ok(_) => Err(AttestOrSprotError::Sprot( + SprotError::Protocol(SprotProtocolError::UnexpectedResponse), + ) + .into()), + Err(e) => Err(AttestOrSprotError::Sprot(e).into()), + } + } + + fn cert_len( + &mut self, + _: &userlib::RecvMessage, + index: u32, + ) -> Result> { + let body = ReqBody::Attest(AttestReq::CertLen(index)); + let tx_size = Request::pack(&body, &mut self.tx_buf); + let rsp = self.do_send_recv_retries(tx_size, TIMEOUT_QUICK, 1)?; + match rsp.body { + Ok(RspBody::Attest(Ok(AttestRsp::CertLen(s)))) => Ok(s), + Ok(RspBody::Attest(Err(e))) => { + Err(AttestOrSprotError::Attest(e).into()) + } + Ok(RspBody::Attest(_)) | Ok(_) => Err(AttestOrSprotError::Sprot( + SprotError::Protocol(SprotProtocolError::UnexpectedResponse), + ) + .into()), + Err(e) => Err(AttestOrSprotError::Sprot(e).into()), + } + } } mod idl { use super::{ - DumpOrSprotError, PulseStatus, RawCabooseOrSprotError, RotBootInfo, - RotState, SlotId, SprotError, SprotIoStats, SprotStatus, - SwitchDuration, UpdateTarget, + AttestOrSprotError, DumpOrSprotError, PulseStatus, + RawCabooseOrSprotError, RotBootInfo, RotState, SlotId, SprotError, + SprotIoStats, SprotStatus, SwitchDuration, UpdateTarget, }; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); diff --git a/idl/sprot.idol b/idl/sprot.idol index b0a7a7545..b09d2755f 100644 --- a/idl/sprot.idol +++ b/idl/sprot.idol @@ -168,5 +168,42 @@ Interface( encoding: Hubpack, idempotent: true, ), - } + "cert_chain_len": ( + doc: "Return the length of the alias cert chain", + reply: Result( + ok: "u32", + err: Complex("AttestOrSprotError"), + ), + encoding: Hubpack, + idempotent: true, + ), + "cert_len": ( + doc: "Get length of a cert in the cert chain", + args: { + "index" : "u32", + }, + reply: Result( + ok: "u32", + err: Complex("AttestOrSprotError"), + ), + encoding: Hubpack, + idempotent: true, + ), + "cert": ( + doc: "Get a cert from the alias cert chaing", + args: { + "index" : "u32", + "offset" : "u32", + }, + leases: { + "dest": (type: "[u8]", write: true), + }, + reply: Result( + ok: "()", + err: Complex("AttestOrSprotError"), + ), + encoding: Hubpack, + idempotent: true, + ), + } ) diff --git a/task/attest-api/Cargo.toml b/task/attest-api/Cargo.toml index acc895b70..d38c64195 100644 --- a/task/attest-api/Cargo.toml +++ b/task/attest-api/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] derive-idol-err = { path = "../../lib/derive-idol-err" } +gateway-messages = { workspace = true } hubpack = { workspace = true } idol-runtime = { workspace = true } num-traits = { workspace = true }