use std::collections::TryReserveError; use zeroize::Zeroizing; /// The smallest capacity allocated when a `SecretBuffer` first grows. const MIN_CAPACITY: usize = 256; /// A growable byte buffer that never leaves copies of its contents in freed /// memory. /// /// A plain `Vec` abandons its old allocation, unwiped, whenever it grows. This /// buffer grows by allocating a larger buffer, copying the contents across, /// and zeroizing the old allocation before releasing it. The final allocation /// is zeroized when the buffer is dropped. #[derive(Default)] pub(crate) struct SecretBuffer { bytes: Zeroizing>, } impl SecretBuffer { /// Creates an empty buffer without allocating. pub(crate) fn new() -> Self { Self::default() } /// Returns the buffer's contents. pub(crate) fn as_slice(&self) -> &[u8] { &self.bytes } /// Ensures there is room for `additional` more bytes without the /// underlying `Vec` reallocating on its own. fn reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { let required = self.bytes.len().saturating_add(additional); if required <= self.bytes.capacity() { return Ok(()); } let capacity = required .max(self.bytes.capacity().saturating_mul(2)) .max(MIN_CAPACITY); let mut grown = Vec::new(); grown.try_reserve_exact(capacity)?; grown.extend_from_slice(&self.bytes); // Replacing the `Zeroizing` wrapper drops the old one, which wipes the // old allocation before freeing it. self.bytes = Zeroizing::new(grown); Ok(()) } } impl cbor4ii::core::enc::Write for SecretBuffer { type Error = TryReserveError; fn push(&mut self, input: &[u8]) -> Result<(), Self::Error> { self.reserve(input.len())?; self.bytes.extend_from_slice(input); Ok(()) } } #[cfg(test)] mod tests { use cbor4ii::core::enc::Write as _; use super::*; #[test] fn growth_preserves_contents() { let mut buffer = SecretBuffer::new(); let mut expected = Vec::new(); for i in 0..1000u32 { let chunk = i.to_be_bytes(); buffer.push(&chunk).unwrap(); expected.extend_from_slice(&chunk); } assert_eq!(buffer.as_slice(), expected.as_slice()); } #[test] fn extending_never_outgrows_the_reservation() { let mut buffer = SecretBuffer::new(); for len in [1, 255, 256, 257, 1000, 5000] { buffer.reserve(len).unwrap(); let reserved = buffer.bytes.as_ptr(); buffer.push(&vec![0xa5; len]).unwrap(); // The push must land in the allocation `reserve` prepared, not in // one the `Vec` obtained by reallocating behind our back. assert_eq!(buffer.bytes.as_ptr(), reserved); } } }