secure_gate/
dynamic.rs

1// ==========================================================================
2// src/dynamic.rs
3// ==========================================================================
4
5extern crate alloc;
6
7use alloc::boxed::Box;
8
9/// Heap-allocated secure secret wrapper.
10///
11/// This is a thin wrapper around `Box<T>` with enforced explicit exposure.
12/// Suitable for dynamic-sized secrets like `String` or `Vec<u8>`.
13///
14/// Security invariants:
15/// - No `Deref` or `AsRef` — prevents silent access.
16/// - `Debug` is always redacted.
17/// - With `zeroize`, wipes the entire allocation on drop (including spare capacity).
18///
19/// # Examples
20///
21/// Basic usage:
22/// ```
23/// use secure_gate::Dynamic;
24/// let secret: Dynamic<String> = "hunter2".into();
25/// assert_eq!(secret.expose_secret(), "hunter2");
26/// ```
27///
28/// Mutable access:
29/// ```
30/// use secure_gate::Dynamic;
31/// let mut secret = Dynamic::<String>::new("pass".to_string());
32/// secret.expose_secret_mut().push('!');
33/// assert_eq!(secret.expose_secret(), "pass!");
34/// ```
35///
36/// With `zeroize` (automatic wipe):
37/// ```
38/// # #[cfg(feature = "zeroize")]
39/// # {
40/// use secure_gate::Dynamic;
41/// let secret = Dynamic::<Vec<u8>>::new(vec![1u8; 32]);
42/// drop(secret); // heap wiped automatically
43/// # }
44/// ```
45pub struct Dynamic<T: ?Sized>(Box<T>);
46
47impl<T: ?Sized> Dynamic<T> {
48    /// Wrap an already-boxed value.
49    ///
50    /// Zero-cost — just wraps the `Box`.
51    #[inline(always)]
52    pub fn new_boxed(value: Box<T>) -> Self {
53        Dynamic(value)
54    }
55
56    /// Wrap a value by boxing it.
57    ///
58    /// Uses `Into<Box<T>>` for flexibility.
59    #[inline(always)]
60    pub fn new<U>(value: U) -> Self
61    where
62        U: Into<Box<T>>,
63    {
64        Dynamic(value.into())
65    }
66
67    /// Expose the inner value for read-only access.
68    ///
69    /// This is the **only** way to read the secret — loud and auditable.
70    #[inline(always)]
71    pub const fn expose_secret(&self) -> &T {
72        &self.0
73    }
74
75    /// Expose the inner value for mutable access.
76    ///
77    /// This is the **only** way to mutate the secret — loud and auditable.
78    #[inline(always)]
79    pub fn expose_secret_mut(&mut self) -> &mut T {
80        &mut self.0
81    }
82
83
84    /// Convert to a non-cloneable variant.
85    ///
86    /// Prevents accidental cloning of the secret.
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// use secure_gate::{Dynamic, DynamicNoClone};
92    /// let secret = Dynamic::<String>::new("no copy".to_string());
93    /// let no_clone: DynamicNoClone<String> = secret.no_clone();
94    /// assert_eq!(no_clone.expose_secret(), "no copy");
95    /// ```
96    #[inline(always)]
97    pub fn no_clone(self) -> crate::DynamicNoClone<T> {
98        crate::DynamicNoClone::new(self.0)
99    }
100}
101
102// Explicit zeroization — only available with `zeroize` feature
103#[cfg(feature = "zeroize")]
104impl<T: ?Sized + zeroize::Zeroize> Dynamic<T> {
105    /// Explicitly zeroize the secret immediately.
106    ///
107    /// This is useful when you want to wipe memory before the value goes out of scope,
108    /// or when you want to make the zeroization intent explicit in the code.
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// # #[cfg(feature = "zeroize")]
114    /// # {
115    /// use secure_gate::Dynamic;
116    /// let mut password = Dynamic::<String>::new("secret".to_string());
117    /// // ... use password ...
118    /// password.zeroize_now();  // Explicit wipe - makes intent clear
119    /// # }
120    /// ```
121    #[inline]
122    pub fn zeroize_now(&mut self) {
123        self.0.zeroize();
124    }
125}
126
127impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129        f.write_str("[REDACTED]")
130    }
131}
132
133// Clone impls — gated correctly
134#[cfg(not(feature = "zeroize"))]
135impl<T: Clone> Clone for Dynamic<T> {
136    #[inline(always)]
137    fn clone(&self) -> Self {
138        Dynamic(self.0.clone())
139    }
140}
141
142#[cfg(feature = "zeroize")]
143impl<T: Clone + zeroize::Zeroize> Clone for Dynamic<T> {
144    #[inline(always)]
145    fn clone(&self) -> Self {
146        Dynamic(self.0.clone())
147    }
148}
149
150// === Ergonomic helpers for common heap types ===
151impl Dynamic<String> {
152    #[inline(always)]
153    pub const fn len(&self) -> usize {
154        self.0.len()
155    }
156
157    #[inline(always)]
158    pub const fn is_empty(&self) -> bool {
159        self.0.is_empty()
160    }
161}
162
163impl<T> Dynamic<Vec<T>> {
164    #[inline(always)]
165    pub const fn len(&self) -> usize {
166        self.0.len()
167    }
168
169    #[inline(always)]
170    pub const fn is_empty(&self) -> bool {
171        self.0.is_empty()
172    }
173}
174
175// === Convenient From impls ===
176impl<T> From<T> for Dynamic<T> {
177    #[inline(always)]
178    fn from(value: T) -> Self {
179        Self(Box::new(value))
180    }
181}
182
183impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
184    #[inline(always)]
185    fn from(boxed: Box<T>) -> Self {
186        Self(boxed)
187    }
188}
189
190impl From<&str> for Dynamic<String> {
191    #[inline(always)]
192    fn from(s: &str) -> Self {
193        Self(Box::new(s.to_string()))
194    }
195}
196
197// Constant-time equality — only available with `conversions` feature
198#[cfg(feature = "conversions")]
199impl<T> Dynamic<T>
200where
201    T: ?Sized + AsRef<[u8]>,
202{
203    #[inline]
204    pub fn ct_eq(&self, other: &Self) -> bool {
205        use crate::conversions::SecureConversionsExt;
206        self.expose_secret()
207            .as_ref()
208            .ct_eq(other.expose_secret().as_ref())
209    }
210}
211
212// Random generation — only available with `rand` feature
213#[cfg(feature = "rand")]
214impl Dynamic<Vec<u8>> {
215    /// Generate fresh random bytes of the specified length using the OS RNG.
216    ///
217    /// This is a convenience method that generates random bytes directly
218    /// without going through `DynamicRng`. Equivalent to:
219    /// `DynamicRng::generate(len).into_inner()`
220    ///
221    /// # Example
222    ///
223    /// ```
224    /// # #[cfg(feature = "rand")]
225    /// # {
226    /// use secure_gate::Dynamic;
227    /// let random: Dynamic<Vec<u8>> = Dynamic::generate_random(64);
228    /// assert_eq!(random.len(), 64);
229    /// # }
230    /// ```
231    #[inline]
232    pub fn generate_random(len: usize) -> Self {
233        crate::rng::DynamicRng::generate(len).into_inner()
234    }
235}
236
237// Zeroize integration
238#[cfg(feature = "zeroize")]
239impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
240    fn zeroize(&mut self) {
241        self.0.zeroize();
242    }
243}
244
245#[cfg(feature = "zeroize")]
246impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}