Utilities
Overview
The util module provides two utilities:
ct_eq— constant-time byte-slice equality backed byCRYPTO_memcmpSecretBuf— a heap buffer that is securely zeroed viaOPENSSL_cleansewhen dropped
ct_eq — Constant-Time Equality
#![allow(unused)]
fn main() {
/// Compare two byte slices for equality in constant time.
///
/// Returns `true` if both slices have the same length and content.
/// Returns `false` without leaking which byte differed or at what index.
///
/// Backed by OpenSSL's `CRYPTO_memcmp`, which is resistant to
/// compiler dead-store elimination and branch-prediction side channels.
pub fn ct_eq(a: &[u8], b: &[u8]) -> bool;
}
Use ct_eq when comparing MAC tags, authentication tokens, or any value where
a timing side channel could assist an attacker. Do not use == on secret
byte slices in security-sensitive paths.
#![allow(unused)]
fn main() {
use native_ossl::util::ct_eq;
let expected_tag = /* HMAC output */;
let received_tag = /* from network */;
if !ct_eq(&expected_tag, &received_tag) {
return Err("MAC verification failed");
}
}
SecretBuf — Secure Heap Buffer
#![allow(unused)]
fn main() {
pub struct SecretBuf { /* Vec<u8> */ }
impl SecretBuf {
/// Wrap an existing allocation. The buffer will be securely zeroed on drop.
pub fn new(data: Vec<u8>) -> Self;
/// Allocate a zero-initialised buffer of `len` bytes.
pub fn with_len(len: usize) -> Self;
/// Copy `data` into a new secure buffer.
pub fn from_slice(data: &[u8]) -> Self;
/// Number of bytes in the buffer.
pub fn len(&self) -> usize;
/// `true` if the buffer holds no bytes.
pub fn is_empty(&self) -> bool;
/// Mutable byte slice. Write derived key material directly into this.
pub fn as_mut_slice(&mut self) -> &mut [u8];
}
impl AsRef<[u8]> for SecretBuf { /* immutable byte slice */ }
impl Drop for SecretBuf { /* OPENSSL_cleanse on the allocation */ }
unsafe impl Send for SecretBuf {}
unsafe impl Sync for SecretBuf {}
}
SecretBuf intentionally does not implement Copy or Clone. Both would defeat
the zeroing guarantee: a clone creates a second copy of the secret material and
the original’s Drop only zeroes the first allocation.
When to Use SecretBuf
Use SecretBuf when the buffer will hold key material, passwords, or any other
secret that must not persist in memory after it is no longer needed.
For ordinary ciphertext or message data that is not secret in itself, a plain
Vec<u8> is sufficient.
How OPENSSL_cleanse Differs from Rust’s Drop
Rust’s compiler may optimise away writes to memory that it can prove are dead before the allocation is freed. This “dead store elimination” can remove a zeroing loop added at the end of a function’s scope, leaving the secret in freed heap memory until it is overwritten by a future allocation.
OPENSSL_cleanse is specifically designed to resist this optimisation. OpenSSL
implements it as a volatile write loop (or a platform memory-clearing intrinsic
such as explicit_bzero) that the compiler cannot remove, regardless of
optimisation level. This is the technique required by FIPS 140-3 for zeroization
of sensitive data.
Examples
Allocate a Key Buffer and Fill with Random Data
use native_ossl::util::SecretBuf;
use native_ossl::rand::Rand;
let mut key = SecretBuf::with_len(32);
Rand::fill(key.as_mut_slice())?;
// Use key.as_ref() for read-only access.
// When `key` is dropped, OPENSSL_cleanse zeroes the 32 bytes.
Wrap KDF Output in a Secure Buffer
When deriving key material, write the output directly into a SecretBuf to
ensure it is zeroed after use:
use native_ossl::util::SecretBuf;
use native_ossl::kdf::HkdfBuilder;
use native_ossl::digest::DigestAlg;
let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;
let ikm = b"input key material";
let salt = b"random salt";
let info = b"context";
let mut derived = SecretBuf::with_len(32);
HkdfBuilder::new(&sha256)
.key(ikm)
.salt(salt)
.info(info)
.derive(derived.as_mut_slice())?;
// derived is now ready to use as a symmetric key.
// On drop it is cleared with OPENSSL_cleanse.
Wrap an Existing Allocation
If key material arrives as a Vec<u8> from a deserialization step, transfer
ownership into a SecretBuf immediately:
use native_ossl::util::SecretBuf;
let raw_key: Vec<u8> = load_key_from_store()?;
let key = SecretBuf::new(raw_key);
// raw_key is consumed; only key owns the bytes now.
Design Notes
- No
Clone— cloning would create a second heap allocation containing the same secret. The original’sDropwould only clear the first copy. If a second reference is needed, useArc<Mutex<SecretBuf>>. - No
Copy—SecretBufis notCopyfor the same reason. Moving it transfers exclusive ownership; the destructor runs exactly once. - Zero-length buffers —
SecretBuf::new(vec![])is valid.Dropskips theOPENSSL_cleansecall when the length is zero to avoid passing a null pointer to the C function. from_slicecopies —SecretBuf::from_sliceallocates a new buffer and copies the input. If the input itself is sensitive, you are responsible for zeroing or dropping it promptly. Usefrom_slicewhen the caller holds a borrowed slice and needs an owned secure copy.