Skip to content

Commit 0fe2b8b

Browse files
committed
Add ProcessPrng implementation
1 parent 10d486b commit 0fe2b8b

File tree

3 files changed

+40
-54
lines changed

3 files changed

+40
-54
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ libc = { version = "0.2.154", default-features = false }
2323
[target.'cfg(target_os = "wasi")'.dependencies]
2424
wasi = { version = "0.11", default-features = false }
2525

26+
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
27+
windows-targets = "0.52"
28+
2629
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies]
2730
wasm-bindgen = { version = "0.2.62", default-features = false, optional = true }
2831
js-sys = { version = "0.3", optional = true }

src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ impl Error {
5353
/// Called from an ES module on Node.js. This is unsupported, see:
5454
/// <https://docs.rs/getrandom#nodejs-es-module-support>.
5555
pub const NODE_ES_MODULE: Error = internal_error(14);
56+
/// Calling Windows ProcessPrng failed.
57+
pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15);
5658

5759
/// Codes below this point represent OS Errors (i.e. positive i32 values).
5860
/// Codes at or above this point, but below [`Error::CUSTOM_START`] are
@@ -172,6 +174,7 @@ fn internal_desc(error: Error) -> Option<&'static str> {
172174
Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"),
173175
Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"),
174176
Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"),
177+
Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"),
175178
_ => None,
176179
}
177180
}

src/windows.rs

+34-54
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,40 @@
1-
//! Implementation for Windows
1+
//! Implementation for Windows 10 and later
2+
//!
3+
//! On Windows 10 and later, ProcessPrng "is the primary interface to the
4+
//! user-mode per-processer PRNGs" and only requires BCryptPrimitives.dll,
5+
//! making it a better option than the other Windows RNG APIs:
6+
//! - BCryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
7+
//! - Requires Bcrypt.dll (which loads BCryptPrimitives.dll anyway)
8+
//! - Can cause crashes/hangs as BCrypt accesses the Windows Registry:
9+
//! https://github.com/rust-lang/rust/issues/99341
10+
//! - Causes issues inside sandboxed code:
11+
//! https://issues.chromium.org/issues/40277768
12+
//! - CryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
13+
//! - Deprecated and not available on UWP targets
14+
//! - Requires Advapi32.lib/Advapi32.dll
15+
//! - Wrapper around ProcessPrng
16+
//! - RtlGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
17+
//! - Deprecated and not available on UWP targets
18+
//! - Requires Advapi32.dll (and using name "SystemFunction036")
19+
//! - Wrapper around ProcessPrng
20+
//! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng
221
use crate::Error;
3-
use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr};
22+
use core::mem::MaybeUninit;
423

5-
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002;
6-
7-
#[link(name = "bcrypt")]
8-
extern "system" {
9-
fn BCryptGenRandom(
10-
hAlgorithm: *mut c_void,
11-
pBuffer: *mut u8,
12-
cbBuffer: u32,
13-
dwFlags: u32,
14-
) -> u32;
15-
}
16-
17-
// Forbidden when targetting UWP
18-
#[cfg(not(target_vendor = "uwp"))]
19-
#[link(name = "advapi32")]
20-
extern "system" {
21-
#[link_name = "SystemFunction036"]
22-
fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8;
23-
}
24+
// ProcessPrng lacks an import library, so we use the windows-targets crate to
25+
// link to it. The following code was generated via windows-bindgen with APIs:
26+
// Windows.Win32.Foundation.TRUE
27+
// Windows.Win32.Security.Cryptography.ProcessPrng
28+
windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL);
29+
#[repr(transparent)]
30+
#[derive(PartialEq, Eq)]
31+
pub struct BOOL(pub i32);
32+
pub const TRUE: BOOL = BOOL(1i32);
2433

2534
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
26-
// Prevent overflow of u32
27-
for chunk in dest.chunks_mut(u32::max_value() as usize) {
28-
// BCryptGenRandom was introduced in Windows Vista
29-
let ret = unsafe {
30-
BCryptGenRandom(
31-
ptr::null_mut(),
32-
chunk.as_mut_ptr().cast::<u8>(),
33-
chunk.len() as u32,
34-
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
35-
)
36-
};
37-
// NTSTATUS codes use the two highest bits for severity status.
38-
if ret >> 30 == 0b11 {
39-
// Failed. Try RtlGenRandom as a fallback.
40-
#[cfg(not(target_vendor = "uwp"))]
41-
{
42-
let ret = unsafe {
43-
RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32)
44-
};
45-
if ret != 0 {
46-
continue;
47-
}
48-
}
49-
// We zeroize the highest bit, so the error code will reside
50-
// inside the range designated for OS codes.
51-
let code = ret ^ (1 << 31);
52-
// SAFETY: the second highest bit is always equal to one,
53-
// so it's impossible to get zero. Unfortunately the type
54-
// system does not have a way to express this yet.
55-
let code = unsafe { NonZeroU32::new_unchecked(code) };
56-
return Err(Error::from(code));
57-
}
35+
// ProcessPrng should always return TRUE, but we check just in case.
36+
match unsafe { ProcessPrng(dest.as_mut_ptr() as *mut u8, dest.len()) } {
37+
TRUE => Ok(()),
38+
_ => Err(Error::WINDOWS_PROCESS_PRNG),
5839
}
59-
Ok(())
6040
}

0 commit comments

Comments
 (0)