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}