purecrypto 0.6.29

A pure-Rust cryptography toolkit with no foreign-code dependencies, from constant-time primitives up to keys, X.509 and TLS.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
//! Random number generation.
//!
//! [`RngCore`] is the byte-source interface; [`CryptoRng`] marks generators
//! that are cryptographically secure. [`HmacDrbg`] is a deterministic
//! SP 800-90A generator built on our HMAC, and (with `std`) [`OsRng`] draws
//! directly from the operating system's entropy pool. The OS-side source
//! varies by target:
//!
//! * **Apple** (macOS, iOS, tvOS, watchOS, visionOS): `arc4random_buf(3)`
//!   from `libSystem`. Always seeded by the kernel before userspace runs;
//!   no early-boot caveat.
//! * **Windows**: `ProcessPrng` from `bcryptprimitives.dll`.
//! * **Linux** (default): `/dev/urandom` (per-thread cached fd).
//! * **Linux with `linux-getrandom` feature**: `getrandom(2)` via raw
//!   syscalls (no `libc` dep). Blocks until the kernel CSPRNG is seeded —
//!   recommended for processes that may start very early in boot.
//!   Supported arches: x86_64, aarch64, armv7, riscv64; other Linux arches
//!   transparently fall through to `/dev/urandom`.
//! * **Other Unix** (FreeBSD, OpenBSD, NetBSD, etc.): `/dev/urandom`.

mod hmac_drbg;
#[cfg(all(feature = "linux-getrandom", target_os = "linux"))]
mod linux_getrandom;

// WebAssembly entropy backends. `wasm32` has no ambient OS CSPRNG, so `OsRng`
// routes to the host: an imported function on `wasm32-unknown-unknown`, or
// `wasi_snapshot_preview1::random_get` on `wasm32-wasip1` (feature
// `wasi-getrandom`). The gate matches exactly one available backend so the
// `wasm` module never compiles without one.
#[cfg(all(
    target_arch = "wasm32",
    any(
        target_os = "unknown",
        all(target_os = "wasi", feature = "wasi-getrandom"),
    )
))]
mod wasm;
#[cfg(all(
    target_arch = "wasm32",
    any(
        target_os = "unknown",
        all(target_os = "wasi", feature = "wasi-getrandom"),
    )
))]
pub use wasm::OsRng;

pub use hmac_drbg::HmacDrbg;

/// A source of random bytes.
pub trait RngCore {
    /// Fills `dest` entirely with random bytes.
    fn fill_bytes(&mut self, dest: &mut [u8]);

    /// Returns the next random `u32` (little-endian from [`Self::fill_bytes`]).
    #[inline]
    fn next_u32(&mut self) -> u32 {
        let mut b = [0u8; 4];
        self.fill_bytes(&mut b);
        u32::from_le_bytes(b)
    }

    /// Returns the next random `u64` (little-endian from [`Self::fill_bytes`]).
    #[inline]
    fn next_u64(&mut self) -> u64 {
        let mut b = [0u8; 8];
        self.fill_bytes(&mut b);
        u64::from_le_bytes(b)
    }
}

/// Marker for generators that are cryptographically secure — suitable for keys,
/// nonces, and other secret material.
///
/// This is a promise about the implementation, not something the type system
/// can verify; do not implement it for non-CSPRNGs.
pub trait CryptoRng {}

// Forwarding impls so a `&mut R` (and therefore a `&mut dyn RngCore`) can be
// used anywhere an `R: RngCore` is expected by value. Generic primitives in the
// crate take the RNG as a sized `R: RngCore` parameter; these blanket impls are
// what let a trait-object RNG (e.g. the `&mut dyn CryptoRngCore` the [`key`]
// facade hands around) bridge into them.
//
// [`key`]: crate::key
impl<R: RngCore + ?Sized> RngCore for &mut R {
    #[inline]
    fn fill_bytes(&mut self, dest: &mut [u8]) {
        (**self).fill_bytes(dest)
    }
    #[inline]
    fn next_u32(&mut self) -> u32 {
        (**self).next_u32()
    }
    #[inline]
    fn next_u64(&mut self) -> u64 {
        (**self).next_u64()
    }
}

impl<R: CryptoRng + ?Sized> CryptoRng for &mut R {}

