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}