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}