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    /// Consume the wrapper and return the inner `Box<T>`.
84    ///
85    /// Note: If `zeroize` is enabled, prefer dropping the `Dynamic` to ensure wiping.
86    #[inline(always)]
87    pub fn into_inner(self) -> Box<T> {
88        self.0
89    }
90
91    /// Convert to a non-cloneable variant.
92    ///
93    /// Prevents accidental cloning of the secret.
94    ///
95    /// # Example
96    ///
97    /// ```
98    /// use secure_gate::{Dynamic, DynamicNoClone};
99    /// let secret = Dynamic::<String>::new("no copy".to_string());
100    /// let no_clone: DynamicNoClone<String> = secret.no_clone();
101    /// assert_eq!(no_clone.expose_secret(), "no copy");
102    /// ```
103    #[inline(always)]
104    pub fn no_clone(self) -> crate::DynamicNoClone<T> {
105        crate::DynamicNoClone::new(self.0)
106    }
107}
108
109impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
110    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111        f.write_str("[REDACTED]")
112    }
113}
114
115// Clone impls — gated correctly
116#[cfg(not(feature = "zeroize"))]
117impl<T: Clone> Clone for Dynamic<T> {
118    #[inline(always)]
119    fn clone(&self) -> Self {
120        Dynamic(self.0.clone())
121    }
122}
123
124#[cfg(feature = "zeroize")]
125impl<T: Clone + zeroize::Zeroize> Clone for Dynamic<T> {
126    #[inline(always)]
127    fn clone(&self) -> Self {
128        Dynamic(self.0.clone())
129    }
130}
131
132// === Ergonomic helpers for common heap types ===
133impl Dynamic<String> {
134    pub fn finish_mut(&mut self) -> &mut String {
135        let s = &mut *self.0;
136        s.shrink_to_fit();
137        s
138    }
139
140    #[inline(always)]
141    pub const fn len(&self) -> usize {
142        self.0.len()
143    }
144
145    #[inline(always)]
146    pub const fn is_empty(&self) -> bool {
147        self.0.is_empty()
148    }
149}
150
151impl<T> Dynamic<Vec<T>> {
152    pub fn finish_mut(&mut self) -> &mut Vec<T> {
153        let v = &mut *self.0;
154        v.shrink_to_fit();
155        v
156    }
157
158    #[inline(always)]
159    pub const fn len(&self) -> usize {
160        self.0.len()
161    }
162
163    #[inline(always)]
164    pub const fn is_empty(&self) -> bool {
165        self.0.is_empty()
166    }
167}
168
169// === Convenient From impls ===
170impl<T> From<T> for Dynamic<T> {
171    #[inline(always)]
172    fn from(value: T) -> Self {
173        Self(Box::new(value))
174    }
175}
176
177impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
178    #[inline(always)]
179    fn from(boxed: Box<T>) -> Self {
180        Self(boxed)
181    }
182}
183
184impl From<&str> for Dynamic<String> {
185    #[inline(always)]
186    fn from(s: &str) -> Self {
187        Self(Box::new(s.to_string()))
188    }
189}
190
191// Constant-time equality — only available with `conversions` feature
192#[cfg(feature = "conversions")]
193impl<T> Dynamic<T>
194where
195    T: ?Sized + AsRef<[u8]>,
196{
197    #[inline]
198    pub fn ct_eq(&self, other: &Self) -> bool {
199        use crate::conversions::SecureConversionsExt;
200        self.expose_secret()
201            .as_ref()
202            .ct_eq(other.expose_secret().as_ref())
203    }
204}
205
206// Zeroize integration
207#[cfg(feature = "zeroize")]
208impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
209    fn zeroize(&mut self) {
210        self.0.zeroize();
211    }
212}
213
214#[cfg(feature = "zeroize")]
215impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}