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 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 length of the secret in bytes.
184    ///
185    /// Always safe to call — does not expose secret contents.
186    fn len(&self) -> usize;
187
188    /// Returns `true` if the secret is empty.
189    ///
190    /// Always safe to call — does not expose secret contents.
191    #[inline(always)]
192    fn is_empty(&self) -> bool {
193        self.len() == 0
194    }
195
196    /// Consumes the wrapper and returns the inner value wrapped in [`InnerSecret`],
197    /// preserving automatic zeroization on drop.
198    ///
199    /// This is the safe, idiomatic path when ownership of the secret is required — for
200    /// example, to hand the value to an API that takes `T` by value, to move between
201    /// wrapper types, or at FFI boundaries where the callee takes ownership.
202    ///
203    /// The zeroization contract transfers to the caller: when the returned
204    /// `InnerSecret<Self::Inner>` drops, it calls `Self::Inner::zeroize()` automatically,
205    /// exactly as the wrapper's own `Drop` impl would have.
206    ///
207    /// # Availability
208    ///
209    /// Only callable when `Self::Inner: Sized + Default + Zeroize`. The `Default` bound
210    /// is required to construct a zero-sentinel that the wrapper's `Drop` impl runs on
211    /// after the real secret is moved out. The `Zeroize` bound is required so the
212    /// returned `InnerSecret<T>` can call `zeroize()` on drop. For types that intentionally
213    /// omit `Default` (e.g. custom key types where an all-zero value is invalid or
214    /// dangerous), `into_inner` is not callable — use `with_secret` or `expose_secret`
215    /// instead.
216    ///
217    /// The three concrete implementations in this crate all satisfy the bounds:
218    /// - `Fixed<[u8; N]>` — `[u8; N]: Default + Zeroize` ✓
219    /// - `Dynamic<String>` — `String: Default + Zeroize` ✓
220    /// - `Dynamic<Vec<T>>` — `Vec<T>: Default + Zeroize` ✓
221    ///
222    /// # Debug Behavior
223    ///
224    /// The returned [`InnerSecret<T>`] always redacts `Debug` as `[REDACTED]`, preserving
225    /// the wrapper-level redaction invariant after ownership transfer.
226    ///
227    /// # Examples
228    ///
229    /// ```rust
230    /// use secure_gate::{Fixed, RevealSecret};
231    ///
232    /// let key = Fixed::new([0xABu8; 16]);
233    /// let owned: secure_gate::InnerSecret<[u8; 16]> = key.into_inner();
234    /// // `owned` zeroizes its 16 bytes when it drops — same guarantee as Fixed<[u8; 16]>.
235    /// assert_eq!(*owned, [0xABu8; 16]);
236    /// assert_eq!(format!("{:?}", owned), "[REDACTED]");
237    /// ```
238    ///
239    /// ```rust
240    /// # #[cfg(feature = "alloc")]
241    /// # {
242    /// use secure_gate::{Dynamic, RevealSecret};
243    ///
244    /// let pw = Dynamic::<String>::new("hunter2".to_string());
245    /// let owned: secure_gate::InnerSecret<String> = pw.into_inner();
246    /// assert_eq!(*owned, "hunter2");
247    /// // `owned` zeroizes its heap buffer when it drops.
248    /// # }
249    /// ```
250    fn into_inner(self) -> crate::InnerSecret<Self::Inner>
251    where
252        Self: Sized,
253        Self::Inner: Sized + Default + zeroize::Zeroize;
254}