Skip to main content

secure_types/
writer.rs

1//! An [`std::io::Write`] adapter that appends into locked, zeroizing memory.
2
3use std::io::{self, Write};
4
5use crate::SecureBytes;
6
7/// An [`io::Write`] that appends everything written to it into a [`SecureBytes`].
8///
9/// Handing a secret to `serde_json::to_string`/`to_vec` leaves the plaintext in an
10/// ordinary `String`/`Vec` that nothing zeroizes, and `impl Serialize` cannot wipe
11/// that buffer for you — a `Serialize` impl only ever sees a generic
12/// `serde::Serializer`. Building the serializer around this writer instead keeps the
13/// only heap copy of the plaintext in memory that is locked while unused and zeroized
14/// on drop. Growth cannot leave a stale copy behind either: `SecureVec::reserve`
15/// zeroizes the old allocation after moving the elements.
16///
17/// # Example
18///
19/// ```
20/// use secure_types::{SecureBytes, SecureBytesWriter};
21/// use std::io::Write;
22///
23/// let mut buffer = SecureBytes::new_with_capacity(32).unwrap();
24/// write!(SecureBytesWriter::new(&mut buffer), "secret").unwrap();
25///
26/// buffer.unlock_slice(|bytes| assert_eq!(bytes, b"secret"));
27/// ```
28///
29/// This writer targets [`SecureBytes`]. If you want the result as a
30/// [`SecureString`](crate::SecureString), follow up with `SecureString::try_from`,
31/// which validates the UTF-8 in a single pass.
32///
33/// The `codec` feature's `encode` writes its own format into locked memory; this writer is
34/// for pointing some *other* serializer at locked memory.
35pub struct SecureBytesWriter<'a> {
36   bytes: &'a mut SecureBytes,
37}
38
39impl<'a> SecureBytesWriter<'a> {
40   /// Wraps `bytes`, which receives everything written to the returned writer.
41   pub fn new(bytes: &'a mut SecureBytes) -> Self {
42      Self { bytes }
43   }
44}
45
46impl Write for SecureBytesWriter<'_> {
47   fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
48      self
49         .bytes
50         .extend_from_slice(buf)
51         .map_err(|error| io::Error::new(io::ErrorKind::OutOfMemory, error))?;
52      Ok(buf.len())
53   }
54
55   /// Appends in one go, where the default implementation would call [`write`]
56   /// repeatedly and pay an `mprotect` pair per fragment.
57   ///
58   /// [`write`]: Write::write
59   fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
60      self
61         .bytes
62         .extend_from_slice(buf)
63         .map_err(|error| io::Error::new(io::ErrorKind::OutOfMemory, error))?;
64      Ok(())
65   }
66
67   fn flush(&mut self) -> io::Result<()> {
68      // Nothing is buffered outside the `SecureBytes`.
69      Ok(())
70   }
71}