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}