diff --git a/Cargo.lock b/Cargo.lock index 5016191ac..51b372a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1214,16 +1214,23 @@ dependencies = [ name = "drv-lpc55-rng" version = "0.1.0" dependencies = [ + "anyhow", + "build-util", "cfg-if", "drv-lpc55-syscon-api", "drv-rng-api", + "hubpack", "idol", "idol-runtime", + "lib-dice", "lib-lpc55-rng", "num-traits", "rand_chacha", "rand_core", + "ringbuf", + "serde", "sha3", + "stage0-handoff", "userlib", "zerocopy 0.6.6", "zeroize", diff --git a/app/lpc55xpresso/app.toml b/app/lpc55xpresso/app.toml index 633df6198..9100f1529 100644 --- a/app/lpc55xpresso/app.toml +++ b/app/lpc55xpresso/app.toml @@ -112,11 +112,13 @@ task-slots = ["gpio_driver", "syscon_driver"] [tasks.rng_driver] name = "drv-lpc55-rng" +features = ["dice-seed"] priority = 3 uses = ["rng", "pmc"] start = true -stacksize = 2600 +stacksize = 2704 task-slots = ["syscon_driver"] +extern-regions = ["dice_rng"] [tasks.pong] name = "task-pong" diff --git a/app/rot-carrier/app.toml b/app/rot-carrier/app.toml index 54ff7aa7f..016f4cfe8 100644 --- a/app/rot-carrier/app.toml +++ b/app/rot-carrier/app.toml @@ -101,8 +101,9 @@ name = "drv-lpc55-rng" priority = 5 uses = ["rng", "pmc"] start = true -stacksize = 2600 +stacksize = 2704 task-slots = ["syscon_driver"] +extern-regions = ["dice_rng"] [tasks.sprot] name = "drv-lpc55-sprot-server" diff --git a/chips/lpc55/memory.toml b/chips/lpc55/memory.toml index 47be0adc4..1d8573a24 100644 --- a/chips/lpc55/memory.toml +++ b/chips/lpc55/memory.toml @@ -156,3 +156,19 @@ size = 0x800 read = true write = true execute = false + +[[dice_rng]] +name = "a" +address =0x40101a00 +size = 0x100 +read = true +write = true +execute = false + +[[dice_rng]] +name = "b" +address =0x40101a00 +size = 0x100 +read = true +write = true +execute = false diff --git a/drv/lpc55-rng/Cargo.toml b/drv/lpc55-rng/Cargo.toml index f8eb2858b..512e3c3ed 100644 --- a/drv/lpc55-rng/Cargo.toml +++ b/drv/lpc55-rng/Cargo.toml @@ -5,23 +5,33 @@ edition = "2021" [dependencies] cfg-if = { workspace = true } +hubpack.workspace = true idol-runtime = { workspace = true } num-traits = { workspace = true } rand_chacha = { workspace = true } rand_core = { workspace = true } +serde.workspace = true sha3.workspace = true zerocopy = { workspace = true } zeroize.workspace = true drv-lpc55-syscon-api = { path = "../lpc55-syscon-api" } drv-rng-api = { path = "../rng-api" } +lib-dice.path = "../../lib/dice" lib-lpc55-rng.path = "../../lib/lpc55-rng" +ringbuf.path = "../../lib/ringbuf" +stage0-handoff = { path = "../../lib/stage0-handoff", optional = true } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } [build-dependencies] -idol = { workspace = true } +anyhow.workspace = true +build-util.path = "../../build/util" +cfg-if.workspace = true +idol.workspace = true +serde.workspace = true [features] +dice-seed = ["stage0-handoff"] no-ipc-counters = ["idol/no-counters"] # This section is here to discourage RLS/rust-analyzer from doing test builds, diff --git a/drv/lpc55-rng/build.rs b/drv/lpc55-rng/build.rs index 4f53fdc48..8fb4688da 100644 --- a/drv/lpc55-rng/build.rs +++ b/drv/lpc55-rng/build.rs @@ -2,15 +2,62 @@ // 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/. -fn main() -> Result<(), Box> { +use anyhow::{anyhow, Result}; +use idol::{server::ServerStyle, CounterSettings}; + +cfg_if::cfg_if! { + if #[cfg(feature = "dice-seed")] { + mod config { + include!("src/config.rs"); + } + use anyhow::Context; + use config::DataRegion; + use std::{fs::File, io::Write}; + + const CFG_SRC: &str = "rng-config.rs"; + } +} + +#[cfg(feature = "dice-seed")] +fn extern_regions_to_cfg(path: &str) -> Result<()> { + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join(path); + let mut out = + File::create(dest_path).context(format!("creating {}", path))?; + + let data_regions = build_util::task_extern_regions::()?; + if data_regions.is_empty() { + return Err(anyhow!("no data regions found")); + } + + writeln!(out, "use crate::config::DataRegion;\n\n")?; + + let region = data_regions + .get("dice_rng") + .ok_or_else(|| anyhow::anyhow!("dice_certs data region not found"))?; + + Ok(writeln!( + out, + r##"pub const DICE_RNG: DataRegion = DataRegion {{ + address: {:#x}, + size: {:#x}, +}};"##, + region.address, region.size + )?) +} + +fn main() -> Result<()> { idol::Generator::new() - .with_counters( - idol::CounterSettings::default().with_server_counters(false), - ) + .with_counters(CounterSettings::default().with_server_counters(false)) .build_server_support( "../../idl/rng.idol", "server_stub.rs", - idol::server::ServerStyle::InOrder, - )?; + ServerStyle::InOrder, + ) + .map_err(|e| anyhow!(e))?; + + #[cfg(feature = "dice-seed")] + extern_regions_to_cfg(CFG_SRC)?; + Ok(()) } diff --git a/drv/lpc55-rng/src/config.rs b/drv/lpc55-rng/src/config.rs new file mode 100644 index 000000000..1e90b6d8a --- /dev/null +++ b/drv/lpc55-rng/src/config.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/. + +#[derive(serde::Deserialize, Default, Debug)] +#[serde(rename_all = "kebab-case")] +#[cfg(feature = "dice-seed")] +pub struct DataRegion { + pub address: u32, + pub size: u32, +} diff --git a/drv/lpc55-rng/src/main.rs b/drv/lpc55-rng/src/main.rs index 828082e87..371deeb25 100644 --- a/drv/lpc55-rng/src/main.rs +++ b/drv/lpc55-rng/src/main.rs @@ -9,13 +9,17 @@ #![no_std] #![no_main] +mod config; + use core::{cmp, usize}; use drv_lpc55_syscon_api::Syscon; use drv_rng_api::RngError; use idol_runtime::{ClientError, NotificationHandler, RequestError}; +use lib_dice::{RngSeed, SeedBuf}; use lib_lpc55_rng::Lpc55Rng; use rand_chacha::ChaCha20Rng; use rand_core::{impls, Error, RngCore, SeedableRng}; +use ringbuf::ringbuf; use sha3::{ digest::crypto_common::{generic_array::GenericArray, OutputSizeUser}, digest::FixedOutputReset, @@ -24,8 +28,39 @@ use sha3::{ use userlib::*; use zeroize::Zeroizing; +cfg_if::cfg_if! { + if #[cfg(any(feature = "dice-seed"))] { + use config::DataRegion; + use hubpack::SerializedSize; + use lib_dice::RngData; + use ringbuf::ringbuf_entry; + use serde::Deserialize; + use stage0_handoff::{HandoffData, HandoffDataLoadError}; + + // 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"), "/rng-config.rs")); + } + + use build::DICE_RNG; + } +} + task_slot!(SYSCON, syscon_driver); +#[derive(Copy, Clone, PartialEq)] +enum Trace { + #[cfg(feature = "dice-seed")] + NoDiceSeed, + #[cfg(feature = "dice-seed")] + HandoffError(HandoffDataLoadError), + None, +} + +ringbuf!(Trace, 16, Trace::None); + // low-budget rand::rngs::adapter::ReseedingRng w/o fork stuff struct ReseedingRng { inner: T, @@ -42,19 +77,37 @@ where H: FixedOutputReset + Default + Digest, [u8; 32]: From::OutputSize>>, { - fn new(mut reseeder: R, threshold: usize) -> Result { + fn new( + seed: Option<&RngSeed>, + mut reseeder: R, + threshold: usize, + ) -> Result { let threshold = if threshold == 0 { usize::MAX } else { threshold }; + let mut mixer = H::default(); + if let Some(seed) = seed { + // mix platform unique seed drived by measured boot + Digest::update(&mut mixer, seed.as_bytes()); + } + + // w/ 32 bytes from HRNG + let mut buf = Zeroizing::new(T::Seed::default()); + reseeder.try_fill_bytes(buf.as_mut())?; + Digest::update(&mut mixer, buf.as_ref()); + + // create initial instance of the SeedableRng from the seed + let inner = T::from_seed(mixer.finalize_fixed_reset().into()); + Ok(ReseedingRng { - inner: T::from_rng(&mut reseeder)?, + inner, reseeder, threshold, bytes_until_reseed: threshold, - mixer: H::default(), + mixer, }) } } @@ -116,8 +169,14 @@ where struct Lpc55RngServer(ReseedingRng); impl Lpc55RngServer { - fn new(reseeder: Lpc55Rng, threshold: usize) -> Result { - Ok(Lpc55RngServer(ReseedingRng::new(reseeder, threshold)?)) + fn new( + seed: Option<&RngSeed>, + reseeder: Lpc55Rng, + threshold: usize, + ) -> Result { + Ok(Lpc55RngServer(ReseedingRng::new( + seed, reseeder, threshold, + )?)) } } @@ -156,12 +215,64 @@ impl NotificationHandler for Lpc55RngServer { } } +/// Load a type implementing HandoffData (and others) from a config::DataRegion. +/// Errors will be reported in the ringbuf and will return in None. +#[cfg(feature = "dice-seed")] +fn load_data_from_region< + T: for<'a> Deserialize<'a> + HandoffData + SerializedSize, +>( + region: &DataRegion, +) -> Option { + use core::slice; + + // 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) + }; + + // this can be replaced w/ .ok() if we get rid of the ringbuf entry + match T::load_from_addr(data) { + Ok(d) => Some(d), + Err(e) => { + ringbuf_entry!(Trace::HandoffError(e)); + None + } + } +} + +/// Get the seed derived by the lpc55-rot-startup and passed to us through +/// the stage0-handoff memory region. +/// +/// If use of DICE seed in seeding the PRNG is not enabled then this function +/// will just return None. Otherwise it will attempt to get the seed from the +/// dice-rng region of the stage0-handoff memory. If it's not able to get +/// the seed it will put an entry in the ringbuf and panic. +pub fn get_dice_seed() -> Option { + cfg_if::cfg_if! { + if #[cfg(feature = "dice-seed")] { + match load_data_from_region::(&DICE_RNG) { + Some(rng_data) => Some(rng_data.seed), + _ => { + ringbuf_entry!(Trace::NoDiceSeed); + panic!(); + }, + } + } else { + None + } + } +} + #[export_name = "main"] fn main() -> ! { + let seed = get_dice_seed(); + let rng = Lpc55Rng::new(&Syscon::from(SYSCON.get_task_id())); let threshold = 0x100000; // 1 MiB - let mut rng = Lpc55RngServer::new(rng, threshold) + let mut rng = Lpc55RngServer::new(seed.as_ref(), rng, threshold) .expect("Failed to create Lpc55RngServer"); let mut buffer = [0u8; idl::INCOMING_SIZE];