Skip to main content

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 the [`revealed_secrets`](crate::traits::revealed_secrets) module
30//! and SECURITY.md for the full list of auditable access surfaces.
31//!
32//! # Key Traits
33//!
34//! | Trait                  | Access     | Preferred Method          | Escape Hatch             | Metadata          | Feature     |
35//! |------------------------|------------|---------------------------|--------------------------|-------------------|-------------|
36//! | [`RevealSecret`]                | Read-only  | `with_secret` (scoped)    | `expose_secret`     | — (see [`SecretLen`]) | Always |
37//! | [`crate::RevealSecretMut`]      | Mutable    | `with_secret_mut` (scoped)| `expose_secret_mut` | Inherits above    | Always |
38//!
39//! # Security Model
40//!
41//! - **Core wrappers** (`Fixed<T>`, `Dynamic<T>`) implement both traits → full access.
42//! - **Generated newtypes** (`fixed_newtype!` / `dynamic_newtype!`) implement both, forwarding to the wrapper they hold.
43//! - **`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.
44//! - **Zero-cost** — all methods are `#[inline(always)]` where possible.
45//! - **Scoped access preferred** — `with_secret` / `with_secret_mut` limit borrow lifetime, reducing leak risk.
46//! - **Direct exposure** (`expose_secret` / `expose_secret_mut`) is provided for legitimate needs (FFI, third-party APIs), but marked as an escape hatch.
47//! - **Owned consumption** (`into_inner`) is available when the secret must be moved out of the
48//!   wrapper. It transfers ownership of the plain value and nothing is copied; the wrapper's
49//!   zeroize-on-drop does not follow it, so the caller owns the value's lifetime from there.
50//!
51//! # Note for `RevealSecret` Implementors
52//!
53//! Adding `into_inner` to this trait means every `RevealSecret` implementor must provide
54//! an owned-extraction implementation. For wrappers intentionally limited to borrowing
55//! semantics, implement `into_inner` with `unimplemented!()` or a compile-time guard, and
56//! document the design rationale clearly.
57//!
58//! # Usage Guidelines
59//!
60//! The preferred and recommended way to access secrets is the scoped `with_secret` /
61//! `with_secret_mut` methods. `expose_secret` / `expose_secret_mut` are escape hatches
62//! for rare cases and should be audited closely. `into_inner` is reserved for the uncommon
63//! case where ownership of the inner value is required.
64//!
65//! - **Always prefer scoped methods** (`with_secret`, `with_secret_mut`) in application code.
66//! - Use direct exposure only when necessary (e.g., passing raw pointer + length to C FFI).
67//! - Audit every `expose_secret*` call — they should be rare and well-justified.
68//! - Audit every `into_inner` call — it transfers ownership out of the wrapper's protection.
69//!
70//! # Examples
71//!
72//! Scoped (recommended):
73//!
74//! ```rust
75//! use secure_gate::{Fixed, RevealSecret};
76//!
77//! let secret = Fixed::new([42u8; 4]);
78//! let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
79//! assert_eq!(sum, 42 * 4);
80//! ```
81//!
82//! Direct (escape hatch – use with caution):
83//!
84//! ```rust
85//! use secure_gate::{Fixed, RevealSecret};
86//!
87//! let secret = Fixed::new([42u8; 4]);
88//!
89//! // Example: FFI call needing raw pointer + length
90//! // unsafe {
91//! //     c_function(secret.expose_secret().as_ptr(), secret.len());
92//! // }
93//! ```
94//!
95//! Mutable scoped:
96//!
97//! ```rust
98//! use secure_gate::{Fixed, RevealSecret, RevealSecretMut};
99//!
100//! let mut secret = Fixed::new([0u8; 4]);
101//! secret.with_secret_mut(|bytes| bytes[0] = 99);
102//! assert_eq!(secret.expose_secret()[0], 99);
103//! ```
104//!
105//! Owned consumption (into_inner):
106//!
107//! ```rust
108//! use secure_gate::{Fixed, RevealSecret};
109//!
110//! let key = Fixed::new([0xABu8; 16]);
111//! // Consumes `key` and transfers ownership of the bytes: they move into `owned`
112//! // and an inert sentinel is left for `Fixed::drop` to zeroize in their place.
113//! let owned: [u8; 16] = key.into_inner();
114//! assert_eq!(owned, [0xABu8; 16]);
115//! // `owned` is an ordinary array now — you own its lifetime.
116//! ```
117//!
118//! Polymorphic generic code:
119//!
120//! ```rust
121//! use secure_gate::SecretLen;
122//!
123//! fn print_length<S: SecretLen>(secret: &S) {
124//!     println!("Length: {} bytes", secret.len());
125//! }
126//! ```
127//!
128//! These traits are the foundation of secure-gate's security model: all secret access is
129//! explicit, auditable, and controlled. Prefer scoped methods in nearly all cases.
130//!
131//! # Implementation Notes
132//!
133//! Long-lived `expose_secret()` references can defeat scoping — the borrow outlives the
134//! call site and the compiler cannot enforce that the secret is not retained. This is an
135//! intentional escape hatch for FFI and legacy APIs; audit every call site.
136
137/// Read-only access to a wrapped secret.
138///
139/// Implemented for **all** [`Fixed<T>`](crate::Fixed) and [`Dynamic<T>`](crate::Dynamic),
140/// whatever the inner type — length metadata lives in the narrower [`SecretLen`].
141/// Prefer the scoped [`with_secret`](Self::with_secret) method; use
142/// [`expose_secret`](Self::expose_secret) only when a long-lived reference is
143/// unavoidable. See [`RevealSecretMut`](crate::RevealSecretMut) for the mutable
144/// counterpart.
145pub trait RevealSecret {
146    /// The inner secret type being revealed.
147    ///
148    /// This can be a sized type (e.g. `[u8; N]`, `u32`) or unsized (e.g. `str`, `[u8]`).
149    type Inner: ?Sized;
150
151    /// Provides scoped (recommended) read-only access to the secret.
152    ///
153    /// The closure receives a reference that cannot escape — the borrow ends when
154    /// the closure returns, minimizing the lifetime of the exposed secret.
155    /// Prefer this over [`expose_secret`](Self::expose_secret) in all application code.
156    ///
157    /// # The reference cannot escape. A copy of what it points at can.
158    ///
159    /// This method governs *access*. It does not — and cannot — constrain what the
160    /// closure body does with the bytes once it can see them. Every line below compiles,
161    /// and each leaves an owned copy in ordinary memory that this wrapper will never
162    /// wipe, because it does not know the copy exists:
163    ///
164    /// ```rust
165    /// use secure_gate::{Fixed, RevealSecret};
166    ///
167    /// let key = Fixed::new([0xABu8; 32]);
168    ///
169    /// let escaped: [u8; 32] = key.with_secret(|p| *p);  // `[u8; N]` is `Copy`
170    /// let also:    [u8; 32] = key.with_secret(|&p| p);  // same, with no `*` to notice
171    /// assert_eq!(escaped, also);                        // both outlived the closure
172    /// ```
173    ///
174    /// The deref forms need `Self::Inner: Copy`, so the borrow checker rejects them for
175    /// `Dynamic<Vec<u8>>` and `Dynamic<String>` (`E0507`). **That immunity does not extend
176    /// to a method that copies**, and every inner type has one — `to_vec`, `clone`,
177    /// `to_string`, `<[u8; N]>::try_from`. Those copy *through* the borrow instead of
178    /// moving out of it, so no rule applies and no operator appears in the source. No
179    /// `rustc` or `clippy` lint detects any of it.
180    ///
181    /// Copy wrapper-to-wrapper instead, so the bytes never exist outside something that
182    /// wipes them:
183    ///
184    /// ```rust
185    /// use secure_gate::{Fixed, RevealSecret, RevealSecretMut};
186    ///
187    /// let src = Fixed::new([0x5Au8; 32]);
188    /// let mut dst = Fixed::new([0u8; 32]);
189    ///
190    /// src.with_secret(|s| dst.with_secret_mut(|d| d.copy_from_slice(s)));
191    /// assert!(dst.with_secret(|d| d.iter().all(|&b| b == 0x5A)));
192    /// ```
193    ///
194    /// Where the destination is new, prefer a sized constructor and fill it in place —
195    /// [`Fixed::new_with`](crate::Fixed::new_with) or `Dynamic::new_with(len, |slot| …)`.
196    /// See `SECURITY.md` § "Copying the secret out of a reveal borrow" for the full
197    /// scoping, the detection patterns and what they cannot catch.
198    ///
199    /// # Examples
200    ///
201    /// ```rust
202    /// use secure_gate::{Fixed, RevealSecret};
203    ///
204    /// let secret = Fixed::new([42u8; 4]);
205    /// let sum: u32 = secret.with_secret(|bytes| bytes.iter().map(|&b| b as u32).sum());
206    /// assert_eq!(sum, 42 * 4);
207    /// ```
208    fn with_secret<F, R>(&self, f: F) -> R
209    where
210        F: FnOnce(&Self::Inner) -> R;
211
212    /// Returns a direct (auditable) read-only reference to the secret.
213    ///
214    /// Long-lived `expose_secret()` references can defeat scoping — prefer
215    /// [`with_secret`](Self::with_secret) in application code. Use this only when
216    /// a long-lived reference is unavoidable (e.g. FFI, third-party APIs).
217    ///
218    /// Everything in [`with_secret`](Self::with_secret)'s note on copying applies here
219    /// with a wider window: `*secret.expose_secret()` copies a `Copy` inner value, and
220    /// `secret.expose_secret().to_vec()` copies any of them. There is no closure to bound
221    /// how far the reference travels first, which is why this is the auditable spelling
222    /// rather than the recommended one.
223    ///
224    /// # Coming from `secrecy`
225    ///
226    /// This is the closest analogue to `secrecy`'s `ExposeSecret::expose_secret`, and the
227    /// spelling is the same on purpose — this crate kept that vocabulary when it went its
228    /// own way. What differs is that it is not the only door. As of `secrecy` 0.10.3,
229    /// `fn expose_secret(&self) -> &S` is documented there as *"the only method providing
230    /// access to a secret"*; here it is the auditable escape hatch, and
231    /// [`with_secret`](Self::with_secret) is the recommended path.
232    ///
233    /// That matters because the habit transfers silently. Reaching for the familiar name
234    /// compiles, works, and protects the secret exactly as it did before — so nothing
235    /// signals that the narrower tool exists. `with_secret` confines the borrow to a
236    /// closure, which this method cannot do; neither one stops the body from *copying* the
237    /// secret out. Prefer `with_secret` unless a long-lived reference is genuinely
238    /// unavoidable, and see `SECURITY.md` § "Copying the secret out of a reveal borrow"
239    /// for what neither of them can prevent.
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// use secure_gate::{Fixed, RevealSecret};
245    ///
246    /// let secret = Fixed::new([42u8; 4]);
247    ///
248    /// // Auditable escape hatch — FFI use case:
249    /// // unsafe { c_fn(secret.expose_secret().as_ptr(), secret.len()); }
250    /// let _ = secret.expose_secret();
251    /// ```
252    fn expose_secret(&self) -> &Self::Inner;
253
254    /// Consumes the wrapper and transfers ownership of the plain inner value.
255    ///
256    /// This is the safe, idiomatic path when ownership of the secret is required — for
257    /// example, to hand the value to an API that takes `T` by value, to move between
258    /// wrapper types, or at FFI boundaries where the callee takes ownership.
259    ///
260    /// **Protection ends here.** The returned value is an ordinary `[u8; N]` / `String` /
261    /// `Vec<T>` with no zeroize-on-drop and no redacted `Debug` — you own the secret and
262    /// its lifetime from this call onward. The value is moved, never duplicated-and-kept:
263    /// an inert [`SentinelValue`](crate::SentinelValue) is left for the wrapper's `Drop` to
264    /// zeroize in its place, so exactly one copy survives the call and it is yours. For
265    /// `Dynamic` that move is literal — the allocation itself is handed over, nothing is
266    /// read or written. For `Fixed` the secret is stored inline, so the bytes are
267    /// transferred into your slot and the wrapper's slot receives the sentinel; there is no
268    /// indirection to hand over instead. If you want the protection to continue, do not call this
269    /// — keep the wrapper, or move the value into a new one.
270    ///
271    /// # Availability
272    ///
273    /// Only callable when `Self::Inner: Sized + SentinelValue + Zeroize`. The
274    /// [`SentinelValue`](crate::SentinelValue) bound is required to construct the inert
275    /// placeholder that the wrapper's `Drop` impl runs on after the real secret is moved
276    /// out. (A plain `Default` bound is deliberately **not** used: the standard library
277    /// only implements `Default` for arrays up to 32 elements, which would make
278    /// `into_inner` unusable for `[u8; 64]` and other common key sizes.) The `Zeroize`
279    /// bound is required because the wrapper still zeroizes the sentinel on drop.
280    /// For custom inner types that intentionally omit `SentinelValue` (e.g. key types
281    /// where no safe placeholder value exists), `into_inner` is not callable — use
282    /// `with_secret` or `expose_secret` instead.
283    ///
284    /// The three concrete implementations in this crate all satisfy the bounds:
285    /// - `Fixed<[u8; N]>` — `[u8; N]: SentinelValue + Zeroize` for **any** `N` ✓
286    /// - `Dynamic<String>` — `String: SentinelValue + Zeroize` ✓
287    /// - `Dynamic<Vec<T>>` — `Vec<T>: SentinelValue + Zeroize` ✓
288    ///
289    /// # Allocation Behavior
290    ///
291    /// `Fixed::into_inner` is zero-cost (no allocation). The `Dynamic::into_inner`
292    /// impls allocate one small `Box<T>` sentinel (24 bytes on 64-bit) before swapping
293    /// out the real secret; if that allocation panics (OOM), the original `inner` is
294    /// untouched and `Dynamic::drop` zeroizes the secret during unwind. Confidentiality
295    /// is preserved on the panic path. See the per-impl docs on
296    /// [`Dynamic`](crate::Dynamic) for the exact pattern.
297    ///
298    /// # Examples
299    ///
300    /// ```rust
301    /// use secure_gate::{Fixed, RevealSecret};
302    ///
303    /// let key = Fixed::new([0xABu8; 16]);
304    /// let owned: [u8; 16] = key.into_inner();
305    /// assert_eq!(owned, [0xABu8; 16]);
306    /// // `owned` is a plain array — no wiping, no redaction. You own it.
307    /// ```
308    ///
309    /// ```rust
310    /// # #[cfg(feature = "alloc")]
311    /// # {
312    /// use secure_gate::{Dynamic, RevealSecret};
313    ///
314    /// let pw = Dynamic::<String>::new("hunter2".to_string());
315    /// let owned: String = pw.into_inner();
316    /// assert_eq!(owned, "hunter2");
317    /// // Same allocation, moved out — no copy was made.
318    /// # }
319    /// ```
320    fn into_inner(self) -> Self::Inner
321    where
322        Self: Sized,
323        Self::Inner: Sized + crate::SentinelValue + zeroize::Zeroize;
324}
325
326/// Length metadata for secrets whose inner type has a meaningful length.
327///
328/// Separated from [`RevealSecret`] so the core access trait can be implemented
329/// for **every** inner type — including local user-defined ones (see the
330/// [`CloneableSecret`](crate::CloneableSecret) newtype pattern), which have no
331/// meaningful `len()`. Implemented for `Fixed<[T; N]>`, `Dynamic<String>`, and
332/// `Dynamic<Vec<T>>`.
333///
334/// # Security
335///
336/// These methods do not expose secret **contents** — but that is not the same
337/// as length being harmless, and the distinction matters:
338///
339/// - For `Fixed<[u8; N]>`, `len()` is `N`: a compile-time constant already
340///   visible in the type. Nothing is revealed.
341/// - For variable-length secrets (`Dynamic<String>`, `Dynamic<Vec<u8>>`),
342///   length **can be sensitive**. A password's length narrows a brute-force
343///   search; a token's length can fingerprint its issuer. Treat it as metadata
344///   *about* a secret, not as public data: validate against it (minimum-length
345///   checks, buffer sizing) but never log it, put it in an error message that
346///   leaves the process, or persist it next to an identifier.
347///
348/// This trait is deliberately separate from [`RevealSecret`] so that measuring
349/// a secret is a visible choice. `use secure_gate::SecretLen;` is an audit
350/// marker — `grep SecretLen` finds every module that measures secrets — and
351/// generic code bounded only on `RevealSecret` cannot call `len()`. The other
352/// route, `with_secret(|s| s.len())`, needs no trait but surfaces in the
353/// `with_secret` audit sweep instead. Neither is prevented; both are greppable.
354/// This is accident-prevention, not a barrier.
355///
356/// Constant-time comparison is **not** length-hiding: `ConstantTimeEq` on
357/// variable-length secrets delegates to `subtle`, whose slice comparison
358/// short-circuits when lengths differ, so length *inequality* is observable
359/// through timing whether or not `len()` is ever called. Lengths are usually
360/// public in the protocols where that matters; for password verification, be
361/// aware of it.
362///
363/// # Examples
364///
365/// ```rust
366/// use secure_gate::{Fixed, SecretLen};
367///
368/// let key = Fixed::new([0u8; 32]);
369/// assert_eq!(key.len(), 32);
370/// assert_eq!(key.byte_len(), 32);
371/// assert!(!key.is_empty());
372/// ```
373pub trait SecretLen: RevealSecret {
374    /// Returns the number of elements in the secret, matching the underlying
375    /// container's `len()` — element count for `Vec<T>` and `[T; N]`, byte
376    /// count for `String` (where the element is a byte).
377    fn len(&self) -> usize;
378
379    /// Returns the total size of the secret in bytes.
380    ///
381    /// For single-byte element types (`Vec<u8>`, `String`, `[u8; N]`) this
382    /// equals `len()`. Implementations for multi-byte element types override
383    /// this to return `len() * core::mem::size_of::<T>()`.
384    #[inline(always)]
385    fn byte_len(&self) -> usize {
386        self.len()
387    }
388
389    /// Returns `true` if the secret is empty.
390    #[inline(always)]
391    fn is_empty(&self) -> bool {
392        self.len() == 0
393    }
394}