diff --git a/Cargo.lock b/Cargo.lock index 512dba96..a7d4f609 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -785,6 +785,12 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + [[package]] name = "lazy_static" version = "1.4.0" @@ -890,6 +896,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + [[package]] name = "micromath" version = "1.1.1" @@ -955,6 +973,7 @@ dependencies = [ "hal-core", "hal-x86_64", "maitake", + "merlin", "mycelium-alloc", "mycelium-trace", "mycelium-util", @@ -1463,6 +1482,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1847,6 +1878,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "valuable" version = "0.1.0" @@ -2078,3 +2115,24 @@ dependencies = [ "num-traits", "yaxpeax-arch", ] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index 5418c5c4..f1e5188d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ rlibc = "1.0" bootloader = { version = "0.10.13" } embedded-graphics = "0.7" mycotest = { path = "mycotest" } +merlin = { version = "3.0", default_features = false } futures-util = { version = "0.3", default-features = false, features = ["async-await", "async-await-macro"] } # we don't depend on this directly, but we need to ensure that `tracing` uses a # Miri-compatible version of `once_cell`. diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index d4e2e6d1..ab41922e 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -4,6 +4,7 @@ use hal_x86_64::{cpu, serial, vga}; pub use hal_x86_64::{cpu::entropy::seed_rng, mm, NAME}; use mycelium_util::sync::InitOnce; +pub mod entropy; mod framebuf; pub mod interrupt; mod oops; diff --git a/src/arch/x86_64/entropy.rs b/src/arch/x86_64/entropy.rs index 1e3dda80..4aa8e0a7 100644 --- a/src/arch/x86_64/entropy.rs +++ b/src/arch/x86_64/entropy.rs @@ -1 +1,91 @@ //! Hardware entropy sources. +use hal_x86_64::cpu::entropy; +use mycelium_util::sync::Lazy; + +/// This is a wrapper type so that we don't globally implement `rand::CryptoRng` +/// for the `hal_core::cpu::entropy::PitEntropy` type, which is...Not a +/// cryptographically secure RNG... +/// +/// # Notes +/// +/// Use this RNG **only** for calls to `TranscriptRngBuilder::finalize`! It is +/// *not* actually cryptographically secure! +pub(crate) struct RekeyRng(entropy::PitEntropy); + +pub(crate) fn fill_bytes(buf: &mut [u8]) -> usize { + static RDRAND: Lazy> = Lazy::new(|| entropy::Rdrand::new().ok()); + // static RDTSC: Lazy> = Lazy::new(|| hal_core::cpu::Rdtsc::new().ok()); + let total_len = buf.len(); + + if let Some(rdrand) = &*RDRAND { + fill_bytes_rdrand(rdrand, buf); + let bytes = total_len - buf.len(); + tracing::trace!(bytes, "generated entropy from RDRAND"); + bytes + } else { + todo!("eliza: use other entropy sources?") + } +} + +fn fill_bytes_rdrand(rdrand: &entropy::Rdrand, mut buf: &mut [u8]) { + while buf.len() >= 8 { + let random = match rdrand.try_next_u64() { + Some(random) => random, + None => return, + }; + let (chunk, rest) = { buf }.split_at_mut(8); + buf = rest; + + chunk.copy_from_slice(&random.to_ne_bytes()); + } + + let remaining = buf.len(); + if remaining > 4 { + let random = match rdrand.try_next_u64() { + Some(random) => random, + None => return, + }; + + buf.copy_from_slice(&random.to_ne_bytes()[..remaining]); + } else { + let random = match rdrand.try_next_u32() { + Some(random) => random, + None => return, + }; + + buf.copy_from_slice(&random.to_ne_bytes()[..remaining]); + } +} + +// === impl RekeyRng === + +impl rand::RngCore for RekeyRng { + 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, dest: &mut [u8]) { + self.0.fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.0.try_fill_bytes(dest) + } +} + +/// This impl is necessary so that we can use this RNG for +/// `TranscriptRng::finalize`, since the `merlin` API requires that RNG be a +/// `CryptoRng`. This RNG is **not actually cryptographically secure**, but this +/// is fine, since we are not actually proving a transcript, just using the +/// transcript for its internal `#![no_std]`-compatible STROBE implementation. +impl rand::CryptoRng for RekeyRng {} + +impl RekeyRng { + pub(crate) fn new() -> Self { + Self(entropy::PitEntropy::new()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b9f7ace6..199e829e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; extern crate rlibc; pub mod arch; +pub mod random; pub mod rt; pub mod wasm; @@ -175,6 +176,8 @@ fn kernel_main() -> ! { } }); + rt::spawn(random::entropy_upkeep(time::Duration::from_secs(1))); + let mut core = rt::Core::new(); loop { core.run(); diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 00000000..5c99d81d --- /dev/null +++ b/src/random.rs @@ -0,0 +1,86 @@ +use crate::arch; +use maitake::{sync::RwLock, time}; +use mycelium_util::sync::{spin, Lazy}; + +pub(crate) type SystemRng = merlin::TranscriptRng; + +/// Sponge for entropy generated by hardware entropy sources. +/// +/// We are not, in fact, using `merlin` to prove a public-coin argument +/// transcript, but instead, we are using its internal `#![no_std]`-compatible +/// [STROBE implementation] as a sponge for entropy generated by system hardware +/// that can be used to quickly produce a fast and secure RNG. +/// +/// [STROBE implementation]: https://merlin.cool/transcript/rng.html +static SPONGE: Lazy> = Lazy::new(init); + +pub(crate) async fn entropy_upkeep(interval: time::Duration) { + // buffer for the entropy source output + tracing::info!("spawned RNG entropy upkeep task"); + let mut buf = [0u8; 64]; + loop { + // clippy tells us that this auto-deref would be done by the compiler, + // but + #[allow(clippy::explicit_auto_deref)] + fill_randomness(&mut *SPONGE.write().await, &mut buf); + time::sleep(interval).await; + } +} + +/// Initialize a new random number generator. +/// +/// This function spins if the RNG entropy sponge is currently being updated. +#[must_use] +pub(crate) fn new_rng_blocking() -> SystemRng { + let mut boff = spin::Backoff::new(); + let sponge = loop { + if let Some(sponge) = SPONGE.try_read() { + break sponge; + } + boff.spin(); + }; + build_rng(&sponge) +} + +/// Initialize a new random number generator. +/// +/// This function waits until the RNG entropy sponge is done being updated. +#[allow(dead_code)] // i'll use this later +pub(crate) async fn new_rng() -> SystemRng { + let sponge = SPONGE.read().await; + build_rng(&sponge) +} + +fn build_rng(sponge: &merlin::Transcript) -> SystemRng { + tracing::debug!("building a new RNG"); + sponge + .build_rng() + .finalize(&mut arch::entropy::RekeyRng::new()) +} + +fn init() -> RwLock { + tracing::info!("initializing system RNG..."); + let mut sponge = merlin::Transcript::new(b"rng"); + let mut buf = [0u8; 64]; + fill_randomness(&mut sponge, &mut buf); + + tracing::info!("RNG initialized!"); + RwLock::new(sponge) +} + +fn fill_randomness(sponge: &mut merlin::Transcript, buf: &mut [u8]) { + // Number of iterations of filling the sponge to do if we haven't run out of + // rdrand randomness. + // + // This is just so that the bg task will never loop infinitely if rdrand is + // refilling too fast. + const MAX_ITERS: usize = 64; + for _ in 0..MAX_ITERS { + let bytes = arch::entropy::fill_bytes(buf); + sponge.append_message(b"entropy", &buf[..bytes]); + if bytes < buf.len() { + // called rdrand too much, i guess! + break; + } + } +} diff --git a/src/rt.rs b/src/rt.rs index 6901f310..dca703ff 100644 --- a/src/rt.rs +++ b/src/rt.rs @@ -28,12 +28,7 @@ pub struct Core { /// have a situation where all idle steal from the first available worker, /// resulting in other cores ending up with huge queues of idle tasks while /// the first core's queue is always empty. - /// - /// This is *not* a cryptographically secure random number generator, since - /// randomness of this value is not required for security. Instead, it just - /// helps ensure a good distribution of load. Therefore, we use a fast, - /// non-cryptographic RNG. - rng: rand_xoshiro::Xoroshiro128PlusPlus, + rng: crate::random::SystemRng, } struct Runtime { @@ -82,7 +77,7 @@ impl Core { Self { scheduler, id, - rng: crate::arch::seed_rng(), + rng: crate::random::new_rng_blocking(), running: AtomicBool::new(false), } }