diff --git a/Cargo.lock b/Cargo.lock index 96dc2d12c..f841cbf1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4001,8 +4001,10 @@ dependencies = [ "mutable-statics", "num-traits", "ringbuf", + "salty", "serde", "serde_with 3.3.0", + "sha3", "stage0-handoff", "unwrap-lite", "userlib", diff --git a/app/lpc55xpresso/app-sprot.toml b/app/lpc55xpresso/app-sprot.toml index 4d9943df0..98ce9ce2c 100644 --- a/app/lpc55xpresso/app-sprot.toml +++ b/app/lpc55xpresso/app-sprot.toml @@ -183,7 +183,7 @@ task-slots = ["swd"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 46592, ram = 32768} +max-sizes = {flash = 47328, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true @@ -211,8 +211,8 @@ pins = [ [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 14528, ram = 16384} -stacksize = 11264 +max-sizes = {flash = 33904, ram = 16384} +stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] diff --git a/app/lpc55xpresso/app.toml b/app/lpc55xpresso/app.toml index 7fe7977c1..e0b499e7b 100644 --- a/app/lpc55xpresso/app.toml +++ b/app/lpc55xpresso/app.toml @@ -142,8 +142,8 @@ extern-regions = ["sram2"] [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 14800, ram = 16384} -stacksize = 11264 +max-sizes = {flash = 33904, ram = 16384} +stacksize = 12304 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 747b9518a..285eee892 100644 --- a/app/oxide-rot-1/app-dev.toml +++ b/app/oxide-rot-1/app-dev.toml @@ -77,7 +77,7 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 46592, ram = 32768} +max-sizes = {flash = 47328, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true @@ -158,8 +158,8 @@ binary_path = "../../target/gimlet-c/dist/default/final.bin" [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 14800, ram = 16384} -stacksize = 11264 +max-sizes = {flash = 33904, ram = 16384} +stacksize = 12304 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 67b607114..878c1f9a9 100644 --- a/app/oxide-rot-1/app.toml +++ b/app/oxide-rot-1/app.toml @@ -68,7 +68,7 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 46592, ram = 32768} +max-sizes = {flash = 47328, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true @@ -137,8 +137,8 @@ task-slots = ["swd"] [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 14800, ram = 16384} -stacksize = 11264 +max-sizes = {flash = 33904, ram = 16384} +stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] diff --git a/app/rot-carrier/app.toml b/app/rot-carrier/app.toml index 47b17599c..56666b518 100644 --- a/app/rot-carrier/app.toml +++ b/app/rot-carrier/app.toml @@ -108,7 +108,7 @@ task-slots = ["syscon_driver"] [tasks.sprot] name = "drv-lpc55-sprot-server" priority = 6 -max-sizes = {flash = 46592, ram = 32768} +max-sizes = {flash = 47328, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true @@ -204,8 +204,8 @@ binary_path = "../../target/gemini-bu/dist/final.bin" [tasks.attest] name = "task-attest" priority = 5 -max-sizes = {flash = 14800, ram = 16384} -stacksize = 11264 +max-sizes = {flash = 33904, ram = 16384} +stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] diff --git a/drv/lpc55-sprot-server/src/handler.rs b/drv/lpc55-sprot-server/src/handler.rs index 4526ab112..8921054fc 100644 --- a/drv/lpc55-sprot-server/src/handler.rs +++ b/drv/lpc55-sprot-server/src/handler.rs @@ -43,10 +43,11 @@ pub struct StartupState { } /// Marker for data which should be copied after the packet is encoded -pub enum TrailingData { +pub enum TrailingData<'a> { Caboose { slot: SlotId, start: u32, size: u32 }, AttestCert { index: u32, offset: u32, size: u32 }, AttestLog { offset: u32, size: u32 }, + Attest { nonce: &'a [u8], write_size: u32 }, RotPage { page: RotPage }, } @@ -57,7 +58,7 @@ pub struct Handler { attest: Attest, } -impl Handler { +impl<'a> Handler { pub fn new() -> Handler { Handler { sprocket: crate::handler::sprockets::init(), @@ -198,15 +199,35 @@ impl Handler { } } } + Some(TrailingData::Attest { nonce, write_size }) => { + if write_size as usize > 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 + .attest(nonce, &mut buf[..write_size as usize]) + .map_err(|e| RspBody::Attest(Err(e)))?; + Ok(write_size as usize) + }) { + Ok(size) => size, + Err(e) => Response::pack(&Ok(e), tx_buf), + } + } + } _ => Response::pack(&rsp_body, tx_buf), } } pub fn handle_request( &mut self, - req: Request<'_>, + req: Request<'a>, stats: &mut RotIoStats, - ) -> Result<(RspBody, Option), SprotError> { + ) -> Result<(RspBody, Option>), SprotError> { match req.body { ReqBody::Status => { let status = RotStatus { @@ -367,6 +388,23 @@ impl Handler { }; Ok((RspBody::Attest(rsp), None)) } + ReqBody::Attest(AttestReq::Attest { + nonce_size, + write_size, + }) => Ok(( + RspBody::Attest(Ok(AttestRsp::Attest)), + Some(TrailingData::Attest { + nonce: &req.blob[..nonce_size as usize], + write_size, + }), + )), + ReqBody::Attest(AttestReq::AttestLen) => { + let rsp = match self.attest.attest_len() { + Ok(l) => Ok(AttestRsp::AttestLen(l)), + Err(e) => Err(e), + }; + Ok((RspBody::Attest(rsp), None)) + } } } } diff --git a/drv/sprot-api/src/lib.rs b/drv/sprot-api/src/lib.rs index 70ea696b0..f9726eaed 100644 --- a/drv/sprot-api/src/lib.rs +++ b/drv/sprot-api/src/lib.rs @@ -387,6 +387,8 @@ pub enum AttestReq { Record { algorithm: HashAlgorithm }, Log { offset: u32, size: u32 }, LogLen, + Attest { nonce_size: u32, write_size: u32 }, + AttestLen, } /// A response used for RoT updates @@ -414,6 +416,8 @@ pub enum AttestRsp { Record, Log, LogLen(u32), + Attest, + AttestLen(u32), } /// The body of a sprot response. diff --git a/drv/stm32h7-sprot-server/src/main.rs b/drv/stm32h7-sprot-server/src/main.rs index b32452391..f7633fc8a 100644 --- a/drv/stm32h7-sprot-server/src/main.rs +++ b/drv/stm32h7-sprot-server/src/main.rs @@ -6,7 +6,7 @@ #![no_main] #![deny(elided_lifetimes_in_paths)] -use attest_api::HashAlgorithm; +use attest_api::{AttestError, HashAlgorithm, NONCE_MAX_SIZE, NONCE_MIN_SIZE}; use core::convert::Into; use drv_lpc55_update_api::{ RotBootInfo, RotPage, SlotId, SwitchDuration, UpdateTarget, @@ -1010,6 +1010,89 @@ impl idl::InOrderSpRotImpl for ServerImpl { Err(e) => Err(AttestOrSprotError::Sprot(e).into()), } } + + fn attest( + &mut self, + _msg: &userlib::RecvMessage, + nonce: idol_runtime::LenLimit< + idol_runtime::Leased, + NONCE_MAX_SIZE, + >, + dest: idol_runtime::Leased, + ) -> Result<(), idol_runtime::RequestError> + where + AttestOrSprotError: From, + { + if nonce.len() < NONCE_MIN_SIZE { + return Err( + AttestOrSprotError::Attest(AttestError::BadLease).into() + ); + } + + let nonce_size = u32::try_from(nonce.len()).unwrap_lite(); + let write_size = u32::try_from(dest.len()).unwrap_lite(); + + let body = ReqBody::Attest(AttestReq::Attest { + nonce_size, + write_size, + }); + let tx_size = Request::pack_with_cb(&body, self.tx_buf, |buf| { + nonce + .read_range(0..nonce.len(), buf) + .map_err(|_| SprotProtocolError::TaskRestarted)?; + Ok::>( + nonce.len(), + ) + })?; + + let rsp = self.do_send_recv_retries(tx_size, TIMEOUT_MEDIUM, 1)?; + + match rsp.body { + Ok(RspBody::Attest(Ok(AttestRsp::Attest))) => { + // Copy response data into the lease + if rsp.blob.len() < dest.len() { + return Err(idol_runtime::RequestError::Fail( + idol_runtime::ClientError::BadLease, + )); + } + dest.write_range(0..dest.len(), &rsp.blob[..dest.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 attest_len( + &mut self, + _msg: &userlib::RecvMessage, + ) -> Result> { + let body = ReqBody::Attest(AttestReq::AttestLen); + let tx_size = Request::pack(&body, self.tx_buf); + let rsp = self.do_send_recv_retries(tx_size, TIMEOUT_QUICK, 1)?; + match rsp.body { + Ok(RspBody::Attest(Ok(AttestRsp::AttestLen(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 { diff --git a/idl/attest.idol b/idl/attest.idol index 1baf4c0d4..bbfff102c 100644 --- a/idl/attest.idol +++ b/idl/attest.idol @@ -78,5 +78,26 @@ Interface( encoding: Hubpack, idempotent: true, ), + "attest": ( + doc: "Get an attestation", + leases: { + "nonce": (type: "[u8]", read: true, max_len: Some(128)), + "dest": (type: "[u8]", write: true), + }, + reply: Result( + ok: "()", + err: Complex("AttestError"), + ), + encoding: Hubpack, + ), + "attest_len": ( + doc: "Get the length of an attestation", + reply: Result( + ok: "u32", + err: Complex("AttestError"), + ), + encoding: Hubpack, + idempotent: true, + ), } ) diff --git a/idl/sprot.idol b/idl/sprot.idol index 2e2ae8f8d..5315b786b 100644 --- a/idl/sprot.idol +++ b/idl/sprot.idol @@ -257,5 +257,27 @@ Interface( encoding: Hubpack, idempotent: true, ), + "attest": ( + doc: "Get an attestation", + args: {}, + leases: { + "nonce": (type: "[u8]", read: true, max_len: Some(128)), + "dest": (type: "[u8]", write: true), + }, + reply: Result( + ok: "()", + err: Complex("AttestOrSprotError"), + ), + encoding: Hubpack, + ), + "attest_len": ( + doc: "Get length of a serialized attestation", + reply: Result( + ok: "u32", + err: Complex("AttestOrSprotError"), + ), + encoding: Hubpack, + idempotent: true, + ), } ) diff --git a/task/attest-api/src/lib.rs b/task/attest-api/src/lib.rs index 9b6935063..c23cedc8e 100644 --- a/task/attest-api/src/lib.rs +++ b/task/attest-api/src/lib.rs @@ -24,6 +24,8 @@ pub enum AttestError { BadLease, UnsupportedAlgorithm, SerializeLog, + SerializeSignature, + SignatureTooBig, } impl From for AttestError { @@ -39,4 +41,7 @@ pub enum HashAlgorithm { Sha3_256, } +pub const NONCE_MIN_SIZE: usize = 32; +pub const NONCE_MAX_SIZE: usize = 128; + include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/task/attest/Cargo.toml b/task/attest/Cargo.toml index dfe4c3a74..e9c9e5496 100644 --- a/task/attest/Cargo.toml +++ b/task/attest/Cargo.toml @@ -11,11 +11,13 @@ idol-runtime = { workspace = true } mutable-statics = { path = "../../lib/mutable-statics" } num-traits = { workspace = true } ringbuf = { path = "../../lib/ringbuf" } +salty.workspace = true serde = { workspace = true } serde_with = { version = "3.3.0", default-features = false, features = ["macros"] } stage0-handoff = { path = "../../lib/stage0-handoff" } attest-api = { path = "../attest-api" } attest-data.workspace = true +sha3.workspace = true unwrap-lite = { path = "../../lib/unwrap-lite" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } zerocopy = { workspace = true } diff --git a/task/attest/src/main.rs b/task/attest/src/main.rs index 5497fa7d7..dfb1f56f4 100644 --- a/task/attest/src/main.rs +++ b/task/attest/src/main.rs @@ -11,16 +11,20 @@ mod config; -use attest_api::{AttestError, HashAlgorithm}; -use attest_data::{Log, Measurement, Sha3_256Digest}; +use attest_api::{AttestError, HashAlgorithm, NONCE_MAX_SIZE, NONCE_MIN_SIZE}; +use attest_data::{ + Attestation, Ed25519Signature, Log, Measurement, Sha3_256Digest, +}; use config::DataRegion; use core::slice; use hubpack::SerializedSize; -use idol_runtime::{ClientError, Leased, RequestError, W}; -use lib_dice::{AliasData, CertData}; +use idol_runtime::{ClientError, Leased, LenLimit, RequestError, R, W}; +use lib_dice::{AliasData, CertData, SeedBuf}; use mutable_statics::mutable_statics; use ringbuf::{ringbuf, ringbuf_entry}; +use salty::signature::Keypair; use serde::Deserialize; +use sha3::{Digest as CryptDigest, Sha3_256}; use stage0_handoff::{HandoffData, HandoffDataLoadError}; use zerocopy::AsBytes; @@ -48,6 +52,7 @@ enum Trace { BadLease(usize), LogLen(u32), Log, + ClientError(ClientError), None, } @@ -78,6 +83,7 @@ fn load_data_from_region< struct AttestServer { alias_data: Option, + alias_keypair: Option, buf: &'static mut [u8; Log::MAX_SIZE], cert_data: Option, measurements: Log, @@ -88,8 +94,15 @@ impl Default for AttestServer { let buf = mutable_statics! { static mut LOG_BUF: [u8; Log::MAX_SIZE] = [|| 0; _]; }; + + let alias_data: Option = load_data_from_region(&ALIAS_DATA); + let alias_keypair = alias_data + .as_ref() + .map(|d| Keypair::from(d.alias_seed.as_bytes())); + Self { - alias_data: load_data_from_region(&ALIAS_DATA), + alias_data, + alias_keypair, buf, cert_data: load_data_from_region(&CERT_DATA), measurements: Log::default(), @@ -268,6 +281,103 @@ impl idl::InOrderAttestImpl for AttestServer { Ok(len) } + + fn attest( + &mut self, + _: &userlib::RecvMessage, + nonce: LenLimit, { NONCE_MAX_SIZE }>, + dest: Leased, + ) -> Result<(), RequestError> { + if nonce.len() < NONCE_MIN_SIZE { + let err = AttestError::BadLease; + ringbuf_entry!(Trace::AttestError(err)); + return Err(err.into()); + } + + // serialize measurement log + let len = + hubpack::serialize(self.buf, &self.measurements).map_err(|_| { + let e = AttestError::SerializeLog; + ringbuf_entry!(Trace::AttestError(e)); + e + })?; + let _ = u32::try_from(len).map_err(|_| { + let e = AttestError::LogTooBig; + ringbuf_entry!(Trace::AttestError(e)); + e + })?; + + // sha3_256(hubpack(measurement_log) | nonce) + let mut digest = Sha3_256::new(); + digest.update(&self.buf[..len]); + + let len = nonce.len(); + let mut nonce_bytes = [0u8; NONCE_MAX_SIZE]; + nonce + .read_range(0..len, &mut nonce_bytes[..len]) + .map_err(|_| { + let e = ClientError::WentAway; + ringbuf_entry!(Trace::ClientError(e)); + RequestError::Fail(e) + })?; + + let nonce = nonce_bytes; + digest.update(&nonce[..len]); + + // get key pair used to generate signatures / attestations + // NOTE: replace `map_err` w/ `inspect_err` when it's stable + let alias_keypair = self + .alias_keypair + .as_ref() + .ok_or(AttestError::NoCerts) + .map_err(|e| { + ringbuf_entry!(Trace::AttestError(e)); + e + })?; + + // generate attestation: + // sign(alias_priv, sha3_256(hubpack(measurement_log) | nonce) + let digest = digest.finalize(); + let signature = alias_keypair.sign(&digest); + let signature = + Attestation::Ed25519(Ed25519Signature::from(signature.to_bytes())); + + // serialize / hubpack attestation into temp buffer + let len = hubpack::serialize(self.buf, &signature).map_err(|_| { + let e = AttestError::SerializeSignature; + ringbuf_entry!(Trace::AttestError(e)); + e + })?; + + if dest.len() != len { + let err = AttestError::BadLease; + ringbuf_entry!(Trace::AttestError(err)); + return Err(err.into()); + } + + // copy attestation from temp buffer to output lease + dest.write_range(0..dest.len(), &self.buf[0..len]) + .map_err(|_| { + let e = ClientError::WentAway; + ringbuf_entry!(Trace::ClientError(e)); + RequestError::Fail(e) + }) + } + + fn attest_len( + &mut self, + _: &userlib::RecvMessage, + ) -> Result> { + // this may become inaccurate when additional variants are added to + // `enum Attestation` + let len = u32::try_from(Attestation::MAX_SIZE).map_err(|_| { + let e = AttestError::SignatureTooBig; + ringbuf_entry!(Trace::AttestError(e)); + e + })?; + + Ok(len) + } } #[export_name = "main"]