//! Types types for dealing with (secret-) values //! //! These types use type level coloring to make accidential leackage of secrets extra hard. Both [Secret] and [Public] own their data, but the memory backing //! [Secret] is special: //! - as it is heap allocated, we can actively zeroize the memory before freeing it. //! - guard pages before and after each allocation trap accidential sequential reads that creep towards our secrets //! - the memory is mlocked, e.g. it is never swapped use crate::{ sodium::{rng, zeroize}, util::{cpy, mutating}, }; use lazy_static::lazy_static; use libsodium_sys as libsodium; use std::{ collections::HashMap, convert::TryInto, fmt, ops::{Deref, DerefMut}, os::raw::c_void, ptr::null_mut, sync::Mutex, }; // This might become a problem in library usage; it's effectively a memory // leak which probably isn't a problem right now because most memory will // be reused… lazy_static! { static ref SECRET_CACHE: Mutex = Mutex::new(SecretMemoryPool::new()); } /// Pool that stores secret memory allocations /// /// Allocation of secret memory is expensive. Thus, this struct provides a /// pool of secret memory, readily available to yield protected, slices of /// memory. /// /// Further information about the protection in place can be found in in the /// [libsodium documentation](https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations) #[derive(Debug)] // TODO check on Debug derive, is that clever pub struct SecretMemoryPool { pool: HashMap>, } impl SecretMemoryPool { /// Create a new [SecretMemoryPool] #[allow(clippy::new_without_default)] pub fn new() -> Self { let pool = HashMap::new(); Self { pool } } /// Return secrete back to the pool for future re-use /// /// This consumes the [Secret], but its memory is re-used. pub fn release(&mut self, mut s: Secret) { unsafe { self.release_by_ref(&mut s); } std::mem::forget(s); } /// Return secret back to the pool for future re-use, by slice /// /// # Safety /// /// After calling this function on a [Secret], the secret must never be /// used again for anything. unsafe fn release_by_ref(&mut self, s: &mut Secret) { s.zeroize(); let Secret { ptr: secret } = s; // don't call Secret::drop, that could cause a double free self.pool.entry(N).or_default().push(*secret); } /// Take protected memory from the pool, allocating new one if no suitable /// chunk is found in the inventory. /// /// The secret is guaranteed to be full of nullbytes /// /// # Safety /// /// This function contains an unsafe call to [libsodium::sodium_malloc]. /// This call has no known safety invariants, thus nothing can go wrong™. /// However, just like normal `malloc()` this can return a null ptr. Thus /// the returned pointer is checked for null; causing the program to panic /// if it is null. pub fn take(&mut self) -> Secret { let entry = self.pool.entry(N).or_default(); let secret = entry.pop().unwrap_or_else(|| { let ptr = unsafe { libsodium::sodium_malloc(N) }; assert!( !ptr.is_null(), "libsodium::sodium_mallloc() returned a null ptr" ); ptr }); let mut s = Secret { ptr: secret }; s.zeroize(); s } } impl Drop for SecretMemoryPool { /// # Safety /// /// The drop implementation frees the contained elements using /// [libsodium::sodium_free]. This is safe as long as every `*mut c_void` /// contained was initialized with a call to [libsodium::sodium_malloc] fn drop(&mut self) { for ptr in self.pool.drain().flat_map(|(_, x)| x.into_iter()) { unsafe { libsodium::sodium_free(ptr); } } } } /// # Safety /// /// No safety implications are known, since the `*mut c_void` in /// is essentially used like a `&mut u8` [SecretMemoryPool]. unsafe impl Send for SecretMemoryPool {} /// Store for a secret /// /// Uses memory allocated with [libsodium::sodium_malloc], /// esentially can do the same things as `[u8; N].as_mut_ptr()`. pub struct Secret { ptr: *mut c_void, } impl Clone for Secret { fn clone(&self) -> Self { let mut new = Self::zero(); new.secret_mut().clone_from_slice(self.secret()); new } } impl Drop for Secret { fn drop(&mut self) { self.zeroize(); // the invariant that the [Secret] is not used after the // `release_by_ref` call is guaranteed, since this is a drop implementation unsafe { SECRET_CACHE.lock().unwrap().release_by_ref(self) }; self.ptr = null_mut(); } } impl Secret { pub fn from_slice(slice: &[u8]) -> Self { let mut new_self = Self::zero(); new_self.secret_mut().copy_from_slice(slice); new_self } /// Returns a new [Secret] that is zero initialized pub fn zero() -> Self { // Using [SecretMemoryPool] here because this operation is expensive, // yet it is used in hot loops let s = SECRET_CACHE.lock().unwrap().take(); assert_eq!(s.secret(), &[0u8; N]); s } /// Returns a new [Secret] that is randomized pub fn random() -> Self { mutating(Self::zero(), |r| r.randomize()) } /// Sets all data of an existing secret to null bytes pub fn zeroize(&mut self) { zeroize(self.secret_mut()); } /// Sets all data an existing secret to random bytes pub fn randomize(&mut self) { rng(self.secret_mut()); } /// Borrows the data pub fn secret(&self) -> &[u8; N] { // - calling `from_raw_parts` is safe, because `ptr` is initalized with // as `N` byte allocation from the creation of `Secret` onwards. `ptr` // stays valid over the full lifetime of `Secret` // // - calling uwnrap is safe, because we can guarantee that the slice has // exactly the required size `N` to create an array of `N` elements. let ptr = self.ptr as *const u8; let slice = unsafe { std::slice::from_raw_parts(ptr, N) }; slice.try_into().unwrap() } /// Borrows the data mutably pub fn secret_mut(&mut self) -> &mut [u8; N] { // the same safety argument as for `secret()` holds let ptr = self.ptr as *mut u8; let slice = unsafe { std::slice::from_raw_parts_mut(ptr, N) }; slice.try_into().unwrap() } } /// The Debug implementation of [Secret] does not reveal the secret data, /// instead a placeholder `` is used impl fmt::Debug for Secret { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("") } } /// Contains information in the form of a byte array that may be known to the /// public // TODO: We should get rid of the Public type; just use a normal value #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Public { pub value: [u8; N], } impl Public { /// Create a new [Public] from a byte slice pub fn from_slice(value: &[u8]) -> Self { mutating(Self::zero(), |r| cpy(value, &mut r.value)) } /// Create a new [Public] from a byte array pub fn new(value: [u8; N]) -> Self { Self { value } } /// Create a zero initialized [Public] pub fn zero() -> Self { Self { value: [0u8; N] } } /// Create a random initialized [Public] pub fn random() -> Self { mutating(Self::zero(), |r| r.randomize()) } /// Randomize all bytes in an existing [Public] pub fn randomize(&mut self) { rng(&mut self.value); } } /// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter] pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("[{}]=")?; if v.len() > 64 { for byte in &v[..32] { std::fmt::LowerHex::fmt(byte, fmt)?; } fmt.write_str("…")?; for byte in &v[v.len() - 32..] { std::fmt::LowerHex::fmt(byte, fmt)?; } } else { for byte in v { std::fmt::LowerHex::fmt(byte, fmt)?; } } Ok(()) } impl fmt::Debug for Public { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { debug_crypto_array(&self.value, fmt) } } impl Deref for Public { type Target = [u8; N]; fn deref(&self) -> &[u8; N] { &self.value } } impl DerefMut for Public { fn deref_mut(&mut self) -> &mut [u8; N] { &mut self.value } } #[cfg(test)] mod test { use super::*; /// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations /// promises us that allocated memory is initialized with this magic byte const SODIUM_MAGIC_BYTE: u8 = 0xdb; /// must be called before any interaction with libsodium fn init() { unsafe { libsodium_sys::sodium_init() }; } /// checks that whe can malloc with libsodium #[test] fn sodium_malloc() { init(); const N: usize = 8; let ptr = unsafe { libsodium_sys::sodium_malloc(N) }; let mem = unsafe { std::slice::from_raw_parts(ptr as *mut u8, N) }; assert_eq!(mem, &[SODIUM_MAGIC_BYTE; N]) } /// checks that whe can free with libsodium #[test] fn sodium_free() { init(); const N: usize = 8; let ptr = unsafe { libsodium_sys::sodium_malloc(N) }; unsafe { libsodium_sys::sodium_free(ptr) } } /// check that we can alloc using the magic pool #[test] fn secret_memory_pool_take() { init(); const N: usize = 0x100; let mut pool = SecretMemoryPool::new(); let secret: Secret = pool.take(); assert_eq!(secret.secret(), &[0; N]); } /// check that a secrete lives, even if its [SecretMemoryPool] is deleted #[test] fn secret_memory_pool_drop() { init(); const N: usize = 0x100; let mut pool = SecretMemoryPool::new(); let secret: Secret = pool.take(); std::mem::drop(pool); assert_eq!(secret.secret(), &[0; N]); } /// check that a secrete can be reborn, freshly initialized with zero #[test] fn secret_memory_pool_release() { init(); const N: usize = 1; let mut pool = SecretMemoryPool::new(); let mut secret: Secret = pool.take(); let old_secret_ptr = secret.ptr; secret.secret_mut()[0] = 0x13; pool.release(secret); // now check that we get the same ptr let new_secret: Secret = pool.take(); assert_eq!(old_secret_ptr, new_secret.ptr); // and that the secret was zeroized assert_eq!(new_secret.secret(), &[0; N]); } }