From 5725a5d0fbe06be2dba1db3e8f71fde2d7929e5f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 14 Jan 2018 15:50:05 +0100 Subject: [PATCH 1/8] Add EntropySource --- src/lib.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index fbec03b23f5..a110dbad59b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1017,6 +1017,106 @@ impl Rng for ThreadRng { } } +/// An RNG provided specifically for seeding PRNG's. +/// +/// `EntropySource` uses the interface for random numbers provided by the +/// operating system (`OsRng`). If that returns an error, it will fall back to +/// the `JitterRng` entropy collector. Occasionally it will then check if +/// `OsRng` is still not available, and switch back if possible. +#[cfg(feature="std")] +#[derive(Debug)] +pub struct EntropySource { + rng: EntropySourceInner, + counter: u32, +} + +#[cfg(feature="std")] +#[derive(Debug)] +enum EntropySourceInner { + Os(OsRng), + Jitter(JitterRng), +} + +#[cfg(feature="std")] +impl EntropySource { + pub fn new() -> Result { + match OsRng::new() { + Ok(r) => + Ok(EntropySource { rng: EntropySourceInner::Os(r), + counter: 0u32 }), + Err(e1) => { + match JitterRng::new() { + Ok(r) => + Ok(EntropySource { rng: EntropySourceInner::Jitter(r), + counter: 0 }), + Err(_) => + Err(Error::with_cause( + ErrorKind::Unavailable, + "Both OS and Jitter entropy sources are unavailable", + e1)) + } + } + } + } +} + +#[cfg(feature="std")] +impl Rng for EntropySource { + fn next_u32(&mut self) -> u32 { + impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.try_fill_bytes(dest).unwrap(); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + let mut switch_rng = None; + let mut result; + match self.rng { + EntropySourceInner::Os(ref mut rng) => { + result = rng.try_fill_bytes(dest); + if result.is_err() { + // Fall back to JitterRng. + let mut rng2_result = JitterRng::new(); + if let Ok(mut rng2) = rng2_result { + result = rng2.try_fill_bytes(dest); // Can't fail + switch_rng = Some(EntropySourceInner::Jitter(rng2)); + } + } + } + EntropySourceInner::Jitter(ref mut rng) => { + if self.counter < 8 { + result = rng.try_fill_bytes(dest); // use JitterRng + self.counter = (self.counter + 1) % 8; + } else { + // Try if OsRng is still unavailable + let os_rng_result = OsRng::new(); + if let Ok(mut os_rng) = os_rng_result { + result = os_rng.try_fill_bytes(dest); + if result.is_ok() { + switch_rng = Some(EntropySourceInner::Os(os_rng)); + } else { + result = rng.try_fill_bytes(dest); // use JitterRng + } + } else { + result = rng.try_fill_bytes(dest); // use JitterRng + } + } + } + } + if let Some(rng) = switch_rng { + self.rng = rng; + self.counter = 0; + } + result + } +} + /// Generates a random value using the thread-local random number generator. /// /// `random()` can generate various types of random things, and so may require From b6935b08da6a0ca79a67a0e5ec4215be15c736d5 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 14 Jan 2018 15:51:02 +0100 Subject: [PATCH 2/8] Use EntropySource in NewSeeded --- src/lib.rs | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a110dbad59b..efdcb6fd133 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -843,29 +843,8 @@ pub trait NewRng: SeedableRng { #[cfg(feature="std")] impl NewRng for R { fn new() -> Result { - // Note: error handling would be easier with try/catch blocks - fn new_os() -> Result { - let mut r = OsRng::new()?; - T::from_rng(&mut r) - } - - fn new_jitter() -> Result { - let mut r = JitterRng::new()?; - T::from_rng(&mut r) - } - - trace!("Seeding new RNG"); - new_os().or_else(|e1| { - warn!("OsRng failed [falling back to JitterRng]: {:?}", e1); - new_jitter().map_err(|_e2| { - warn!("JitterRng failed: {:?}", _e2); - // TODO: can we somehow return both error sources? - Error::with_cause( - ErrorKind::Unavailable, - "seeding a new RNG failed: both OS and Jitter entropy sources failed", - e1) - }) - }) + let mut source = EntropySource::new()?; + R::from_rng(&mut source) } } From 9ccbceab64ec1e504c6d6cd14e3b67601ce94de9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 21 Jan 2018 15:55:51 +0100 Subject: [PATCH 3/8] Remove Reseeder trait --- src/lib.rs | 16 +++++++-------- src/reseeding.rs | 52 +++++++++++++----------------------------------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index efdcb6fd133..c32f098160e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -287,7 +287,7 @@ use prng::Isaac64Rng as IsaacWordRng; use distributions::{Range, IndependentSample}; use distributions::range::SampleRange; -#[cfg(feature="std")] use reseeding::{ReseedingRng, ReseedWithNew}; +#[cfg(feature="std")] use reseeding::ReseedingRng; // public modules pub mod distributions; @@ -942,20 +942,20 @@ pub fn weak_rng() -> XorShiftRng { #[cfg(feature="std")] #[derive(Clone, Debug)] pub struct ThreadRng { - rng: Rc>>, + rng: Rc>>, } #[cfg(feature="std")] thread_local!( - static THREAD_RNG_KEY: Rc>> = { + static THREAD_RNG_KEY: Rc>> = { const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768; - let r = match StdRng::new() { - Ok(r) => r, - Err(e) => panic!("could not initialize thread_rng: {:?}", e) - }; + let mut entropy_source = EntropySource::new() + .expect("could not initialize thread_rng"); + let r = StdRng::from_rng(&mut entropy_source) + .expect("could not initialize thread_rng"); let rng = ReseedingRng::new(r, THREAD_RNG_RESEED_THRESHOLD, - ReseedWithNew); + entropy_source); Rc::new(RefCell::new(rng)) } ); diff --git a/src/reseeding.rs b/src/reseeding.rs index b9a9b7a2f56..aab46f44b91 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -11,9 +11,7 @@ //! A wrapper around another RNG that reseeds it after it //! generates a certain number of random bytes. -use {Rng, Error}; -#[cfg(feature="std")] -use NewRng; +use {Rng, SeedableRng, Error}; /// A wrapper around any RNG which reseeds the underlying RNG after it /// has generated a certain number of random bytes. @@ -23,10 +21,10 @@ pub struct ReseedingRng { generation_threshold: u64, bytes_generated: u64, /// Controls the behaviour when reseeding the RNG. - pub reseeder: Rsdr, + reseeder: Rsdr, } -impl> ReseedingRng { +impl ReseedingRng { /// Create a new `ReseedingRng` with the given parameters. /// /// # Arguments @@ -48,14 +46,14 @@ impl> ReseedingRng { pub fn reseed_if_necessary(&mut self) { if self.bytes_generated >= self.generation_threshold { trace!("Reseeding RNG after {} bytes", self.bytes_generated); - self.reseeder.reseed(&mut self.rng).unwrap(); + R::from_rng(&mut self.reseeder).map(|result| self.rng = result).unwrap(); self.bytes_generated = 0; } } } -impl> Rng for ReseedingRng { +impl Rng for ReseedingRng { fn next_u32(&mut self) -> u32 { self.reseed_if_necessary(); self.bytes_generated += 4; @@ -81,35 +79,10 @@ impl> Rng for ReseedingRng { } } -/// Something that can be used to reseed an RNG via `ReseedingRng`. -/// -/// Note that implementations should support `Clone` only if reseeding is -/// deterministic (no external entropy source). This is so that a `ReseedingRng` -/// only supports `Clone` if fully deterministic. -pub trait Reseeder { - /// Reseed the given RNG. - /// - /// On error, this should just forward the source error; errors are handled - /// by the caller. - fn reseed(&mut self, rng: &mut R) -> Result<(), Error>; -} - -/// Reseed an RNG using `NewRng` to replace the current instance. -#[cfg(feature="std")] -#[derive(Debug)] -pub struct ReseedWithNew; - -#[cfg(feature="std")] -impl Reseeder for ReseedWithNew { - fn reseed(&mut self, rng: &mut R) -> Result<(), Error> { - R::new().map(|result| *rng = result) - } -} - #[cfg(test)] mod test { use {impls, le}; - use super::{ReseedingRng, Reseeder}; + use super::{ReseedingRng}; use {SeedableRng, Rng, Error}; struct Counter { @@ -140,17 +113,20 @@ mod test { } #[derive(Debug, Clone)] - struct ReseedCounter; - impl Reseeder for ReseedCounter { - fn reseed(&mut self, rng: &mut Counter) -> Result<(), Error> { - *rng = Counter { i: 0 }; + struct ResetCounter; + impl Rng for ResetCounter { + fn next_u32(&mut self) -> u32 { unimplemented!() } + fn next_u64(&mut self) -> u64 { unimplemented!() } + fn fill_bytes(&mut self, _dest: &mut [u8]) { unimplemented!() } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + for i in dest.iter_mut() { *i = 0; } Ok(()) } } #[test] fn test_reseeding() { - let mut rs = ReseedingRng::new(Counter {i:0}, 400, ReseedCounter); + let mut rs = ReseedingRng::new(Counter {i:0}, 400, ResetCounter); let mut i = 0; for _ in 0..1000 { From 672b0383f02b0f6021ab3bf62b7da06f858497b7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 18 Jan 2018 08:47:40 +0100 Subject: [PATCH 4/8] Rename to EntropyRng and make new infallible --- src/lib.rs | 128 +++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c32f098160e..317d4c60cd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -843,8 +843,7 @@ pub trait NewRng: SeedableRng { #[cfg(feature="std")] impl NewRng for R { fn new() -> Result { - let mut source = EntropySource::new()?; - R::from_rng(&mut source) + R::from_rng(EntropyRng::new()) } } @@ -942,15 +941,14 @@ pub fn weak_rng() -> XorShiftRng { #[cfg(feature="std")] #[derive(Clone, Debug)] pub struct ThreadRng { - rng: Rc>>, + rng: Rc>>, } #[cfg(feature="std")] thread_local!( - static THREAD_RNG_KEY: Rc>> = { + static THREAD_RNG_KEY: Rc>> = { const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768; - let mut entropy_source = EntropySource::new() - .expect("could not initialize thread_rng"); + let mut entropy_source = EntropyRng::new(); let r = StdRng::from_rng(&mut entropy_source) .expect("could not initialize thread_rng"); let rng = ReseedingRng::new(r, @@ -998,49 +996,42 @@ impl Rng for ThreadRng { /// An RNG provided specifically for seeding PRNG's. /// -/// `EntropySource` uses the interface for random numbers provided by the -/// operating system (`OsRng`). If that returns an error, it will fall back to -/// the `JitterRng` entropy collector. Occasionally it will then check if -/// `OsRng` is still not available, and switch back if possible. +/// `EntropyRng` uses the interface for random numbers provided by the operating +/// system ([`OsRng`]). If that returns an error, it will fall back to the +/// [`JitterRng`] entropy collector. Occasionally it will then check if `OsRng` +/// is still not available, and switch back if possible. +/// +/// [`OsRng`]: os/struct.OsRng.html +/// [`JitterRng`]: jitter/struct.JitterRng.html #[cfg(feature="std")] #[derive(Debug)] -pub struct EntropySource { - rng: EntropySourceInner, +pub struct EntropyRng { + rng: EntropySource, counter: u32, } #[cfg(feature="std")] #[derive(Debug)] -enum EntropySourceInner { +enum EntropySource { Os(OsRng), Jitter(JitterRng), + None, } #[cfg(feature="std")] -impl EntropySource { - pub fn new() -> Result { - match OsRng::new() { - Ok(r) => - Ok(EntropySource { rng: EntropySourceInner::Os(r), - counter: 0u32 }), - Err(e1) => { - match JitterRng::new() { - Ok(r) => - Ok(EntropySource { rng: EntropySourceInner::Jitter(r), - counter: 0 }), - Err(_) => - Err(Error::with_cause( - ErrorKind::Unavailable, - "Both OS and Jitter entropy sources are unavailable", - e1)) - } - } - } +impl EntropyRng { + /// Create a new `EntropyRng`. + /// + /// This method will do no system calls or other initialization routines, + /// those are done on first use. This is done to make `new` infallible, + /// and `try_fill_bytes` the only place to report errors. + pub fn new() -> Self { + EntropyRng { rng: EntropySource::None, counter: 0u32 } } } #[cfg(feature="std")] -impl Rng for EntropySource { +impl Rng for EntropyRng { fn next_u32(&mut self) -> u32 { impls::next_u32_via_fill(self) } @@ -1054,45 +1045,66 @@ impl Rng for EntropySource { } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + fn try_os_new(dest: &mut [u8]) -> Result + { + let mut rng = OsRng::new()?; + rng.try_fill_bytes(dest)?; + Ok(rng) + } + + fn try_jitter_new(dest: &mut [u8]) -> Result + { + let mut rng = JitterRng::new()?; + rng.try_fill_bytes(dest)?; + Ok(rng) + } + let mut switch_rng = None; - let mut result; match self.rng { - EntropySourceInner::Os(ref mut rng) => { - result = rng.try_fill_bytes(dest); - if result.is_err() { - // Fall back to JitterRng. - let mut rng2_result = JitterRng::new(); - if let Ok(mut rng2) = rng2_result { - result = rng2.try_fill_bytes(dest); // Can't fail - switch_rng = Some(EntropySourceInner::Jitter(rng2)); + EntropySource::None => { + let os_rng_result = try_os_new(dest); + match os_rng_result { + Ok(os_rng) => { + switch_rng = Some(EntropySource::Os(os_rng)); } - } - } - EntropySourceInner::Jitter(ref mut rng) => { - if self.counter < 8 { - result = rng.try_fill_bytes(dest); // use JitterRng - self.counter = (self.counter + 1) % 8; - } else { - // Try if OsRng is still unavailable - let os_rng_result = OsRng::new(); - if let Ok(mut os_rng) = os_rng_result { - result = os_rng.try_fill_bytes(dest); - if result.is_ok() { - switch_rng = Some(EntropySourceInner::Os(os_rng)); + Err(os_rng_error) => { + if let Ok(jitter_rng) = try_jitter_new(dest) { + switch_rng = Some(EntropySource::Jitter(jitter_rng)); } else { - result = rng.try_fill_bytes(dest); // use JitterRng + return Err(os_rng_error); } + } + } + } + EntropySource::Os(ref mut rng) => { + let os_rng_result = rng.try_fill_bytes(dest); + if os_rng_result.is_err() { + if let Ok(jitter_rng) = try_jitter_new(dest) { + switch_rng = Some(EntropySource::Jitter(jitter_rng)); } else { - result = rng.try_fill_bytes(dest); // use JitterRng + return os_rng_result; } } } + EntropySource::Jitter(ref mut rng) => { + if self.counter >= 8 { + if let Ok(os_rng) = try_os_new(dest) { + switch_rng = Some(EntropySource::Os(os_rng)); + } else { + self.counter = (self.counter + 1) % 8; + return rng.try_fill_bytes(dest); // use JitterRng + } + } else { + self.counter = (self.counter + 1) % 8; + return rng.try_fill_bytes(dest); // use JitterRng + } + } } if let Some(rng) = switch_rng { self.rng = rng; self.counter = 0; } - result + Ok(()) } } From 62108e75dfd1a13d0b648428be8ca0f1d3b547be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 22 Jan 2018 19:44:35 +0100 Subject: [PATCH 5/8] Use `fill_bytes_via_u32` in JitterRng --- src/jitter.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jitter.rs b/src/jitter.rs index a3914834b5d..3bf7963aac6 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -742,7 +742,12 @@ impl Rng for JitterRng { } fn fill_bytes(&mut self, dest: &mut [u8]) { - impls::fill_bytes_via_u64(self, dest) + // Fill using `next_u32`. This is faster for filling small slices (four + // bytes or less), while the overhead is negligible. + // + // This is done especially for wrappers that implement `next_u32` + // themselves via `fill_bytes`. + impls::fill_bytes_via_u32(self, dest) } } From 43a226a539397efff70169534256477b382c2d1e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 22 Jan 2018 20:11:32 +0100 Subject: [PATCH 6/8] Remove retry counter --- src/lib.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 317d4c60cd2..01dba27d482 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -998,7 +998,7 @@ impl Rng for ThreadRng { /// /// `EntropyRng` uses the interface for random numbers provided by the operating /// system ([`OsRng`]). If that returns an error, it will fall back to the -/// [`JitterRng`] entropy collector. Occasionally it will then check if `OsRng` +/// [`JitterRng`] entropy collector. Every time it will then check if `OsRng` /// is still not available, and switch back if possible. /// /// [`OsRng`]: os/struct.OsRng.html @@ -1007,7 +1007,6 @@ impl Rng for ThreadRng { #[derive(Debug)] pub struct EntropyRng { rng: EntropySource, - counter: u32, } #[cfg(feature="std")] @@ -1026,7 +1025,7 @@ impl EntropyRng { /// those are done on first use. This is done to make `new` infallible, /// and `try_fill_bytes` the only place to report errors. pub fn new() -> Self { - EntropyRng { rng: EntropySource::None, counter: 0u32 } + EntropyRng { rng: EntropySource::None } } } @@ -1087,22 +1086,15 @@ impl Rng for EntropyRng { } } EntropySource::Jitter(ref mut rng) => { - if self.counter >= 8 { - if let Ok(os_rng) = try_os_new(dest) { - switch_rng = Some(EntropySource::Os(os_rng)); - } else { - self.counter = (self.counter + 1) % 8; - return rng.try_fill_bytes(dest); // use JitterRng - } + if let Ok(os_rng) = try_os_new(dest) { + switch_rng = Some(EntropySource::Os(os_rng)); } else { - self.counter = (self.counter + 1) % 8; return rng.try_fill_bytes(dest); // use JitterRng } } } if let Some(rng) = switch_rng { self.rng = rng; - self.counter = 0; } Ok(()) } From a9e2f3ead7f17333affdf0daae7939bf43982593 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 1 Feb 2018 18:28:47 +0100 Subject: [PATCH 7/8] Add logging to EntropyRng --- src/lib.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 01dba27d482..0e603152e23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1067,26 +1067,41 @@ impl Rng for EntropyRng { switch_rng = Some(EntropySource::Os(os_rng)); } Err(os_rng_error) => { - if let Ok(jitter_rng) = try_jitter_new(dest) { - switch_rng = Some(EntropySource::Jitter(jitter_rng)); - } else { - return Err(os_rng_error); + warn!("EntropyRng: OsRng failed [falling back to JitterRng]: {}", + os_rng_error); + match try_jitter_new(dest) { + Ok(jitter_rng) => { + switch_rng = Some(EntropySource::Jitter(jitter_rng)); + } + Err(jitter_error) => { + warn!("EntropyRng: JitterRng failed: {}", + jitter_error); + return Err(os_rng_error); + } } } } } EntropySource::Os(ref mut rng) => { let os_rng_result = rng.try_fill_bytes(dest); - if os_rng_result.is_err() { - if let Ok(jitter_rng) = try_jitter_new(dest) { - switch_rng = Some(EntropySource::Jitter(jitter_rng)); - } else { - return os_rng_result; + if let Err(os_rng_error) = os_rng_result { + warn!("EntropyRng: OsRng failed [falling back to JitterRng]: {}", + os_rng_error); + match try_jitter_new(dest) { + Ok(jitter_rng) => { + switch_rng = Some(EntropySource::Jitter(jitter_rng)); + } + Err(jitter_error) => { + warn!("EntropyRng: JitterRng failed: {}", + jitter_error); + return Err(os_rng_error); + } } } } EntropySource::Jitter(ref mut rng) => { if let Ok(os_rng) = try_os_new(dest) { + info!("EntropyRng: OsRng available [switching back from JitterRng]"); switch_rng = Some(EntropySource::Os(os_rng)); } else { return rng.try_fill_bytes(dest); // use JitterRng From 1cff629f4a0d1e4ffdff89f411930fb6e512dca6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 2 Feb 2018 07:27:16 +0100 Subject: [PATCH 8/8] Silence unused variable warning --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e603152e23..23b417b1a80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1073,9 +1073,9 @@ impl Rng for EntropyRng { Ok(jitter_rng) => { switch_rng = Some(EntropySource::Jitter(jitter_rng)); } - Err(jitter_error) => { + Err(_jitter_error) => { warn!("EntropyRng: JitterRng failed: {}", - jitter_error); + _jitter_error); return Err(os_rng_error); } } @@ -1091,9 +1091,9 @@ impl Rng for EntropyRng { Ok(jitter_rng) => { switch_rng = Some(EntropySource::Jitter(jitter_rng)); } - Err(jitter_error) => { + Err(_jitter_error) => { warn!("EntropyRng: JitterRng failed: {}", - jitter_error); + _jitter_error); return Err(os_rng_error); } }