Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Utilities

Overview

The util module provides two utilities:

  • ct_eq — constant-time byte-slice equality backed by CRYPTO_memcmp
  • SecretBuf — a heap buffer that is securely zeroed via OPENSSL_cleanse when 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’s Drop would only clear the first copy. If a second reference is needed, use Arc<Mutex<SecretBuf>>.
  • No CopySecretBuf is not Copy for the same reason. Moving it transfers exclusive ownership; the destructor runs exactly once.
  • Zero-length buffersSecretBuf::new(vec![]) is valid. Drop skips the OPENSSL_cleanse call when the length is zero to avoid passing a null pointer to the C function.
  • from_slice copiesSecretBuf::from_slice allocates a new buffer and copies the input. If the input itself is sensitive, you are responsible for zeroing or dropping it promptly. Use from_slice when the caller holds a borrowed slice and needs an owned secure copy.