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 {
35 sys::EVP_RAND_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr)
36 };
37 if ptr.is_null() {
38 return Err(ErrorStack::drain());
39 }
40 Ok(RandAlg { ptr, _lib_ctx: None })
41 }
42
43 /// Fetch a RAND algorithm within an explicit library context.
44 ///
45 /// Use this when the DRBG must be bound to a specific provider set
46 /// (e.g. a FIPS-isolated `LibCtx`).
47 ///
48 /// The `Arc<LibCtx>` is retained for the lifetime of the `RandAlg` to
49 /// ensure the context outlives the algorithm descriptor.
50 ///
51 /// # Errors
52 ///
53 /// Returns `Err` if the algorithm is not available in `ctx`.
54 pub fn fetch_in(
55 ctx: &Arc<crate::lib_ctx::LibCtx>,
56 name: &CStr,
57 props: Option<&CStr>,
58 ) -> Result<Self, ErrorStack> {
59 let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
60 let ptr = unsafe {
61 sys::EVP_RAND_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr)
62 };
63 if ptr.is_null() {
64 return Err(ErrorStack::drain());
65 }
66 Ok(RandAlg { ptr, _lib_ctx: Some(Arc::clone(ctx)) })
67 }
68
69 /// Return the raw `EVP_RAND*` pointer. Valid for the lifetime of `self`.
70 #[must_use]
71 pub(crate) fn as_ptr(&self) -> *mut sys::EVP_RAND {
72 self.ptr
73 }
74}
75
76impl Drop for RandAlg {
77 fn drop(&mut self) {
78 unsafe { sys::EVP_RAND_free(self.ptr) };
79 }
80}
81
82// SAFETY: `EVP_RAND*` is reference-counted and immutable after fetch.
83unsafe impl Send for RandAlg {}
84unsafe impl Sync for RandAlg {}
85
86// ── Rand — simple fill helpers (Phase 4.4) ────────────────────────────────────
87
88/// Zero-size type with static methods wrapping `RAND_bytes` / `RAND_priv_bytes`.
89pub struct Rand;
90
91impl Rand {
92 /// Fill `buf` with cryptographically random bytes.
93 ///
94 /// Zero-copy: writes directly into the caller's buffer.
95 ///
96 /// # Panics
97 ///
98 /// Panics if `buf.len() > i32::MAX`.
99 ///
100 /// # Errors
101 ///
102 /// Returns `Err` if the PRNG fails (e.g. not seeded).
103 pub fn fill(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
104 let n = i32::try_from(buf.len()).expect("buffer too large for RAND_bytes");
105 let rc = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) };
106 if rc != 1 {
107 return Err(crate::error::ErrorStack::drain());
108 }
109 Ok(())
110 }
111
112 /// Fill `buf` with private (non-predictable) random bytes.
113 ///
114 /// # Panics
115 ///
116 /// Panics if `buf.len() > i32::MAX`.
117 ///
118 /// # Errors
119 pub fn fill_private(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
120 let n = i32::try_from(buf.len()).expect("buffer too large for RAND_priv_bytes");
121 let rc = unsafe { sys::RAND_priv_bytes(buf.as_mut_ptr(), n) };
122 if rc != 1 {
123 return Err(crate::error::ErrorStack::drain());
124 }
125 Ok(())
126 }
127
128 /// Allocate and fill a `Vec<u8>` of `n` random bytes.
129 ///
130 /// Prefer `fill` when the destination buffer is already allocated.
131 ///
132 /// # Errors
133 pub fn bytes(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
134 let mut buf = vec![0u8; n];
135 Self::fill(&mut buf)?;
136 Ok(buf)
137 }
138
139 /// Allocate and fill a `Vec<u8>` of `n` private random bytes.
140 ///
141 /// Equivalent to `bytes` but uses `RAND_priv_bytes` — suitable for key
142 /// material and other values that must not be disclosed.
143 ///
144 /// # Errors
145 pub fn bytes_private(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
146 let mut buf = vec![0u8; n];
147 Self::fill_private(&mut buf)?;
148 Ok(buf)
149 }
150}
151
152// ── GenerateRequest — named struct for EVP_RAND_generate parameters ───────────
153
154/// Parameters for `RandCtx::generate`.
155///
156/// Use struct update syntax for partial overrides:
157/// ```ignore
158/// GenerateRequest { prediction_resistance: true, ..Default::default() }
159/// ```
160pub struct GenerateRequest<'a> {
161 /// Requested security strength in bits (e.g. 256).
162 pub strength: u32,
163 /// If `true`, OpenSSL reseeds before generating.
164 pub prediction_resistance: bool,
165 /// Optional additional input bytes fed into the DRBG.
166 pub additional_input: Option<&'a [u8]>,
167}
168
169impl Default for GenerateRequest<'_> {
170 fn default() -> Self {
171 GenerateRequest {
172 strength: 256,
173 prediction_resistance: false,
174 additional_input: None,
175 }
176 }
177}
178
179// ── RandState ─────────────────────────────────────────────────────────────────
180
181/// Lifecycle state of a `RandCtx` DRBG instance.
182///
183/// Returned by [`RandCtx::state`]. Maps the `EVP_RAND_STATE_*` constants
184/// from `<openssl/evp.h>`.
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum RandState {
187 /// Context created but not yet instantiated (seeded).
188 Uninitialised,
189 /// Context is seeded and ready to generate.
190 Ready,
191 /// Context entered an unrecoverable error state.
192 Error,
193 /// Unrecognised value returned by OpenSSL (forward-compat guard).
194 Unknown(i32),
195}
196
197// ── RandCtx — EVP_RAND_CTX wrapper ───────────────────────────────────────────
198
199/// A seeded DRBG context (`EVP_RAND_CTX*`).
200///
201/// Has `up_ref` so `Clone` is implemented; wrapping in `Arc<RandCtx>` is safe.
202pub struct RandCtx {
203 ptr: *mut sys::EVP_RAND_CTX,
204}
205
206impl RandCtx {
207 /// Create a new uninstantiated DRBG context from an algorithm descriptor.
208 ///
209 /// Provide `parent = None` to seed from the global PRNG.
210 ///
211 /// # Errors
212 pub fn new(alg: &RandAlg, parent: Option<&RandCtx>) -> Result<Self, crate::error::ErrorStack> {
213 let parent_ptr = parent.map_or(std::ptr::null_mut(), |p| p.ptr);
214 let ptr = unsafe { sys::EVP_RAND_CTX_new(alg.as_ptr(), parent_ptr) };
215 if ptr.is_null() {
216 return Err(crate::error::ErrorStack::drain());
217 }
218 Ok(RandCtx { ptr })
219 }
220
221 /// Instantiate (seed) the DRBG at the requested security strength.
222 ///
223 /// Must be called before `generate` if the context was not auto-seeded.
224 /// When `parent = None` was used in `new`, pass `strength ≤ 128` to stay
225 /// within the system seed-source's entropy ceiling.
226 ///
227 /// # Errors
228 pub fn instantiate(
229 &mut self,
230 strength: u32,
231 prediction_resistance: bool,
232 params: Option<&crate::params::Params<'_>>,
233 ) -> Result<(), crate::error::ErrorStack> {
234 let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
235 let rc = unsafe {
236 sys::EVP_RAND_instantiate(
237 self.ptr,
238 strength,
239 i32::from(prediction_resistance),
240 std::ptr::null(),
241 0,
242 params_ptr,
243 )
244 };
245 if rc != 1 {
246 return Err(crate::error::ErrorStack::drain());
247 }
248 Ok(())
249 }
250
251 /// Generate random bytes into `out`.
252 ///
253 /// Zero-copy: writes directly into the caller's slice.
254 ///
255 /// # Errors
256 pub fn generate(
257 &mut self,
258 out: &mut [u8],
259 req: &GenerateRequest<'_>,
260 ) -> Result<(), crate::error::ErrorStack> {
261 let (ai_ptr, ai_len) = req.additional_input
262 .map_or((std::ptr::null(), 0), |s| (s.as_ptr(), s.len()));
263
264 let rc = unsafe {
265 sys::EVP_RAND_generate(
266 self.ptr,
267 out.as_mut_ptr(),
268 out.len(),
269 req.strength,
270 i32::from(req.prediction_resistance),
271 ai_ptr,
272 ai_len,
273 )
274 };
275 if rc != 1 {
276 return Err(crate::error::ErrorStack::drain());
277 }
278 Ok(())
279 }
280
281 /// Generate random bytes with default parameters (strength=256, no prediction
282 /// resistance, no additional input).
283 ///
284 /// Equivalent to calling `generate` with [`GenerateRequest::default`].
285 /// Call [`Self::instantiate`] before the first `fill`.
286 ///
287 /// # Errors
288 pub fn fill(&mut self, out: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
289 self.generate(out, &GenerateRequest::default())
290 }
291
292 /// Security strength of this DRBG instance in bits.
293 #[must_use]
294 pub fn strength(&self) -> u32 {
295 unsafe { sys::EVP_RAND_get_strength(self.ptr) }
296 }
297
298 /// Current lifecycle state of this DRBG context.
299 ///
300 /// Returns [`RandState::Ready`] after a successful [`Self::instantiate`].
301 #[must_use]
302 pub fn state(&self) -> RandState {
303 match unsafe { sys::EVP_RAND_get_state(self.ptr) } {
304 0 => RandState::Uninitialised,
305 1 => RandState::Ready,
306 2 => RandState::Error,
307 n => RandState::Unknown(n),
308 }
309 }
310
311 /// Return a reference to the process-wide **public** DRBG.
312 ///
313 /// The returned [`GlobalRandCtx`] does **not** free the pointer when dropped —
314 /// the global DRBG is owned by the OpenSSL runtime.
315 ///
316 /// Pass `Some(global.as_rand_ctx())` as the `parent` argument to
317 /// [`RandCtx::new`] to chain a child DRBG off the global instance.
318 ///
319 /// Requires OpenSSL 3.2+.
320 ///
321 /// # Errors
322 ///
323 /// Returns `Err` if the global DRBG is not yet initialised.
324 #[cfg(ossl320)]
325 pub fn public() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
326 let ptr = unsafe { sys::RAND_get0_public(std::ptr::null_mut()) };
327 if ptr.is_null() {
328 return Err(crate::error::ErrorStack::drain());
329 }
330 Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
331 }
332
333 /// Return a reference to the process-wide **private** DRBG.
334 ///
335 /// Same semantics as [`RandCtx::public`].
336 ///
337 /// Requires OpenSSL 3.2+.
338 ///
339 /// # Errors
340 #[cfg(ossl320)]
341 pub fn private_global() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
342 let ptr = unsafe { sys::RAND_get0_private(std::ptr::null_mut()) };
343 if ptr.is_null() {
344 return Err(crate::error::ErrorStack::drain());
345 }
346 Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
347 }
348}
349
350// ── GlobalRandCtx (OpenSSL 3.2+) ─────────────────────────────────────────────
351
352/// A borrowed handle to one of the process-wide global DRBGs.
353///
354/// Obtained from [`RandCtx::public`] or [`RandCtx::private_global`].
355///
356/// **Does not implement `Clone` or `Drop`** — the global DRBG is owned by the
357/// OpenSSL runtime and must not be freed through this handle. Implements
358/// `Deref<Target = RandCtx>` so all read-only `RandCtx` operations are available,
359/// and `as_rand_ctx()` provides a `&RandCtx` to pass as a parent to [`RandCtx::new`].
360#[cfg(ossl320)]
361pub struct GlobalRandCtx(std::mem::ManuallyDrop<RandCtx>);
362
363// SAFETY: the underlying EVP_RAND_CTX* is the global DRBG; it is safe to move
364// this handle to another thread.
365#[cfg(ossl320)]
366unsafe impl Send for GlobalRandCtx {}
367
368#[cfg(ossl320)]
369impl std::ops::Deref for GlobalRandCtx {
370 type Target = RandCtx;
371 fn deref(&self) -> &RandCtx {
372 &self.0
373 }
374}
375
376#[cfg(ossl320)]
377impl GlobalRandCtx {
378 /// Borrow as `&RandCtx` for use as the `parent` argument in [`RandCtx::new`].
379 pub fn as_rand_ctx(&self) -> &RandCtx {
380 &self.0
381 }
382}
383
384impl Clone for RandCtx {
385 fn clone(&self) -> Self {
386 unsafe { sys::EVP_RAND_CTX_up_ref(self.ptr) };
387 RandCtx { ptr: self.ptr }
388 }
389}
390
391impl Drop for RandCtx {
392 fn drop(&mut self) {
393 unsafe { sys::EVP_RAND_CTX_free(self.ptr) };
394 }
395}
396
397unsafe impl Send for RandCtx {}
398unsafe impl Sync for RandCtx {}
399
400// ── Tests ─────────────────────────────────────────────────────────────────────
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn fetch_ctr_drbg_succeeds() {
408 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
409 drop(alg);
410 }
411
412 #[test]
413 fn fetch_nonexistent_fails() {
414 assert!(RandAlg::fetch(c"NONEXISTENT_RAND_XYZ", None).is_err());
415 }
416
417 #[test]
418 fn rand_fill_nonzero() {
419 let mut buf = [0u8; 32];
420 Rand::fill(&mut buf).unwrap();
421 // Probability of all-zero is 2^(-256) — treat as impossible.
422 assert_ne!(buf, [0u8; 32]);
423 }
424
425 #[test]
426 fn rand_bytes_len() {
427 let v = Rand::bytes(64).unwrap();
428 assert_eq!(v.len(), 64);
429 }
430
431 #[test]
432 fn rand_ctx_two_outputs_differ() {
433 let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
434 let mut ctx = RandCtx::new(&alg, None).unwrap();
435 // The default CTR-DRBG uses AES-256-CTR, which requires 256-bit entropy
436 // from SEED-SRC. Many systems cap at 128 bits without a parent DRBG.
437 // Explicitly configure AES-128-CTR (128-bit strength) so instantiation
438 // succeeds against the system SEED-SRC.
439 let params = crate::params::ParamBuilder::new().unwrap()
440 .push_utf8_string(c"cipher", c"AES-128-CTR").unwrap()
441 .build().unwrap();
442 ctx.instantiate(128, false, Some(¶ms)).unwrap();
443
444 let req = GenerateRequest { strength: 128, ..Default::default() };
445 let mut a = [0u8; 32];
446 let mut b = [0u8; 32];
447 ctx.generate(&mut a, &req).unwrap();
448 ctx.generate(&mut b, &req).unwrap();
449 // Two consecutive generates should produce different output.
450 assert_ne!(a, b);
451 }
452}