diff --git a/Cargo.lock b/Cargo.lock index ef8cc4fecd..e2c7d39a71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,20 @@ dependencies = [ "critical-section", ] +[[package]] +name = "attest-api" +version = "0.1.0" +dependencies = [ + "derive-idol-err", + "hubpack", + "idol", + "idol-runtime", + "num-traits", + "serde", + "userlib", + "zerocopy", +] + [[package]] name = "atty" version = "0.2.14" @@ -3836,6 +3850,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "task-attest" +version = "0.1.0" +dependencies = [ + "anyhow", + "attest-api", + "build-util", + "hubpack", + "idol", + "idol-runtime", + "lib-dice", + "num-traits", + "ringbuf", + "serde", + "stage0-handoff", + "unwrap-lite", + "userlib", + "zerocopy", +] + [[package]] name = "task-caboose-reader" version = "0.1.0" diff --git a/app/lpc55xpresso/app-sprot.toml b/app/lpc55xpresso/app-sprot.toml index a90ff2e3f4..2bacbbfaec 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 = 32768, ram = 32768} +max-sizes = {flash = 33056, ram = 32768} uses = ["flexcomm8", "bootrom"] features = ["spi0"] start = true @@ -208,6 +208,14 @@ pins = [ { name = "SP_RESET", pin = { port = 1, pin = 5}, alt = 0, direction = "input"}, ] +[tasks.attest] +name = "task-attest" +priority = 5 +max-sizes = {flash = 12256, ram = 16384} +stacksize = 9000 +start = true +extern-regions = ["dice_alias", "dice_certs"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/app/lpc55xpresso/app.toml b/app/lpc55xpresso/app.toml index f2049f71cb..6df4de7da1 100644 --- a/app/lpc55xpresso/app.toml +++ b/app/lpc55xpresso/app.toml @@ -133,6 +133,14 @@ task-slots = ["jefe"] stacksize = 1200 extern-regions = ["sram2"] +[tasks.attest] +name = "task-attest" +priority = 5 +max-sizes = {flash = 12256, ram = 16384} +stacksize = 9000 +start = true +extern-regions = ["dice_alias", "dice_certs"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/app/oxide-rot-1/app-dev.toml b/app/oxide-rot-1/app-dev.toml index 63b48d3654..270da11a3d 100644 --- a/app/oxide-rot-1/app-dev.toml +++ b/app/oxide-rot-1/app-dev.toml @@ -155,6 +155,14 @@ stacksize = 2048 [tasks.sp_measure.config] binary_path = "../../target/gimlet-c/dist/default/final.bin" +[tasks.attest] +name = "task-attest" +priority = 5 +max-sizes = {flash = 12256, ram = 16384} +stacksize = 9000 +start = true +extern-regions = ["dice_alias", "dice_certs"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/app/oxide-rot-1/app.toml b/app/oxide-rot-1/app.toml index d1a437634e..9850eb6e37 100644 --- a/app/oxide-rot-1/app.toml +++ b/app/oxide-rot-1/app.toml @@ -9,7 +9,7 @@ version = 0 [kernel] name = "oxide-rot-1" -requires = {flash = 59840, ram = 2528} +requires = {flash = 59840, ram = 2696} features = ["dice-mfg"] [caboose] @@ -134,6 +134,14 @@ start = true stacksize = 2600 task-slots = ["swd"] +[tasks.attest] +name = "task-attest" +priority = 5 +max-sizes = {flash = 12256, ram = 16384} +stacksize = 9000 +start = true +extern-regions = ["dice_alias", "dice_certs"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/app/rot-carrier/app.toml b/app/rot-carrier/app.toml index e3153c78b1..b5d2259a6b 100644 --- a/app/rot-carrier/app.toml +++ b/app/rot-carrier/app.toml @@ -201,6 +201,14 @@ stacksize = 2048 [tasks.sp_measure.config] binary_path = "../../target/gemini-bu/dist/final.bin" +[tasks.attest] +name = "task-attest" +priority = 5 +max-sizes = {flash = 12256, ram = 16384} +stacksize = 9000 +start = true +extern-regions = ["dice_alias", "dice_certs"] + [signing.certs] signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] diff --git a/chips/lpc55/chip.toml b/chips/lpc55/chip.toml index 3c68c03181..f3508c2361 100644 --- a/chips/lpc55/chip.toml +++ b/chips/lpc55/chip.toml @@ -53,14 +53,6 @@ size = 4096 # this is the start of the USB SRAM AHB peripheral (0x4000 bytes total) # we appropriate this SRAM for passing DICE artifacts -[dice_certs] -address = 0x40100000 -size = 0xa00 - -[dice_alias] -address = 0x40100a00 -size = 0x800 - [dice_spmeasure] address = 0x40101200 size = 0x800 diff --git a/chips/lpc55/memory.toml b/chips/lpc55/memory.toml index 4445350cfa..ee95ebee9d 100644 --- a/chips/lpc55/memory.toml +++ b/chips/lpc55/memory.toml @@ -107,3 +107,37 @@ write = false execute = false dma = true +# RAM region used to hand common part of DICE certificate chain forward to +# Hubris tasks +[[dice_certs]] +name = "a" +address = 0x40100000 +size = 0xa00 +read = true +write = false +execute = false + +[[dice_certs]] +name = "b" +address = 0x40100000 +size = 0xa00 +read = true +write = false +execute = false + +# RAM region used to hand DICE artifacts forward to the attestation responder +[[dice_alias]] +name = "a" +address = 0x40100a00 +size = 0x800 +read = true +write = true +execute = false + +[[dice_alias]] +name = "b" +address = 0x40100a00 +size = 0x800 +read = true +write = true +execute = false diff --git a/idl/attest.idol b/idl/attest.idol new file mode 100644 index 0000000000..c56d9ef4a5 --- /dev/null +++ b/idl/attest.idol @@ -0,0 +1,45 @@ +// Interface to 'attest' task. + +Interface( + name: "Attest", + ops: { + "cert_chain_len": ( + doc: "Get the number of certs in the attestation cert chain", + args: {}, + reply: Result( + ok: "u32", + err: Complex("AttestError"), + ), + encoding: Hubpack, + idempotent: true, + ), + "cert": ( + doc: "Get a cert from the RoT-R", + args: { + "index" : "u32", + "offset" : "u32", + }, + leases: { + "dest": (type: "[u8]", write: true), + }, + reply: Result( + ok: "()", + err: Complex("AttestError"), + ), + 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("AttestError"), + ), + encoding: Hubpack, + idempotent: true, + ) + } +) diff --git a/lib/dice/src/lib.rs b/lib/dice/src/lib.rs index 0cda81a597..f0d556296d 100644 --- a/lib/dice/src/lib.rs +++ b/lib/dice/src/lib.rs @@ -295,7 +295,7 @@ impl RngSeed { } #[derive(Deserialize, Serialize, SerializedSize)] -pub struct PersistIdCert(SizedBlob); +pub struct PersistIdCert(pub SizedBlob); #[derive(Deserialize, Serialize, SerializedSize)] -pub struct IntermediateCert(SizedBlob); +pub struct IntermediateCert(pub SizedBlob); diff --git a/lib/stage0-handoff/src/lib.rs b/lib/stage0-handoff/src/lib.rs index 53a251acb4..e466ebde8a 100644 --- a/lib/stage0-handoff/src/lib.rs +++ b/lib/stage0-handoff/src/lib.rs @@ -30,7 +30,7 @@ const_assert!(DICE_RANGE.end <= UPDATE_RANGE.start); const_assert!(UPDATE_RANGE.end <= MEM_RANGE.end); /// The error returned when `HandoffData::load` fails. #[derive( - Debug, Clone, PartialEq, Eq, Deserialize, Serialize, SerializedSize, + Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, SerializedSize, )] pub enum HandoffDataLoadError { Deserialize, diff --git a/task/attest-api/Cargo.toml b/task/attest-api/Cargo.toml new file mode 100644 index 0000000000..acc895b704 --- /dev/null +++ b/task/attest-api/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "attest-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +derive-idol-err = { path = "../../lib/derive-idol-err" } +hubpack = { workspace = true } +idol-runtime = { workspace = true } +num-traits = { workspace = true } +serde = { workspace = true } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +zerocopy = { workspace = true } + +[build-dependencies] +idol = { workspace = true } +serde = { workspace = true } + +[lib] +test = false +bench = false diff --git a/task/attest-api/build.rs b/task/attest-api/build.rs new file mode 100644 index 0000000000..ceed19d624 --- /dev/null +++ b/task/attest-api/build.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use idol::client; +use std::error::Error; + +fn main() -> Result<(), Box> { + client::build_client_stub("../../idl/attest.idol", "client_stub.rs")?; + Ok(()) +} diff --git a/task/attest-api/src/lib.rs b/task/attest-api/src/lib.rs new file mode 100644 index 0000000000..75beb2ad09 --- /dev/null +++ b/task/attest-api/src/lib.rs @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! API crate for the 'attest' task. + +#![no_std] + +use hubpack::SerializedSize; +use serde::{Deserialize, Serialize}; +use userlib::sys_send; + +#[derive( + Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, SerializedSize, +)] +pub enum AttestError { + CertTooBig, + InvalidCertIndex, + NoCerts, + OutOfRange, +} + +include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/task/attest/Cargo.toml b/task/attest/Cargo.toml new file mode 100644 index 0000000000..b8152536bc --- /dev/null +++ b/task/attest/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "task-attest" +version = "0.1.0" +edition = "2021" + +[dependencies] +lib-dice = { path = "../../lib/dice" } +hubpack = { workspace = true } +idol-runtime = { workspace = true } +num-traits = { workspace = true } +ringbuf = { path = "../../lib/ringbuf" } +serde = { workspace = true } +stage0-handoff = { path = "../../lib/stage0-handoff" } +attest-api = { path = "../attest-api" } +unwrap-lite = { path = "../../lib/unwrap-lite" } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +zerocopy = { workspace = true } + +[build-dependencies] +anyhow.workspace = true +idol.workspace = true +serde.workspace = true + +build-util = { path = "../../build/util" } + +[[bin]] +name = "task-attest" +test = false +bench = false diff --git a/task/attest/build.rs b/task/attest/build.rs new file mode 100644 index 0000000000..b1994f045d --- /dev/null +++ b/task/attest/build.rs @@ -0,0 +1,62 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::{Context, Result}; +use idol::server::{self, ServerStyle}; +use std::{fs::File, io::Write}; + +mod config { + include!("src/config.rs"); +} + +use config::DataRegion; + +const CFG_SRC: &str = "attest-config.rs"; + +fn main() -> Result<()> { + server::build_server_support( + "../../idl/attest.idol", + "server_stub.rs", + ServerStyle::InOrder, + ) + .unwrap(); + + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join(CFG_SRC); + let mut out = + File::create(dest_path).context(format!("creating {}", CFG_SRC))?; + + let data_regions = build_util::task_extern_regions::()?; + if data_regions.is_empty() { + return Err(anyhow::anyhow!("no data regions found")); + } + + let region = data_regions + .get("dice_certs") + .ok_or_else(|| anyhow::anyhow!("dice_certs data region not found"))?; + writeln!( + out, + r##"use crate::config::DataRegion; +pub const CERT_DATA: DataRegion = DataRegion {{ + address: {:#x}, + size: {:#x}, +}};"##, + region.address, region.size + )?; + + let region = data_regions + .get("dice_alias") + .ok_or_else(|| anyhow::anyhow!("dice_alias data region not found"))?; + writeln!( + out, + r##" +pub const ALIAS_DATA: DataRegion = DataRegion {{ + address: {:#x}, + size: {:#x}, +}};"##, + region.address, region.size + )?; + + Ok(()) +} diff --git a/task/attest/src/config.rs b/task/attest/src/config.rs new file mode 100644 index 0000000000..2b647d4f98 --- /dev/null +++ b/task/attest/src/config.rs @@ -0,0 +1,11 @@ +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use serde::Deserialize; + +#[derive(Deserialize, Default, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct DataRegion { + pub address: u32, + pub size: u32, +} diff --git a/task/attest/src/main.rs b/task/attest/src/main.rs new file mode 100644 index 0000000000..406bbc5e6d --- /dev/null +++ b/task/attest/src/main.rs @@ -0,0 +1,209 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Root of trust for reporting (RoT-R) task. +//! +//! Use the attest-api crate to interact with this task. + +#![no_std] +#![no_main] + +mod config; + +use attest_api::AttestError; +use config::DataRegion; +use core::slice; +use lib_dice::{AliasData, CertData}; +use hubpack::SerializedSize; +use idol_runtime::{ClientError, Leased, RequestError, W}; +use ringbuf::{ringbuf, ringbuf_entry}; +use serde::Deserialize; +use stage0_handoff::{HandoffData, HandoffDataLoadError}; +use zerocopy::AsBytes; + +// This file is generated by the crate build.rs. It contains instances of +// config::DataRegion structs describing regions of memory configured & +// exposed to this task by the hubris build. +mod build { + include!(concat!(env!("OUT_DIR"), "/attest-config.rs")); +} + +use build::{ALIAS_DATA, CERT_DATA}; + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + Cert, + CertChainLen(u32), + CertLen(usize), + AttestError(AttestError), + HandoffError(HandoffDataLoadError), + BufSize(usize), + Index(u32), + Offset(u32), + Startup, + None, +} + +ringbuf!(Trace, 16, Trace::None); + +/// Load a type implementing HandoffData (and others) from a config::DataRegion. +/// Errors will be reported in the ringbuf and will return in None. +fn load_data_from_region< + T: for<'a> Deserialize<'a> + HandoffData + SerializedSize, +>( + region: &DataRegion, +) -> Option { + // Safety: This memory is setup by code executed before hubris and + // exposed using the kernel `extern-regions` mechanism. The safety of + // this code is an extension of our trust in the hubris kernel / build. + let data = unsafe { + slice::from_raw_parts(region.address as *mut u8, region.size as usize) + }; + + match T::load_from_addr(data) { + Ok(d) => Some(d), + Err(e) => { + ringbuf_entry!(Trace::HandoffError(e)); + None + } + } +} + +struct AttestServer { + alias_data: Option, + cert_data: Option, +} + +impl Default for AttestServer { + fn default() -> Self { + Self { + alias_data: load_data_from_region(&ALIAS_DATA), + cert_data: load_data_from_region(&CERT_DATA), + } + } +} + +impl AttestServer { + fn get_cert_bytes_from_index( + &self, + index: u32, + ) -> Result<&[u8], RequestError> { + let alias_data = self.alias_data.as_ref().ok_or(AttestError::NoCerts)?; + let cert_data = self.cert_data.as_ref().ok_or(AttestError::NoCerts)?; + + match index { + // Cert chains start with the leaf and stop at the last + // intermediate before the root. We mimic an array with + // the leaf cert at index 0, and the last intermediate as + // the chain length - 1. + 0 => Ok(alias_data.alias_cert.as_bytes()), + 1 => Ok(cert_data.deviceid_cert.as_bytes()), + 2 => Ok(&cert_data.persistid_cert.0.as_bytes() + [0..cert_data.persistid_cert.0.size as usize]), + 3 => { + if let Some(cert) = cert_data.intermediate_cert.as_ref() { + Ok(&cert.0.as_bytes()[0..cert.0.size as usize]) + } else { + Err(AttestError::InvalidCertIndex.into()) + } + } + _ => Err(AttestError::InvalidCertIndex.into()), + } + } +} + +impl idl::InOrderAttestImpl for AttestServer { + /// Get length of cert chain from Alias to mfg intermediate + fn cert_chain_len( + &mut self, + _: &userlib::RecvMessage, + ) -> Result> { + let cert_data = self.cert_data.as_ref().ok_or(AttestError::NoCerts)?; + // The cert chain will vary in length: + // - kernel w/ feature 'dice-self' will have 3 certs in the chain w/ + // the final cert being a self signed, puf derived identity key + // - kernel /w feature 'dice-mfg' will have 4 certs in the chain w/ + // the final cert being the intermediate that signs the identity + // cert + let chain_len = if cert_data.intermediate_cert.is_none() { + 3 + } else { + 4 + }; + + ringbuf_entry!(Trace::CertChainLen(chain_len)); + Ok(chain_len) + } + + /// Get length of cert at provided index in cert chain + fn cert_len( + &mut self, + _: &userlib::RecvMessage, + index: u32, + ) -> Result> { + let len = self.get_cert_bytes_from_index(index)?.len(); + ringbuf_entry!(Trace::CertLen(len)); + + let len = u32::try_from(len).map_err(|_| { + >>::into( + AttestError::CertTooBig, + ) + })?; + + Ok(len) + } + + /// Get a cert from the AliasCert chain + fn cert( + &mut self, + _: &userlib::RecvMessage, + index: u32, + offset: u32, + dest: Leased, + ) -> Result<(), RequestError> { + ringbuf_entry!(Trace::Cert); + ringbuf_entry!(Trace::Index(index)); + ringbuf_entry!(Trace::Offset(offset)); + ringbuf_entry!(Trace::BufSize(dest.len())); + + let cert = self.get_cert_bytes_from_index(index)?; + if cert.is_empty() { + let err = AttestError::InvalidCertIndex; + ringbuf_entry!(Trace::AttestError(err)); + return Err(err.into()); + } + + let offset = offset as usize; + // the offset provided must not exceed the length of the cert & there + // must be sufficient data from the offset to the end of the cert to + // fill the lease + if cert.len() < offset || dest.len() > cert.len() - offset { + let err = AttestError::OutOfRange; + ringbuf_entry!(Trace::AttestError(err)); + return Err(err.into()); + } + + dest.write_range(0..dest.len(), &cert[offset..offset + dest.len()]) + .map_err(|_| RequestError::Fail(ClientError::WentAway))?; + + Ok(()) + } +} + +#[export_name = "main"] +fn main() -> ! { + ringbuf_entry!(Trace::Startup); + + let mut buffer = [0; idl::INCOMING_SIZE]; + let mut attest = AttestServer::default(); + loop { + idol_runtime::dispatch(&mut buffer, &mut attest); + } +} + +mod idl { + use super::AttestError; + + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +}