diff --git a/src/lib.rs b/src/lib.rs index e0ad902..e93d88b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,18 +26,15 @@ extern crate tiny_keccak; pub mod error; mod into_fr; pub mod poly; +mod secret; pub mod serde_impl; -use std::env; use std::fmt; use std::hash::{Hash, Hasher}; -use std::mem::{size_of, size_of_val}; use std::ptr::copy_nonoverlapping; use byteorder::{BigEndian, ByteOrder}; -use errno::errno; use init_with::InitWith; -use memsec::{memzero, mlock, munlock}; use pairing::bls12_381::{Bls12, Fr, G1Affine, G2Affine, G1, G2}; use pairing::{CurveAffine, CurveProjective, Engine, Field}; use rand::{ChaChaRng, OsRng, Rand, Rng, SeedableRng}; @@ -46,56 +43,7 @@ use tiny_keccak::sha3_256; use error::{Error, Result}; use into_fr::IntoFr; use poly::{Commitment, Poly}; - -lazy_static! { - // Sets whether or not `mlock`ing is enabled. Memory locking is enabled by default; it can be - // disabled by setting the environment variable `MLOCK_SECRETS=false`. This is useful when you - // are running on a system where you do not have the ability to increase the systems locked - // memory limit (which can be found using the Unix command: `ulimit -l`). For example, we - // disable `mlock`ing of secrets when testing crates that depend on `threshold_crypto` when - // running in Travis CI because Travis has a locked memory limit of 64kb, which we may exceed - // while running `cargo test`. Disabling `mlock`ing for secret values allows secret keys to be - // swapped/core-dumped to disk, resulting in unmanaged copies of secrets to hang around in - // memory; this is significantly less secure than enabling memory locking (the default). Only - // set `MLOCK_SECRETS=false` in development/testing. - pub(crate) static ref SHOULD_MLOCK_SECRETS: bool = match env::var("MLOCK_SECRETS") { - Ok(s) => s.parse().unwrap_or(true), - _ => true, - }; - - // The size in bytes of a single `Fr` field element. - static ref FR_SIZE: usize = size_of::(); -} - -// Overwrites a single field element with zeros. -pub(crate) fn clear_fr(fr_ptr: *mut u8) { - unsafe { memzero(fr_ptr, *FR_SIZE) }; -} - -/// Marks a type as containing one or more secret prime field elements. -pub(crate) trait ContainsSecret { - /// Calls the `mlock` system call on the region of memory allocated for the secret prime field - /// element or elements. This results in that region of memory not being being copied to disk, - /// either in a swap to disk or core dump. This method is called on every created instance of - /// a secret type. - /// - /// # Errors - /// - /// An `Error::MlockFailed` is returned if we failed to `mlock` the secret data. - fn mlock_secret_memory(&self) -> Result<()>; - - /// Undoes the `mlock` on the secret region of memory via the `munlock` system call. - /// - /// # Errors - /// - /// An `Error::MunlockFailed` is returned if we failed to `munlock` the secret data; this - /// method is called on each secret type when it goes out of scope. - fn munlock_secret_memory(&self) -> Result<()>; - - /// Overwrites the secret prime field element or elements with zeros; this method is called on - /// each each secret type when it goes out of scope. - fn zero_secret_memory(&self); -} +use secret::{clear_fr, ContainsSecret, MemRange, FR_SIZE}; /// Wrapper for a byte array, whose `Debug` implementation outputs shortened hexadecimal strings. pub struct HexBytes<'a>(pub &'a [u8]); @@ -303,15 +251,15 @@ impl Clone for SecretKey { } } -// Zeroes out and unlocks the memory allocated from the `SecretKey`'s field element. -// -// # Panics -// -// Panics if we fail to unlock the memory containing the field element. +/// Zeroes out and unlocks the memory allocated from the `SecretKey`'s field element. +/// +/// # Panics +/// +/// Panics if we fail to unlock the memory containing the field element. impl Drop for SecretKey { fn drop(&mut self) { - self.zero_secret_memory(); - if let Err(e) = self.munlock_secret_memory() { + self.zero_secret(); + if let Err(e) = self.munlock_secret() { panic!("Failed to drop `SecretKey`: {}", e); } } @@ -325,47 +273,10 @@ impl fmt::Debug for SecretKey { } impl ContainsSecret for SecretKey { - fn mlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } + fn secret_memory(&self) -> MemRange { let ptr = &*self.0 as *const Fr as *mut u8; - let n_bytes = size_of_val(&*self.0); - let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; - if mlock_succeeded { - Ok(()) - } else { - let e = Error::MlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn munlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let ptr = &*self.0 as *const Fr as *mut u8; - let n_bytes = size_of_val(&*self.0); - let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; - if munlock_succeeded { - Ok(()) - } else { - let e = Error::MunlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn zero_secret_memory(&self) { - let ptr = &*self.0 as *const Fr as *mut u8; - clear_fr(ptr); + let n_bytes = *FR_SIZE; + MemRange { ptr, n_bytes } } } @@ -415,7 +326,7 @@ impl SecretKey { } clear_fr(fr_ptr as *mut u8); let sk = SecretKey(boxed_fr); - sk.mlock_secret_memory()?; + sk.mlock_secret()?; Ok(sk) } diff --git a/src/poly.rs b/src/poly.rs index 5b63358..165d284 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -22,13 +22,13 @@ use std::hash::{Hash, Hasher}; use std::mem::size_of_val; use std::{cmp, iter, ops}; -use errno::errno; -use memsec::{memzero, mlock, munlock}; use pairing::bls12_381::{Fr, G1Affine, G1}; use pairing::{CurveAffine, CurveProjective, Field}; use rand::Rng; -use super::{clear_fr, ContainsSecret, Error, IntoFr, Result, FR_SIZE, SHOULD_MLOCK_SECRETS}; +use error::Result; +use into_fr::IntoFr; +use secret::{clear_fr, ContainsSecret, MemRange, Safe}; /// A univariate polynomial in the prime field. #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -42,8 +42,8 @@ pub struct Poly { /// /// # Panics /// -/// Panics if we have hit the system's locked memory limit when `mlock`ing the new instance of -/// `Poly`. +/// Panics if we reach the system's locked memory limit when locking the polynomial's coefficients +/// into RAM. impl Clone for Poly { fn clone(&self) -> Self { Poly::try_from(self.coeff.clone()) @@ -60,8 +60,7 @@ impl Debug for Poly { /// # Panics /// -/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been -/// truncated from the `coeff` vector. +/// Panics if we reach the system's locked memory limit. #[cfg_attr(feature = "cargo-clippy", allow(suspicious_op_assign_impl))] impl> ops::AddAssign for Poly { fn add_assign(&mut self, rhs: B) { @@ -69,8 +68,7 @@ impl> ops::AddAssign for Poly { let rhs_len = rhs.borrow().coeff.len(); if rhs_len > len { self.coeff.resize(rhs_len, Fr::zero()); - let n_coeffs_added = rhs_len - len; - if let Err(e) = self.extend_mlock(n_coeffs_added) { + if let Err(e) = self.mlock_secret() { panic!( "Failed to extend `Poly` memory lock during add-assign: {}", e @@ -80,9 +78,7 @@ impl> ops::AddAssign for Poly { for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { self_c.add_assign(rhs_c); } - if let Err(e) = self.remove_zeros() { - panic!("Failed to unlock `Poly` memory during add-assign: {}", e); - } + self.remove_zeros(); } } @@ -105,24 +101,19 @@ impl> ops::Add for Poly { /// # Panics /// -/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been -/// truncated from the `coeff` vector. +/// Panics if we reach the system's locked memory limit. impl<'a> ops::Add for Poly { type Output = Poly; fn add(mut self, rhs: Fr) -> Self::Output { - if self.coeff.is_empty() { - if !rhs.is_zero() { - self.coeff.push(rhs); - if let Err(e) = self.extend_mlock(1) { - panic!("Failed to extend `Poly` memory lock during add: {}", e); - } + if self.is_zero() && !rhs.is_zero() { + self.coeff.push(rhs); + if let Err(e) = self.mlock_secret() { + panic!("Failed to extend `Poly` memory lock during add: {}", e); } } else { self.coeff[0].add_assign(&rhs); - if let Err(e) = self.remove_zeros() { - panic!("Failed to unlock `Poly` memory during add: {}", e); - } + self.remove_zeros(); } self } @@ -138,16 +129,14 @@ impl<'a> ops::Add for Poly { /// # Panics /// -/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been -/// truncated from the `coeff` vector. +/// Panics if we reach the system's locked memory limit. impl> ops::SubAssign for Poly { fn sub_assign(&mut self, rhs: B) { let len = self.coeff.len(); let rhs_len = rhs.borrow().coeff.len(); if rhs_len > len { self.coeff.resize(rhs_len, Fr::zero()); - let n_coeffs_added = rhs_len - len; - if let Err(e) = self.extend_mlock(n_coeffs_added) { + if let Err(e) = self.mlock_secret() { panic!( "Failed to extend `Poly` memory lock during sub-assign: {}", e @@ -157,9 +146,7 @@ impl> ops::SubAssign for Poly { for (self_c, rhs_c) in self.coeff.iter_mut().zip(&rhs.borrow().coeff) { self_c.sub_assign(rhs_c); } - if let Err(e) = self.remove_zeros() { - panic!("Failed to unlock `Poly` memory during sub-assign: {}", e); - } + self.remove_zeros(); } } @@ -201,8 +188,7 @@ impl<'a> ops::Sub for Poly { /// # Panics /// -/// Panics if we hit the system's locked memory limit or if we fail to unlock memory that has been -/// truncated from the `coeff` vector. +/// Panics if we reach the system's locked memory limit. // Clippy thinks using any `+` and `-` in a `Mul` implementation is suspicious. #[cfg_attr(feature = "cargo-clippy", allow(suspicious_arithmetic_impl))] impl<'a, B: Borrow> ops::Mul for &'a Poly { @@ -210,19 +196,20 @@ impl<'a, B: Borrow> ops::Mul for &'a Poly { fn mul(self, rhs: B) -> Self::Output { let rhs = rhs.borrow(); - if rhs.coeff.is_empty() || self.coeff.is_empty() { + if rhs.is_zero() || self.is_zero() { return Poly::zero(); } - let mut coeff = vec![Fr::zero(); self.coeff.len() + rhs.borrow().coeff.len() - 1]; - let mut s; // TODO: Mlock and zero on drop. - for (i, cs) in self.coeff.iter().enumerate() { - for (j, cr) in rhs.coeff.iter().enumerate() { - s = *cs; - s.mul_assign(cr); - coeff[i + j].add_assign(&s); + let n_coeffs = self.coeff.len() + rhs.coeff.len() - 1; + let mut coeffs = vec![Fr::zero(); n_coeffs]; + let mut tmp = Safe::new(Box::new(Fr::zero())); + for (i, ca) in self.coeff.iter().enumerate() { + for (j, cb) in rhs.coeff.iter().enumerate() { + *tmp = *ca; + tmp.mul_assign(cb); + coeffs[i + j].add_assign(&*tmp); } } - Poly::try_from(coeff) + Poly::try_from(coeffs) .unwrap_or_else(|e| panic!("Failed to create a new `Poly` during muliplication: {}", e)) } } @@ -244,7 +231,8 @@ impl> ops::MulAssign for Poly { impl ops::MulAssign for Poly { fn mul_assign(&mut self, rhs: Fr) { if rhs.is_zero() { - *self = Poly::zero(); + self.zero_secret(); + self.coeff.clear(); } else { for c in &mut self.coeff { c.mul_assign(&rhs); @@ -262,10 +250,7 @@ impl<'a> ops::Mul<&'a Fr> for Poly { fn mul(mut self, rhs: &Fr) -> Self::Output { if rhs.is_zero() { - self.zero_secret_memory(); - if let Err(e) = self.munlock_secret_memory() { - panic!("Failed to unlock `Poly` during multiplication: {}", e); - } + self.zero_secret(); self.coeff.clear(); } else { self.coeff.iter_mut().for_each(|c| c.mul_assign(rhs)); @@ -312,8 +297,8 @@ impl ops::Mul for Poly { /// Panics if we fail to munlock the `coeff` vector. impl Drop for Poly { fn drop(&mut self) { - self.zero_secret_memory(); - if let Err(e) = self.munlock_secret_memory() { + self.zero_secret(); + if let Err(e) = self.munlock_secret() { panic!("Failed to munlock `Poly` during drop: {}", e); } } @@ -333,56 +318,10 @@ impl From> for Poly { } impl ContainsSecret for Poly { - fn mlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } + fn secret_memory(&self) -> MemRange { let ptr = self.coeff.as_ptr() as *mut u8; let n_bytes = size_of_val(self.coeff.as_slice()); - if n_bytes == 0 { - return Ok(()); - } - let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; - if mlock_succeeded { - Ok(()) - } else { - let e = Error::MlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn munlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let ptr = self.coeff.as_ptr() as *mut u8; - let n_bytes = size_of_val(self.coeff.as_slice()); - if n_bytes == 0 { - return Ok(()); - } - let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; - if munlock_succeeded { - Ok(()) - } else { - let e = Error::MunlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn zero_secret_memory(&self) { - let ptr = self.coeff.as_ptr() as *mut u8; - let n_bytes = size_of_val(self.coeff.as_slice()); - unsafe { - memzero(ptr, n_bytes); - } + MemRange { ptr, n_bytes } } } @@ -396,7 +335,7 @@ impl Poly { /// Returns an `Error::MlockFailed` if we have reached the systems's locked memory limit. pub fn try_from(coeff: Vec) -> Result { let poly = Poly { coeff }; - poly.mlock_secret_memory()?; + poly.mlock_secret()?; Ok(poly) } @@ -433,6 +372,11 @@ impl Poly { Poly { coeff: vec![] } } + /// Returns `true` if the polynomial is the constant value `0`. + pub fn is_zero(&self) -> bool { + self.coeff.iter().all(|coeff| coeff.is_zero()) + } + /// Returns the polynomial with constant value `1`. This constructor is identical to /// `Poly::try_one()` in every way except that this constructor panics if locking the `coeff` /// vector into RAM fails, whereas `Poly::try_one()` returns an `Err`. @@ -596,16 +540,10 @@ impl Poly { } /// Removes all trailing zero coefficients. - /// - /// # Errors - /// - /// An `Error::MunlockFailed` is returned if we failed to `munlock` the truncated portion of - /// the `coeff` vector. - fn remove_zeros(&mut self) -> Result<()> { + fn remove_zeros(&mut self) { let zeros = self.coeff.iter().rev().take_while(|c| c.is_zero()).count(); let len = self.coeff.len() - zeros; self.coeff.truncate(len); - self.truncate_mlock(zeros) } /// Returns the unique polynomial `f` of degree `samples.len() - 1` with the given values @@ -645,57 +583,6 @@ impl Poly { Ok(poly) } - // Removes the `mlock` for `len` elements that have been truncated from the `coeff` vector. - fn truncate_mlock(&self, len: usize) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let n_bytes_truncated = *FR_SIZE * len; - if n_bytes_truncated == 0 { - return Ok(()); - } - unsafe { - let ptr = self.coeff.as_ptr().offset(self.coeff.len() as isize) as *mut u8; - let munlock_succeeded = munlock(ptr, n_bytes_truncated); - if munlock_succeeded { - Ok(()) - } else { - let e = Error::MunlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes: n_bytes_truncated, - }; - Err(e) - } - } - } - - // Extends the `mlock` on the `coeff` vector when `len` new elements are added. - fn extend_mlock(&self, len: usize) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let n_bytes_extended = *FR_SIZE * len; - if n_bytes_extended == 0 { - return Ok(()); - } - let offset = (self.coeff.len() - len) as isize; - unsafe { - let ptr = self.coeff.as_ptr().offset(offset) as *mut u8; - let mlock_succeeded = mlock(ptr, n_bytes_extended); - if mlock_succeeded { - Ok(()) - } else { - let e = Error::MunlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes: n_bytes_extended, - }; - Err(e) - } - } - } - /// Generates a non-redacted debug string. This method differs from /// the `Debug` implementation in that it *does* leak the secret prime /// field elements. @@ -799,7 +686,7 @@ impl Clone for BivarPoly { degree: self.degree, coeff: self.coeff.clone(), }; - if let Err(e) = poly.mlock_secret_memory() { + if let Err(e) = poly.mlock_secret() { panic!("Failed to clone `BivarPoly`: {}", e); } poly @@ -811,8 +698,8 @@ impl Clone for BivarPoly { /// Panics if we fail to munlock the `coeff` vector. impl Drop for BivarPoly { fn drop(&mut self) { - self.zero_secret_memory(); - if let Err(e) = self.munlock_secret_memory() { + self.zero_secret(); + if let Err(e) = self.munlock_secret() { panic!("Failed to munlock `BivarPoly` during drop: {}", e); } } @@ -829,56 +716,10 @@ impl Debug for BivarPoly { } impl ContainsSecret for BivarPoly { - fn mlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let ptr = self.coeff.as_ptr() as *mut u8; + fn secret_memory(&self) -> MemRange { + let ptr = self.coeff.as_ptr() as *const Fr as *mut u8; let n_bytes = size_of_val(self.coeff.as_slice()); - if n_bytes == 0 { - return Ok(()); - } - let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; - if mlock_succeeded { - Ok(()) - } else { - let e = Error::MlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn munlock_secret_memory(&self) -> Result<()> { - if !*SHOULD_MLOCK_SECRETS { - return Ok(()); - } - let ptr = self.coeff.as_ptr() as *mut u8; - let n_bytes = size_of_val(self.coeff.as_slice()); - if n_bytes == 0 { - return Ok(()); - } - let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; - if munlock_succeeded { - Ok(()) - } else { - let e = Error::MunlockFailed { - errno: errno(), - addr: format!("{:?}", ptr), - n_bytes, - }; - Err(e) - } - } - - fn zero_secret_memory(&self) { - let ptr = self.coeff.as_ptr() as *mut u8; - let n_bytes = size_of_val(self.coeff.as_slice()); - unsafe { - memzero(ptr, n_bytes); - } + MemRange { ptr, n_bytes } } } @@ -907,7 +748,7 @@ impl BivarPoly { degree, coeff: (0..coeff_pos(degree + 1, 0)).map(|_| rng.gen()).collect(), }; - poly.mlock_secret_memory()?; + poly.mlock_secret()?; Ok(poly) } diff --git a/src/secret.rs b/src/secret.rs new file mode 100644 index 0000000..c68c776 --- /dev/null +++ b/src/secret.rs @@ -0,0 +1,187 @@ +//! Utilities for working with secret values. This module includes functionality for locking (and +//! unlocking) memory into RAM and overwriting memory with zeros. + +use std::env; +use std::mem::{size_of, size_of_val}; +use std::ops::{Deref, DerefMut}; + +use errno::errno; +use memsec::{memzero, mlock, munlock}; +use pairing::bls12_381::Fr; + +use error::{Error, Result}; + +lazy_static! { + /// Sets whether or not `mlock`ing is enabled. Memory locking is enabled by default; it can be + /// disabled by setting the environment variable `MLOCK_SECRETS=false`. This is useful when you + /// are running on a system where you do not have the ability to increase the systems locked + /// memory limit (which can be found using the Unix command: `ulimit -l`). For example, we + /// disable `mlock`ing of secrets when testing crates that depend on `threshold_crypto` when + /// running in Travis CI because Travis has a locked memory limit of 64kb, which we may exceed + /// while running `cargo test`. Disabling `mlock`ing for secret values allows secret keys to + /// be swapped/core-dumped to disk, resulting in unmanaged copies of secrets to hang around in + /// memory; this is significantly less secure than enabling memory locking (the default). Only + /// set `MLOCK_SECRETS=false` in development/testing. + pub(crate) static ref SHOULD_MLOCK_SECRETS: bool = match env::var("MLOCK_SECRETS") { + Ok(s) => s.parse().unwrap_or(true), + _ => true, + }; + + /// The size in bytes of a single field element. + pub(crate) static ref FR_SIZE: usize = size_of::(); +} + +/// Overwrites a single field element with zeros. +pub(crate) fn clear_fr(fr_ptr: *mut u8) { + unsafe { memzero(fr_ptr, *FR_SIZE) }; +} + +pub(crate) struct MemRange { + pub ptr: *mut u8, + pub n_bytes: usize, +} + +/// Marks a type as containing some secret value. +pub(crate) trait ContainsSecret { + /// Returns the range of memory marked as secret. + fn secret_memory(&self) -> MemRange; + + /// Locks a region of memory marked as secret into RAM. + /// + /// The region of memory marked as secret will not be copied to disk, for example, during a + /// swap-to-disk or core dump. This method should be called upon instantiation of every type + /// that implements `ContainsSecret`. + /// + /// Operating systems set a limit on the ammount of memory that a process may lock into RAM. + /// Due to this limitation, this method returns a `Result` in the event that memory locking + /// fails. + /// + /// # Errors + /// + /// An `Error::MlockFailed` is returned if we reach the system's locked memory limit or if we + /// attempt to lock an invalid region of memory. + fn mlock_secret(&self) -> Result<()> { + if !*SHOULD_MLOCK_SECRETS { + return Ok(()); + } + let MemRange { ptr, n_bytes } = self.secret_memory(); + if n_bytes == 0 { + return Ok(()); + } + let mlock_succeeded = unsafe { mlock(ptr, n_bytes) }; + if mlock_succeeded { + Ok(()) + } else { + let e = Error::MlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + /// Unlocks the memory lock for a region of memory marked as secret. If the secret region of + /// memory had not previosly been locked via the `.mlock_secret()` method, then this method + /// does nothing. + /// + /// Once this method has been called, the secret region of memory will no longer be protected + /// from being copied to disk. This method should be called upon destruction of every type that + /// implements `ContainsSecret`. + /// + /// # Errors + /// + /// An `Error::MlockFailed` is returned if we attempt to lock an invalid region memory. + fn munlock_secret(&self) -> Result<()> { + if !*SHOULD_MLOCK_SECRETS { + return Ok(()); + } + let MemRange { ptr, n_bytes } = self.secret_memory(); + if n_bytes == 0 { + return Ok(()); + } + let munlock_succeeded = unsafe { munlock(ptr, n_bytes) }; + if munlock_succeeded { + Ok(()) + } else { + let e = Error::MunlockFailed { + errno: errno(), + addr: format!("{:?}", ptr), + n_bytes, + }; + Err(e) + } + } + + /// Overwrites the secret region of memory with zeros. + /// + /// This method should be called upon destruction of every type that implements `ContainsSecret`. + fn zero_secret(&self) { + let MemRange { ptr, n_bytes } = self.secret_memory(); + unsafe { memzero(ptr, n_bytes) }; + } +} + +/// A wrapper around temporary values to ensuer that they are locked into RAM and cleared on drop. +/// +/// `Safe` is meant to be used a wrapper around `T`, where `T` is either an `&mut U` or +/// `Box`. +pub(crate) struct Safe(T); + +impl Deref for Safe +where + T: DerefMut, +{ + type Target = T::Target; + + fn deref(&self) -> &Self::Target { + &*(self.0) + } +} + +impl DerefMut for Safe +where + T: DerefMut, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *(self.0) + } +} + +impl Drop for Safe +where + T: DerefMut, +{ + fn drop(&mut self) { + self.zero_secret(); + if let Err(e) = self.munlock_secret() { + panic!("Failed to drop `Safe`: {}", e); + } + } +} + +impl ContainsSecret for Safe +where + T: DerefMut, +{ + fn secret_memory(&self) -> MemRange { + let ptr = &*self.0 as *const T::Target as *mut u8; + let n_bytes = size_of_val(&*self.0); + MemRange { ptr, n_bytes } + } +} + +impl Safe +where + T: DerefMut, +{ + pub(crate) fn new(x: T) -> Self { + Safe::try_new(x).unwrap_or_else(|e| panic!("Failed to create `Safe`: {}", e)) + } + + pub(crate) fn try_new(x: T) -> Result { + let safe = Safe(x); + safe.mlock_secret()?; + Ok(safe) + } +}