/// Object-safe combination of [`RngCore`] and [`CryptoRng`].
///
/// APIs that need a *secure* RNG behind a trait object take
/// `&mut dyn CryptoRngCore` rather than two separate bounds (which `dyn` cannot
/// express). The blanket impl covers every `T: RngCore + CryptoRng`, and the
/// forwarding `RngCore`/`CryptoRng` impls on `&mut R` above make
/// `&mut dyn CryptoRngCore` itself usable wherever a sized `R: RngCore +
/// CryptoRng` is required.
pub trait CryptoRngCore: RngCore + CryptoRng {}

impl<T: RngCore + CryptoRng + ?Sized> CryptoRngCore for T {}

/// Operating-system entropy source.
///
/// Reads from `/dev/urandom`. Available on Unix targets with the `std` feature.
#[cfg(all(feature = "std", unix))]
#[derive(Debug, Clone, Copy, Default)]
pub struct OsRng;

/// Operating-system entropy source for the `fullrust` target (libc-free Linux).
///
/// Reads `/dev/urandom` through plain `std::fs`; `File::open` is `O_CLOEXEC` on
/// this target, so the fd is not inherited across `execve(2)` — the same
/// guarantee the unix path pins explicitly with `custom_flags`. `fullrust` is
/// not a member of the `unix` family, so it needs its own `OsRng` here.
#[cfg(all(feature = "std", target_os = "fullrust"))]
#[derive(Debug, Clone, Copy, Default)]
pub struct OsRng;

#[cfg(all(feature = "std", target_os = "fullrust"))]
impl RngCore for OsRng {
    fn fill_bytes(&mut self, dest: &mut [u8]) {
        use std::io::Read;
        if dest.is_empty() {
            return;
        }
        std::fs::File::open("/dev/urandom")
            .and_then(|mut f| f.read_exact(dest))
            .expect("OsRng: reading /dev/urandom failed");
    }
}

#[cfg(all(feature = "std", target_os = "fullrust"))]
impl CryptoRng for OsRng {}

// Per-thread cached `/dev/urandom` file handle. Keep one open file per
// thread to avoid the open/close overhead that dominates the cost of a
// small entropy draw. `/dev/urandom` survives indefinitely under POSIX,
// so a cached handle stays valid for the thread's lifetime. RefCell is
// fine here: the borrow is uncontended (one thread); a panic between
// `borrow_mut` and unborrow would poison the cell, but the only
// operation inside the borrow is `read_exact`, whose panic path would
// terminate the thread anyway via the `expect` below.
//
// Skipped on Apple targets: arc4random_buf is always used there.
#[cfg(all(feature = "std", unix, not(target_vendor = "apple")))]
std::thread_local! {
    static URANDOM: core::cell::RefCell<Option<std::fs::File>> =
        const { core::cell::RefCell::new(None) };
}

// Apple platforms (macOS, iOS, tvOS, watchOS, visionOS) expose
// `arc4random_buf(3)` from libSystem, which is always linked. The
// function is documented to always succeed (failure aborts the
// process); the kernel seeds the underlying CSPRNG before any user
// process runs, so there's no early-boot caveat to worry about.
//
// The extern declaration is wrapped in a submodule so the
// `#![allow(unsafe_code)]` scope is local — the rest of mod.rs stays
// under the crate-wide `unsafe_code = "deny"` policy.
#[cfg(all(feature = "std", unix, target_vendor = "apple"))]
mod os_apple {
    #![allow(unsafe_code)]
    unsafe extern "C" {
        pub(super) fn arc4random_buf(buf: *mut core::ffi::c_void, len: usize);
    }
}

#[cfg(all(feature = "std", unix))]
impl RngCore for OsRng {
    fn fill_bytes(&mut self, dest: &mut [u8]) {
        if dest.is_empty() {
            return;
        }

        // Apple platforms — always use arc4random_buf. The two
        // mutually-exclusive cfg blocks below ensure each target sees
        // exactly one branch, so there's no `unreachable_code` lint.
        #[cfg(target_vendor = "apple")]
        {
            // SAFETY: arc4random_buf writes exactly `len` bytes into a
            // caller-supplied buffer and has no failure mode (it
            // aborts the process on internal CSPRNG failure).
            #[allow(unsafe_code)]
            unsafe {
                os_apple::arc4random_buf(dest.as_mut_ptr() as *mut core::ffi::c_void, dest.len());
            }
        }

        #[cfg(not(target_vendor = "apple"))]
        {
            // Linux with the `linux-getrandom` feature: try the syscall
            // first; fall back to /dev/urandom on ENOSYS or unsupported
            // arch, panic on any other errno.
            #[cfg(all(feature = "linux-getrandom", target_os = "linux"))]
            match linux_getrandom::try_getrandom(dest) {
                Ok(()) => return,
                Err(linux_getrandom::Error::NotImplemented) => {} // fall through
                Err(linux_getrandom::Error::Other(e)) => {
                    panic!("getrandom(2) failed with errno {e}");
                }
            }

            urandom_fill(dest);
        }
    }
}

