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/// Mutable access:
32/// ```
33/// use secure_gate::Dynamic;
34/// let mut secret = Dynamic::<String>::new("pass".to_string());
35/// secret.expose_secret_mut().push('!');
36/// assert_eq!(secret.expose_secret(), "pass!");
37/// ```
38///
39/// With `zeroize` (automatic wipe):
40/// ```
41/// # #[cfg(feature = "zeroize")]
42/// # {
43/// use secure_gate::Dynamic;
44/// let secret = Dynamic::<Vec<u8>>::new(vec![1u8; 32]);
45/// drop(secret); // heap wiped automatically
46/// # }
47/// ```
48pub struct Dynamic<T: ?Sized>(Box<T>);
49
50impl<T: ?Sized> Dynamic<T> {
51 /// Wrap an already-boxed value.
52 ///
53 /// Zero-cost — just wraps the `Box`.
54 #[inline(always)]
55 pub fn new_boxed(value: Box<T>) -> Self {
56 Dynamic(value)
57 }
58
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// Clone impl — opt-in only when T is CloneableSecret
94#[cfg(feature = "zeroize")]
95impl<T: crate::CloneableSecret> Clone for Dynamic<T> {
96 #[inline(always)]
97 fn clone(&self) -> Self {
98 Dynamic(self.0.clone())
99 }
100}
101
102// === Additional conversions ===
103
104impl From<&[u8]> for Dynamic<Vec<u8>> {
105 #[inline(always)]
106 fn from(slice: &[u8]) -> Self {
107 Self::new(slice.to_vec())
108 }
109}
110
111// === Ergonomic helpers for common heap types ===
112impl Dynamic<String> {
113 /// Returns the length of the string in bytes.
114 ///
115 /// This is safe public metadata — does not expose the secret.
116 #[inline(always)]
117 pub const fn len(&self) -> usize {
118 self.0.len()
119 }
120
121 /// Returns `true` if the string is empty (zero bytes).
122 ///
123 /// This is safe public metadata — does not expose the secret.
124 #[inline(always)]
125 pub const fn is_empty(&self) -> bool {
126 self.0.is_empty()
127 }
128}
129
130impl<T> Dynamic<Vec<T>> {
131 /// Returns the number of elements in the vector.
132 ///
133 /// This is safe public metadata — does not expose the secret.
134 #[inline(always)]
135 pub const fn len(&self) -> usize {
136 self.0.len()
137 }
138
139 /// Returns `true` if the vector is empty (zero elements).
140 ///
141 /// This is safe public metadata — does not expose the secret.
142 #[inline(always)]
143 pub const fn is_empty(&self) -> bool {
144 self.0.is_empty()
145 }
146}
147
148// === Convenient From impls ===
149impl<T> From<T> for Dynamic<T> {
150 #[inline(always)]
151 fn from(value: T) -> Self {
152 Self(Box::new(value))
153 }
154}
155
156impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
157 #[inline(always)]
158 fn from(boxed: Box<T>) -> Self {
159 Self(boxed)
160 }
161}
162
163impl From<&str> for Dynamic<String> {
164 #[inline(always)]
165 fn from(s: &str) -> Self {
166 Self(Box::new(s.to_string()))
167 }
168}
169
170// Constant-time equality — only available with `ct-eq` feature
171#[cfg(feature = "ct-eq")]
172impl<T> Dynamic<T>
173where
174 T: ?Sized + AsRef<[u8]>,
175{
176 #[inline]
177 pub fn ct_eq(&self, other: &Self) -> bool {
178 use crate::eq::ConstantTimeEq;
179 self.expose_secret()
180 .as_ref()
181 .ct_eq(other.expose_secret().as_ref())
182 }
183}
184
185// Random generation — only available with `rand` feature
186#[cfg(feature = "rand")]
187impl Dynamic<Vec<u8>> {
188 /// Generate fresh random bytes of the specified length using the OS RNG.
189 ///
190 /// This is a convenience method that generates random bytes directly
191 /// without going through `DynamicRng`. Equivalent to:
192 /// `DynamicRng::generate(len).into_inner()`
193 ///
194 /// # Example
195 ///
196 /// ```
197 /// # #[cfg(feature = "rand")]
198 /// # {
199 /// use secure_gate::Dynamic;
200 /// let random: Dynamic<Vec<u8>> = Dynamic::generate_random(64);
201 /// assert_eq!(random.len(), 64);
202 /// # }
203 /// ```
204 #[inline]
205 pub fn generate_random(len: usize) -> Self {
206 crate::random::DynamicRng::generate(len).into_inner()
207 }
208
209 /// Try to generate random bytes for Dynamic.
210 ///
211 /// Returns an error if the RNG fails.
212 ///
213 /// # Example
214 ///
215 /// ```
216 /// # #[cfg(feature = "rand")]
217 /// # {
218 /// use secure_gate::Dynamic;
219 /// let random: Result<Dynamic<Vec<u8>>, rand::rand_core::OsError> = Dynamic::try_generate_random(64);
220 /// assert!(random.is_ok());
221 /// # }
222 /// ```
223 #[inline]
224 pub fn try_generate_random(len: usize) -> Result<Self, OsError> {
225 crate::random::DynamicRng::try_generate(len)
226 .map(|rng: crate::random::DynamicRng| rng.into_inner())
227 }
228}
229
230// Zeroize integration
231#[cfg(feature = "zeroize")]
232impl<T: ?Sized + zeroize::Zeroize> zeroize::Zeroize for Dynamic<T> {
233 fn zeroize(&mut self) {
234 self.0.zeroize();
235 }
236}
237
238#[cfg(feature = "zeroize")]
239impl<T: ?Sized + zeroize::Zeroize> zeroize::ZeroizeOnDrop for Dynamic<T> {}