secure_gate/
fixed.rs

1// src/fixed.rs
2//! Stack-allocated, zero-cost secure wrappers for fixed-size secrets.
3//!
4//! `Fixed<T>` is a transparent wrapper around any type `T` that lives entirely on the stack.
5//! It provides:
6//! - Zero-cost abstraction (`Deref`/`DerefMut`)
7//! - Automatic redaction in `Debug`
8//! - Full `expose_secret()` API (the canonical way to access the secret)
9//! - Special ergonomics for `[u8; N]` arrays (crypto keys, nonces, etc.)
10
11use core::convert::From;
12use core::ops::{Deref, DerefMut};
13
14/// A zero-cost, stack-allocated wrapper for sensitive data.
15///
16/// `Fixed<T>` stores its value directly in the struct (no heap allocation).
17/// It behaves exactly like `T` thanks to `Deref`/`DerefMut`, but:
18/// - Prints as `[REDACTED]` in debug output
19/// - Provides `.expose_secret()` as the explicit, loud way to access the secret
20/// - Works perfectly with `fixed_alias!` for beautiful type aliases
21///
22/// # Examples
23///
24/// ```
25/// use secure_gate::{Fixed, fixed_alias};
26///
27/// // Define a beautiful type alias (this is the recommended pattern)
28/// fixed_alias!(Aes256Key, 32);
29///
30/// // Generate a random key and convert it directly
31/// let raw_key = [42u8; 32];  // In real code: use rand::Rng::gen()
32/// let key: Aes256Key = raw_key.into();
33///
34/// // Access the bytes
35/// let bytes: &[u8] = key.expose_secret();
36/// assert_eq!(bytes.len(), 32);
37/// ```
38pub struct Fixed<T>(pub T);
39
40impl<T> Fixed<T> {
41    /// Create a new `Fixed` wrapper around a value.
42    ///
43    /// This is usually not called directly — prefer `fixed_alias!` + `.into()`.
44    #[inline(always)]
45    pub const fn new(value: T) -> Self {
46        Fixed(value)
47    }
48}
49
50impl<T> Deref for Fixed<T> {
51    type Target = T;
52
53    #[inline(always)]
54    fn deref(&self) -> &T {
55        &self.0
56    }
57}
58
59impl<T> DerefMut for Fixed<T> {
60    #[inline(always)]
61    fn deref_mut(&mut self) -> &mut T {
62        &mut self.0
63    }
64}
65
66/// Convert a byte slice into a fixed-size secret.
67///
68/// Panics if the slice length doesn't match exactly.
69///
70/// # Panics
71///
72/// Panics with "slice length mismatch" if `bytes.len() != N`.
73impl<const N: usize> Fixed<[u8; N]> {
74    #[inline]
75    pub fn from_slice(bytes: &[u8]) -> Self {
76        assert_eq!(bytes.len(), N, "slice length mismatch");
77        let mut arr = [0u8; N];
78        arr.copy_from_slice(&bytes[..N]);
79        Self::new(arr)
80    }
81}
82
83/// Convert a raw array into a fixed-size secret.
84///
85/// This enables the beautiful `let key: Aes256Key = rng.gen().into();` pattern.
86impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
87    #[inline(always)]
88    fn from(arr: [u8; N]) -> Self {
89        Self::new(arr)
90    }
91}
92
93/// Borrow as a byte slice — useful for crypto APIs.
94impl<const N: usize> AsRef<[u8]> for Fixed<[u8; N]> {
95    #[inline(always)]
96    fn as_ref(&self) -> &[u8] {
97        &self.0
98    }
99}
100
101/// Mutably borrow as a byte slice — e.g. for key scheduling.
102impl<const N: usize> AsMut<[u8]> for Fixed<[u8; N]> {
103    #[inline(always)]
104    fn as_mut(&mut self) -> &mut [u8] {
105        &mut self.0
106    }
107}
108
109/// All `Fixed<T>` values print as `[REDACTED]` to prevent accidental leakage.
110impl<T> core::fmt::Debug for Fixed<T> {
111    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112        f.write_str("[REDACTED]")
113    }
114}
115
116impl<T> Fixed<T> {
117    /// Access the secret value immutably.
118    ///
119    /// This is the **canonical** way to read the secret — loud and clear.
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use secure_gate::{Fixed, fixed_alias};
125    ///
126    /// fixed_alias!(Aes256Key, 32);
127    ///
128    /// let key: Aes256Key = [1u8; 32].into();
129    /// let bytes: &[u8] = key.expose_secret();
130    /// assert_eq!(bytes[0], 1);
131    /// ```
132    #[inline(always)]
133    pub fn expose_secret(&self) -> &T {
134        &self.0
135    }
136
137    /// Access the secret value mutably.
138    ///
139    /// Use this for in-place operations like key derivation.
140    #[inline(always)]
141    pub fn expose_secret_mut(&mut self) -> &mut T {
142        &mut self.0
143    }
144
145    /// **Deprecated**: Use [`expose_secret`] instead.
146    ///
147    /// Kept for backward compatibility with v0.5.x.
148    #[deprecated(since = "0.5.5", note = "use `expose_secret` instead")]
149    #[doc(hidden)]
150    #[inline(always)]
151    pub fn view(&self) -> &T {
152        self.expose_secret()
153    }
154
155    /// **Deprecated**: Use [`expose_secret_mut`] instead.
156    #[deprecated(since = "0.5.5", note = "use `expose_secret_mut` instead")]
157    #[doc(hidden)]
158    #[inline(always)]
159    pub fn view_mut(&mut self) -> &mut T {
160        self.expose_secret_mut()
161    }
162
163    /// Consume the wrapper and return the inner value.
164    ///
165    /// This is useful when you need to pass the secret to a function that takes ownership.
166    #[inline(always)]
167    pub fn into_inner(self) -> T {
168        self.0
169    }
170}
171
172/// `Clone` is implemented when the inner type is `Clone`.
173impl<T: Clone> Clone for Fixed<T> {
174    #[inline(always)]
175    fn clone(&self) -> Self {
176        Self(self.0.clone())
177    }
178}
179
180/// `Copy` is implemented for small fixed-size byte arrays.
181impl<const N: usize> Copy for Fixed<[u8; N]> where [u8; N]: Copy {}