add production TRNG to cramium target for kernel

This commit is contained in:
bunnie 2024-01-20 16:16:07 +08:00
parent 96102630b3
commit 5df2e27447
2 changed files with 64 additions and 89 deletions

View file

@ -16,6 +16,7 @@ xous-kernel = { package = "xous", version = "0.9.57", features = [
] }
utralib = { version = "0.1.24", optional = true, default_features = false }
rand_chacha = { version = "0.3.1", optional = true, default_features = false }
cramium-hal = { path = "../libs/cramium-hal", optional = true }
[target.'cfg(any(windows,unix))'.dev-dependencies]
xous-kernel = { package = "xous", version = "0.9.57", features = [
@ -37,7 +38,7 @@ xous-kernel = { package = "xous", version = "0.9.57", features = ["v2p"] }
critical-section = "1.1.1"
[features]
cramium-soc = ["utralib/cramium-soc"]
cramium-soc = ["utralib/cramium-soc", "cramium-hal", "rand_chacha"]
cramium-fpga = ["utralib/cramium-fpga"]
atsama5d27 = ["utralib/atsama5d27"]
precursor = ["utralib/precursor"]
@ -54,9 +55,6 @@ v2p = ["xous-kernel/v2p"]
# patches for simulation targets ONLY. Applying these flags will result in totally broken security.
hwsim = []
fake-rng = [
"rand_chacha",
] # works around lack of a real TRNG in the full-chip sim for now. This should go away for production.
# default = ["print-panics", "debug-print", "wrap-print"]
default = ["print-panics"]

View file

@ -2,88 +2,53 @@
// SPDX-FileCopyrightText: 2022 Foundation Devices, Inc. <hello@foundationdevices.com>
// SPDX-License-Identifier: Apache-2.0
#[cfg(not(feature = "fake-rng"))]
use utralib::generated::*;
#[cfg(not(feature = "fake-rng"))]
use cramium_hal::sce;
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha8Rng;
use xous_kernel::{MemoryFlags, MemoryType, PID};
#[cfg(not(feature = "fake-rng"))]
use crate::mem::MemoryManager;
/// The manually chosen virtual address has to be in the top 4MiB as it is the
/// only page shared among all processes.
///
/// See https://github.com/betrusted-io/xous-core/blob/master/docs/memory.md
#[cfg(not(feature = "fake-rng"))]
pub const TRNG_KERNEL_ADDR: usize = 0xffce_0000;
#[cfg(not(feature = "fake-rng"))]
pub static mut TRNG_KERNEL: Option<TrngKernel> = None;
#[cfg(feature = "fake-rng")]
pub static mut TRNG_KERNEL: Option<sce::trng::Trng> = None;
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "fake-rng")]
static LOCAL_RNG_STATE: AtomicU32 = AtomicU32::new(2);
#[cfg(not(feature = "fake-rng"))]
pub struct TrngKernel {
pub trng_kernel_csr: CSR<u32>,
}
#[cfg(not(feature = "fake-rng"))]
impl TrngKernel {
pub fn new(addr: usize) -> TrngKernel { TrngKernel { trng_kernel_csr: CSR::new(addr as *mut u32) } }
pub fn init(&mut self) {
if false {
// raw random path - left in for debugging urandom, can strip out later
while self.trng_kernel_csr.rf(utra::trng_kernel::STATUS_AVAIL) == 0 {}
// discard the first entry, as it is always 0x0000_0000
// this is because the read register is a pipeline stage behind the FIFO
// once the pipeline has been filled, there is no need to prime it again.
self.trng_kernel_csr.rf(utra::trng_kernel::DATA_DATA);
} else {
// urandom path (recommended)
// simulations show this isn't strictly necessary, but I prefer to have it
// just in case a subtle bug in the reset logic leaves something deterministic
// in the connecting logic: the simulation coverage stops at the edge of the TRNG block.
for _ in 0..4 {
// wait until the urandom port is initialized
while self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_VALID_URANDOM_VALID) == 0 {}
// pull a dummy piece of data
self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_URANDOM);
}
}
}
pub fn get_u32(&mut self) -> u32 {
if false {
// raw random path
while self.trng_kernel_csr.rf(utra::trng_kernel::STATUS_AVAIL) == 0 {}
self.trng_kernel_csr.rf(utra::trng_kernel::DATA_DATA)
} else {
// urandom path (recommended)
while self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_VALID_URANDOM_VALID) == 0 {}
self.trng_kernel_csr.rf(utra::trng_kernel::URANDOM_URANDOM)
}
}
}
// these values are overwritten on boot with something out of the TRNG.
static LOCAL_RNG_STATE_LSB: AtomicU32 = AtomicU32::new(0);
static LOCAL_RNG_STATE_MSB: AtomicU32 = AtomicU32::new(0);
/// Initialize TRNG driver.
///
/// Needed so that the kernel can allocate names.
#[cfg(not(feature = "fake-rng"))]
/// This driver "owns" the hardware TRNG on the chip. Because there aren't two
/// TRNG ports on the Cramium implementation, it means that userspace applications
/// have to extract random numbers from the kernel through the SID request mechanism.
/// This is ostensibly harmless, if everything is implemented well, but if there is
/// a problem it does mean that the state of the kernel TRNG is disclosed to userspace.
/// What can you do though, this chip just has one TRNG.
///
/// The final implementation for user applications is recommended to have a supplementary
/// noise source in addition to the on-chip noise source, so that user applications
/// combine both the feed from the kernel and the supplementary source to seed a CSPRNG.
///
/// This particular application doesn't trust the on-chip TRNG so much: it takes
/// values from the TRNG and folds them into a seed pool that is then groomed by
/// a ChaCha8 RNG CSPRNG. Every request for a random number folds another 32 bits
/// from the TRNG into the CSPRNG pool, so even if the TRNG is fairly poor, quite
/// rapidly the pool of numbers should diverge. However, from the kernel's perspective,
/// it will function correctly even if the CSPRNG is fully deterministic, it just isn't
/// secure against attackers trying to guess a server address.
pub fn init() {
// Map the TRNG so that we can allocate names
// hardware guarantees that:
// - TRNG will automatically power on
// - Both TRNGs are enabled, with conservative defaults
// - Kernel FIFO will fill with TRNGs such that at least the next 512 calls to get_u32() will succeed
// without delay
// - The kernel will start a TRNG server
// - All further security decisions and policies are 100% delegated to this new server.
MemoryManager::with_mut(|memory_manager| {
memory_manager
.map_range(
utra::trng_kernel::HW_TRNG_KERNEL_BASE as *mut u8,
utralib::generated::HW_TRNG_BASE as *mut u8,
(TRNG_KERNEL_ADDR & !4095) as *mut u8,
4096,
PID::new(1).unwrap(),
@ -93,34 +58,46 @@ pub fn init() {
.expect("unable to map TRNG_KERNEL")
});
let mut trng_kernel = TrngKernel::new(TRNG_KERNEL_ADDR);
trng_kernel.init();
let mut trng_kernel = sce::trng::Trng::new(TRNG_KERNEL_ADDR);
trng_kernel.setup_raw_generation(256);
// setup the initial seeds with stuff out of the TRNG.
LOCAL_RNG_STATE_LSB.store(trng_kernel.get_u32().expect("TRNG error"), Ordering::SeqCst);
LOCAL_RNG_STATE_MSB.store(trng_kernel.get_u32().expect("TRNG error"), Ordering::SeqCst);
// move the trng_kernel object into the static mut state
unsafe {
TRNG_KERNEL = Some(trng_kernel);
}
// now run the CSPRNG a few cycles to fold more entropy into the pool before we start
// to use it for reals. 16 iterations will extract 512 bits out of the TRNG, so even
// if it is somewhat degraded, we'll be in good shape...
for _ in 0..16 {
let _ = get_u32();
}
}
/// Retrieve random `u32`.
#[cfg(not(feature = "fake-rng"))]
pub fn get_u32() -> u32 {
unsafe { TRNG_KERNEL.as_mut().expect("TRNG_KERNEL driver not initialized").get_u32() }
}
/// Initialize TRNG driver.
///
/// Needed so that the kernel can allocate names.
#[cfg(feature = "fake-rng")]
pub fn init() {}
#[cfg(feature = "fake-rng")]
pub fn get_u32() -> u32 {
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha8Rng;
let mut rng = ChaCha8Rng::seed_from_u64(LOCAL_RNG_STATE.load(Ordering::SeqCst) as u64);
let r = rng.next_u32();
LOCAL_RNG_STATE.store(rng.next_u64() as u32, Ordering::SeqCst);
r
let mut state = LOCAL_RNG_STATE_LSB.load(Ordering::SeqCst) as u64
| (LOCAL_RNG_STATE_MSB.load(Ordering::SeqCst) as u64) << 32;
// XOR in 32 bits of entropy from the HW TRNG pool.
state ^= unsafe {
TRNG_KERNEL
.as_mut()
.expect("TRNG_KERNEL driver not initialized")
.get_u32()
.expect("Error in random number generation")
} as u64;
let mut rng = ChaCha8Rng::seed_from_u64(state);
let next_state = rng.next_u64();
// next state is extracted before the returned value, to reduce state
// leakage outside of the RNG.
let ret_val = rng.next_u32();
LOCAL_RNG_STATE_LSB.store(next_state as u32, Ordering::SeqCst);
LOCAL_RNG_STATE_MSB.store((next_state >> 32) as u32, Ordering::SeqCst);
ret_val
}