#[cfg(all(feature = "std", unix, not(target_vendor = "apple")))]
fn urandom_fill(dest: &mut [u8]) {
    use std::io::Read;
    use std::os::unix::fs::OpenOptionsExt;

    // O_CLOEXEC ensures the fd is not inherited across `execve(2)`, so a
    // child process forked-then-exec'd by the host application cannot end
    // up holding a stray fd to /dev/urandom. Stdlib's `File::open` adds
    // O_CLOEXEC on modern Rust/Linux, but pinning it here makes the
    // property local to this file rather than implicit in the toolchain
    // version. The numeric value differs across Unix variants:
    //   Linux        — O_CLOEXEC = 0o2_000_000
    //   FreeBSD      — O_CLOEXEC = 0x0010_0000
    //   OpenBSD      — O_CLOEXEC = 0x0001_0000
    //   NetBSD       — O_CLOEXEC = 0x0040_0000
    //   illumos/Sol. — O_CLOEXEC = 0x80_0000
    // On other Unix targets we leave `custom_flags` at zero and rely on
    // the stdlib default (no regression from the previous behaviour).
    #[cfg(target_os = "linux")]
    const O_CLOEXEC: i32 = 0o2_000_000;
    #[cfg(target_os = "freebsd")]
    const O_CLOEXEC: i32 = 0x0010_0000;
    #[cfg(target_os = "openbsd")]
    const O_CLOEXEC: i32 = 0x0001_0000;
    #[cfg(target_os = "netbsd")]
    const O_CLOEXEC: i32 = 0x0040_0000;
    #[cfg(any(target_os = "illumos", target_os = "solaris"))]
    const O_CLOEXEC: i32 = 0x80_0000;
    #[cfg(not(any(
        target_os = "linux",
        target_os = "freebsd",
        target_os = "openbsd",
        target_os = "netbsd",
        target_os = "illumos",
        target_os = "solaris",
    )))]
    const O_CLOEXEC: i32 = 0;

    URANDOM.with(|cell| {
        let mut slot = cell.borrow_mut();
        if slot.is_none() {
            *slot = Some(
                std::fs::OpenOptions::new()
                    .read(true)
                    .custom_flags(O_CLOEXEC)
                    .open("/dev/urandom")
                    .expect("failed to open /dev/urandom"),
            );
        }
        slot.as_mut()
            .unwrap()
            .read_exact(dest)
            .expect("failed to read entropy from /dev/urandom");
    });
}

#[cfg(all(feature = "std", unix))]
impl CryptoRng for OsRng {}

/// Operating-system entropy source for Windows, via the Win32 system CSPRNG.
///
/// Unlike Unix's `/dev/urandom`, Windows has no file-based entropy, so this
/// calls `ProcessPrng` (bcryptprimitives.dll) directly — the crate's only use
/// of `unsafe` outside the `ffi` module, confined here behind
/// `#![allow(unsafe_code)]`.
#[cfg(all(feature = "std", windows))]
mod os_windows {
    #![allow(unsafe_code)]
    use super::{CryptoRng, RngCore};

    // `BOOL ProcessPrng(PBYTE pbData, SIZE_T cbData)` — documented to always
    // succeed (it returns TRUE), drawing from the same CSPRNG as BCryptGenRandom.
    // `kind = "raw-dylib"` synthesizes the import from the DLL directly: the
    // Windows SDK ships bcryptprimitives.dll but no import `.lib` for it (this is
    // the approach the `getrandom` crate uses for the same function).
    #[link(name = "bcryptprimitives", kind = "raw-dylib")]
    unsafe extern "system" {
        fn ProcessPrng(data: *mut u8, len: usize) -> i32;
    }

