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 {
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(&params)).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}