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}