secure_gate/
dynamic.rs

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