Skip to main content

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