Skip to main content

native_ossl/
util.rs

1//! General-purpose utilities.
2
3use native_ossl_sys as sys;
4
5// ── Constant-time comparison ──────────────────────────────────────────────────
6
7/// Compare two byte slices in constant time, returning `true` iff they are equal.
8///
9/// The comparison time is proportional to the slice length and does not depend
10/// on the data values, preventing timing side-channel attacks.
11///
12/// Slices of different lengths return `false` immediately (without constant-time
13/// behaviour), since lengths are generally not considered secret.  If you need
14/// to conceal the length, pad both inputs to the same size before calling.
15///
16/// Backed by `CRYPTO_memcmp` from OpenSSL.
17#[must_use]
18pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
19    if a.len() != b.len() {
20        return false;
21    }
22    if a.is_empty() {
23        return true;
24    }
25    // SAFETY: both slices are valid for `a.len()` bytes (guaranteed by Rust)
26    // and are not mutated; CRYPTO_memcmp reads them in constant time.
27    let rc = unsafe {
28        sys::CRYPTO_memcmp(
29            a.as_ptr().cast::<std::ffi::c_void>(),
30            b.as_ptr().cast::<std::ffi::c_void>(),
31            a.len(),
32        )
33    };
34    rc == 0
35}
36
37// ── SecretBuf ─────────────────────────────────────────────────────────────────
38
39/// A heap buffer that is securely zeroed via `OPENSSL_cleanse` on drop.
40///
41/// Use to hold key material, passwords, and other sensitive byte sequences.
42/// The zeroing is performed by OpenSSL's `OPENSSL_cleanse`, which is the
43/// FIPS-approved memory-clearing function and is not eliminated by the
44/// compiler's dead-store optimiser.
45///
46/// # Example
47///
48/// ```ignore
49/// use native_ossl::util::SecretBuf;
50///
51/// let mut key = SecretBuf::with_len(32);
52/// native_ossl::rand::Rand::fill(key.as_mut_slice()).unwrap();
53/// // key bytes are securely erased when `key` is dropped.
54/// ```
55pub struct SecretBuf {
56    data: Vec<u8>,
57}
58
59impl SecretBuf {
60    /// Wrap an existing allocation. Takes ownership; the buffer will be
61    /// securely zeroed when the `SecretBuf` is dropped.
62    #[must_use]
63    pub fn new(data: Vec<u8>) -> Self {
64        SecretBuf { data }
65    }
66
67    /// Allocate a zero-initialised buffer of `len` bytes.
68    #[must_use]
69    pub fn with_len(len: usize) -> Self {
70        SecretBuf {
71            data: vec![0u8; len],
72        }
73    }
74
75    /// Copy `data` into a new secure buffer.
76    #[must_use]
77    pub fn from_slice(data: &[u8]) -> Self {
78        SecretBuf {
79            data: data.to_vec(),
80        }
81    }
82
83    /// Number of bytes in the buffer.
84    #[must_use]
85    pub fn len(&self) -> usize {
86        self.data.len()
87    }
88
89    /// `true` if the buffer holds no bytes.
90    #[must_use]
91    pub fn is_empty(&self) -> bool {
92        self.data.is_empty()
93    }
94
95    /// Expose the buffer as a mutable byte slice.
96    ///
97    /// Useful for writing derived key material directly into the buffer.
98    pub fn as_mut_slice(&mut self) -> &mut [u8] {
99        &mut self.data
100    }
101}
102
103impl AsRef<[u8]> for SecretBuf {
104    fn as_ref(&self) -> &[u8] {
105        &self.data
106    }
107}
108
109impl Drop for SecretBuf {
110    fn drop(&mut self) {
111        if !self.data.is_empty() {
112            unsafe {
113                sys::OPENSSL_cleanse(
114                    self.data.as_mut_ptr().cast::<std::ffi::c_void>(),
115                    self.data.len(),
116                );
117            }
118        }
119    }
120}
121
122// SAFETY: the buffer is owned; no aliasing across threads.
123unsafe impl Send for SecretBuf {}
124unsafe impl Sync for SecretBuf {}
125
126#[cfg(test)]
127mod tests {
128    use super::{ct_eq, SecretBuf};
129
130    #[test]
131    fn ct_eq_equal_slices() {
132        assert!(ct_eq(b"hello", b"hello"));
133    }
134
135    #[test]
136    fn ct_eq_different_values() {
137        assert!(!ct_eq(b"hello", b"world"));
138    }
139
140    #[test]
141    fn ct_eq_different_lengths() {
142        assert!(!ct_eq(b"hi", b"hii"));
143    }
144
145    #[test]
146    fn ct_eq_empty_slices() {
147        assert!(ct_eq(b"", b""));
148    }
149
150    #[test]
151    fn ct_eq_one_byte_differ() {
152        assert!(!ct_eq(&[0u8; 32], &{
153            let mut v = [0u8; 32];
154            v[31] = 1;
155            v
156        }));
157    }
158
159    #[test]
160    fn with_len_creates_correct_size() {
161        let buf = SecretBuf::with_len(32);
162        assert_eq!(buf.len(), 32);
163        assert!(!buf.is_empty());
164    }
165
166    #[test]
167    fn from_slice_copies_data() {
168        let src = b"secret key material";
169        let buf = SecretBuf::from_slice(src);
170        assert_eq!(buf.as_ref(), src);
171    }
172
173    #[test]
174    fn new_wraps_ownership() {
175        let v = vec![1u8, 2, 3];
176        let buf = SecretBuf::new(v);
177        assert_eq!(buf.as_ref(), &[1, 2, 3]);
178    }
179
180    #[test]
181    fn empty_buf_is_empty() {
182        let buf = SecretBuf::new(vec![]);
183        assert!(buf.is_empty());
184        // Drop must not call cleanse on a zero-length buffer (no panic).
185    }
186
187    #[test]
188    fn as_mut_slice_writes_through() {
189        let mut buf = SecretBuf::with_len(4);
190        buf.as_mut_slice().copy_from_slice(&[10, 20, 30, 40]);
191        assert_eq!(buf.as_ref(), &[10, 20, 30, 40]);
192    }
193}