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}