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, ExposeSecret};
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, ExposeSecret};
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, ExposeSecret, ExposeSecretMut};
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>(pub(crate) 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
67/// Debug implementation (always redacted).
68impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        f.write_str("[REDACTED]")
71    }
72}
73
74/// Opt-in Clone — only for types marked `CloneSafe`.
75#[cfg(feature = "zeroize")]
76impl<T: crate::CloneSafe> Clone for Dynamic<T> {
77    #[inline(always)]
78    fn clone(&self) -> Self {
79        Dynamic(self.0.clone())
80    }
81}
82
83/// # Additional conversions
84/// Wrap a byte slice into a [`Dynamic`] [`Vec<u8>`].
85impl From<&[u8]> for Dynamic<Vec<u8>> {
86    #[inline(always)]
87    fn from(slice: &[u8]) -> Self {
88        Self::new(slice.to_vec())
89    }
90}
91
92/// # Ergonomic helpers for common heap types
93impl Dynamic<String> {}
94
95impl<T> Dynamic<Vec<T>> {}
96
97/// # Convenient From impls
98/// Wrap a value in a [`Dynamic`] secret by boxing it.
99impl<T> From<T> for Dynamic<T> {
100    #[inline(always)]
101    fn from(value: T) -> Self {
102        Self(Box::new(value))
103    }
104}
105
106/// Wrap a boxed value in a [`Dynamic`] secret.
107impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
108    #[inline(always)]
109    fn from(boxed: Box<T>) -> Self {
110        Self(boxed)
111    }
112}
113
114/// Wrap a string slice in a [`Dynamic`] [`String`].
115impl From<&str> for Dynamic<String> {
116    #[inline(always)]
117    fn from(s: &str) -> Self {
118        Self(Box::new(s.to_string()))
119    }
120}
121
122#[cfg(feature = "ct-eq")]
123impl Dynamic<String> {
124    /// Constant-time equality comparison.
125    ///
126    /// Compares the byte contents of two `Dynamic<String>` instances in constant time
127    /// to prevent timing attacks. The strings are compared as UTF-8 byte sequences.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// # #[cfg(feature = "ct-eq")]
133    /// # {
134    /// use secure_gate::Dynamic;
135    /// let a: Dynamic<String> = Dynamic::new("secret".to_string());
136    /// let b: Dynamic<String> = Dynamic::new("secret".to_string());
137    /// assert!(a.ct_eq(&b));
138    /// # }
139    /// ```
140    #[inline]
141    pub fn ct_eq(&self, other: &Self) -> bool {
142        use crate::traits::ConstantTimeEq;
143        self.0.as_bytes().ct_eq(other.0.as_bytes())
144    }
145}
146
147#[cfg(feature = "ct-eq")]
148impl Dynamic<Vec<u8>> {
149    /// Constant-time equality comparison.
150    ///
151    /// Compares the byte contents of two `Dynamic<Vec<u8>>` instances in constant time
152    /// to prevent timing attacks. The vectors are compared as byte slices.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// # #[cfg(feature = "ct-eq")]
158    /// # {
159    /// use secure_gate::Dynamic;
160    /// let a: Dynamic<Vec<u8>> = Dynamic::new(vec![1, 2, 3]);
161    /// let b: Dynamic<Vec<u8>> = Dynamic::new(vec![1, 2, 3]);
162    /// assert!(a.ct_eq(&b));
163    /// # }
164    /// ```
165    #[inline]
166    pub fn ct_eq(&self, other: &Self) -> bool {
167        use crate::traits::ConstantTimeEq;
168        self.0.as_slice().ct_eq(other.0.as_slice())
169    }
170}
171
172/// Random generation — only available with `rand` feature.
173#[cfg(feature = "rand")]
174impl Dynamic<Vec<u8>> {
175    /// Generate fresh random bytes of the specified length using the OS RNG.
176    ///
177    /// This is a convenience method that generates random bytes directly
178    /// without going through `DynamicRandom`. Equivalent to:
179    /// `DynamicRandom::generate(len).into_inner()`
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// # #[cfg(feature = "rand")]
185    /// # {
186    /// use secure_gate::{Dynamic, ExposeSecret};
187    /// let random: Dynamic<Vec<u8>> = Dynamic::generate_random(64);
188    /// assert_eq!(random.len(), 64);
189    /// # }
190    /// ```
191    #[inline]
192    pub fn generate_random(len: usize) -> Self {
193        crate::random::DynamicRandom::generate(len).into_inner()
194    }
195
196    /// Try to generate random bytes for Dynamic.
197    ///
198    /// Returns an error if the RNG fails.
199    ///
200    /// # Example
201    ///
202    /// ```
203    /// # #[cfg(feature = "rand")]
204    /// # {
205    /// use secure_gate::Dynamic;
206    /// let random: Result<Dynamic<Vec<u8>>, rand::rand_core::OsError> = Dynamic::try_generate_random(64);
207    /// assert!(random.is_ok());
208    /// # }
209    /// ```
210    #[inline]
211    pub fn try_generate_random(len: usize) -> Result<Self, OsError> {
212        crate::random::DynamicRandom::try_generate(len)
213            .map(|rng: crate::random::DynamicRandom| rng.into_inner())
214    }
215}
216
217/// Zeroize integration.
218#[cfg(feature = "zeroize")]
219impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
220    fn zeroize(&mut self) {
221        self.0.zeroize();
222    }
223}
224
225/// Zeroize on drop integration.
226#[cfg(feature = "zeroize")]
227impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}