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