    /// Operating-system entropy source.
    #[derive(Debug, Clone, Copy, Default)]
    pub struct OsRng;

    impl RngCore for OsRng {
        fn fill_bytes(&mut self, dest: &mut [u8]) {
            if dest.is_empty() {
                return;
            }
            let ok = unsafe { ProcessPrng(dest.as_mut_ptr(), dest.len()) };
            assert!(ok != 0, "ProcessPrng failed to produce entropy");
        }
    }

    impl CryptoRng for OsRng {}
}

#[cfg(all(feature = "std", windows))]
pub use os_windows::OsRng;

#[cfg(all(test, feature = "std", any(unix, windows)))]
mod tests {
    use super::*;

    #[test]
    fn os_rng_fills_and_varies() {
        let mut rng = OsRng;
        let mut a = [0u8; 32];
        let mut b = [0u8; 32];
        rng.fill_bytes(&mut a);
        rng.fill_bytes(&mut b);
        // Astronomically unlikely to be all-zero or identical.
        assert_ne!(a, [0u8; 32]);
        assert_ne!(a, b);
    }

    // Regression: F1 (audit 04-rng.md) — the cached `/dev/urandom` fd MUST
    // carry FD_CLOEXEC so it is not inherited across `execve(2)`. The
    // `O_CLOEXEC` open flag sets that fd flag at creation time; this test
    // primes the per-thread cache by drawing some bytes, then re-opens
    // `/dev/urandom` the same way and confirms the flag is present on the
    // freshly opened fd (the cached fd lives behind a `thread_local!` so we
    // can't borrow it without restructuring; opening a second time
    // exercises the same code path).
    #[cfg(all(unix, not(target_vendor = "apple")))]
    #[test]
    fn urandom_fd_is_cloexec() {
        use std::os::unix::fs::OpenOptionsExt;
        use std::os::unix::io::AsRawFd;

        // Prime the per-thread cache so the open path runs at least once.
        let mut rng = OsRng;
        let mut buf = [0u8; 16];
        rng.fill_bytes(&mut buf);

        // Re-open with the same flags and verify FD_CLOEXEC. Mirrors the
        // O_CLOEXEC value used by `urandom_fill`.
        #[cfg(target_os = "linux")]
        const O_CLOEXEC: i32 = 0o2_000_000;
        #[cfg(target_os = "freebsd")]
        const O_CLOEXEC: i32 = 0x0010_0000;
        #[cfg(target_os = "openbsd")]
        const O_CLOEXEC: i32 = 0x0001_0000;
        #[cfg(target_os = "netbsd")]
        const O_CLOEXEC: i32 = 0x0040_0000;
        #[cfg(any(target_os = "illumos", target_os = "solaris"))]
        const O_CLOEXEC: i32 = 0x80_0000;
        #[cfg(not(any(
            target_os = "linux",
            target_os = "freebsd",
            target_os = "openbsd",
            target_os = "netbsd",
            target_os = "illumos",
            target_os = "solaris",
        )))]
        const O_CLOEXEC: i32 = 0;

        // On targets where we don't set the flag (no known O_CLOEXEC
        // constant), the test is informational: the stdlib usually sets
        // it anyway on modern Rust, but we don't guarantee it here.
        if O_CLOEXEC == 0 {
            return;
        }

        let f = std::fs::OpenOptions::new()
            .read(true)
            .custom_flags(O_CLOEXEC)
            .open("/dev/urandom")
            .expect("open /dev/urandom");

        // F_GETFD = 1, FD_CLOEXEC = 1 (both fixed across Unix). Use raw
        // libc-style probe via a tiny extern; we deliberately don't pull
        // a libc dep, so spell it inline.
        #[allow(unsafe_code)]
        mod probe {
            unsafe extern "C" {
                pub(super) fn fcntl(fd: i32, cmd: i32, ...) -> i32;
            }
        }
        const F_GETFD: i32 = 1;
        const FD_CLOEXEC: i32 = 1;
        #[allow(unsafe_code)]
        let flags = unsafe { probe::fcntl(f.as_raw_fd(), F_GETFD) };
        assert!(flags >= 0, "fcntl(F_GETFD) failed");
        assert!(
            flags & FD_CLOEXEC != 0,
            "/dev/urandom fd missing FD_CLOEXEC (got flags = {flags:#x})"
        );
    }
}