secret_utils/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
4
5//! Secret handling utilities for the PAKEs-Conflux workspace.
6//!
7//! This crate is intended to centralize secret-handling patterns across the
8//! `aucpace`, `spake2`, and `srp` crates. It will provide:
9//! - Typed wrappers for secret material (passwords, verifiers, scalars, derived keys).
10//! - Reliable in-memory erasure via zeroization semantics.
11//! - Clear API boundaries that prevent accidental exposure or cloning of secrets.
12//! - Testing guidance and utilities to validate zeroization behavior where feasible.
13//!
14//! Design goals
15//! - Minimize accidental copies of secret data.
16//! - Ensure secrets are zeroized on drop and after critical transitions.
17//! - Provide clear documentation and policies for secret lifecycles.
18//! - Remain no_std-friendly with an `alloc`-based default.
19//!
20//! Scope (initial scaffolding)
21//! - This initial version is documentation-only with module placeholders. There
22//! are no public APIs yet. Follow-up phases will introduce concrete wrappers,
23//! traits, and utilities, along with unit and integration tests.
24//!
25//! Feature flags
26//! - `alloc` (default): Enables heap-backed containers to support secret buffers.
27//! - `std`: Convenience alias that implies `alloc`. Intended for environments
28//! where the standard library is available.
29//!
30//! Usage policy (to be enforced in subsequent phases)
31//! - All password bytes, ephemeral private scalars, long-lived verifiers, and
32//! derived session keys must be wrapped by secret types provided here.
33//! - Public APIs must not expose raw secret bytes. Controlled exposure
34//! methods will be provided and documented.
35//! - Conversions to/from public representations (e.g., serialized forms) will be
36//! centralized in audited helpers.
37//!
38//! Tests and CI (to be added in later phases)
39//! - Unit tests to verify zeroization semantics and API boundaries.
40//! - Integration tests to exercise protocol flows without leaking secrets.
41//! - CI gates to help prevent regressions in secret-handling policies.
42
43#[cfg(feature = "alloc")]
44extern crate alloc;
45
46/// Placeholder module for secret wrappers.
47///
48/// This module will host strongly-typed wrappers (e.g., secret byte buffers,
49/// scalar wrappers) with drop-time zeroization and constrained exposure.
50/// No items are defined yet; content will be added in subsequent phases.
51pub mod wrappers {
52 //! Zeroizing secret wrappers for byte-oriented secrets.
53 //!
54 //! Notes:
55 //! - These wrappers are currently behind the `alloc` feature to remain
56 //! compatible with `no_std` builds where `alloc` is unavailable.
57 //! - Introducing these types does not change any public API in dependent
58 //! crates yet. They are provided here for upcoming incremental adoption.
59 //!
60 //! Intended usage:
61 //! - `SecretBytes`: for password bytes or other sensitive buffers provided by users.
62 //! - `SecretKey`: for derived session keys or key material that must be cleared on drop.
63
64 #[cfg(feature = "alloc")]
65 use alloc::vec::Vec;
66 #[cfg(feature = "alloc")]
67 use core::ops::Deref;
68 #[cfg(feature = "alloc")]
69 use zeroize::{Zeroize, ZeroizeOnDrop};
70
71 /// Zeroizing wrapper for secret byte buffers (e.g., passwords).
72 #[cfg(feature = "alloc")]
73 #[derive(Zeroize, ZeroizeOnDrop)]
74 pub struct SecretBytes(Vec<u8>);
75
76 #[cfg(feature = "alloc")]
77 impl SecretBytes {
78 /// Create a new `SecretBytes` from an owned byte vector.
79 pub fn new(bytes: Vec<u8>) -> Self {
80 Self(bytes)
81 }
82
83 /// Borrow the inner bytes without copying.
84 pub fn expose(&self) -> &[u8] {
85 &self.0
86 }
87
88 /// Consume and return the inner `Vec<u8>`.
89 ///
90 /// Note: this transfers ownership of the secret data to the caller.
91 /// Prefer to keep secrets wrapped and scoped when possible.
92 pub fn into_inner(mut self) -> Vec<u8> {
93 core::mem::take(&mut self.0)
94 }
95 }
96
97 #[cfg(feature = "alloc")]
98 impl AsRef<[u8]> for SecretBytes {
99 fn as_ref(&self) -> &[u8] {
100 &self.0
101 }
102 }
103
104 #[cfg(feature = "alloc")]
105 impl Deref for SecretBytes {
106 type Target = [u8];
107
108 fn deref(&self) -> &Self::Target {
109 &self.0
110 }
111 }
112
113 #[cfg(feature = "alloc")]
114 impl From<Vec<u8>> for SecretBytes {
115 fn from(v: Vec<u8>) -> Self {
116 Self(v)
117 }
118 }
119
120 #[cfg(feature = "alloc")]
121 impl core::fmt::Debug for SecretBytes {
122 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123 write!(f, "SecretBytes([redacted], len={})", self.0.len())
124 }
125 }
126
127 /// Zeroizing wrapper for derived session keys or other key material.
128 #[cfg(feature = "alloc")]
129 #[derive(Zeroize, ZeroizeOnDrop)]
130 pub struct SecretKey(Vec<u8>);
131
132 #[cfg(feature = "alloc")]
133 impl core::fmt::Debug for SecretKey {
134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 write!(f, "SecretKey([redacted], len={})", self.0.len())
136 }
137 }
138
139 #[cfg(feature = "alloc")]
140 impl SecretKey {
141 /// Create a new `SecretKey` from an owned byte vector.
142 pub fn new(bytes: Vec<u8>) -> Self {
143 Self(bytes)
144 }
145
146 /// Borrow the inner key bytes without copying.
147 pub fn expose(&self) -> &[u8] {
148 &self.0
149 }
150
151 /// Perform a best-effort constant-time equality check against another key.
152 ///
153 /// Note: This avoids early returns and processes both inputs in full,
154 /// but constant-time properties can still be impacted by compiler or platform.
155 /// Prefer minimizing comparisons of secret data in application code.
156 pub fn ct_eq(&self, other: &Self) -> bool {
157 let a = &self.0;
158 let b = &other.0;
159
160 // Fold length difference into accumulator to avoid short-circuiting on length.
161 let max_len = if a.len() > b.len() { a.len() } else { b.len() };
162 let mut acc: u8 = (a.len() ^ b.len()) as u8;
163
164 let mut i = 0;
165 while i < max_len {
166 // Use get().copied().unwrap_or(0) to avoid panics and avoid data-dependent branching.
167 let av = a.get(i).copied().unwrap_or(0);
168 let bv = b.get(i).copied().unwrap_or(0);
169 acc |= av ^ bv;
170 i += 1;
171 }
172 acc == 0
173 }
174
175 /// Consume and return the inner `Vec<u8>`.
176 ///
177 /// Note: this transfers ownership of the secret key to the caller.
178 pub fn into_inner(mut self) -> Vec<u8> {
179 core::mem::take(&mut self.0)
180 }
181 }
182
183 #[cfg(feature = "alloc")]
184 impl AsRef<[u8]> for SecretKey {
185 fn as_ref(&self) -> &[u8] {
186 &self.0
187 }
188 }
189
190 // Added to support transparent slice access to key bytes.
191 #[cfg(feature = "alloc")]
192 impl Deref for SecretKey {
193 type Target = [u8];
194
195 fn deref(&self) -> &Self::Target {
196 &self.0
197 }
198 }
199
200 // Added to allow constructing SecretKey from existing Vec<u8> without copying.
201 #[cfg(feature = "alloc")]
202 impl From<Vec<u8>> for SecretKey {
203 fn from(v: Vec<u8>) -> Self {
204 Self(v)
205 }
206 }
207}
208
209/// Placeholder module for secret-related traits and policies.
210///
211/// This module will define shared traits and policy helpers for secret lifecycles,
212/// zeroization semantics, and conversion boundaries.
213pub mod traits {
214 //! Future contents:
215 //! - Traits describing zeroization guarantees
216 //! - Traits for controlled exposure and borrowing
217 //! - Helpers for documenting and enforcing lifecycles
218 //!
219 //! Intentionally empty in this initial scaffold.
220}
221
222/// Placeholder module for internal test utilities.
223///
224/// This module will eventually include optional test-only helpers to validate
225/// zeroization and to instrument secret lifecycles under controlled conditions.
226#[cfg(any(test, doc))]
227pub mod test_utils {
228 //! Future contents:
229 //! - Test-only helpers for memory inspections (where viable)
230 //! - Utilities to construct scoped secrets for lifecycle tests
231 //!
232 //! Intentionally empty in this initial scaffold.
233}
234
235#[cfg(test)]
236mod tests {
237 use super::wrappers::{SecretBytes, SecretKey};
238 use alloc::format;
239 use alloc::vec;
240 use zeroize::Zeroize;
241
242 #[test]
243 fn secret_key_zeroize_sets_to_zero() {
244 let mut key = SecretKey::new(vec![1u8, 2, 3, 4, 5]);
245 // Ensure it's initially non-zero
246 assert!(key.expose().iter().any(|&b| b != 0));
247 // Zeroize and verify all bytes are zero
248 key.zeroize();
249 assert!(key.expose().iter().all(|&b| b == 0));
250 }
251
252 #[test]
253 fn secret_bytes_zeroize_sets_to_zero() {
254 let mut bytes = SecretBytes::new(vec![10u8, 11, 12, 13]);
255 // Ensure it's initially non-zero
256 assert!(bytes.expose().iter().any(|&b| b != 0));
257 // Zeroize and verify all bytes are zero
258 bytes.zeroize();
259 assert!(bytes.expose().iter().all(|&b| b == 0));
260 }
261
262 #[test]
263 fn secret_key_debug_is_redacted() {
264 let key = SecretKey::new(vec![9u8, 8, 7]);
265 let s = format!("{:?}", key);
266 // Ensure the debug output is redacted and includes the length
267 assert!(s.contains("SecretKey([redacted]"));
268 assert!(s.contains("len=3"));
269 // Ensure raw contents are not present
270 assert!(!s.contains("9, 8, 7"));
271 }
272
273 #[test]
274 fn secret_bytes_debug_is_redacted() {
275 let bytes = SecretBytes::new(vec![1u8, 2, 3, 4]);
276 let s = format!("{:?}", bytes);
277 assert!(s.contains("SecretBytes([redacted]"));
278 assert!(s.contains("len=4"));
279 assert!(!s.contains("1, 2, 3, 4"));
280 }
281
282 #[test]
283 fn secret_key_into_inner_round_trip() {
284 let original = vec![1u8, 2, 3, 4, 5];
285 let key = SecretKey::new(original.clone());
286 let out = key.into_inner();
287 assert_eq!(out, vec![1u8, 2, 3, 4, 5]);
288 }
289
290 #[test]
291 fn secret_bytes_into_inner_round_trip() {
292 let original = vec![10u8, 11, 12, 13];
293 let bytes = SecretBytes::new(original.clone());
294 let out = bytes.into_inner();
295 assert_eq!(out, vec![10u8, 11, 12, 13]);
296 }
297
298 #[test]
299 fn secret_key_as_ref_and_deref() {
300 let key = SecretKey::new(vec![42u8, 43, 44]);
301 assert_eq!(key.as_ref(), &[42u8, 43, 44]);
302 assert_eq!(&*key, &[42u8, 43, 44]);
303 }
304
305 #[test]
306 fn secret_bytes_as_ref_and_deref() {
307 let bytes = SecretBytes::new(vec![7u8, 8, 9]);
308 assert_eq!(bytes.as_ref(), &[7u8, 8, 9]);
309 assert_eq!(&*bytes, &[7u8, 8, 9]);
310 }
311
312 #[test]
313 fn secret_key_ct_eq_true_and_false() {
314 let a1 = SecretKey::new(vec![1u8, 2, 3, 4]);
315 let a2 = SecretKey::new(vec![1u8, 2, 3, 4]);
316 let b = SecretKey::new(vec![1u8, 2, 3, 5]);
317 assert!(a1.ct_eq(&a2));
318 assert!(!a1.ct_eq(&b));
319 }
320}