secure_gate/
dynamic.rs

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