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> {}