pub(crate) fn zeroize_slice(bytes: &mut [u8]) {
for byte in bytes.iter_mut() {
unsafe { std::ptr::write_volatile(byte, 0) };
}
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
}
pub(crate) fn zeroize_string(s: &mut str) {
let bytes = unsafe { s.as_bytes_mut() };
zeroize_slice(bytes);
}
pub(crate) struct ScrubbedBytes(Vec<u8>);
impl ScrubbedBytes {
pub(crate) fn new(bytes: Vec<u8>) -> Self {
ScrubbedBytes(bytes)
}
pub(crate) fn as_slice(&self) -> &[u8] {
&self.0
}
}
impl Drop for ScrubbedBytes {
fn drop(&mut self) {
zeroize_slice(&mut self.0);
}
}
pub(crate) struct ScrubbedString(String);
impl ScrubbedString {
pub(crate) fn new(s: String) -> Self {
ScrubbedString(s)
}
pub(crate) fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl Drop for ScrubbedString {
fn drop(&mut self) {
zeroize_string(&mut self.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrappers_expose_their_payload_until_dropped() {
let b = ScrubbedBytes::new(b"seedbytes".to_vec());
assert_eq!(b.as_slice(), b"seedbytes");
let s = ScrubbedString::new("seedtext".to_string());
assert_eq!(s.as_bytes(), b"seedtext");
}
#[test]
fn zeroize_clears_every_byte() {
let mut buf = vec![0xAAu8; 64];
zeroize_slice(&mut buf);
assert!(buf.iter().all(|b| *b == 0), "every byte must be zeroed");
let mut text = "sensitive".to_string();
zeroize_string(&mut text);
assert!(
text.as_bytes().iter().all(|b| *b == 0),
"every byte of the string must be zeroed",
);
}
}