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