diff --git a/Cargo.lock b/Cargo.lock index 4eb6d6b39d..bf05728b33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1289,17 +1289,29 @@ 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", + "indexmap 1.9.1", + "lib-dice", + "lib-lpc55-rng", "lpc55-pac", + "mutable-statics", "num-traits", "rand_chacha", "rand_core", + "ringbuf", + "serde", + "sha3", + "stage0-handoff", "userlib", "zerocopy 0.6.6", + "zeroize", ] [[package]] @@ -3069,6 +3081,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "lib-lpc55-rng" +version = "0.1.0" +dependencies = [ + "drv-lpc55-syscon-api", + "drv-rng-api", + "lpc55-pac", + "rand_core", +] + [[package]] name = "lib-lpc55-usart" version = "0.1.0" diff --git a/app/lpc55xpresso/app.toml b/app/lpc55xpresso/app.toml index 91f91becd1..4c9aecab75 100644 --- a/app/lpc55xpresso/app.toml +++ b/app/lpc55xpresso/app.toml @@ -113,11 +113,11 @@ task-slots = ["gpio_driver", "syscon_driver"] [tasks.rng_driver] name = "drv-lpc55-rng" priority = 3 -max-sizes = {flash = 16384, ram = 4096} uses = ["rng", "pmc"] start = true -stacksize = 2200 +stacksize = 3504 task-slots = ["syscon_driver"] +extern-regions = ["dice_certs", "dice_rng"] [tasks.pong] name = "task-pong" diff --git a/app/oxide-rot-1/app-dev.toml b/app/oxide-rot-1/app-dev.toml index 5a50d25b94..5f8662e60a 100644 --- a/app/oxide-rot-1/app-dev.toml +++ b/app/oxide-rot-1/app-dev.toml @@ -165,6 +165,15 @@ stacksize = 12304 start = true extern-regions = ["dice_alias", "dice_certs"] +[tasks.rng_driver] +name = "drv-lpc55-rng" +priority = 6 +uses = ["rng", "pmc"] +start = true +stacksize = 4200 +task-slots = ["syscon_driver"] +extern-regions = ["dice_certs", "dice_rng"] + [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 ec85ba8bb8..c22f06be85 100644 --- a/app/rot-carrier/app.toml +++ b/app/rot-carrier/app.toml @@ -99,11 +99,11 @@ pins = [ [tasks.rng_driver] name = "drv-lpc55-rng" priority = 5 -max-sizes = {flash = 16384, ram = 4096} uses = ["rng", "pmc"] start = true -stacksize = 2200 +stacksize = 4200 task-slots = ["syscon_driver"] +extern-regions = ["dice_certs", "dice_rng"] [tasks.sprot] name = "drv-lpc55-sprot-server" diff --git a/chips/lpc55/memory.toml b/chips/lpc55/memory.toml index 47be0adc47..1d8573a246 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 7704265cb6..34de7db84c 100644 --- a/drv/lpc55-rng/Cargo.toml +++ b/drv/lpc55-rng/Cargo.toml @@ -5,21 +5,37 @@ edition = "2021" [dependencies] cfg-if = { workspace = true } +hubpack.workspace = true idol-runtime = { workspace = true } -lpc55-pac = { workspace = true } +lpc55-pac.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" +mutable-statics.path = "../../lib/mutable-statics" +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 +indexmap = { workspace = true, optional = true } +serde.workspace = true [features] +default = ["dice-seed"] +dice-seed = ["indexmap", "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 4f53fdc481..740f75941d 100644 --- a/drv/lpc55-rng/build.rs +++ b/drv/lpc55-rng/build.rs @@ -2,15 +2,75 @@ // 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 data_region { + include!("src/data-region.rs"); + } + use anyhow::Context; + use data_region::DataRegion; + use indexmap::IndexMap; + use std::{fs::File, io::Write}; + + const CFG_SRC: &str = "rng-config.rs"; + } +} + +#[cfg(feature = "dice-seed")] +fn extern_region_to_cfg( + out: &mut W, + data_regions: &IndexMap, + name: &str, +) -> Result<()> { + let region = data_regions.get(name).ok_or_else(|| { + anyhow::anyhow!(format!("external region not found: {}", name)) + })?; + + Ok(writeln!( + out, + r##"pub const {}_REGION: DataRegion = DataRegion {{ + address: {:#x}, + size: {:#x}, +}};"##, + name.to_uppercase(), + region.address, + region.size + )?) +} + +#[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::data_region::DataRegion;\n\n")?; + + extern_region_to_cfg(&mut out, &data_regions, "dice_certs")?; + extern_region_to_cfg(&mut out, &data_regions, "dice_rng") +} + +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 0000000000..1a7176f0a5 --- /dev/null +++ b/drv/lpc55-rng/src/config.rs @@ -0,0 +1,54 @@ +// 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 crate::data_region::DataRegion; +use hubpack::SerializedSize; +use serde::Deserialize; +use stage0_handoff::{HandoffData, HandoffDataLoadError}; + +pub enum HandoffDataRegion { + DiceCerts, + DiceRng, +} + +pub const DICE_CERTS: HandoffDataRegion = HandoffDataRegion::DiceCerts; +pub const DICE_RNG: HandoffDataRegion = HandoffDataRegion::DiceRng; + +// This file is generated by the crate build.rs. +mod build { + include!(concat!(env!("OUT_DIR"), "/rng-config.rs")); +} + +use build::{DICE_CERTS_REGION, DICE_RNG_REGION}; + +impl HandoffDataRegion { + pub fn data_region(&self) -> DataRegion { + match self { + Self::DiceCerts => DICE_CERTS_REGION, + Self::DiceRng => DICE_RNG_REGION, + } + } + + /// Load a type implementing HandoffData (and others) from a config::DataRegion. + /// Errors will be reported in the ringbuf and will return None. + #[inline(always)] + pub fn load_data< + T: for<'a> Deserialize<'a> + HandoffData + SerializedSize, + >( + &self, + ) -> Result { + use core::slice; + + let region = self.data_region(); + // 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 pre-main, kernel, + // and build process. + let data = unsafe { + slice::from_raw_parts(region.address as *mut u8, region.size) + }; + + T::load_from_addr(data) + } +} diff --git a/drv/lpc55-rng/src/data-region.rs b/drv/lpc55-rng/src/data-region.rs new file mode 100644 index 0000000000..fbd20f716d --- /dev/null +++ b/drv/lpc55-rng/src/data-region.rs @@ -0,0 +1,10 @@ +// 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")] +pub struct DataRegion { + pub address: usize, + pub size: usize, +} diff --git a/drv/lpc55-rng/src/main.rs b/drv/lpc55-rng/src/main.rs index e71e9e7591..6c26aa201e 100644 --- a/drv/lpc55-rng/src/main.rs +++ b/drv/lpc55-rng/src/main.rs @@ -9,119 +9,113 @@ #![no_std] #![no_main] -use core::mem::size_of; -use drv_lpc55_syscon_api::{Peripheral, Syscon}; +use core::{cmp, usize}; +use drv_lpc55_syscon_api::Syscon; use drv_rng_api::RngError; use idol_runtime::{ClientError, NotificationHandler, RequestError}; +use lib_dice::{persistid_cert_tmpl::SUBJECT_CN_LENGTH, RngSeed, SeedBuf}; +use lib_lpc55_rng::Lpc55Rng; +use lpc55_pac::Peripherals; +use mutable_statics::mutable_statics; use rand_chacha::ChaCha20Rng; -use rand_core::block::{BlockRng, BlockRngCore}; use rand_core::{impls, Error, RngCore, SeedableRng}; +use ringbuf::ringbuf; +use sha3::{ + digest::crypto_common::{generic_array::GenericArray, OutputSizeUser}, + digest::FixedOutputReset, + Digest, Sha3_256, +}; use userlib::task_slot; +use zeroize::Zeroizing; -use lpc55_pac as device; +cfg_if::cfg_if! { + if #[cfg(feature = "dice-seed")] { + mod config; + #[path="data-region.rs"] + mod data_region; -task_slot!(SYSCON, syscon_driver); - -struct Lpc55Core { - pmc: &'static lpc55_pac::pmc::RegisterBlock, - rng: &'static lpc55_pac::rng::RegisterBlock, - syscon: Syscon, -} - -impl Lpc55Core { - fn new() -> Self { - let syscon = SYSCON.get_task_id(); - Lpc55Core { - pmc: unsafe { &*device::PMC::ptr() }, - rng: unsafe { &*device::RNG::ptr() }, - syscon: Syscon::from(syscon), - } - } -} - -impl BlockRngCore for Lpc55Core { - type Item = u32; - type Results = [u32; 1]; - - fn generate(&mut self, results: &mut Self::Results) { - results[0] = self.rng.random_number.read().bits(); + use ringbuf::ringbuf_entry; + use stage0_handoff::HandoffDataLoadError; + use userlib::UnwrapLite; } } -struct Lpc55Rng(BlockRng); - -impl Lpc55Rng { - fn new() -> Self { - Lpc55Rng(BlockRng::new(Lpc55Core::new())) - } - - fn init(&self) { - self.0 - .core - .pmc - .pdruncfg0 - .modify(|_, w| w.pden_rng().poweredon()); - - self.0.core.syscon.enable_clock(Peripheral::Rng); +task_slot!(SYSCON, syscon_driver); - self.0.core.syscon.enter_reset(Peripheral::Rng); - self.0.core.syscon.leave_reset(Peripheral::Rng); - } +#[derive(Copy, Clone, PartialEq)] +enum Trace { + None, + #[cfg(feature = "dice-seed")] + NoDiceSeed(HandoffDataLoadError), + #[cfg(feature = "dice-seed")] + NoSeedPersonalization(HandoffDataLoadError), } -impl RngCore for Lpc55Rng { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - fn fill_bytes(&mut self, bytes: &mut [u8]) { - self.0.fill_bytes(bytes) - } - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - if self.0.core.pmc.pdruncfg0.read().pden_rng().bits() { - return Err(RngError::PoweredOff.into()); - } - - self.0.try_fill_bytes(bytes) - } -} +ringbuf!(Trace, 16, Trace::None); // low-budget rand::rngs::adapter::ReseedingRng w/o fork stuff -struct ReseedingRng { +struct ReseedingRng { inner: T, - reseeder: Lpc55Rng, + reseeder: R, threshold: usize, bytes_until_reseed: usize, + mixer: &'static mut H, } -impl ReseedingRng +impl ReseedingRng where - T: SeedableRng, + T: SeedableRng + RngCore, + R: RngCore, + H: FixedOutputReset + Default + Digest, + [u8; 32]: From::OutputSize>>, { - fn new(mut reseeder: Lpc55Rng, threshold: usize) -> Result { - use ::core::usize::MAX; + fn new( + seed: Option<&RngSeed>, + mut reseeder: R, + pid: Option<&[u8; SUBJECT_CN_LENGTH]>, + threshold: usize, + mixer: &'static mut H, + ) -> Result { + let threshold = if threshold == 0 { + usize::MAX + } else { + threshold + }; - let threshold = if threshold == 0 { MAX } else { threshold }; + if let Some(seed) = seed { + // mix platform unique seed derived by measured boot + Digest::update(mixer, seed.as_bytes()); + } + + if let Some(pid) = pid { + // mix in unique platform id + Digest::update(mixer, pid); + } + + // w/ 32 bytes from HRNG + let mut buf = Zeroizing::new(T::Seed::default()); + reseeder.try_fill_bytes(buf.as_mut())?; + Digest::update(mixer, buf.as_ref()); + + // create initial instance of the SeedableRng from the seed + let inner = T::from_seed(mixer.finalize_fixed_reset().into()); - // try_trait_v2 is still experimental - let inner = match T::from_rng(&mut reseeder) { - Ok(rng) => rng, - Err(err) => return Err(err), - }; Ok(ReseedingRng { inner, reseeder, threshold, bytes_until_reseed: threshold, + mixer, }) } } -impl RngCore for ReseedingRng +impl RngCore for ReseedingRng where - T: SeedableRng + RngCore, + T: SeedableRng + RngCore, + R: RngCore, + H: FixedOutputReset + Default + Digest, + [u8; 32]: From::OutputSize>>, { fn next_u32(&mut self) -> u32 { impls::next_u32_via_fill(self) @@ -134,59 +128,83 @@ where .expect("Failed to get entropy from RNG.") } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - let num_bytes = dest.len(); - if num_bytes >= self.bytes_until_reseed || num_bytes >= self.threshold { - // try_trait_v2 is still experimental - self.inner = match T::from_rng(&mut self.reseeder) { - Ok(rng) => rng, - Err(e) => return Err(e), - }; - self.bytes_until_reseed = self.threshold; - } else { - self.bytes_until_reseed -= num_bytes; - } - self.inner.try_fill_bytes(dest) - } -} + let mut filled = 0; + + while filled < dest.len() { + if self.bytes_until_reseed > 0 { + // fill dest as much as we can + let len = + cmp::min(dest.len() - filled, self.bytes_until_reseed); + self.inner.try_fill_bytes(&mut dest[filled..filled + len])?; + + filled += len; + self.bytes_until_reseed -= len; + } else { + // create seed for next PRNG & reset mixer + let mut buf = Zeroizing::new(T::Seed::default()); + + // mix 32 bytes from current PRNG instance + self.inner.try_fill_bytes(buf.as_mut())?; + Digest::update(self.mixer, buf.as_mut()); -struct Lpc55RngServer(ReseedingRng); + // w/ 32 bytes from HRNG + self.reseeder.try_fill_bytes(buf.as_mut())?; + Digest::update(self.mixer, buf.as_mut()); -impl Lpc55RngServer { - fn new(reseeder: Lpc55Rng, threshold: usize) -> Result { - Ok(Lpc55RngServer(ReseedingRng::new(reseeder, threshold)?)) + // seed new RNG instance & reset mixer + self.inner = + T::from_seed(self.mixer.finalize_fixed_reset().into()); + + // reset reseed countdown + self.bytes_until_reseed = self.threshold; + } + } + + Ok(()) } } -impl idl::InOrderRngImpl for Lpc55RngServer { +struct Lpc55RngServer( + ReseedingRng, +); + +impl idl::InOrderRngImpl for Lpc55RngServer +where + T: SeedableRng + RngCore, + R: RngCore, + H: FixedOutputReset + Default + Digest, + [u8; 32]: From::OutputSize>>, +{ fn fill( &mut self, _: &userlib::RecvMessage, dest: idol_runtime::Leased, ) -> Result> { let mut cnt = 0; - const STEP: usize = size_of::(); - let mut buf = [0u8; STEP]; - // fill in multiples of STEP / RNG register size - for _ in 0..(dest.len() / STEP) { - self.0.try_fill_bytes(&mut buf).map_err(RngError::from)?; - dest.write_range(cnt..cnt + STEP, &buf) - .map_err(|_| RequestError::Fail(ClientError::WentAway))?; - cnt += STEP; - } - // fill in remaining - let remain = dest.len() - cnt; - assert!(remain < STEP); - if remain > 0 { - self.0.try_fill_bytes(&mut buf).map_err(RngError::from)?; - dest.write_range(dest.len() - remain..dest.len(), &buf) + let mut buf = [0u8; 32]; + while cnt < dest.len() { + let len = cmp::min(buf.len(), dest.len() - cnt); + + self.0 + .try_fill_bytes(&mut buf[..len]) + .map_err(RngError::from)?; + dest.write_range(cnt..cnt + len, &buf[..len]) .map_err(|_| RequestError::Fail(ClientError::WentAway))?; - cnt += remain; + + cnt += len; } + Ok(cnt) } } -impl NotificationHandler for Lpc55RngServer { +impl NotificationHandler for Lpc55RngServer +where + T: SeedableRng + RngCore, + R: RngCore, + H: FixedOutputReset + Default + Digest, + [u8; 32]: From::OutputSize>>, +{ fn current_notification_mask(&self) -> u32 { // We don't use notifications, don't listen for any. 0 @@ -197,18 +215,98 @@ impl NotificationHandler for Lpc55RngServer { } } +/// 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")] { + use config::DICE_RNG; + use lib_dice::RngData; + + match DICE_RNG.load_data::() { + Ok(rng_data) => Some(rng_data.seed), + Err(e) => { + ringbuf_entry!(Trace::NoDiceSeed(e)); + panic!(); + }, + } + } else { + None + } + } +} + +/// Get the platform identifier / barcode string from the platform identity +/// cert passed to hubris by the lpc55-rot-startup through the stage0-handoff +/// memory region. +/// +/// If use of the platform identifier string is not enabled then this function +/// will return `None`. Otherwise it will try to get the platform identity +/// string from the stage0-handoff region. If it's unable to get this data it +/// will put an entry into the ringbuf and panic. +pub fn get_seed_personalization() -> Option<[u8; SUBJECT_CN_LENGTH]> { + cfg_if::cfg_if! { + if #[cfg(feature = "dice-seed")] { + use config::DICE_CERTS; + use lib_dice::{persistid_cert_tmpl::SUBJECT_CN_RANGE, CertData}; + + match DICE_CERTS.load_data::() { + Ok(cert_data) => Some( + cert_data.persistid_cert.0.as_bytes()[SUBJECT_CN_RANGE] + .try_into() + .unwrap_lite(), + ), + Err(e) => { + ringbuf_entry!(Trace::NoSeedPersonalization(e)); + panic!(); + }, + } + } else { + None + } + } +} + #[export_name = "main"] fn main() -> ! { - let rng = Lpc55Rng::new(); - rng.init(); + let rng = { + let peripherals = Peripherals::take().unwrap(); + + Lpc55Rng::new( + peripherals.PMC, + peripherals.RNG, + &Syscon::from(SYSCON.get_task_id()), + ) + }; + + let mixer = mutable_statics! { + static mut MIXER: [Sha3_256; 1] = [Sha3_256::new; _]; + }; + + let reseeding_rng: ReseedingRng = { + let seed = get_dice_seed(); + let pid = get_seed_personalization(); + let threshold = 0x100000; // 1 MiB + ReseedingRng::new( + seed.as_ref(), + rng, + pid.as_ref(), + threshold, + &mut mixer[0], + ) + .unwrap_lite() + }; - let threshold = 0x100000; // 1 MiB - let mut rng = Lpc55RngServer::new(rng, threshold) - .expect("Failed to create Lpc55RngServer"); + let mut server = Lpc55RngServer(reseeding_rng); let mut buffer = [0u8; idl::INCOMING_SIZE]; loop { - idol_runtime::dispatch(&mut buffer, &mut rng); + idol_runtime::dispatch(&mut buffer, &mut server); } } diff --git a/drv/rng-api/src/lib.rs b/drv/rng-api/src/lib.rs index e89f9a4d5d..ec733005a8 100644 --- a/drv/rng-api/src/lib.rs +++ b/drv/rng-api/src/lib.rs @@ -17,8 +17,7 @@ use userlib::{sys_send, FromPrimitive}; Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, counters::Count, )] pub enum RngError { - PoweredOff = 1, - NoData, + NoData = 1, ClockError, SeedError, UnknownRngError, diff --git a/lib/dice/src/lib.rs b/lib/dice/src/lib.rs index f0d556296d..06cfcf2f6c 100644 --- a/lib/dice/src/lib.rs +++ b/lib/dice/src/lib.rs @@ -30,7 +30,7 @@ mod alias_cert_tmpl; mod deviceid_cert_tmpl; mod handoff; mod mfg; -mod persistid_cert_tmpl; +pub mod persistid_cert_tmpl; mod persistid_csr_tmpl; pub use crate::mfg::{ DiceMfg, DiceMfgState, PersistIdSeed, SelfMfg, SerialMfg, diff --git a/lib/dice/src/persistid_cert_tmpl.rs b/lib/dice/src/persistid_cert_tmpl.rs index d4c03afb08..68140acfd1 100644 --- a/lib/dice/src/persistid_cert_tmpl.rs +++ b/lib/dice/src/persistid_cert_tmpl.rs @@ -12,7 +12,10 @@ use core::ops::Range; pub const SIZE: usize = 441; pub const SERIAL_NUMBER_RANGE: Range = 15..16; pub const ISSUER_CN_RANGE: Range = 82..114; -pub const SUBJECT_CN_RANGE: Range = 207..239; +pub const SUBJECT_CN_START: usize = 207; +pub const SUBJECT_CN_END: usize = 239; +pub const SUBJECT_CN_RANGE: Range = SUBJECT_CN_START..SUBJECT_CN_END; +pub const SUBJECT_CN_LENGTH: usize = SUBJECT_CN_END - SUBJECT_CN_START; pub const PUB_RANGE: Range = 251..283; pub const SIG_RANGE: Range = 377..441; pub const SIGNDATA_RANGE: Range = 4..367; diff --git a/lib/lpc55-rng/Cargo.toml b/lib/lpc55-rng/Cargo.toml new file mode 100644 index 0000000000..dc635d92d3 --- /dev/null +++ b/lib/lpc55-rng/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lib-lpc55-rng" +version = "0.1.0" +edition = "2021" + +[dependencies] +lpc55-pac.workspace = true +rand_core.workspace = true + +drv-lpc55-syscon-api.path = "../../drv/lpc55-syscon-api" +drv-rng-api.path = "../../drv/rng-api" + +[lib] +test = false +doctest = false +bench = false diff --git a/lib/lpc55-rng/src/lib.rs b/lib/lpc55-rng/src/lib.rs new file mode 100644 index 0000000000..26669166bf --- /dev/null +++ b/lib/lpc55-rng/src/lib.rs @@ -0,0 +1,71 @@ +// 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/. + +#![no_std] +#![no_main] + +use core::{cmp, mem}; +use drv_lpc55_syscon_api::{Peripheral, Syscon}; +use lpc55_pac::{PMC, RNG}; +use rand_core::{impls, Error, RngCore}; + +/// The Lpc55Rng is a thin wrapper around the LPC55 hardware random number +/// generator (HRNG). +pub struct Lpc55Rng { + pub pmc: PMC, + pub rng: RNG, +} + +impl Lpc55Rng { + /// Create a new Lpc55Rng instance after powering on, enabling the clocks + /// and reseting the underlying HRNG. + pub fn new(pmc: PMC, rng: RNG, syscon: &Syscon) -> Self { + pmc.pdruncfg0.modify(|_, w| w.pden_rng().poweredon()); + + syscon.enable_clock(Peripheral::Rng); + syscon.enter_reset(Peripheral::Rng); + syscon.leave_reset(Peripheral::Rng); + + Lpc55Rng { pmc, rng } + } +} + +impl RngCore for Lpc55Rng { + /// Get the next 4 bytes from the HRNG. + fn next_u32(&mut self) -> u32 { + impls::next_u32_via_fill(self) + } + + /// Get the next 8 bytes from the HRNG. + fn next_u64(&mut self) -> u64 { + impls::next_u64_via_fill(self) + } + + /// Fill the provided buffer with output from the HRNG. + fn fill_bytes(&mut self, bytes: &mut [u8]) { + self.try_fill_bytes(bytes).expect("fill_bytes") + } + + /// Fill the provided buffer with output from the HRNG. If the HRNG + /// can't service the request an error is returned. + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Error> { + let mut filled = 0; + while filled < dst.len() { + // `new` takes ownership of the PMC & RNG before powering on the + // RNG. If it gets turned off between then and now it's a bug. + if self.pmc.pdruncfg0.read().pden_rng().bits() { + panic!(); + } + + let src = self.rng.random_number.read().bits(); + let len = cmp::min(mem::size_of_val(&src), dst[filled..].len()); + + dst[filled..filled + len] + .copy_from_slice(&src.to_le_bytes()[..len]); + filled += len; + } + + Ok(()) + } +}