use std::fmt;
use std::ops::{Deref, DerefMut};
#[allow(unsafe_code)]
pub fn zeroize(buf: &mut [u8]) {
for byte in buf.iter_mut() {
unsafe {
std::ptr::write_volatile(byte, 0);
}
}
}
pub trait ZeroizeOnDrop {
fn zeroize_contents(&mut self);
}
impl ZeroizeOnDrop for Vec<u8> {
fn zeroize_contents(&mut self) {
let cap = self.capacity();
self.resize(cap, 0);
zeroize(self.as_mut_slice());
}
}
impl ZeroizeOnDrop for String {
fn zeroize_contents(&mut self) {
let mut bytes = std::mem::take(self).into_bytes();
bytes.zeroize_contents();
}
}
pub struct Zeroizing<T: ZeroizeOnDrop> {
inner: T,
}
impl<T: ZeroizeOnDrop> Zeroizing<T> {
#[must_use]
pub fn new(value: T) -> Self {
Self { inner: value }
}
}
impl<T: ZeroizeOnDrop> fmt::Debug for Zeroizing<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Zeroizing([REDACTED])")
}
}
impl<T: ZeroizeOnDrop> Deref for Zeroizing<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: ZeroizeOnDrop> DerefMut for Zeroizing<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: ZeroizeOnDrop + Clone> Clone for Zeroizing<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T: ZeroizeOnDrop> Drop for Zeroizing<T> {
fn drop(&mut self) {
self.inner.zeroize_contents();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zeroize_clears_buffer() {
let mut buf = vec![0xDE, 0xAD, 0xBE, 0xEF];
zeroize(&mut buf);
assert!(buf.iter().all(|&b| b == 0), "all bytes should be zero");
}
#[test]
fn zeroize_empty_buffer() {
let mut buf: Vec<u8> = Vec::new();
zeroize(&mut buf); assert!(buf.is_empty());
}
#[test]
fn zeroize_contents_clears_vec_bytes() {
let mut vec = vec![0xFFu8; 64];
assert!(vec.iter().all(|&b| b == 0xFF));
vec.zeroize_contents();
assert!(
vec.iter().all(|&b| b == 0),
"vec contents should be zeroed after zeroize_contents",
);
}
#[test]
fn zeroize_contents_clears_string_bytes() {
let mut s = String::from("super-secret-password");
assert!(!s.is_empty());
s.zeroize_contents();
assert!(
s.is_empty(),
"string is emptied once its buffer is scrubbed"
);
let mut bytes = String::from("super-secret-password").into_bytes();
bytes.zeroize_contents();
assert!(
bytes.iter().all(|&b| b == 0),
"string-backed buffer must be zeroed after zeroize_contents",
);
}
#[test]
fn zeroize_contents_scrubs_spare_capacity() {
let mut vec = Vec::with_capacity(64);
vec.extend_from_slice(&[0xAAu8; 64]);
vec.truncate(8); let cap = vec.capacity();
vec.zeroize_contents();
assert_eq!(vec.len(), cap);
assert!(
vec.iter().all(|&b| b == 0),
"spare capacity must be scrubbed"
);
}
#[test]
fn drop_invokes_zeroize_contents() {
use std::cell::Cell;
use std::rc::Rc;
struct Spy {
zeroized: Rc<Cell<bool>>,
}
impl ZeroizeOnDrop for Spy {
fn zeroize_contents(&mut self) {
self.zeroized.set(true);
}
}
let flag = Rc::new(Cell::new(false));
{
let _z = Zeroizing::new(Spy {
zeroized: Rc::clone(&flag),
});
assert!(!flag.get(), "not zeroized before drop");
} assert!(flag.get(), "Zeroizing::drop must call zeroize_contents");
}
#[test]
fn debug_redacts_contents() {
let secret = Zeroizing::new(vec![1, 2, 3]);
let debug_output = format!("{secret:?}");
assert_eq!(debug_output, "Zeroizing([REDACTED])");
assert!(
!debug_output.contains('1'),
"debug output must not leak contents",
);
}
#[test]
fn deref_provides_access() {
let secret = Zeroizing::new(vec![10u8, 20, 30]);
assert_eq!(secret.len(), 3);
assert_eq!(secret[0], 10);
let mut secret_mut = Zeroizing::new(vec![0u8; 4]);
secret_mut[2] = 42;
assert_eq!(secret_mut[2], 42);
}
#[test]
fn clone_produces_independent_copy() {
let original = Zeroizing::new(vec![0xAA; 8]);
let mut cloned = original.clone();
cloned[0] = 0x00;
assert_eq!(original[0], 0xAA);
assert_eq!(cloned[0], 0x00);
}
#[test]
fn zeroizing_is_not_partial_eq() {
let a = Zeroizing::new(vec![1, 2, 3]);
let b = Zeroizing::new(vec![1, 2, 3]);
assert_eq!(&*a, &*b, "inner values should be equal via Deref");
}
}