Skip to main content

secure_gate/
dynamic.rs

1#[cfg(feature = "alloc")]
2extern crate alloc;
3use alloc::boxed::Box;
4
5#[cfg(feature = "rand")]
6use rand::{rngs::OsRng, TryRngCore};
7
8#[cfg(feature = "encoding-base64")]
9use crate::traits::decoding::base64_url::FromBase64UrlStr;
10#[cfg(feature = "encoding-bech32")]
11use crate::traits::decoding::bech32::FromBech32Str;
12#[cfg(feature = "encoding-bech32m")]
13use crate::traits::decoding::bech32m::FromBech32mStr;
14#[cfg(feature = "encoding-hex")]
15use crate::traits::decoding::hex::FromHexStr;
16
17/// Dynamic-sized heap-allocated secure secret wrapper.
18///
19/// This is a thin wrapper around `Box<T>` with enforced explicit exposure.
20/// Suitable for dynamic-sized secrets like `String` or `Vec<u8>`.
21/// The inner field is private, forcing all access through explicit methods.
22///
23/// Security invariants:
24/// - No `Deref` or `AsRef` — prevents silent access.
25/// - `Debug` is always redacted.
26/// - With `zeroize`, wipes the entire allocation on drop (including spare capacity).
27pub struct Dynamic<T: ?Sized> {
28    inner: Box<T>,
29}
30
31impl<T: ?Sized> Dynamic<T> {
32    /// Wrap a value by boxing it.
33    ///
34    /// Uses `Into<Box<T>>` for flexibility.
35    #[inline(always)]
36    pub fn new<U>(value: U) -> Self
37    where
38        U: Into<Box<T>>,
39    {
40        let inner = value.into();
41        Self { inner }
42    }
43}
44
45/// # Ergonomic helpers for common heap types
46impl Dynamic<String> {}
47
48impl<T> Dynamic<Vec<T>> {}
49
50// From impls for Dynamic types
51impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
52    /// Wrap a boxed value in a [`Dynamic`] secret.
53    #[inline(always)]
54    fn from(boxed: Box<T>) -> Self {
55        Self { inner: boxed }
56    }
57}
58
59impl From<&[u8]> for Dynamic<Vec<u8>> {
60    /// Wrap a byte slice in a [`Dynamic`] [`Vec<u8>`].
61    #[inline(always)]
62    fn from(slice: &[u8]) -> Self {
63        Self::new(slice.to_vec())
64    }
65}
66
67impl From<&str> for Dynamic<String> {
68    /// Wrap a string slice in a [`Dynamic`] [`String`].
69    #[inline(always)]
70    fn from(input: &str) -> Self {
71        Self::new(input.to_string())
72    }
73}
74
75impl<T: 'static> From<T> for Dynamic<T> {
76    /// Wrap a value in a [`Dynamic`] secret by boxing it.
77    #[inline(always)]
78    fn from(value: T) -> Self {
79        Self {
80            inner: Box::new(value),
81        }
82    }
83}
84
85impl crate::ExposeSecret for Dynamic<String> {
86    type Inner = String;
87    #[inline(always)]
88    fn with_secret<F, R>(&self, f: F) -> R
89    where
90        F: FnOnce(&String) -> R,
91    {
92        f(&self.inner)
93    }
94    #[inline(always)]
95    fn expose_secret(&self) -> &String {
96        &self.inner
97    }
98    #[inline(always)]
99    fn len(&self) -> usize {
100        self.inner.len()
101    }
102}
103
104impl<T> crate::ExposeSecret for Dynamic<Vec<T>> {
105    type Inner = Vec<T>;
106    #[inline(always)]
107    fn with_secret<F, R>(&self, f: F) -> R
108    where
109        F: FnOnce(&Vec<T>) -> R,
110    {
111        f(&self.inner)
112    }
113    #[inline(always)]
114    fn expose_secret(&self) -> &Vec<T> {
115        &self.inner
116    }
117    #[inline(always)]
118    fn len(&self) -> usize {
119        self.inner.len() * core::mem::size_of::<T>()
120    }
121}
122
123impl crate::ExposeSecretMut for Dynamic<String> {
124    #[inline(always)]
125    fn with_secret_mut<F, R>(&mut self, f: F) -> R
126    where
127        F: FnOnce(&mut String) -> R,
128    {
129        f(&mut self.inner)
130    }
131    #[inline(always)]
132    fn expose_secret_mut(&mut self) -> &mut String {
133        &mut self.inner
134    }
135}
136
137impl<T> crate::ExposeSecretMut for Dynamic<Vec<T>> {
138    #[inline(always)]
139    fn with_secret_mut<F, R>(&mut self, f: F) -> R
140    where
141        F: FnOnce(&mut Vec<T>) -> R,
142    {
143        f(&mut self.inner)
144    }
145    #[inline(always)]
146    fn expose_secret_mut(&mut self) -> &mut Vec<T> {
147        &mut self.inner
148    }
149}
150
151// Random generation — only available with `rand` feature.
152#[cfg(feature = "rand")]
153impl Dynamic<alloc::vec::Vec<u8>> {
154    /// Fill with fresh random bytes of the specified length using the System RNG.
155    ///
156    /// Panics on RNG failure for fail-fast crypto code. Guarantees secure entropy
157    /// from system sources.
158    #[inline]
159    pub fn from_random(len: usize) -> Self {
160        let mut bytes = vec![0u8; len];
161        OsRng
162            .try_fill_bytes(&mut bytes)
163            .expect("OsRng failure is a program error");
164        Self::from(bytes)
165    }
166}
167
168// Decoding constructors — only available with encoding features.
169#[cfg(feature = "encoding-hex")]
170impl Dynamic<alloc::vec::Vec<u8>> {
171    /// Decode a hex string into a Dynamic secret.
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// # #[cfg(feature = "encoding-hex")]
177    /// use secure_gate::{Dynamic, ExposeSecret};
178    /// let hex_string = "424344";
179    /// let secret = Dynamic::try_from_hex(hex_string).unwrap();
180    /// assert_eq!(secret.expose_secret().len(), 3);
181    /// ```
182    pub fn try_from_hex(s: &str) -> Result<Self, crate::error::HexError> {
183        let bytes = s.try_from_hex()?;
184        Ok(Self::new(bytes))
185    }
186}
187
188#[cfg(feature = "encoding-base64")]
189impl Dynamic<alloc::vec::Vec<u8>> {
190    /// Decode a base64url string into a Dynamic secret.
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// # #[cfg(feature = "encoding-base64")]
196    /// use secure_gate::{Dynamic, ExposeSecret};
197    /// let b64_string = "QkNE";
198    /// let secret = Dynamic::try_from_base64url(b64_string).unwrap();
199    /// assert_eq!(secret.expose_secret().len(), 3);
200    /// ```
201    pub fn try_from_base64url(s: &str) -> Result<Self, crate::error::Base64Error> {
202        let bytes = s.try_from_base64url()?;
203        Ok(Self::new(bytes))
204    }
205}
206
207#[cfg(feature = "encoding-bech32")]
208impl Dynamic<alloc::vec::Vec<u8>> {
209    /// Decode a bech32 string into a Dynamic secret, discarding the HRP.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// # #[cfg(feature = "encoding-bech32")]
215    /// use secure_gate::{Dynamic, ExposeSecret, ToBech32};
216    /// let original: Dynamic<Vec<u8>> = Dynamic::new(vec![1, 2, 3, 4]);
217    /// let bech32_string = original.with_secret(|s| s.to_bech32("test"));
218    /// let decoded = Dynamic::try_from_bech32(&bech32_string).unwrap();
219    /// // HRP "test" is discarded, bytes are stored
220    /// ```
221    pub fn try_from_bech32(s: &str) -> Result<Self, crate::error::Bech32Error> {
222        let (_hrp, bytes) = s.try_from_bech32()?;
223        Ok(Self::new(bytes))
224    }
225}
226
227#[cfg(feature = "encoding-bech32m")]
228impl Dynamic<alloc::vec::Vec<u8>> {
229    /// Decode a bech32m string into a Dynamic secret, discarding the HRP.
230    ///
231    /// # Example
232    ///
233    /// ```
234    /// # #[cfg(feature = "encoding-bech32m")]
235    /// use secure_gate::Dynamic;
236    /// // Note: Bech32m strings must be valid Bech32m format
237    /// let bech32m_string = "abc1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw";
238    /// let secret = Dynamic::try_from_bech32m(bech32m_string);
239    /// // Returns Result<Dynamic<Vec<u8>>, Bech32Error>
240    /// ```
241    pub fn try_from_bech32m(s: &str) -> Result<Self, crate::error::Bech32Error> {
242        let (_hrp, bytes) = s.try_from_bech32m()?;
243        Ok(Self::new(bytes))
244    }
245}
246
247#[cfg(feature = "ct-eq")]
248impl<T: ?Sized> crate::ConstantTimeEq for Dynamic<T>
249where
250    T: crate::ConstantTimeEq,
251{
252    fn ct_eq(&self, other: &Self) -> bool {
253        self.inner.ct_eq(&other.inner)
254    }
255}
256
257// Constant-time equality for Dynamic types
258#[cfg(feature = "ct-eq")]
259impl Dynamic<String> {
260    /// Constant-time equality comparison.
261    ///
262    /// Compares the byte contents of two instances in constant time
263    /// to prevent timing attacks.
264    #[inline]
265    pub fn ct_eq(&self, other: &Self) -> bool {
266        use crate::traits::ConstantTimeEq;
267        self.inner.as_bytes().ct_eq(other.inner.as_bytes())
268    }
269}
270
271#[cfg(feature = "ct-eq")]
272impl Dynamic<Vec<u8>> {
273    /// Constant-time equality comparison.
274    ///
275    /// Compares the byte contents of two instances in constant time
276    /// to prevent timing attacks.
277    #[inline]
278    pub fn ct_eq(&self, other: &Self) -> bool {
279        use crate::traits::ConstantTimeEq;
280        self.inner.as_slice().ct_eq(other.inner.as_slice())
281    }
282}
283
284#[cfg(feature = "ct-eq-hash")]
285impl<T> crate::ConstantTimeEqExt for Dynamic<T>
286where
287    T: AsRef<[u8]> + crate::ConstantTimeEq + ?Sized,
288{
289    fn len(&self) -> usize {
290        (*self.inner).as_ref().len()
291    }
292
293    fn ct_eq_hash(&self, other: &Self) -> bool {
294        crate::traits::ct_eq_hash_bytes((*self.inner).as_ref(), (*other.inner).as_ref())
295    }
296    // ct_eq_auto uses default impl
297}
298
299// Redacted Debug implementation
300impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
301    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
302        f.write_str("[REDACTED]")
303    }
304}
305
306#[cfg(feature = "cloneable")]
307impl<T: crate::CloneableType> Clone for Dynamic<T> {
308    fn clone(&self) -> Self {
309        Self::new(self.inner.clone())
310    }
311}
312
313#[cfg(feature = "serde-serialize")]
314impl<T> serde::Serialize for Dynamic<T>
315where
316    T: crate::SerializableType,
317{
318    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
319    where
320        S: serde::Serializer,
321    {
322        self.inner.serialize(serializer)
323    }
324}
325
326// Serde deserialization for Dynamic<String>
327#[cfg(feature = "serde-deserialize")]
328impl<'de> serde::Deserialize<'de> for Dynamic<String> {
329    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
330    where
331        D: serde::Deserializer<'de>,
332    {
333        let s: String = serde::Deserialize::deserialize(deserializer)?;
334        Ok(Dynamic::new(s))
335    }
336}
337
338// Serde deserialization for Dynamic<Vec<u8>>
339#[cfg(feature = "serde-deserialize")]
340impl<'de> serde::Deserialize<'de> for Dynamic<alloc::vec::Vec<u8>> {
341    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
342    where
343        D: serde::Deserializer<'de>,
344    {
345        let vec: alloc::vec::Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
346        Ok(Dynamic::new(vec))
347    }
348}
349
350// Zeroize integration
351#[cfg(feature = "zeroize")]
352impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
353    fn zeroize(&mut self) {
354        self.inner.zeroize();
355    }
356}
357
358/// Zeroize on drop integration
359#[cfg(feature = "zeroize")]
360impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}