native_ossl/rand.rs
1//! Random number generation — `Rand` (simple), `RandAlg`, and `RandCtx`.
2//!
3//! Phase 3.4 delivers `RandAlg`; Phase 4.4 extends this module with
4//! `Rand` and `RandCtx`.
5
6use crate::error::ErrorStack;
7use native_ossl_sys as sys;
8use std::ffi::CStr;
9use std::sync::Arc;
10
11// ── RandAlg — algorithm descriptor ───────────────────────────────────────────
12
13/// An OpenSSL RAND algorithm descriptor (`EVP_RAND*`).
14///
15/// Fetched once and reused to create `RandCtx` instances.
16pub struct RandAlg {
17 ptr: *mut sys::EVP_RAND,
18 /// Keeps the library context alive for the lifetime of this descriptor.
19 /// `EVP_RAND*` is bound to the context it was fetched from; dropping the
20 /// context before the algorithm descriptor would leave the pointer dangling.
21 _lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
22}
23
24impl RandAlg {
25 /// Fetch a RAND algorithm from the global default library context.
26 ///
27 /// Common algorithm names: `c"CTR-DRBG"`, `c"HASH-DRBG"`, `c"HMAC-DRBG"`.
28 ///
29 /// # Errors
30 ///
31 /// Returns `Err` if the algorithm is not available.
32 pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
33 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
34 let ptr = unsafe { sys::EVP_RAND_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
35 if ptr.is_null() {
36 return Err(ErrorStack::drain());
37 }
38 Ok(RandAlg {
39 ptr,
40 _lib_ctx: None,
41 })
42 }
43
44 /// Fetch a RAND algorithm within an explicit library context.
45 ///
46 /// Use this when the DRBG must be bound to a specific provider set
47 /// (e.g. a FIPS-isolated `LibCtx`).
48 ///
49 /// The `Arc<LibCtx>` is retained for the lifetime of the `RandAlg` to
50 /// ensure the context outlives the algorithm descriptor.
51 ///
52 /// # Errors
53 ///
54 /// Returns `Err` if the algorithm is not available in `ctx`.
55 pub fn fetch_in(
56 ctx: &Arc<crate::lib_ctx::LibCtx>,
57 name: &CStr,
58 props: Option<&CStr>,
59 ) -> Result<Self, ErrorStack> {
60 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
61 let ptr = unsafe { sys::EVP_RAND_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
62 if ptr.is_null() {
63 return Err(ErrorStack::drain());
64 }
65 Ok(RandAlg {
66 ptr,
67 _lib_ctx: Some(Arc::clone(ctx)),
68 })
69 }
70
71 /// Return the raw `EVP_RAND*` pointer. Valid for the lifetime of `self`.
72 #[must_use]
73 pub(crate) fn as_ptr(&self) -> *mut sys::EVP_RAND {
74 self.ptr
75 }
76
77 /// Security strength of this algorithm descriptor in bits.
78 ///
79 /// Calls `EVP_RAND_get_params` with the `"strength"` key to read the
80 /// declared security strength of the algorithm (e.g. 256 for CTR-DRBG
81 /// with AES-256, 128 for HMAC-DRBG with SHA-256).
82 ///
83 /// # Errors
84 ///
85 /// Returns `Err` if the OpenSSL parameter query fails.
86 pub fn params_strength(&self) -> Result<u32, ErrorStack> {
87 // Build a one-element query array with an initial value of 0.
88 // OSSL_PARAM_BLD_push_uint records the key name and the u32 type;
89 // EVP_RAND_get_params fills in the actual value in-place.
90 let mut params = crate::params::ParamBuilder::new()?
91 .push_uint(c"strength", 0)?
92 .build()?;
93 // SAFETY:
94 // - self.ptr is non-null: guaranteed by `fetch` / `fetch_in` which
95 // return Err on null before constructing RandAlg.
96 // - Lifetime: params lives for the duration of this call; the mutable
97 // pointer is valid until params is dropped at the end of this scope.
98 // - Exclusivity: no other reference to the Params array exists; we
99 // own it exclusively for the duration of this call.
100 crate::ossl_call!(sys::EVP_RAND_get_params(self.ptr, params.as_mut_ptr()))?;
101 params.get_uint(c"strength")
102 }
103}
104
105impl Drop for RandAlg {
106 fn drop(&mut self) {
107 unsafe { sys::EVP_RAND_free(self.ptr) };
108 }
109}
110
111// SAFETY: `EVP_RAND*` is reference-counted and immutable after fetch.
112unsafe impl Send for RandAlg {}
113unsafe impl Sync for RandAlg {}
114
115// ── Rand — simple fill helpers (Phase 4.4) ────────────────────────────────────
116
117/// Zero-size type with static methods wrapping `RAND_bytes` / `RAND_priv_bytes`.
118pub struct Rand;
119
120impl Rand {
121 /// Fill `buf` with cryptographically random bytes.
122 ///
123 /// Zero-copy: writes directly into the caller's buffer.
124 ///
125 /// # Panics
126 ///
127 /// Panics if `buf.len() > i32::MAX`.
128 ///
129 /// # Errors
130 ///
131 /// Returns `Err` if the PRNG fails (e.g. not seeded).
132 pub fn fill(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
133 let n = i32::try_from(buf.len()).expect("buffer too large for RAND_bytes");
134 let rc = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) };
135 if rc != 1 {
136 return Err(crate::error::ErrorStack::drain());
137 }
138 Ok(())
139 }
140
141 /// Fill `buf` with private (non-predictable) random bytes.
142 ///
143 /// # Panics
144 ///
145 /// Panics if `buf.len() > i32::MAX`.
146 ///
147 /// # Errors
148 pub fn fill_private(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
149 let n = i32::try_from(buf.len()).expect("buffer too large for RAND_priv_bytes");
150 let rc = unsafe { sys::RAND_priv_bytes(buf.as_mut_ptr(), n) };
151 if rc != 1 {
152 return Err(crate::error::ErrorStack::drain());
153 }
154 Ok(())
155 }
156
157 /// Allocate and fill a `Vec<u8>` of `n` random bytes.
158 ///
159 /// Prefer `fill` when the destination buffer is already allocated.
160 ///
161 /// # Errors
162 pub fn bytes(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
163 let mut buf = vec![0u8; n];
164 Self::fill(&mut buf)?;
165 Ok(buf)
166 }
167
168 /// Allocate and fill a `Vec<u8>` of `n` private random bytes.
169 ///
170 /// Equivalent to `bytes` but uses `RAND_priv_bytes` — suitable for key
171 /// material and other values that must not be disclosed.
172 ///
173 /// # Errors
174 pub fn bytes_private(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
175 let mut buf = vec![0u8; n];
176 Self::fill_private(&mut buf)?;
177 Ok(buf)
178 }
179}
180
181// ── GenerateRequest — named struct for EVP_RAND_generate parameters ───────────
182
183/// Parameters for `RandCtx::generate`.
184///
185/// Use struct update syntax for partial overrides:
186/// ```ignore
187/// GenerateRequest { prediction_resistance: true, ..Default::default() }
188/// ```
189pub struct GenerateRequest<'a> {
190 /// Requested security strength in bits (e.g. 256).
191 pub strength: u32,
192 /// If `true`, OpenSSL reseeds before generating.
193 pub prediction_resistance: bool,
194 /// Optional additional input bytes fed into the DRBG.
195 pub additional_input: Option<&'a [u8]>,
196}
197
198impl Default for GenerateRequest<'_> {
199 fn default() -> Self {
200 GenerateRequest {
201 strength: 256,
202 prediction_resistance: false,
203 additional_input: None,
204 }
205 }
206}
207
208// ── RandState ─────────────────────────────────────────────────────────────────
209
210/// Lifecycle state of a `RandCtx` DRBG instance.
211///
212/// Returned by [`RandCtx::state`]. Maps the `EVP_RAND_STATE_*` constants
213/// from `<openssl/evp.h>`.
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum RandState {
216 /// Context created but not yet instantiated (seeded).
217 Uninitialised,
218 /// Context is seeded and ready to generate.
219 Ready,
220 /// Context entered an unrecoverable error state.
221 Error,
222 /// Unrecognised value returned by OpenSSL (forward-compat guard).
223 Unknown(i32),
224}
225
226// ── RandCtx — EVP_RAND_CTX wrapper ───────────────────────────────────────────
227
228/// A seeded DRBG context (`EVP_RAND_CTX*`).
229///
230/// Has `up_ref` so `Clone` is implemented; wrapping in `Arc<RandCtx>` is safe.
231pub struct RandCtx {
232 ptr: *mut sys::EVP_RAND_CTX,
233}
234
235impl RandCtx {
236 /// Create a new uninstantiated DRBG context from an algorithm descriptor.
237 ///
238 /// Provide `parent = None` to seed from the global PRNG.
239 ///
240 /// # Errors
241 pub fn new(alg: &RandAlg, parent: Option<&RandCtx>) -> Result<Self, crate::error::ErrorStack> {
242 let parent_ptr = parent.map_or(std::ptr::null_mut(), |p| p.ptr);
243 let ptr = unsafe { sys::EVP_RAND_CTX_new(alg.as_ptr(), parent_ptr) };
244 if ptr.is_null() {
245 return Err(crate::error::ErrorStack::drain());
246 }
247 Ok(RandCtx { ptr })
248 }
249
250 /// Instantiate (seed) the DRBG at the requested security strength.
251 ///
252 /// Must be called before `generate` if the context was not auto-seeded.
253 /// When `parent = None` was used in `new`, pass `strength ≤ 128` to stay
254 /// within the system seed-source's entropy ceiling.
255 ///
256 /// # Errors
257 pub fn instantiate(
258 &mut self,
259 strength: u32,
260 prediction_resistance: bool,
261 params: Option<&crate::params::Params<'_>>,
262 ) -> Result<(), crate::error::ErrorStack> {
263 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
264 let rc = unsafe {
265 sys::EVP_RAND_instantiate(
266 self.ptr,
267 strength,
268 i32::from(prediction_resistance),
269 std::ptr::null(),
270 0,
271 params_ptr,
272 )
273 };
274 if rc != 1 {
275 return Err(crate::error::ErrorStack::drain());
276 }
277 Ok(())
278 }
279
280 /// Generate random bytes into `out`.
281 ///
282 /// Zero-copy: writes directly into the caller's slice.
283 ///
284 /// # Errors
285 pub fn generate(
286 &mut self,
287 out: &mut [u8],
288 req: &GenerateRequest<'_>,
289 ) -> Result<(), crate::error::ErrorStack> {
290 let (ai_ptr, ai_len) = req
291 .additional_input
292 .map_or((std::ptr::null(), 0), |s| (s.as_ptr(), s.len()));
293
294 let rc = unsafe {
295 sys::EVP_RAND_generate(
296 self.ptr,
297 out.as_mut_ptr(),
298 out.len(),
299 req.strength,
300 i32::from(req.prediction_resistance),
301 ai_ptr,
302 ai_len,
303 )
304 };
305 if rc != 1 {
306 return Err(crate::error::ErrorStack::drain());
307 }
308 Ok(())
309 }
310
311 /// Generate random bytes with default parameters (strength=256, no prediction
312 /// resistance, no additional input).
313 ///
314 /// Equivalent to calling `generate` with [`GenerateRequest::default`].
315 /// Call [`Self::instantiate`] before the first `fill`.
316 ///
317 /// # Errors
318 pub fn fill(&mut self, out: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
319 self.generate(out, &GenerateRequest::default())
320 }
321
322 /// Security strength of this DRBG instance in bits.
323 #[must_use]
324 pub fn strength(&self) -> u32 {
325 unsafe { sys::EVP_RAND_get_strength(self.ptr) }
326 }
327
328 /// Current lifecycle state of this DRBG context.
329 ///
330 /// Returns [`RandState::Ready`] after a successful [`Self::instantiate`].
331 #[must_use]
332 pub fn state(&self) -> RandState {
333 match unsafe { sys::EVP_RAND_get_state(self.ptr) } {
334 0 => RandState::Uninitialised,
335 1 => RandState::Ready,
336 2 => RandState::Error,
337 n => RandState::Unknown(n),
338 }
339 }
340
341 /// Set DRBG parameters (e.g. reseed interval, additional input length).
342 ///
343 /// # Errors
344 ///
345 /// Returns `Err` if `EVP_RAND_CTX_set_params` fails.
346 pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
347 // SAFETY:
348 // - self.ptr is non-null (constructor invariant)
349 // - params.as_ptr() is valid for the duration of this call
350 // - &mut self ensures exclusive access
351 crate::ossl_call!(sys::EVP_RAND_CTX_set_params(self.ptr, params.as_ptr()))
352 }
353
354 /// Return a reference to the process-wide **public** DRBG.
355 ///
356 /// The returned [`GlobalRandCtx`] does **not** free the pointer when dropped —
357 /// the global DRBG is owned by the OpenSSL runtime.
358 ///
359 /// Pass `Some(global.as_rand_ctx())` as the `parent` argument to
360 /// [`RandCtx::new`] to chain a child DRBG off the global instance.
361 ///
362 /// Requires OpenSSL 3.2+.
363 ///
364 /// # Errors
365 ///
366 /// Returns `Err` if the global DRBG is not yet initialised.
367 #[cfg(ossl320)]
368 pub fn public() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
369 let ptr = unsafe { sys::RAND_get0_public(std::ptr::null_mut()) };
370 if ptr.is_null() {
371 return Err(crate::error::ErrorStack::drain());
372 }
373 Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
374 }
375
376 /// Return a reference to the process-wide **private** DRBG.
377 ///
378 /// Same semantics as [`RandCtx::public`].
379 ///
380 /// Requires OpenSSL 3.2+.
381 ///
382 /// # Errors
383 #[cfg(ossl320)]
384 pub fn private_global() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
385 let ptr = unsafe { sys::RAND_get0_private(std::ptr::null_mut()) };
386 if ptr.is_null() {
387 return Err(crate::error::ErrorStack::drain());
388 }
389 Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
390 }
391
392 /// Return a reference to the process-wide **primary** DRBG.
393 ///
394 /// The primary DRBG is the root of the global DRBG hierarchy — the public
395 /// and private DRBGs are seeded from it. It is useful for diagnostics and
396 /// for configuring the root of a custom DRBG chain.
397 ///
398 /// Same ownership semantics as [`RandCtx::public`]: the returned
399 /// [`GlobalRandCtx`] does **not** free the pointer on drop.
400 ///
401 /// Requires OpenSSL 3.2+.
402 ///
403 /// # Errors
404 ///
405 /// Returns `Err` if the primary DRBG is not yet initialised.
406 #[cfg(ossl320)]
407 pub fn primary() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
408 // SAFETY:
409 // - null_mut() selects the default library context (acceptable per OpenSSL docs)
410 // - RAND_get0_primary returns a borrowed pointer to the process-wide DRBG;
411 // ManuallyDrop ensures it is never freed through this handle
412 // - no mutable aliasing: all callers hold a shared borrow via GlobalRandCtx
413 let ptr = unsafe { sys::RAND_get0_primary(std::ptr::null_mut()) };
414 if ptr.is_null() {
415 return Err(crate::error::ErrorStack::drain());
416 }
417 Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
418 }
419}
420
421// ── GlobalRandCtx (OpenSSL 3.2+) ─────────────────────────────────────────────
422
423/// A borrowed handle to one of the process-wide global DRBGs.
424///
425/// Obtained from [`RandCtx::public`] or [`RandCtx::private_global`].
426///
427/// **Does not implement `Clone` or `Drop`** — the global DRBG is owned by the
428/// OpenSSL runtime and must not be freed through this handle. Implements
429/// `Deref<Target = RandCtx>` so all read-only `RandCtx` operations are available,
430/// and `as_rand_ctx()` provides a `&RandCtx` to pass as a parent to [`RandCtx::new`].
431#[cfg(ossl320)]
432pub struct GlobalRandCtx(std::mem::ManuallyDrop<RandCtx>);
433
434// SAFETY: the underlying EVP_RAND_CTX* is the global DRBG; it is safe to move
435// this handle to another thread.
436#[cfg(ossl320)]
437unsafe impl Send for GlobalRandCtx {}
438
439#[cfg(ossl320)]
440impl std::ops::Deref for GlobalRandCtx {
441 type Target = RandCtx;
442 fn deref(&self) -> &RandCtx {
443 &self.0
444 }
445}
446
447#[cfg(ossl320)]
448impl GlobalRandCtx {
449 /// Borrow as `&RandCtx` for use as the `parent` argument in [`RandCtx::new`].
450 #[must_use]
451 pub fn as_rand_ctx(&self) -> &RandCtx {
452 &self.0
453 }
454}
455
456// EVP_RAND_CTX_up_ref was added in OpenSSL 3.1.0.
457#[cfg(ossl310)]
458impl Clone for RandCtx {
459 fn clone(&self) -> Self {
460 unsafe { sys::EVP_RAND_CTX_up_ref(self.ptr) };
461 RandCtx { ptr: self.ptr }
462 }
463}
464
465impl Drop for RandCtx {
466 fn drop(&mut self) {
467 unsafe { sys::EVP_RAND_CTX_free(self.ptr) };
468 }
469}
470
471unsafe impl Send for RandCtx {}
472unsafe impl Sync for RandCtx {}
473
474// ── Tests ─────────────────────────────────────────────────────────────────────
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn fetch_ctr_drbg_succeeds() {
482 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
483 drop(alg);
484 }
485
486 #[test]
487 fn fetch_nonexistent_fails() {
488 assert!(RandAlg::fetch(c"NONEXISTENT_RAND_XYZ", None).is_err());
489 }
490
491 #[test]
492 fn rand_fill_nonzero() {
493 let mut buf = [0u8; 32];
494 Rand::fill(&mut buf).unwrap();
495 // Probability of all-zero is 2^(-256) — treat as impossible.
496 assert_ne!(buf, [0u8; 32]);
497 }
498
499 #[test]
500 fn rand_bytes_len() {
501 let v = Rand::bytes(64).unwrap();
502 assert_eq!(v.len(), 64);
503 }
504
505 #[cfg(ossl320)]
506 #[test]
507 fn rand_primary_is_accessible() {
508 let primary = RandCtx::primary().expect("primary DRBG must be initialised");
509 // The primary DRBG should be in the Ready state after OpenSSL initialisation.
510 assert_eq!(primary.state(), RandState::Ready);
511 }
512
513 #[cfg(ossl320)]
514 #[test]
515 fn rand_primary_strength_nonzero() {
516 let primary = RandCtx::primary().unwrap();
517 assert!(
518 primary.strength() > 0,
519 "primary DRBG must report non-zero strength"
520 );
521 }
522
523 #[test]
524 fn ctr_drbg_params_strength_call_succeeds() {
525 // EVP_RAND_get_params on an algorithm descriptor does not populate
526 // "strength" on OpenSSL 3.5+ (strength depends on instantiation config).
527 // Verify the call does not error; the returned value may legitimately be 0.
528 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
529 alg.params_strength().unwrap();
530 }
531
532 #[test]
533 fn hmac_drbg_params_strength_call_succeeds() {
534 // Same limitation as CTR-DRBG: algorithm-level params_strength may be 0.
535 let alg = RandAlg::fetch(c"HMAC-DRBG", None).unwrap();
536 alg.params_strength().unwrap();
537 }
538
539 #[test]
540 fn rand_ctx_two_outputs_differ() {
541 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
542 let mut ctx = RandCtx::new(&alg, None).unwrap();
543 // The default CTR-DRBG uses AES-256-CTR, which requires 256-bit entropy
544 // from SEED-SRC. Many systems cap at 128 bits without a parent DRBG.
545 // Explicitly configure AES-128-CTR (128-bit strength) so instantiation
546 // succeeds against the system SEED-SRC.
547 let params = crate::params::ParamBuilder::new()
548 .unwrap()
549 .push_utf8_string(c"cipher", c"AES-128-CTR")
550 .unwrap()
551 .build()
552 .unwrap();
553 ctx.instantiate(128, false, Some(¶ms)).unwrap();
554
555 let req = GenerateRequest {
556 strength: 128,
557 ..Default::default()
558 };
559 let mut a = [0u8; 32];
560 let mut b = [0u8; 32];
561 ctx.generate(&mut a, &req).unwrap();
562 ctx.generate(&mut b, &req).unwrap();
563 // Two consecutive generates should produce different output.
564 assert_ne!(a, b);
565 }
566
567 #[test]
568 fn rand_ctx_set_params_strength() {
569 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
570 let mut ctx = RandCtx::new(&alg, None).unwrap();
571 // Configure AES-128-CTR so instantiation succeeds against SEED-SRC.
572 let inst_params = crate::params::ParamBuilder::new()
573 .unwrap()
574 .push_utf8_string(c"cipher", c"AES-128-CTR")
575 .unwrap()
576 .build()
577 .unwrap();
578 ctx.instantiate(128, false, Some(&inst_params)).unwrap();
579
580 // Build a params array with OSSL_DRBG_PARAM_RESEED_REQUESTS.
581 // The key is "reseed_requests" — after instantiation this should be
582 // accepted by EVP_RAND_set_params without error.
583 let set_p = crate::params::ParamBuilder::new()
584 .unwrap()
585 .push_uint(c"reseed_requests", 512)
586 .unwrap()
587 .build()
588 .unwrap();
589 ctx.set_params(&set_p).expect("set_params should succeed");
590 }
591}