mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
pv: test_utils: Add TEST-RAND generator for testing
Implement a test random number generator (RNG) using the TEST-RAND [1] generator provided by OpenSSL. This allows writing tests for OpenSSL functionality where OpenSSL internally uses RNGs, e.g. used for ML-KEM. The implementation uses RAND_set0_public [2] to set the thread-local RNG, which requires OpenSSL >= 3.1. [1] https://docs.openssl.org/3.0/man7/EVP_RAND-TEST-RAND/ [2] https://docs.openssl.org/3.1/man3/RAND_set0_public Assisted-by: IBM Bob:1.0.5 Acked-by: Steffen Eiden <seiden@linux.ibm.com> Acked-by: Timo Keller <tkeller@linux.ibm.com> Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
162a041077
commit
5340d11165
@@ -548,10 +548,167 @@ pub(crate) fn verify_signature<T: HasPublic>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use crate::{get_test_asset, PvCoreError};
|
||||
|
||||
/// Test that deterministic RNG contexts are thread-local and don't interfere.
|
||||
///
|
||||
/// Per OpenSSL documentation (RAND_get0_primary(3)):
|
||||
/// "The public and private DRBG are thread-local instances, which are used by
|
||||
/// RAND_bytes() and RAND_priv_bytes(), respectively."
|
||||
///
|
||||
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
|
||||
///
|
||||
/// Note: RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
|
||||
#[test]
|
||||
fn test_deterministic_rng_thread_isolation() {
|
||||
use std::sync::Barrier;
|
||||
|
||||
use openssl::rand::rand_bytes;
|
||||
|
||||
// Barriers to synchronize: thread1 installs → thread2 installs → both generate → both
|
||||
// complete
|
||||
let barrier_after_t1_install = Arc::new(Barrier::new(2));
|
||||
let barrier_after_t2_install = Arc::new(Barrier::new(2));
|
||||
let barrier_after_rand_bytes = Arc::new(Barrier::new(2));
|
||||
|
||||
let barrier1_clone = Arc::clone(&barrier_after_t1_install);
|
||||
let barrier2_clone = Arc::clone(&barrier_after_t2_install);
|
||||
let barrier3_clone = Arc::clone(&barrier_after_rand_bytes);
|
||||
|
||||
// Thread 1: Install deterministic RNG with specific entropy
|
||||
let thread1 = thread::spawn(move || {
|
||||
let entropy = [0x42u8; 4096];
|
||||
let nonce = [0x24u8; 48];
|
||||
|
||||
// Install thread-local deterministic RNG
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Signal thread2 that we've installed our RNG
|
||||
barrier1_clone.wait();
|
||||
|
||||
// Wait for thread2 to install its RNG
|
||||
barrier2_clone.wait();
|
||||
|
||||
// Now generate bytes while thread2 also has its RNG installed
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Wait for thread2 to also complete rand_bytes
|
||||
barrier3_clone.wait();
|
||||
|
||||
buf
|
||||
});
|
||||
|
||||
// Thread 2: Install different deterministic RNG after thread1
|
||||
let thread2 = thread::spawn(move || {
|
||||
// Wait for thread1 to install its RNG first
|
||||
barrier_after_t1_install.wait();
|
||||
|
||||
// Now install our own thread-local deterministic RNG with different entropy
|
||||
let entropy = [0xAAu8; 4096];
|
||||
let nonce = [0x55u8; 48];
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Signal thread1 that we've installed our RNG
|
||||
barrier_after_t2_install.wait();
|
||||
|
||||
// Generate bytes with our different entropy (concurrently with thread1)
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Wait for thread1 to also complete rand_bytes
|
||||
barrier_after_rand_bytes.wait();
|
||||
|
||||
buf
|
||||
});
|
||||
|
||||
let t1_buf = thread1.join().unwrap();
|
||||
let t2_buf = thread2.join().unwrap();
|
||||
|
||||
// Expected deterministic values for thread 1 (entropy=0x42, nonce=0x24)
|
||||
let expected_t1: [u8; 32] = [
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
];
|
||||
|
||||
// Expected deterministic values for thread 2 (entropy=0xAA, nonce=0x55)
|
||||
let expected_t2: [u8; 32] = [
|
||||
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
|
||||
170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
|
||||
];
|
||||
|
||||
// Verify each thread produced its expected deterministic output
|
||||
assert_eq!(
|
||||
t1_buf, expected_t1,
|
||||
"Thread 1 should produce deterministic output"
|
||||
);
|
||||
assert_eq!(
|
||||
t2_buf, expected_t2,
|
||||
"Thread 2 should produce deterministic output"
|
||||
);
|
||||
|
||||
// Also verify they are different (proves thread-local isolation)
|
||||
assert_ne!(
|
||||
t1_buf, t2_buf,
|
||||
"Different thread-local entropy should produce different output"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that the original RNG is properly restored after DeterministicTestRandGuard is dropped
|
||||
#[test]
|
||||
fn test_deterministic_rng_restoration() {
|
||||
use openssl::rand::rand_bytes;
|
||||
|
||||
// Generate random bytes with system RNG before installing deterministic RNG
|
||||
let mut before_buf = [0u8; 32];
|
||||
rand_bytes(&mut before_buf).unwrap();
|
||||
|
||||
let deterministic_buf = {
|
||||
let entropy = [0x42u8; 4096];
|
||||
let nonce = [0x24u8; 48];
|
||||
|
||||
// Install deterministic RNG
|
||||
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
|
||||
|
||||
// Generate deterministic bytes
|
||||
let mut buf = [0u8; 32];
|
||||
rand_bytes(&mut buf).unwrap();
|
||||
|
||||
// Expected deterministic output
|
||||
let expected: [u8; 32] = [
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
|
||||
];
|
||||
assert_eq!(buf, expected, "Should produce deterministic output");
|
||||
|
||||
buf
|
||||
// _rng is dropped here, should restore original RNG
|
||||
};
|
||||
|
||||
// Generate random bytes again with restored system RNG
|
||||
let mut after_buf = [0u8; 32];
|
||||
rand_bytes(&mut after_buf).unwrap();
|
||||
|
||||
// The system RNG should produce different random values each time
|
||||
// (extremely unlikely to match the deterministic output)
|
||||
assert_ne!(
|
||||
after_buf, deterministic_buf,
|
||||
"After restoration, system RNG should produce different random values"
|
||||
);
|
||||
|
||||
// Also verify that before and after are different (system RNG produces random values)
|
||||
// Note: This could theoretically fail with probability 1/2^256, but that's negligible
|
||||
assert_ne!(
|
||||
before_buf, after_buf,
|
||||
"System RNG should produce different random values on each call"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_ec() {
|
||||
let (ec_key, _) = get_test_keys();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2023
|
||||
// Copyright IBM Corp.
|
||||
|
||||
// DO NOT USE ANY OF THESE ITEMS IN PRODUCTION CODE
|
||||
// USED FOR INTERNAL UNIT AND FVT TESTING ONLY!!!
|
||||
use std::ffi::c_void;
|
||||
use std::fs;
|
||||
use std::mem::{size_of, ManuallyDrop};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::ec::{EcGroup, EcKey};
|
||||
@@ -118,3 +121,379 @@ fn get_keypair(pub_coords: &[u8], priv_num: &[u8]) -> Result<PKey<Private>, Erro
|
||||
key.check_key()?;
|
||||
PKey::from_ec_key(key)
|
||||
}
|
||||
|
||||
// To regenerate these bindings, run:
|
||||
// bindgen bindgen_wrapper.h -o bindgen_output.rs \
|
||||
// --allowlist-function "RAND_get0_public" \
|
||||
// --allowlist-function "RAND_get0_private" --allowlist-function "EVP_RAND_fetch" \
|
||||
// --allowlist-function "EVP_RAND_free" --allowlist-function "EVP_RAND_CTX_new" \
|
||||
// --allowlist-function "EVP_RAND_CTX_free" --allowlist-function "EVP_RAND_CTX_up_ref" \
|
||||
// --allowlist-function "EVP_RAND_instantiate" --allowlist-function "RAND_set0_public" \
|
||||
// --allowlist-function "RAND_set0_private" --allowlist-type "OSSL_PARAM"
|
||||
// where bindgen_wrapper.h contains:
|
||||
// #include <openssl/provider.h>
|
||||
// #include <openssl/rand.h>
|
||||
// #include <openssl/evp.h>
|
||||
// #include <openssl/params.h>
|
||||
mod ffi {
|
||||
use std::ffi::{c_char, c_int, c_uchar, c_uint, c_void};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OsslParam {
|
||||
pub key: *const c_char,
|
||||
pub data_type: c_uint,
|
||||
pub data: *mut c_void,
|
||||
pub data_size: usize,
|
||||
pub return_size: usize,
|
||||
}
|
||||
|
||||
pub enum OsslLibCtx {}
|
||||
pub enum OsslProvider {}
|
||||
pub enum EvpRand {}
|
||||
pub enum EvpRandCtx {}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn RAND_get0_public(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
|
||||
pub fn RAND_get0_private(ctx: *mut OsslLibCtx) -> *mut EvpRandCtx;
|
||||
pub fn EVP_RAND_fetch(
|
||||
libctx: *mut OsslLibCtx,
|
||||
algorithm: *const c_char,
|
||||
properties: *const c_char,
|
||||
) -> *mut EvpRand;
|
||||
pub fn EVP_RAND_free(rand: *mut EvpRand);
|
||||
pub fn EVP_RAND_CTX_new(rand: *mut EvpRand, parent: *mut EvpRandCtx) -> *mut EvpRandCtx;
|
||||
pub fn EVP_RAND_CTX_free(ctx: *mut EvpRandCtx);
|
||||
pub fn EVP_RAND_CTX_up_ref(ctx: *mut EvpRandCtx) -> c_int;
|
||||
pub fn EVP_RAND_instantiate(
|
||||
ctx: *mut EvpRandCtx,
|
||||
strength: c_uint,
|
||||
prediction_resistance: c_int,
|
||||
pstr: *const c_uchar,
|
||||
pstr_len: usize,
|
||||
params: *const OsslParam,
|
||||
) -> c_int;
|
||||
pub fn RAND_set0_public(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
|
||||
pub fn RAND_set0_private(ctx: *mut OsslLibCtx, rand: *mut EvpRandCtx) -> c_int;
|
||||
}
|
||||
}
|
||||
|
||||
// Constants for OSSL_PARAM construction
|
||||
const OSSL_PARAM_OCTET_STRING: u32 = 5;
|
||||
const OSSL_PARAM_UNSIGNED_INTEGER: u32 = 2;
|
||||
const OSSL_PARAM_END: u32 = 0;
|
||||
|
||||
fn ossl_param_end() -> ffi::OsslParam {
|
||||
ffi::OsslParam {
|
||||
key: std::ptr::null(),
|
||||
data_type: OSSL_PARAM_END,
|
||||
data: std::ptr::null_mut(),
|
||||
data_size: 0,
|
||||
return_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn ossl_param_octet_string(name: &'static [u8], data: &mut [u8]) -> ffi::OsslParam {
|
||||
// SAFETY: Constructing OSSL_PARAM for octet string.
|
||||
// - name is a static null-terminated C string, valid for 'static
|
||||
// - data is a valid mutable slice, pointer remains valid during param usage
|
||||
// - Pointer casts are safe as they preserve alignment and validity
|
||||
ffi::OsslParam {
|
||||
key: name.as_ptr().cast(),
|
||||
data_type: OSSL_PARAM_OCTET_STRING,
|
||||
data: data.as_mut_ptr().cast(),
|
||||
data_size: data.len(),
|
||||
return_size: data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ossl_param_uint(name: &'static [u8], value: &mut u32) -> ffi::OsslParam {
|
||||
// SAFETY: Constructing OSSL_PARAM for unsigned integer.
|
||||
// - name is a static null-terminated C string, valid for 'static
|
||||
// - value is a valid mutable reference, pointer remains valid during param usage
|
||||
// - Pointer cast to c_void is safe as it preserves alignment and validity
|
||||
ffi::OsslParam {
|
||||
key: name.as_ptr().cast(),
|
||||
data_type: OSSL_PARAM_UNSIGNED_INTEGER,
|
||||
data: (value as *mut u32).cast::<c_void>(),
|
||||
data_size: size_of::<u32>(),
|
||||
return_size: size_of::<u32>(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FetchedRand(NonNull<ffi::EvpRand>);
|
||||
|
||||
impl FetchedRand {
|
||||
const TEST_RAND_NAME: &'static [u8] = b"TEST-RAND\0";
|
||||
|
||||
fn fetch_test_rand() -> Result<Self, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - null_mut() is valid for optional OSSL_LIB_CTX parameter
|
||||
// - Self::TEST_RAND_NAME is a valid null-terminated C string
|
||||
// - null() is valid for optional properties parameter
|
||||
// - Returns null on error, which we handle via NonNull::new
|
||||
let rand = unsafe {
|
||||
ffi::EVP_RAND_fetch(
|
||||
std::ptr::null_mut(),
|
||||
Self::TEST_RAND_NAME.as_ptr().cast(),
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
NonNull::new(rand).map(Self).ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut ffi::EvpRand {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FetchedRand {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.0 is a valid non-null EVP_RAND pointer that we own.
|
||||
// This is the only place we call free, preventing double-free.
|
||||
unsafe {
|
||||
ffi::EVP_RAND_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RandCtx(NonNull<ffi::EvpRandCtx>);
|
||||
|
||||
impl RandCtx {
|
||||
fn new(rand: &FetchedRand) -> Result<Self, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - rand.as_ptr() is a valid non-null EVP_RAND pointer
|
||||
// - null_mut() is valid for optional parent parameter
|
||||
// - Returns null on error, which we handle via NonNull::new
|
||||
let ctx = unsafe { ffi::EVP_RAND_CTX_new(rand.as_ptr(), std::ptr::null_mut()) };
|
||||
NonNull::new(ctx).map(Self).ok_or_else(ErrorStack::get)
|
||||
}
|
||||
|
||||
fn up_ref(ptr: *mut ffi::EvpRandCtx) -> Result<Self, ErrorStack> {
|
||||
let ptr = NonNull::new(ptr).ok_or_else(ErrorStack::get)?;
|
||||
// SAFETY: ptr is a valid non-null EVP_RAND_CTX pointer.
|
||||
// EVP_RAND_CTX_up_ref increments the reference count.
|
||||
// Returns 1 on success, 0 on failure.
|
||||
let rc = unsafe { ffi::EVP_RAND_CTX_up_ref(ptr.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(Self(ptr))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn current_public() -> Result<Option<Self>, ErrorStack> {
|
||||
// SAFETY: RAND_get0_public returns a borrowed pointer (no ownership transfer).
|
||||
// Returns null if no public RNG is set, which we handle.
|
||||
let ptr = unsafe { ffi::RAND_get0_public(std::ptr::null_mut()) };
|
||||
if ptr.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Self::up_ref(ptr).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_private() -> Result<Option<Self>, ErrorStack> {
|
||||
// SAFETY: RAND_get0_private returns a borrowed pointer (no ownership transfer).
|
||||
// Returns null if no private RNG is set, which we handle.
|
||||
let ptr = unsafe { ffi::RAND_get0_private(std::ptr::null_mut()) };
|
||||
if ptr.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Self::up_ref(ptr).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut ffi::EvpRandCtx {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
|
||||
fn instantiate_test_rand(&self, entropy: &[u8], nonce: &[u8]) -> Result<(), ErrorStack> {
|
||||
// See https://docs.openssl.org/3.1/man7/EVP_RAND-TEST-RAND/#description
|
||||
// for the available parameters.
|
||||
const TEST_ENTROPY_PARAM: &[u8] = b"test_entropy\0";
|
||||
const TEST_NONCE_PARAM: &[u8] = b"test_nonce\0";
|
||||
const STRENGTH_PARAM: &[u8] = b"strength\0";
|
||||
|
||||
let mut entropy = entropy.to_vec();
|
||||
let mut nonce = nonce.to_vec();
|
||||
let mut strength = 256u32;
|
||||
let params = [
|
||||
ossl_param_uint(STRENGTH_PARAM, &mut strength),
|
||||
ossl_param_octet_string(TEST_ENTROPY_PARAM, &mut entropy),
|
||||
ossl_param_octet_string(TEST_NONCE_PARAM, &mut nonce),
|
||||
ossl_param_end(),
|
||||
];
|
||||
|
||||
// SAFETY: Calling OpenSSL C API with valid parameters.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - strength is a valid u32 value
|
||||
// - prediction_resistance=0 is valid
|
||||
// - pstr=null and pstr_len=0 indicate no personalization string
|
||||
// - params points to a valid array of OSSL_PARAM with proper terminator
|
||||
// - All mutable references in params remain valid for the call duration
|
||||
let rc = unsafe {
|
||||
ffi::EVP_RAND_instantiate(
|
||||
self.as_ptr(),
|
||||
strength,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
params.as_ptr(),
|
||||
)
|
||||
};
|
||||
if rc == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn install_as_public(self) -> Result<InstalledRandCtx, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API to transfer ownership.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_public takes ownership of the context on success (rc==1)
|
||||
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
|
||||
let rc = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), self.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
|
||||
fn install_as_private(self) -> Result<InstalledRandCtx, ErrorStack> {
|
||||
// SAFETY: Calling OpenSSL C API to transfer ownership.
|
||||
// - self.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_private takes ownership of the context on success (rc==1)
|
||||
// - We wrap in ManuallyDrop to prevent double-free since OpenSSL now owns it
|
||||
let rc = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), self.as_ptr()) };
|
||||
if rc == 1 {
|
||||
Ok(InstalledRandCtx(ManuallyDrop::new(self)))
|
||||
} else {
|
||||
Err(ErrorStack::get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: self.0 is a valid non-null EVP_RAND_CTX pointer that we own.
|
||||
// This is only called when ownership was NOT transferred to OpenSSL.
|
||||
// InstalledRandCtx uses ManuallyDrop to prevent this from running after transfer.
|
||||
unsafe {
|
||||
ffi::EVP_RAND_CTX_free(self.0.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InstalledRandCtx(ManuallyDrop<RandCtx>);
|
||||
|
||||
impl Drop for InstalledRandCtx {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: Ownership of the EVP_RAND_CTX was transferred to OpenSSL
|
||||
// via RAND_set0_public/private, so we must not call EVP_RAND_CTX_free.
|
||||
// ManuallyDrop prevents RandCtx::drop from running automatically.
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PreviousRandCtx(Option<RandCtx>);
|
||||
|
||||
impl PreviousRandCtx {
|
||||
fn capture_public() -> Result<Self, ErrorStack> {
|
||||
RandCtx::current_public().map(Self)
|
||||
}
|
||||
|
||||
fn capture_private() -> Result<Self, ErrorStack> {
|
||||
RandCtx::current_private().map(Self)
|
||||
}
|
||||
|
||||
fn restore_public(&mut self) {
|
||||
let Some(ctx) = self.0.take() else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: Restoring previously captured RNG context.
|
||||
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_public takes ownership of the context
|
||||
// - We forget ctx to prevent double-free since OpenSSL now owns it
|
||||
// - Ignoring return value as restoration is best-effort during cleanup
|
||||
let _ = unsafe { ffi::RAND_set0_public(std::ptr::null_mut(), ctx.as_ptr()) };
|
||||
std::mem::forget(ctx);
|
||||
}
|
||||
|
||||
fn restore_private(&mut self) {
|
||||
let Some(ctx) = self.0.take() else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: Restoring previously captured RNG context.
|
||||
// - ctx.as_ptr() is a valid non-null EVP_RAND_CTX pointer
|
||||
// - RAND_set0_private takes ownership of the context
|
||||
|
||||
// - We forget ctx to prevent double-free since OpenSSL now owns it
|
||||
// - Ignoring return value as restoration is best-effort during cleanup
|
||||
let _ = unsafe { ffi::RAND_set0_private(std::ptr::null_mut(), ctx.as_ptr()) };
|
||||
std::mem::forget(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeterministicTestRandGuard {
|
||||
previous_public: PreviousRandCtx,
|
||||
previous_private: PreviousRandCtx,
|
||||
_public: InstalledRandCtx,
|
||||
_private: InstalledRandCtx,
|
||||
}
|
||||
|
||||
impl DeterministicTestRandGuard {
|
||||
/// Install OpenSSL >= 3 TEST-RAND as the thread-local public/private RNG for deterministic
|
||||
/// tests.
|
||||
///
|
||||
/// The supplied entropy is consumed across generate calls. The nonce is replayed for each
|
||||
/// nonce request. Per OpenSSL documentation, the public and private DRBG instances are
|
||||
/// thread-local, so each thread can safely install its own deterministic RNG without
|
||||
/// affecting other threads.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// From OpenSSL documentation (RAND_get0_primary(3)):
|
||||
/// > "The public and private DRBG are thread-local instances, which are used by
|
||||
/// > RAND_bytes() and RAND_priv_bytes(), respectively."
|
||||
///
|
||||
/// Reference: <https://docs.openssl.org/3.1/man3/RAND_get0_primary/>
|
||||
///
|
||||
/// **Note:** RAND_set0_public() and RAND_set0_private() require OpenSSL >= 3.1.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an OpenSSL error if the TEST-RAND provider cannot be configured.
|
||||
pub fn install(entropy: &[u8], nonce: &[u8]) -> Result<Self, ErrorStack> {
|
||||
let previous_public = PreviousRandCtx::capture_public()?;
|
||||
let previous_private = PreviousRandCtx::capture_private()?;
|
||||
let rand = FetchedRand::fetch_test_rand()?;
|
||||
|
||||
let public = RandCtx::new(&rand)?;
|
||||
public.instantiate_test_rand(entropy, nonce)?;
|
||||
let public = public.install_as_public()?;
|
||||
|
||||
let private = RandCtx::new(&rand)?;
|
||||
private.instantiate_test_rand(entropy, nonce)?;
|
||||
let private = private.install_as_private()?;
|
||||
|
||||
Ok(Self {
|
||||
previous_public,
|
||||
previous_private,
|
||||
_public: public,
|
||||
_private: private,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DeterministicTestRandGuard {
|
||||
fn drop(&mut self) {
|
||||
self.previous_private.restore_private();
|
||||
self.previous_public.restore_public();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user