secure_gate/
fixed.rs

1use core::fmt;
2
3#[cfg(feature = "rand")]
4use rand::rand_core::OsError;
5
6use crate::FromSliceError;
7
8/// Stack-allocated secure secret wrapper.
9///
10/// This is a zero-cost wrapper for fixed-size secrets like byte arrays or primitives.
11/// The inner field is private, forcing all access through explicit methods.
12///
13/// Security invariants:
14/// - No `Deref` or `AsRef` — prevents silent access or borrowing.
15/// - No implicit `Copy` — even for `[u8; N]`, duplication must be explicit via `.clone()`.
16/// - `Debug` is always redacted.
17///
18/// # Examples
19///
20/// Basic usage:
21/// ```
22/// use secure_gate::{Fixed, ExposeSecret};
23/// let secret = Fixed::new(42u32);
24/// assert_eq!(*secret.expose_secret(), 42);
25/// ```
26///
27/// For byte arrays (most common):
28/// ```
29/// use secure_gate::{fixed_alias, Fixed, ExposeSecret};
30/// fixed_alias!(Aes256Key, 32);
31/// let key_bytes = [0x42u8; 32];
32/// let key: Aes256Key = Fixed::from(key_bytes);
33/// assert_eq!(key.len(), 32);
34/// assert_eq!(key.expose_secret()[0], 0x42);
35/// ```
36///
37/// With `zeroize` feature (automatic wipe on drop):
38/// ```
39/// # #[cfg(feature = "zeroize")]
40/// # {
41/// use secure_gate::Fixed;
42/// let mut secret = Fixed::new([1u8, 2, 3]);
43/// drop(secret); // memory wiped automatically
44/// # }
45/// ```
46pub struct Fixed<T>(pub(crate) T); // ← field is pub(crate) for trait access
47
48impl<T> Fixed<T> {
49    /// Wrap a value in a `Fixed` secret.
50    ///
51    /// This is zero-cost and const-friendly.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use secure_gate::Fixed;
57    /// const SECRET: Fixed<u32> = Fixed::new(42);
58    /// ```
59    #[inline(always)]
60    pub const fn new(value: T) -> Self {
61        Fixed(value)
62    }
63}
64
65/// # Byte-array specific helpers
66impl<const N: usize> Fixed<[u8; N]> {}
67
68/// Implements `TryFrom<&[u8]>` for creating a [`Fixed`] from a byte slice of exact length.
69impl<const N: usize> core::convert::TryFrom<&[u8]> for Fixed<[u8; N]> {
70    type Error = FromSliceError;
71
72    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
73        if slice.len() != N {
74            Err(FromSliceError::new(slice.len(), N))
75        } else {
76            let mut arr = [0u8; N];
77            arr.copy_from_slice(slice);
78            Ok(Self::new(arr))
79        }
80    }
81}
82
83impl<const N: usize> From<[u8; N]> for Fixed<[u8; N]> {
84    /// Wrap a raw byte array in a `Fixed` secret.
85    ///
86    /// Zero-cost conversion.
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// use secure_gate::Fixed;
92    /// let key: Fixed<[u8; 4]> = [1, 2, 3, 4].into();
93    /// ```
94    #[inline(always)]
95    fn from(arr: [u8; N]) -> Self {
96        Self::new(arr)
97    }
98}
99
100/// Debug implementation (always redacted).
101impl<T> fmt::Debug for Fixed<T> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.write_str("[REDACTED]")
104    }
105}
106
107/// Opt-in Clone — only for types marked `CloneSafe` (default no-clone).
108#[cfg(feature = "zeroize")]
109impl<T: crate::CloneSafe> Clone for Fixed<T> {
110    #[inline(always)]
111    fn clone(&self) -> Self {
112        Self(self.0.clone())
113    }
114}
115
116#[cfg(feature = "ct-eq")]
117use crate::ExposeSecret;
118
119/// Constant-time equality — only available with `ct-eq` feature.
120#[cfg(feature = "ct-eq")]
121impl<const N: usize> Fixed<[u8; N]> {
122    /// Constant-time equality comparison.
123    ///
124    /// This is the **only safe way** to compare two fixed-size secrets.
125    /// Available only when the `ct-eq` feature is enabled.
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// # #[cfg(feature = "ct-eq")]
131    /// # {
132    /// use secure_gate::Fixed;
133    /// let a = Fixed::new([1u8; 32]);
134    /// let b = Fixed::new([1u8; 32]);
135    /// assert!(a.ct_eq(&b));
136    /// # }
137    /// ```
138    #[inline]
139    pub fn ct_eq(&self, other: &Self) -> bool {
140        use crate::traits::ConstantTimeEq;
141        self.expose_secret().ct_eq(other.expose_secret())
142    }
143}
144
145/// Random generation — only available with `rand` feature.
146#[cfg(feature = "rand")]
147impl<const N: usize> Fixed<[u8; N]> {
148    /// Generate fresh random bytes using the OS RNG.
149    ///
150    /// This is a convenience method that generates random bytes directly
151    /// without going through `FixedRandom`. Equivalent to:
152    /// `FixedRandom::<N>::generate().into_inner()`
153    ///
154    /// # Example
155    ///
156    /// ```
157    /// # #[cfg(feature = "rand")]
158    /// # {
159    /// use secure_gate::Fixed;
160    /// let key: Fixed<[u8; 32]> = Fixed::generate_random();
161    /// # }
162    /// ```
163    #[inline]
164    pub fn generate_random() -> Self {
165        crate::random::FixedRandom::<N>::generate().into_inner()
166    }
167
168    /// Try to generate random bytes for Fixed.
169    ///
170    /// Returns an error if the RNG fails.
171    ///
172    /// # Example
173    ///
174    /// ```
175    /// # #[cfg(feature = "rand")]
176    /// # {
177    /// use secure_gate::Fixed;
178    /// let key: Result<Fixed<[u8; 32]>, rand::rand_core::OsError> = Fixed::try_generate_random();
179    /// assert!(key.is_ok());
180    /// # }
181    /// ```
182    #[inline]
183    pub fn try_generate_random() -> Result<Self, OsError> {
184        crate::random::FixedRandom::<N>::try_generate()
185            .map(|rng: crate::random::FixedRandom<N>| rng.into_inner())
186    }
187}
188
189/// Zeroize integration.
190#[cfg(feature = "zeroize")]
191impl<T: zeroize::Zeroize> zeroize::Zeroize for Fixed<T> {
192    fn zeroize(&mut self) {
193        self.0.zeroize();
194    }
195}
196
197/// Zeroize on drop integration.
198#[cfg(feature = "zeroize")]
199impl<T: zeroize::Zeroize> zeroize::ZeroizeOnDrop for Fixed<T> {}