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/// For JSON in particular, `serialize_json_into_secure_string` (feature `serde_json`)
30/// wires this up for you.
31///
32/// This writer targets [`SecureBytes`]. If you want the result as a [`SecureString`]
33/// (what `serialize_json_into_secure_string` returns), follow up with
34/// [`SecureString::try_from`], which validates the UTF-8 in a single pass.
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.bytes.extend_from_slice(buf);
49      Ok(buf.len())
50   }
51
52   /// Appends in one go, where the default implementation would call [`write`]
53   /// repeatedly and pay an `mprotect` pair per fragment.
54   ///
55   /// [`write`]: Write::write
56   fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
57      self.bytes.extend_from_slice(buf);
58      Ok(())
59   }
60
61   fn flush(&mut self) -> io::Result<()> {
62      // Nothing is buffered outside the `SecureBytes`.
63      Ok(())
64   }
65}
66
67#[cfg(test)]
68mod tests {
69   use super::*;
70
71   #[test]
72   fn test_writer_appends_to_secure_bytes() {
73      let mut buffer = SecureBytes::new_with_capacity(8).unwrap();
74
75      {
76         let mut writer = SecureBytesWriter::new(&mut buffer);
77         writer.write_all(b"hello ").unwrap();
78         writer.write_all(b"world").unwrap();
79         assert_eq!(writer.write(b"!").unwrap(), 1);
80         writer.flush().unwrap();
81      }
82
83      buffer.unlock_slice(|bytes| assert_eq!(bytes, b"hello world!"));
84   }
85
86   #[test]
87   fn test_writer_grows_without_losing_data() {
88      let mut buffer = SecureBytes::new().unwrap();
89
90      {
91         let mut writer = SecureBytesWriter::new(&mut buffer);
92         // Far beyond the initial capacity, so the buffer reallocates repeatedly.
93         for _ in 0..64 {
94            writer.write_all(&[0xAB; 32]).unwrap();
95         }
96      }
97
98      buffer.unlock_slice(|bytes| {
99         assert_eq!(bytes.len(), 64 * 32);
100         assert!(bytes.iter().all(|byte| *byte == 0xAB));
101      });
102   }
103}