secure_gate/no_clone.rs
1// ==========================================================================
2// src/no_clone.rs
3// ==========================================================================
4
5extern crate alloc;
6
7use alloc::boxed::Box;
8use core::fmt;
9
10/// Non-cloneable stack-allocated secret wrapper.
11///
12/// This is a zero-cost newtype over `Fixed<T>` that deliberately omits `Clone` and `Copy`.
13/// Use this when you want to enforce single-ownership and prevent accidental duplication of secrets.
14///
15/// Converts from `Fixed<T>` via `.no_clone()`.
16///
17/// # Examples
18///
19/// ```
20/// use secure_gate::{Fixed, FixedNoClone};
21/// let secret = Fixed::new([1u8; 32]);
22/// let no_clone: FixedNoClone<[u8; 32]> = secret.no_clone();
23/// // no_clone cannot be cloned
24/// assert_eq!(no_clone.expose_secret()[0], 1);
25/// ```
26///
27/// With `zeroize`:
28/// ```
29/// # #[cfg(feature = "zeroize")]
30/// # {
31/// use secure_gate::FixedNoClone;
32/// let mut secret = FixedNoClone::new([1u8, 2, 3]);
33/// drop(secret); // wiped on drop
34/// # }
35/// ```
36pub struct FixedNoClone<T>(T);
37
38/// Non-cloneable heap-allocated secret wrapper.
39///
40/// This is a thin newtype over `Dynamic<T>` that deliberately omits `Clone`.
41/// Use this for dynamic secrets where duplication must be prevented.
42///
43/// Converts from `Dynamic<T>` via `.no_clone()`.
44///
45/// # Examples
46///
47/// ```
48/// use secure_gate::{Dynamic, DynamicNoClone};
49/// let secret = Dynamic::new("hunter2".to_string());
50/// let no_clone: DynamicNoClone<String> = secret.no_clone();
51/// // no_clone cannot be cloned
52/// assert_eq!(no_clone.expose_secret(), "hunter2");
53/// ```
54pub struct DynamicNoClone<T: ?Sized>(Box<T>);
55
56impl<T> FixedNoClone<T> {
57 /// Wrap a value in a non-cloneable fixed secret.
58 ///
59 /// # Example
60 ///
61 /// ```
62 /// use secure_gate::FixedNoClone;
63 /// let secret = FixedNoClone::new(42u32);
64 /// ```
65 #[inline(always)]
66 pub const fn new(value: T) -> Self {
67 FixedNoClone(value)
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 ///
74 /// # Example
75 ///
76 /// ```
77 /// use secure_gate::FixedNoClone;
78 /// let secret = FixedNoClone::new([42u8; 32]);
79 /// assert_eq!(secret.expose_secret()[0], 42);
80 /// ```
81 #[inline(always)]
82 pub const fn expose_secret(&self) -> &T {
83 &self.0
84 }
85
86 /// Expose the inner value for mutable access.
87 ///
88 /// This is the **only** way to mutate the secret — loud and auditable.
89 ///
90 /// # Example
91 ///
92 /// ```
93 /// use secure_gate::FixedNoClone;
94 /// let mut secret = FixedNoClone::new([1u8, 2, 3]);
95 /// secret.expose_secret_mut()[0] = 99;
96 /// assert_eq!(secret.expose_secret()[0], 99);
97 /// ```
98 #[inline(always)]
99 pub fn expose_secret_mut(&mut self) -> &mut T {
100 &mut self.0
101 }
102}
103
104// Explicit zeroization — only available with `zeroize` feature
105#[cfg(feature = "zeroize")]
106impl<T: Zeroize> FixedNoClone<T> {
107 /// Explicitly zeroize the secret immediately.
108 ///
109 /// This is useful when you want to wipe memory before the value goes out of scope,
110 /// or when you want to make the zeroization intent explicit in the code.
111 ///
112 /// # Example
113 ///
114 /// ```
115 /// # #[cfg(feature = "zeroize")]
116 /// # {
117 /// use secure_gate::FixedNoClone;
118 /// let mut key = FixedNoClone::new([42u8; 32]);
119 /// // ... use key ...
120 /// key.zeroize_now(); // Explicit wipe - makes intent clear
121 /// # }
122 /// ```
123 #[inline]
124 pub fn zeroize_now(&mut self) {
125 self.0.zeroize();
126 }
127}
128
129impl<T: ?Sized> DynamicNoClone<T> {
130 /// Wrap a boxed value in a non-cloneable dynamic secret.
131 ///
132 /// # Example
133 ///
134 /// ```
135 /// use secure_gate::DynamicNoClone;
136 /// let boxed = Box::new("secret".to_string());
137 /// let no_clone = DynamicNoClone::new(boxed);
138 /// ```
139 #[inline(always)]
140 pub fn new(value: Box<T>) -> Self {
141 DynamicNoClone(value)
142 }
143
144 /// Expose the inner value for read-only access.
145 ///
146 /// This is the **only** way to read the secret — loud and auditable.
147 ///
148 /// # Example
149 ///
150 /// ```
151 /// use secure_gate::DynamicNoClone;
152 /// let secret = DynamicNoClone::new(Box::new("hunter2".to_string()));
153 /// assert_eq!(secret.expose_secret(), "hunter2");
154 /// ```
155 #[inline(always)]
156 pub const fn expose_secret(&self) -> &T {
157 &self.0
158 }
159
160 /// Expose the inner value for mutable access.
161 ///
162 /// This is the **only** way to mutate the secret — loud and auditable.
163 ///
164 /// # Example
165 ///
166 /// ```
167 /// use secure_gate::DynamicNoClone;
168 /// let mut secret = DynamicNoClone::new(Box::new("hello".to_string()));
169 /// secret.expose_secret_mut().push_str(" world");
170 /// assert_eq!(secret.expose_secret(), "hello world");
171 /// ```
172 #[inline(always)]
173 pub fn expose_secret_mut(&mut self) -> &mut T {
174 &mut self.0
175 }
176}
177
178// Explicit zeroization — only available with `zeroize` feature
179#[cfg(feature = "zeroize")]
180impl<T: ?Sized + Zeroize> DynamicNoClone<T> {
181 /// Explicitly zeroize the secret immediately.
182 ///
183 /// This is useful when you want to wipe memory before the value goes out of scope,
184 /// or when you want to make the zeroization intent explicit in the code.
185 ///
186 /// # Example
187 ///
188 /// ```
189 /// # #[cfg(feature = "zeroize")]
190 /// # {
191 /// use secure_gate::DynamicNoClone;
192 /// let mut password = DynamicNoClone::new(Box::new("secret".to_string()));
193 /// // ... use password ...
194 /// password.zeroize_now(); // Explicit wipe - makes intent clear
195 /// # }
196 /// ```
197 #[inline]
198 pub fn zeroize_now(&mut self) {
199 self.0.zeroize();
200 }
201}
202
203impl<T> fmt::Debug for FixedNoClone<T> {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 f.write_str("[REDACTED]")
206 }
207}
208
209impl<T: ?Sized> fmt::Debug for DynamicNoClone<T> {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 f.write_str("[REDACTED]")
212 }
213}
214
215// === Ergonomic helpers for common heap types ===
216
217impl DynamicNoClone<String> {
218 /// Returns the length of the secret string in bytes (UTF-8).
219 #[inline(always)]
220 pub const fn len(&self) -> usize {
221 self.0.len()
222 }
223
224 /// Returns true if the secret string is empty.
225 #[inline(always)]
226 pub const fn is_empty(&self) -> bool {
227 self.0.is_empty()
228 }
229}
230
231impl<T> DynamicNoClone<Vec<T>> {
232 /// Returns the length of the secret vector in elements.
233 #[inline(always)]
234 pub const fn len(&self) -> usize {
235 self.0.len()
236 }
237
238 /// Returns true if the secret vector is empty.
239 #[inline(always)]
240 pub const fn is_empty(&self) -> bool {
241 self.0.is_empty()
242 }
243}
244
245#[cfg(feature = "zeroize")]
246use zeroize::{Zeroize, ZeroizeOnDrop};
247
248#[cfg(feature = "zeroize")]
249impl<T: Zeroize> Zeroize for FixedNoClone<T> {
250 fn zeroize(&mut self) {
251 self.0.zeroize();
252 }
253}
254
255#[cfg(feature = "zeroize")]
256impl<T: ?Sized + Zeroize> Zeroize for DynamicNoClone<T> {
257 fn zeroize(&mut self) {
258 self.0.zeroize();
259 }
260}
261
262#[cfg(feature = "zeroize")]
263impl<T: Zeroize> ZeroizeOnDrop for FixedNoClone<T> {}
264
265#[cfg(feature = "zeroize")]
266impl<T: ?Sized + Zeroize> ZeroizeOnDrop for DynamicNoClone<T> {}