|
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 |
2 | 21 | use crate::Error;
|
3 |
| -use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr}; |
| 22 | +use core::mem::MaybeUninit; |
4 | 23 |
|
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); |
24 | 33 |
|
25 | 34 | 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), |
58 | 39 | }
|
59 |
| - Ok(()) |
60 | 40 | }
|
0 commit comments