#![allow(missing_debug_implementations)]
#![allow(unsafe_code)]
use crate::ffi::sodium;
use crate::traits::*;
use std::borrow::BorrowMut;
use std::fmt::{self, Debug, Formatter};
use std::ops::{Deref, DerefMut};
use std::thread;
#[cfg_attr(any(target_arch = "x86", target_arch = "x86_64"), repr(align(4096)))]
#[cfg_attr(all(target_arch = "aarch64", target_vendor = "apple"), repr(align(16384)))]
#[cfg_attr(all(target_arch = "aarch64", not(target_vendor = "apple")), repr(align(65536)))]
#[cfg_attr(not(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
)), repr(align(65536)))]
pub struct Secret<T: Bytes> {
data: T,
}
pub struct RefMut<'a, T: Bytes> {
data: &'a mut T,
}
impl<T: Bytes> Secret<T> {
#[allow(clippy::new_ret_no_self)]
pub fn new<F, U>(f: F) -> U
where
F: FnOnce(RefMut<'_, T>) -> U,
{
tested!(size_of::<T>() == 0);
assert!(
align_of::<Self>() >= page_size::get(),
"secrets: Secret alignment ({}) is smaller than the system page size ({}); \
this target is not yet supported",
align_of::<Self>(),
page_size::get(),
);
let mut secret = Self {
data: T::uninitialized(),
};
assert!(
unsafe { sodium::mlock(&raw mut secret.data) },
"secrets: unable to mlock memory for a Secret"
);
f(RefMut::new(&mut secret.data))
}
}
impl<T: Bytes + Zeroable> Secret<T> {
pub fn zero<F, U>(f: F) -> U
where
F: FnOnce(RefMut<'_, T>) -> U,
{
Self::new(|mut s| {
s.zero();
f(s)
})
}
pub fn from<F, U>(v: &mut T, f: F) -> U
where
F: FnOnce(RefMut<'_, T>) -> U,
{
Self::new(|mut s| {
let _ = &v; unsafe { v.transfer(s.borrow_mut()) };
f(s)
})
}
}
impl<T: Bytes + Randomizable> Secret<T> {
pub fn random<F, U>(f: F) -> U
where
F: FnOnce(RefMut<'_, T>) -> U,
{
Self::new(|mut s| {
s.randomize();
f(s)
})
}
}
impl<T: Bytes> Drop for Secret<T> {
fn drop(&mut self) {
if unsafe { !sodium::munlock(&raw mut self.data) } {
assert!(
thread::panicking(),
"secrets: unable to munlock memory for a Secret"
);
}
}
}
impl<'a, T: Bytes> RefMut<'a, T> {
pub(crate) const fn new(data: &'a mut T) -> Self {
Self { data }
}
}
impl<T: Bytes + Clone> Clone for RefMut<'_, T> {
fn clone(&self) -> Self {
panic!("secrets: a Secret may not be cloned")
}
}
impl<T: Bytes> Debug for RefMut<'_, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{{ {} bytes redacted }}", self.data.size())
}
}
impl<T: Bytes> Deref for RefMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<T: Bytes> DerefMut for RefMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}
impl<T: Bytes> PartialEq for RefMut<'_, T> {
fn eq(&self, rhs: &Self) -> bool {
self.data.constant_eq(rhs.data)
}
}
impl<T: Bytes> Eq for RefMut<'_, T> {}
#[cfg(test)]
mod tests {
use super::*;
use std::ptr;
#[test]
fn it_defaults_to_garbage_data() {
Secret::<u16>::new(|s| assert_eq!(*s, 0xdbdb));
}
#[test]
fn it_aligns_to_at_least_the_page_size() {
let page = page_size::get();
assert!(align_of::<Secret<u8>>() >= page);
assert!(align_of::<Secret<[u8; 32]>>() >= page);
assert!(align_of::<Secret<[u64; 4]>>() >= page);
}
#[test]
fn it_does_not_share_a_page_between_secrets() {
Secret::<u64>::zero(|a| {
let addr_a = &raw const *a as usize;
let addr_b = Secret::<u64>::zero(|b| &raw const *b as usize);
let page = page_size::get();
assert_ne!(addr_a / page, addr_b / page);
});
}
#[test]
fn it_zeroes_when_leaving_scope() {
unsafe {
let mut ptr: *const _ = ptr::null();
Secret::<u128>::new(|mut s| {
sodium::memrandom(s.as_mut_bytes());
ptr = &*s;
});
assert_eq!(*ptr, 0);
}
}
#[test]
fn it_initializes_from_values() {
Secret::from(&mut 5, |s| assert_eq!(*s, 5_u8));
}
#[test]
fn it_zeroes_values_when_initializing_from() {
let mut value = 5_u8;
Secret::from(&mut value, |_| {});
assert_eq!(value, 0);
}
#[test]
fn it_compares_equality() {
Secret::<u32>::from(&mut 0x0123_4567, |a| {
Secret::<u32>::from(&mut 0x0123_4567, |b| {
assert_eq!(a, b);
});
});
}
#[test]
fn it_compares_inequality() {
Secret::<[u64; 4]>::random(|a| {
Secret::<[u64; 4]>::random(|b| {
assert_ne!(a, b);
});
});
}
#[test]
fn it_preserves_secrecy() {
Secret::<[u64; 2]>::zero(|s| {
assert_eq!(
format!("{{ {} bytes redacted }}", 16),
format!("{:?}", s),
);
});
}
#[test]
#[should_panic(expected = "secrets: a Secret may not be cloned")]
fn it_panics_when_cloned() {
#[allow(clippy::redundant_clone)]
Secret::<u16>::zero(|s| {
let _ = s.clone();
});
}
#[test]
#[should_panic(expected = "secrets: unable to mlock memory for a Secret")]
fn it_detects_sodium_mlock_failure() {
sodium::fail();
Secret::<u8>::zero(|_| {});
}
#[test]
#[should_panic(expected = "secrets: unable to munlock memory for a Secret")]
fn it_detects_sodium_munlock_failure() {
Secret::<u8>::zero(|_| sodium::fail());
}
}