secure_gate/traits/reveal_secret.rs
1//! Traits for controlled, polymorphic secret revelation.
2//!
3//! > **Import path:** `use secure_gate::RevealSecret;`
4//!
5//! This module defines the core `RevealSecret` trait. See
6//! [`revealed_secrets`](crate::traits::revealed_secrets) for the owned wrapper types
7//! [`InnerSecret<T>`](crate::InnerSecret) and [`EncodedSecret`](crate::EncodedSecret).
8//!
9//! The design ensures:
10//! - No implicit borrowing (`Deref`, `AsRef`, etc.)
11//! - Scoped access is preferred (minimizes lifetime of exposed references)
12//! - Direct exposure is possible but clearly marked as an escape hatch
13//! - Owned consumption is available for FFI hand-off and type migration
14//! - Metadata (`len`, `is_empty`) is always available without full exposure
15//!
16//! # Three-Tier Access Model
17//!
18//! All secret access follows an explicit hierarchy. Prefer tiers earlier in the list:
19//!
20//! | Tier | Method | When to use |
21//! |------|--------|-------------|
22//! | 1 — Scoped borrow (preferred) | `with_secret` / `with_secret_mut` | Almost all application code |
23//! | 2 — Direct reference (escape hatch) | `expose_secret` / `expose_secret_mut` | FFI, third-party APIs requiring `&T` |
24//! | 3 — Owned consumption | `into_inner` | FFI hand-off, type migration, APIs requiring `T` by value |
25//!
26//! **Audit note:** `into_inner` does not appear in an `expose_secret*` grep sweep —
27//! audit it separately. See SECURITY.md for the full list of auditable access surfaces.
28//!
29//! # Key Traits
30//!
31//! | Trait | Access | Preferred Method | Escape Hatch | Metadata | Feature |
32//! |------------------------|------------|---------------------------|--------------------------|-------------------|-------------|
33//! | [`RevealSecret`] | Read-only | `with_secret` (scoped) | `expose_secret` | `len`, `is_empty` | Always |
34//! | [`crate::RevealSecretMut`] | Mutable | `with_secret_mut` (scoped)| `expose_secret_mut` | Inherits above | Always |
35//!
36//! # Security Model
37//!
38//! - **Core wrappers** (`Fixed<T>`, `Dynamic<T>`) implement both traits → full access.
39//! - **Read-only wrappers** (encoding wrappers, random types) implement only `RevealSecret` → mutation prevented.
40//! - **Zero-cost** — all methods are `#[inline(always)]` where possible.
41//! - **Scoped access preferred** — `with_secret` / `with_secret_mut` limit borrow lifetime, reducing leak risk.
42//! - **Direct exposure** (`expose_secret` / `expose_secret_mut`) is provided for legitimate needs (FFI, third-party APIs), but marked as an escape hatch.
43//! - **Owned consumption** (`into_inner`) is available when the secret must be moved out of the wrapper.
44//! Zeroization transfers to the returned `InnerSecret<T>` — the caller must let it drop normally.
45//!
46//! # Note for `RevealSecret` Implementors
47//!
48//! Adding `into_inner` to this trait means every `RevealSecret` implementor must provide
49//! an owned-extraction implementation. For wrappers intentionally limited to borrowing
50//! semantics, implement `into_inner` with `unimplemented!()` or a compile-time guard, and
51//! document the design rationale clearly.
52//!
53//! # Usage Guidelines
54//!
55//! The preferred and recommended way to access secrets is the scoped `with_secret` /
56//! `with_secret_mut` methods. `expose_secret` / `expose_secret_mut` are escape hatches
57//! for rare cases and should be audited closely. `into_inner` is reserved for the uncommon
58//! case where ownership of the inner value is required.
59//!
60//! - **Always prefer scoped methods** (`with_secret`, `with_secret_mut`) in application code.
61//! - Use direct exposure only when necessary (e.g., passing raw pointer + length to C FFI).
62//! - Audit every `expose_secret*` call — they should be rare and well-justified.
63//! - Audit every `into_inner` call — it transfers ownership out of the wrapper's protection.
64//!
65//! # Examples
66//!
67//! Scoped (recommended):
68//!
69//! ```rust
70//! use secure_gate::{Fixed, RevealSecret};
71//!
72//! let secret = Fixed::new([42u8; 4]);
73//! let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
74//! assert_eq!(sum, 42 * 4);
75//! ```
76//!
77//! Direct (escape hatch – use with caution):
78//!
79//! ```rust
80//! use secure_gate::{Fixed, RevealSecret};
81//!
82//! let secret = Fixed::new([42u8; 4]);
83//!
84//! // Example: FFI call needing raw pointer + length
85//! // unsafe {
86//! // c_function(secret.expose_secret().as_ptr(), secret.len());
87//! // }
88//! ```
89//!
90//! Mutable scoped:
91//!
92//! ```rust
93//! use secure_gate::{Fixed, RevealSecret, RevealSecretMut};
94//!
95//! let mut secret = Fixed::new([0u8; 4]);
96//! secret.with_secret_mut(|bytes| bytes[0] = 99);
97//! assert_eq!(secret.expose_secret()[0], 99);
98//! ```
99//!
100//! Owned consumption (into_inner):
101//!
102//! ```rust
103//! use secure_gate::{Fixed, RevealSecret};
104//!
105//! let key = Fixed::new([0xABu8; 16]);
106//! // Consumes `key`; zeroization transfers to the returned InnerSecret<[u8; 16]>.
107//! let owned: secure_gate::InnerSecret<[u8; 16]> = key.into_inner();
108//! assert_eq!(*owned, [0xABu8; 16]);
109//! assert_eq!(format!("{:?}", owned), "[REDACTED]");
110//! // `owned` zeroizes its bytes when it drops.
111//! ```
112//!
113//! Polymorphic generic code:
114//!
115//! ```rust
116//! use secure_gate::RevealSecret;
117//!
118//! fn print_length<S: RevealSecret>(secret: &S) {
119//! println!("Length: {} bytes", secret.len());
120//! }
121//! ```
122//!
123//! These traits are the foundation of secure-gate's security model: all secret access is
124//! explicit, auditable, and controlled. Prefer scoped methods in nearly all cases.
125//!
126//! # Implementation Notes
127//!
128//! Long-lived `expose_secret()` references can defeat scoping — the borrow outlives the
129//! call site and the compiler cannot enforce that the secret is not retained. This is an
130//! intentional escape hatch for FFI and legacy APIs; audit every call site.
131
132/// Read-only access to a wrapped secret.
133///
134/// Implemented by [`Fixed<T>`](crate::Fixed) and [`Dynamic<T>`](crate::Dynamic).
135/// Prefer the scoped [`with_secret`](Self::with_secret) method; use
136/// [`expose_secret`](Self::expose_secret) only when a long-lived reference is
137/// unavoidable. See [`RevealSecretMut`](crate::RevealSecretMut) for the mutable
138/// counterpart.
139pub trait RevealSecret {
140 /// The inner secret type being revealed.
141 ///
142 /// This can be a sized type (e.g. `[u8; N]`, `u32`) or unsized (e.g. `str`, `[u8]`).
143 type Inner: ?Sized;
144
145 /// Provides scoped (recommended) read-only access to the secret.
146 ///
147 /// The closure receives a reference that cannot escape — the borrow ends when
148 /// the closure returns, minimizing the lifetime of the exposed secret.
149 /// Prefer this over [`expose_secret`](Self::expose_secret) in all application code.
150 ///
151 /// # Examples
152 ///
153 /// ```rust
154 /// use secure_gate::{Fixed, RevealSecret};
155 ///
156 /// let secret = Fixed::new([42u8; 4]);
157 /// let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
158 /// assert_eq!(sum, 42 * 4);
159 /// ```
160 fn with_secret<F, R>(&self, f: F) -> R
161 where
162 F: FnOnce(&Self::Inner) -> R;
163
164 /// Returns a direct (auditable) read-only reference to the secret.
165 ///
166 /// Long-lived `expose_secret()` references can defeat scoping — prefer
167 /// [`with_secret`](Self::with_secret) in application code. Use this only when
168 /// a long-lived reference is unavoidable (e.g. FFI, third-party APIs).
169 ///
170 /// # Examples
171 ///
172 /// ```rust
173 /// use secure_gate::{Fixed, RevealSecret};
174 ///
175 /// let secret = Fixed::new([42u8; 4]);
176 ///
177 /// // Auditable escape hatch — FFI use case:
178 /// // unsafe { c_fn(secret.expose_secret().as_ptr(), secret.len()); }
179 /// let _ = secret.expose_secret();
180 /// ```
181 fn expose_secret(&self) -> &Self::Inner;
182
183 /// Returns the number of elements in the secret, matching the underlying
184 /// container's `len()` — element count for `Vec<T>` and `[T; N]`, byte count
185 /// for `String`/`str` (where the element is a byte).
186 ///
187 /// Always safe to call — does not expose secret contents.
188 fn len(&self) -> usize;
189
190 /// Returns the total size of the secret in bytes.
191 ///
192 /// For single-byte element types (`Vec<u8>`, `String`, `[u8; N]`) this equals
193 /// `len()`. For multi-byte element types override this method to return
194 /// `len() * core::mem::size_of::<T>()`.
195 ///
196 /// Always safe to call — does not expose secret contents.
197 #[inline(always)]
198 fn byte_len(&self) -> usize {
199 self.len()
200 }
201
202 /// Returns `true` if the secret is empty.
203 ///
204 /// Always safe to call — does not expose secret contents.
205 #[inline(always)]
206 fn is_empty(&self) -> bool {
207 self.len() == 0
208 }
209
210 /// Consumes the wrapper and returns the inner value wrapped in [`InnerSecret`],
211 /// preserving automatic zeroization on drop.
212 ///
213 /// This is the safe, idiomatic path when ownership of the secret is required — for
214 /// example, to hand the value to an API that takes `T` by value, to move between
215 /// wrapper types, or at FFI boundaries where the callee takes ownership.
216 ///
217 /// The zeroization contract transfers to the caller: when the returned
218 /// `InnerSecret<Self::Inner>` drops, it calls `Self::Inner::zeroize()` automatically,
219 /// exactly as the wrapper's own `Drop` impl would have.
220 ///
221 /// # Availability
222 ///
223 /// Only callable when `Self::Inner: Sized + Default + Zeroize`. The `Default` bound
224 /// is required to construct a zero-sentinel that the wrapper's `Drop` impl runs on
225 /// after the real secret is moved out. The `Zeroize` bound is required so the
226 /// returned `InnerSecret<T>` can call `zeroize()` on drop. For types that intentionally
227 /// omit `Default` (e.g. custom key types where an all-zero value is invalid or
228 /// dangerous), `into_inner` is not callable — use `with_secret` or `expose_secret`
229 /// instead.
230 ///
231 /// The three concrete implementations in this crate all satisfy the bounds:
232 /// - `Fixed<[u8; N]>` — `[u8; N]: Default + Zeroize` ✓
233 /// - `Dynamic<String>` — `String: Default + Zeroize` ✓
234 /// - `Dynamic<Vec<T>>` — `Vec<T>: Default + Zeroize` ✓
235 ///
236 /// # Debug Behavior
237 ///
238 /// The returned [`InnerSecret<T>`] always redacts `Debug` as `[REDACTED]`, preserving
239 /// the wrapper-level redaction invariant after ownership transfer.
240 ///
241 /// # Allocation Behavior
242 ///
243 /// `Fixed::into_inner` is zero-cost (no allocation). The `Dynamic::into_inner`
244 /// impls allocate one small `Box<T>` sentinel (24 bytes on 64-bit) before swapping
245 /// out the real secret; if that allocation panics (OOM), the original `inner` is
246 /// untouched and `Dynamic::drop` zeroizes the secret during unwind. Confidentiality
247 /// is preserved on the panic path. See the per-impl docs on
248 /// [`Dynamic`](crate::Dynamic) for the exact pattern.
249 ///
250 /// # Examples
251 ///
252 /// ```rust
253 /// use secure_gate::{Fixed, RevealSecret};
254 ///
255 /// let key = Fixed::new([0xABu8; 16]);
256 /// let owned: secure_gate::InnerSecret<[u8; 16]> = key.into_inner();
257 /// // `owned` zeroizes its 16 bytes when it drops — same guarantee as Fixed<[u8; 16]>.
258 /// assert_eq!(*owned, [0xABu8; 16]);
259 /// assert_eq!(format!("{:?}", owned), "[REDACTED]");
260 /// ```
261 ///
262 /// ```rust
263 /// # #[cfg(feature = "alloc")]
264 /// # {
265 /// use secure_gate::{Dynamic, RevealSecret};
266 ///
267 /// let pw = Dynamic::<String>::new("hunter2".to_string());
268 /// let owned: secure_gate::InnerSecret<String> = pw.into_inner();
269 /// assert_eq!(*owned, "hunter2");
270 /// // `owned` zeroizes its heap buffer when it drops.
271 /// # }
272 /// ```
273 fn into_inner(self) -> crate::InnerSecret<Self::Inner>
274 where
275 Self: Sized,
276 Self::Inner: Sized + Default + zeroize::Zeroize;
277}