secure_gate/
zeroize.rs

1// src/zeroize.rs
2//! Zeroizing wrappers that automatically wipe sensitive data on drop.
3//!
4//! This module is only compiled when the `zeroize` feature is enabled.
5//!
6//! ### Types
7//!
8//! | Type                     | Underlying implementation          | Access method                     | Notes |
9//! |--------------------------|-------------------------------------|-----------------------------------|-------|
10//! | `FixedZeroizing<T>`      | `zeroize::Zeroizing<T>` (re-export) | `&*value` or `.deref()`           | Stack-only, zero-cost |
11//! | `DynamicZeroizing<T>`    | `secrecy::SecretBox<T>` wrapper     | `.expose_secret()` / `.expose_secret_mut()` | Heap-only, prevents cloning |
12//!
13//! Both types implement `ZeroizeOnDrop` and wipe the contained secret
14//! (including spare capacity for `Vec<u8>`/`String`) when dropped.
15//!
16//! # Examples
17//!
18//! ```
19//! use secure_gate::{DynamicZeroizing, FixedZeroizing};
20//! use secrecy::ExposeSecret;
21//!
22//! // Fixed-size zeroizing secret
23//! let key = FixedZeroizing::new([42u8; 32]);
24//! assert_eq!(key[..], [42u8; 32]);
25//! drop(key); // memory is zeroed here
26//!
27//! // Heap-allocated zeroizing secret
28//! let pw: DynamicZeroizing<String> = "hunter2".into();
29//! assert_eq!(pw.expose_secret(), "hunter2");
30//! drop(pw); // both used bytes and spare capacity are zeroed
31//! ```
32
33#[cfg(feature = "zeroize")]
34use zeroize::{DefaultIsZeroes, Zeroize, ZeroizeOnDrop, Zeroizing};
35
36#[cfg(feature = "zeroize")]
37use secrecy::{ExposeSecret, SecretBox};
38
39#[cfg(feature = "zeroize")]
40/// Re-export of `zeroize::Zeroizing<T>` for stack-allocated secrets.
41///
42/// This is the canonical zeroizing wrapper for fixed-size data.
43pub type FixedZeroizing<T> = Zeroizing<T>;
44
45#[cfg(feature = "zeroize")]
46/// Zeroizing wrapper for heap-allocated secrets.
47///
48/// Uses `secrecy::SecretBox<T>` internally to prevent accidental cloning
49/// while still providing zeroization of the full allocation (including spare capacity).
50pub struct DynamicZeroizing<T: ?Sized + Zeroize>(SecretBox<T>);
51
52#[cfg(feature = "zeroize")]
53impl<T: ?Sized + Zeroize> DynamicZeroizing<T> {
54    /// Creates a new `DynamicZeroizing` from a boxed value.
55    ///
56    /// The boxed value will be zeroed (including spare capacity) on drop.
57    #[inline(always)]
58    pub fn new(value: Box<T>) -> Self {
59        Self(SecretBox::new(value))
60    }
61}
62
63#[cfg(feature = "zeroize")]
64impl<T: ?Sized + Zeroize> core::fmt::Debug for DynamicZeroizing<T> {
65    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66        f.write_str("[REDACTED]")
67    }
68}
69
70#[cfg(feature = "zeroize")]
71impl<S: ?Sized + Zeroize> ExposeSecret<S> for DynamicZeroizing<S> {
72    #[inline(always)]
73    fn expose_secret(&self) -> &S {
74        self.0.expose_secret()
75    }
76}
77
78#[cfg(feature = "zeroize")]
79impl<T: Zeroize + DefaultIsZeroes> Zeroize for DynamicZeroizing<T> {
80    fn zeroize(&mut self) {
81        self.0.zeroize();
82    }
83}
84
85#[cfg(feature = "zeroize")]
86impl<T: ?Sized + Zeroize> ZeroizeOnDrop for DynamicZeroizing<T> {}
87
88/// Convenience conversions from non-zeroizing wrappers.
89#[cfg(feature = "zeroize")]
90impl<T: Zeroize> From<crate::Fixed<T>> for FixedZeroizing<T> {
91    #[inline(always)]
92    fn from(fixed: crate::Fixed<T>) -> Self {
93        Zeroizing::new(fixed.0)
94    }
95}
96
97#[cfg(feature = "zeroize")]
98impl<T: ?Sized + Zeroize> From<crate::Dynamic<T>> for DynamicZeroizing<T> {
99    #[inline(always)]
100    fn from(dynamic: crate::Dynamic<T>) -> Self {
101        Self(SecretBox::new(dynamic.0))
102    }
103}
104
105/// Zeroize impls for the non-zeroizing wrappers when the `zeroize` feature is active.
106#[cfg(feature = "zeroize")]
107impl<T: Zeroize> Zeroize for crate::Fixed<T> {
108    fn zeroize(&mut self) {
109        self.0.zeroize();
110    }
111}
112
113#[cfg(feature = "zeroize")]
114impl<T: Zeroize + DefaultIsZeroes> Zeroize for crate::Dynamic<T> {
115    fn zeroize(&mut self) {
116        self.0.zeroize();
117    }
118}
119
120#[cfg(feature = "zeroize")]
121impl<T: Zeroize> ZeroizeOnDrop for crate::Fixed<T> {}
122
123#[cfg(feature = "zeroize")]
124impl<T: ?Sized + Zeroize> ZeroizeOnDrop for crate::Dynamic<T> {}
125
126/// Ergonomic `.into()` support for zeroizing heap secrets.
127#[cfg(feature = "zeroize")]
128impl<T: Zeroize + Send + 'static> From<T> for DynamicZeroizing<T> {
129    #[inline(always)]
130    fn from(value: T) -> Self {
131        Self::new(Box::new(value))
132    }
133}
134
135#[cfg(feature = "zeroize")]
136impl<T: Zeroize + DefaultIsZeroes + Send + 'static> From<Box<T>> for DynamicZeroizing<T> {
137    #[inline(always)]
138    fn from(boxed: Box<T>) -> Self {
139        Self::new(boxed)
140    }
141}
142
143#[cfg(feature = "zeroize")]
144impl From<&str> for DynamicZeroizing<String> {
145    #[inline(always)]
146    fn from(s: &str) -> Self {
147        Self::new(Box::new(s.to_string()))
148    }
149}