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
7//! [`EncodedSecret`](crate::EncodedSecret), the wrapper for encoded output.
8//!
9//! The design ensures:
10//! - No implicit borrowing (`Deref`, `AsRef`, etc.) from the secret wrapper itself —
11//! reaching the secret always names a method
12//! - Scoped access is preferred (minimizes lifetime of exposed references)
13//! - Direct exposure is possible but clearly marked as an escape hatch
14//! - Owned consumption is available for FFI hand-off and type migration
15//! - Length metadata (`len`, `is_empty` via [`SecretLen`]) is available without
16//! exposing contents — though length itself can be sensitive; see [`SecretLen`]
17//!
18//! # Three-Tier Access Model
19//!
20//! All secret access follows an explicit hierarchy. Prefer tiers earlier in the list:
21//!
22//! | Tier | Method | When to use |
23//! |------|--------|-------------|
24//! | 1 — Scoped borrow (preferred) | `with_secret` / `with_secret_mut` | Almost all application code |
25//! | 2 — Direct reference (escape hatch) | `expose_secret` / `expose_secret_mut` | FFI, third-party APIs requiring `&T` |
26//! | 3 — Owned consumption | `into_inner` | FFI hand-off, type migration, APIs requiring `T` by value |
27//!
28//! **Audit note:** `into_inner` does not appear in an `expose_secret*` grep sweep —
29//! audit it separately. See SECURITY.md for the full list of auditable access surfaces.
30//!
31//! # Key Traits
32//!
33//! | Trait | Access | Preferred Method | Escape Hatch | Metadata | Feature |
34//! |------------------------|------------|---------------------------|--------------------------|-------------------|-------------|
35//! | [`RevealSecret`] | Read-only | `with_secret` (scoped) | `expose_secret` | — (see [`SecretLen`]) | Always |
36//! | [`crate::RevealSecretMut`] | Mutable | `with_secret_mut` (scoped)| `expose_secret_mut` | Inherits above | Always |
37//!
38//! # Security Model
39//!
40//! - **Core wrappers** (`Fixed<T>`, `Dynamic<T>`) implement both traits → full access.
41//! - **Generated newtypes** (`fixed_newtype!` / `dynamic_newtype!`) implement both, forwarding to the wrapper they hold.
42//! - **`EncodedSecret` implements neither.** It is an output wrapper, not a secret wrapper: its contents are reached through `Deref<Target = str>` and its two named consumers.
43//! - **Zero-cost** — all methods are `#[inline(always)]` where possible.
44//! - **Scoped access preferred** — `with_secret` / `with_secret_mut` limit borrow lifetime, reducing leak risk.
45//! - **Direct exposure** (`expose_secret` / `expose_secret_mut`) is provided for legitimate needs (FFI, third-party APIs), but marked as an escape hatch.
46//! - **Owned consumption** (`into_inner`) is available when the secret must be moved out of the
47//! wrapper. It transfers ownership of the plain value and nothing is copied; the wrapper's
48//! zeroize-on-drop does not follow it, so the caller owns the value's lifetime from there.
49//!
50//! # Note for `RevealSecret` Implementors
51//!
52//! Adding `into_inner` to this trait means every `RevealSecret` implementor must provide
53//! an owned-extraction implementation. For wrappers intentionally limited to borrowing
54//! semantics, implement `into_inner` with `unimplemented!()` or a compile-time guard, and
55//! document the design rationale clearly.
56//!
57//! # Usage Guidelines
58//!
59//! The preferred and recommended way to access secrets is the scoped `with_secret` /
60//! `with_secret_mut` methods. `expose_secret` / `expose_secret_mut` are escape hatches
61//! for rare cases and should be audited closely. `into_inner` is reserved for the uncommon
62//! case where ownership of the inner value is required.
63//!
64//! - **Always prefer scoped methods** (`with_secret`, `with_secret_mut`) in application code.
65//! - Use direct exposure only when necessary (e.g., passing raw pointer + length to C FFI).
66//! - Audit every `expose_secret*` call — they should be rare and well-justified.
67//! - Audit every `into_inner` call — it transfers ownership out of the wrapper's protection.
68//!
69//! # Examples
70//!
71//! Scoped (recommended):
72//!
73//! ```rust
74//! use secure_gate::{Fixed, RevealSecret};
75//!
76//! let secret = Fixed::new([42u8; 4]);
77//! let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
78//! assert_eq!(sum, 42 * 4);
79//! ```
80//!
81//! Direct (escape hatch – use with caution):
82//!
83//! ```rust
84//! use secure_gate::{Fixed, RevealSecret};
85//!
86//! let secret = Fixed::new([42u8; 4]);
87//!
88//! // Example: FFI call needing raw pointer + length
89//! // unsafe {
90//! // c_function(secret.expose_secret().as_ptr(), secret.len());
91//! // }
92//! ```
93//!
94//! Mutable scoped:
95//!
96//! ```rust
97//! use secure_gate::{Fixed, RevealSecret, RevealSecretMut};
98//!
99//! let mut secret = Fixed::new([0u8; 4]);
100//! secret.with_secret_mut(|bytes| bytes[0] = 99);
101//! assert_eq!(secret.expose_secret()[0], 99);
102//! ```
103//!
104//! Owned consumption (into_inner):
105//!
106//! ```rust
107//! use secure_gate::{Fixed, RevealSecret};
108//!
109//! let key = Fixed::new([0xABu8; 16]);
110//! // Consumes `key` and transfers ownership of the bytes: they move into `owned`
111//! // and an inert sentinel is left for `Fixed::drop` to zeroize in their place.
112//! let owned: [u8; 16] = key.into_inner();
113//! assert_eq!(owned, [0xABu8; 16]);
114//! // `owned` is an ordinary array now — you own its lifetime.
115//! ```
116//!
117//! Polymorphic generic code:
118//!
119//! ```rust
120//! use secure_gate::SecretLen;
121//!
122//! fn print_length<S: SecretLen>(secret: &S) {
123//! println!("Length: {} bytes", secret.len());
124//! }
125//! ```
126//!
127//! These traits are the foundation of secure-gate's security model: all secret access is
128//! explicit, auditable, and controlled. Prefer scoped methods in nearly all cases.
129//!
130//! # Implementation Notes
131//!
132//! Long-lived `expose_secret()` references can defeat scoping — the borrow outlives the
133//! call site and the compiler cannot enforce that the secret is not retained. This is an
134//! intentional escape hatch for FFI and legacy APIs; audit every call site.
135
136/// Read-only access to a wrapped secret.
137///
138/// Implemented for **all** [`Fixed<T>`](crate::Fixed) and [`Dynamic<T>`](crate::Dynamic),
139/// whatever the inner type — length metadata lives in the narrower [`SecretLen`].
140/// Prefer the scoped [`with_secret`](Self::with_secret) method; use
141/// [`expose_secret`](Self::expose_secret) only when a long-lived reference is
142/// unavoidable. See [`RevealSecretMut`](crate::RevealSecretMut) for the mutable
143/// counterpart.
144pub trait RevealSecret {
145 /// The inner secret type being revealed.
146 ///
147 /// This can be a sized type (e.g. `[u8; N]`, `u32`) or unsized (e.g. `str`, `[u8]`).
148 type Inner: ?Sized;
149
150 /// Provides scoped (recommended) read-only access to the secret.
151 ///
152 /// The closure receives a reference that cannot escape — the borrow ends when
153 /// the closure returns, minimizing the lifetime of the exposed secret.
154 /// Prefer this over [`expose_secret`](Self::expose_secret) in all application code.
155 ///
156 /// # Examples
157 ///
158 /// ```rust
159 /// use secure_gate::{Fixed, RevealSecret};
160 ///
161 /// let secret = Fixed::new([42u8; 4]);
162 /// let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
163 /// assert_eq!(sum, 42 * 4);
164 /// ```
165 fn with_secret<F, R>(&self, f: F) -> R
166 where
167 F: FnOnce(&Self::Inner) -> R;
168
169 /// Returns a direct (auditable) read-only reference to the secret.
170 ///
171 /// Long-lived `expose_secret()` references can defeat scoping — prefer
172 /// [`with_secret`](Self::with_secret) in application code. Use this only when
173 /// a long-lived reference is unavoidable (e.g. FFI, third-party APIs).
174 ///
175 /// # Examples
176 ///
177 /// ```rust
178 /// use secure_gate::{Fixed, RevealSecret};
179 ///
180 /// let secret = Fixed::new([42u8; 4]);
181 ///
182 /// // Auditable escape hatch — FFI use case:
183 /// // unsafe { c_fn(secret.expose_secret().as_ptr(), secret.len()); }
184 /// let _ = secret.expose_secret();
185 /// ```
186 fn expose_secret(&self) -> &Self::Inner;
187
188 /// Consumes the wrapper and transfers ownership of the plain inner value.
189 ///
190 /// This is the safe, idiomatic path when ownership of the secret is required — for
191 /// example, to hand the value to an API that takes `T` by value, to move between
192 /// wrapper types, or at FFI boundaries where the callee takes ownership.
193 ///
194 /// **Protection ends here.** The returned value is an ordinary `[u8; N]` / `String` /
195 /// `Vec<T>` with no zeroize-on-drop and no redacted `Debug` — you own the secret and
196 /// its lifetime from this call onward. The value is moved, never duplicated-and-kept:
197 /// an inert [`SentinelValue`](crate::SentinelValue) is left for the wrapper's `Drop` to
198 /// zeroize in its place, so exactly one copy survives the call and it is yours. For
199 /// `Dynamic` that move is literal — the allocation itself is handed over, nothing is
200 /// read or written. For `Fixed` the secret is stored inline, so the bytes are
201 /// transferred into your slot and the wrapper's slot receives the sentinel; there is no
202 /// indirection to hand over instead. If you want the protection to continue, do not call this
203 /// — keep the wrapper, or move the value into a new one.
204 ///
205 /// # Availability
206 ///
207 /// Only callable when `Self::Inner: Sized + SentinelValue + Zeroize`. The
208 /// [`SentinelValue`](crate::SentinelValue) bound is required to construct the inert
209 /// placeholder that the wrapper's `Drop` impl runs on after the real secret is moved
210 /// out. (A plain `Default` bound is deliberately **not** used: the standard library
211 /// only implements `Default` for arrays up to 32 elements, which would make
212 /// `into_inner` unusable for `[u8; 64]` and other common key sizes.) The `Zeroize`
213 /// bound is required because the wrapper still zeroizes the sentinel on drop.
214 /// For custom inner types that intentionally omit `SentinelValue` (e.g. key types
215 /// where no safe placeholder value exists), `into_inner` is not callable — use
216 /// `with_secret` or `expose_secret` instead.
217 ///
218 /// The three concrete implementations in this crate all satisfy the bounds:
219 /// - `Fixed<[u8; N]>` — `[u8; N]: SentinelValue + Zeroize` for **any** `N` ✓
220 /// - `Dynamic<String>` — `String: SentinelValue + Zeroize` ✓
221 /// - `Dynamic<Vec<T>>` — `Vec<T>: SentinelValue + Zeroize` ✓
222 ///
223 /// # Allocation Behavior
224 ///
225 /// `Fixed::into_inner` is zero-cost (no allocation). The `Dynamic::into_inner`
226 /// impls allocate one small `Box<T>` sentinel (24 bytes on 64-bit) before swapping
227 /// out the real secret; if that allocation panics (OOM), the original `inner` is
228 /// untouched and `Dynamic::drop` zeroizes the secret during unwind. Confidentiality
229 /// is preserved on the panic path. See the per-impl docs on
230 /// [`Dynamic`](crate::Dynamic) for the exact pattern.
231 ///
232 /// # Examples
233 ///
234 /// ```rust
235 /// use secure_gate::{Fixed, RevealSecret};
236 ///
237 /// let key = Fixed::new([0xABu8; 16]);
238 /// let owned: [u8; 16] = key.into_inner();
239 /// assert_eq!(owned, [0xABu8; 16]);
240 /// // `owned` is a plain array — no wiping, no redaction. You own it.
241 /// ```
242 ///
243 /// ```rust
244 /// # #[cfg(feature = "alloc")]
245 /// # {
246 /// use secure_gate::{Dynamic, RevealSecret};
247 ///
248 /// let pw = Dynamic::<String>::new("hunter2".to_string());
249 /// let owned: String = pw.into_inner();
250 /// assert_eq!(owned, "hunter2");
251 /// // Same allocation, moved out — no copy was made.
252 /// # }
253 /// ```
254 fn into_inner(self) -> Self::Inner
255 where
256 Self: Sized,
257 Self::Inner: Sized + crate::SentinelValue + zeroize::Zeroize;
258}
259
260/// Length metadata for secrets whose inner type has a meaningful length.
261///
262/// Separated from [`RevealSecret`] so the core access trait can be implemented
263/// for **every** inner type — including local user-defined ones (see the
264/// [`CloneableSecret`](crate::CloneableSecret) newtype pattern), which have no
265/// meaningful `len()`. Implemented for `Fixed<[T; N]>`, `Dynamic<String>`, and
266/// `Dynamic<Vec<T>>`.
267///
268/// # Security
269///
270/// These methods do not expose secret **contents** — but that is not the same
271/// as length being harmless, and the distinction matters:
272///
273/// - For `Fixed<[u8; N]>`, `len()` is `N`: a compile-time constant already
274/// visible in the type. Nothing is revealed.
275/// - For variable-length secrets (`Dynamic<String>`, `Dynamic<Vec<u8>>`),
276/// length **can be sensitive**. A password's length narrows a brute-force
277/// search; a token's length can fingerprint its issuer. Treat it as metadata
278/// *about* a secret, not as public data: validate against it (minimum-length
279/// checks, buffer sizing) but never log it, put it in an error message that
280/// leaves the process, or persist it next to an identifier.
281///
282/// This trait is deliberately separate from [`RevealSecret`] so that measuring
283/// a secret is a visible choice. `use secure_gate::SecretLen;` is an audit
284/// marker — `grep SecretLen` finds every module that measures secrets — and
285/// generic code bounded only on `RevealSecret` cannot call `len()`. The other
286/// route, `with_secret(|s| s.len())`, needs no trait but surfaces in the
287/// `with_secret` audit sweep instead. Neither is prevented; both are greppable.
288/// This is accident-prevention, not a barrier.
289///
290/// Constant-time comparison is **not** length-hiding: `ConstantTimeEq` on
291/// variable-length secrets delegates to `subtle`, whose slice comparison
292/// short-circuits when lengths differ, so length *inequality* is observable
293/// through timing whether or not `len()` is ever called. Lengths are usually
294/// public in the protocols where that matters; for password verification, be
295/// aware of it.
296///
297/// # Examples
298///
299/// ```rust
300/// use secure_gate::{Fixed, SecretLen};
301///
302/// let key = Fixed::new([0u8; 32]);
303/// assert_eq!(key.len(), 32);
304/// assert_eq!(key.byte_len(), 32);
305/// assert!(!key.is_empty());
306/// ```
307pub trait SecretLen: RevealSecret {
308 /// Returns the number of elements in the secret, matching the underlying
309 /// container's `len()` — element count for `Vec<T>` and `[T; N]`, byte
310 /// count for `String` (where the element is a byte).
311 fn len(&self) -> usize;
312
313 /// Returns the total size of the secret in bytes.
314 ///
315 /// For single-byte element types (`Vec<u8>`, `String`, `[u8; N]`) this
316 /// equals `len()`. Implementations for multi-byte element types override
317 /// this to return `len() * core::mem::size_of::<T>()`.
318 #[inline(always)]
319 fn byte_len(&self) -> usize {
320 self.len()
321 }
322
323 /// Returns `true` if the secret is empty.
324 #[inline(always)]
325 fn is_empty(&self) -> bool {
326 self.len() == 0
327 }
328}