-
Notifications
You must be signed in to change notification settings - Fork 256
Use ProcessPrng on Windows #415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
6766018
9fa83b5
22635a8
0ba324e
c231147
796321e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,60 +1,40 @@ | ||
| //! Implementation for Windows | ||
| //! Implementation for Windows 10 and later | ||
| //! | ||
| //! On Windows 10 and later, ProcessPrng "is the primary interface to the | ||
| //! user-mode per-processer PRNGs" and only requires BCryptPrimitives.dll, | ||
| //! making it a better option than the other Windows RNG APIs: | ||
| //! - BCryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom | ||
| //! - Requires Bcrypt.dll (which loads BCryptPrimitives.dll anyway) | ||
| //! - Can cause crashes/hangs as BCrypt accesses the Windows Registry: | ||
| //! https://github.com/rust-lang/rust/issues/99341 | ||
| //! - Causes issues inside sandboxed code: | ||
| //! https://issues.chromium.org/issues/40277768 | ||
| //! - CryptGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom | ||
| //! - Deprecated and not available on UWP targets | ||
| //! - Requires Advapi32.lib/Advapi32.dll | ||
| //! - Wrapper around ProcessPrng | ||
| //! - RtlGenRandom: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom | ||
| //! - Deprecated and not available on UWP targets | ||
| //! - Requires Advapi32.dll (and using name "SystemFunction036") | ||
|
josephlr marked this conversation as resolved.
Outdated
|
||
| //! - Wrapper around ProcessPrng | ||
| //! For more information see the Windows RNG Whitepaper: https://aka.ms/win10rng | ||
| use crate::Error; | ||
| use core::{ffi::c_void, mem::MaybeUninit, num::NonZeroU32, ptr}; | ||
| use core::mem::MaybeUninit; | ||
|
|
||
| const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; | ||
|
|
||
| #[link(name = "bcrypt")] | ||
| extern "system" { | ||
| fn BCryptGenRandom( | ||
| hAlgorithm: *mut c_void, | ||
| pBuffer: *mut u8, | ||
| cbBuffer: u32, | ||
| dwFlags: u32, | ||
| ) -> u32; | ||
| } | ||
|
|
||
| // Forbidden when targetting UWP | ||
| #[cfg(not(target_vendor = "uwp"))] | ||
| #[link(name = "advapi32")] | ||
| extern "system" { | ||
| #[link_name = "SystemFunction036"] | ||
| fn RtlGenRandom(RandomBuffer: *mut c_void, RandomBufferLength: u32) -> u8; | ||
| } | ||
| // ProcessPrng lacks an import library, so we use the windows-targets crate to | ||
| // link to it. The following code was generated via windows-bindgen with APIs: | ||
| // Windows.Win32.Foundation.TRUE | ||
| // Windows.Win32.Security.Cryptography.ProcessPrng | ||
| windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL); | ||
| #[repr(transparent)] | ||
| #[derive(PartialEq, Eq)] | ||
| pub struct BOOL(pub i32); | ||
| pub const TRUE: BOOL = BOOL(1i32); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate to have this ugliness without the program that generated it to confirm that it actually created this ugliness. However, I also think it's fine because of how
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good point. For both APIs I didn't directly use the output of I think that referencing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, here's the full output for running // Bindings generated by `windows-bindgen` 0.56.0
#![allow(
non_snake_case,
non_upper_case_globals,
non_camel_case_types,
dead_code,
clippy::all
)]
#[inline]
pub unsafe fn RtlGenRandom(
randombuffer: *mut core::ffi::c_void,
randombufferlength: u32,
) -> BOOLEAN {
windows_targets::link!("advapi32.dll" "system" "SystemFunction036" fn RtlGenRandom(randombuffer : *mut core::ffi::c_void, randombufferlength : u32) -> BOOLEAN);
RtlGenRandom(randombuffer, randombufferlength)
}
#[inline]
pub unsafe fn ProcessPrng(pbdata: &mut [u8]) -> BOOL {
windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata : *mut u8, cbdata : usize) -> BOOL);
ProcessPrng(
core::mem::transmute(pbdata.as_ptr()),
pbdata.len().try_into().unwrap(),
)
}
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOL(pub i32);
impl Default for BOOL {
fn default() -> Self {
unsafe { core::mem::zeroed() }
}
}
impl Clone for BOOL {
fn clone(&self) -> Self {
*self
}
}
impl Copy for BOOL {}
impl core::fmt::Debug for BOOL {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("BOOL").field(&self.0).finish()
}
}
impl windows_core::TypeKind for BOOL {
type TypeKind = windows_core::CopyType;
}
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct BOOLEAN(pub u8);
impl Default for BOOLEAN {
fn default() -> Self {
unsafe { core::mem::zeroed() }
}
}
impl Clone for BOOLEAN {
fn clone(&self) -> Self {
*self
}
}
impl Copy for BOOLEAN {}
impl core::fmt::Debug for BOOLEAN {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("BOOLEAN").field(&self.0).finish()
}
}
impl windows_core::TypeKind for BOOLEAN {
type TypeKind = windows_core::CopyType;
}
pub const TRUE: BOOL = BOOL(1i32); |
||
|
|
||
| pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { | ||
| // Prevent overflow of u32 | ||
| for chunk in dest.chunks_mut(u32::max_value() as usize) { | ||
| // BCryptGenRandom was introduced in Windows Vista | ||
| let ret = unsafe { | ||
| BCryptGenRandom( | ||
| ptr::null_mut(), | ||
| chunk.as_mut_ptr().cast::<u8>(), | ||
| chunk.len() as u32, | ||
| BCRYPT_USE_SYSTEM_PREFERRED_RNG, | ||
| ) | ||
| }; | ||
| // NTSTATUS codes use the two highest bits for severity status. | ||
| if ret >> 30 == 0b11 { | ||
| // Failed. Try RtlGenRandom as a fallback. | ||
| #[cfg(not(target_vendor = "uwp"))] | ||
| { | ||
| let ret = unsafe { | ||
| RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32) | ||
| }; | ||
| if ret != 0 { | ||
| continue; | ||
| } | ||
| } | ||
| // We zeroize the highest bit, so the error code will reside | ||
| // inside the range designated for OS codes. | ||
| let code = ret ^ (1 << 31); | ||
| // SAFETY: the second highest bit is always equal to one, | ||
| // so it's impossible to get zero. Unfortunately the type | ||
| // system does not have a way to express this yet. | ||
| let code = unsafe { NonZeroU32::new_unchecked(code) }; | ||
| return Err(Error::from(code)); | ||
| } | ||
| // ProcessPrng should always return TRUE, but we check just in case. | ||
| match unsafe { ProcessPrng(dest.as_mut_ptr().cast::<u8>(), dest.len()) } { | ||
| TRUE => Ok(()), | ||
| _ => Err(Error::WINDOWS_PROCESS_PRNG), | ||
|
josephlr marked this conversation as resolved.
josephlr marked this conversation as resolved.
|
||
| } | ||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| //! Implementation for Windows 7 and 8 | ||
|
josephlr marked this conversation as resolved.
Outdated
|
||
| //! | ||
| //! For targets where we cannot use ProcessPrng (added in Windows 10), we use | ||
| //! RtlGenRandom. See windows.rs for a more detailed discussion of the Windows | ||
| //! RNG APIs (and why we don't use BCryptGenRandom). On versions prior to | ||
| //! Windows 10, this implementation works, while on Windows 10 and later, this | ||
| //! is a thin wrapper around ProcessPrng. | ||
|
josephlr marked this conversation as resolved.
Outdated
|
||
| //! | ||
| //! This implementation will not work on UWP targets, but those targets require | ||
| //! Windows 10 regardless, so can use the ProcessPrng implementation. | ||
| use crate::Error; | ||
| use core::{ffi::c_void, mem::MaybeUninit}; | ||
|
|
||
| // This code is based on that produced by windows-bindgen with the APIs: | ||
| // Windows.Win32.Foundation.TRUE | ||
| // Windows.Win32.Security.Authentication.Identity.RtlGenRandom | ||
| // but we avoid using windows-targets as it doesn't support older Windows. | ||
| #[link(name = "advapi32")] | ||
| extern "system" { | ||
| #[link_name = "SystemFunction036"] | ||
| fn RtlGenRandom(randombuffer: *mut c_void, randombufferlength: u32) -> BOOLEAN; | ||
| } | ||
| #[repr(transparent)] | ||
| #[derive(PartialEq, Eq)] | ||
| pub struct BOOLEAN(pub u8); | ||
| pub const TRUE: BOOLEAN = BOOLEAN(1u8); | ||
|
josephlr marked this conversation as resolved.
Outdated
|
||
|
|
||
| pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { | ||
| // Prevent overflow of u32 | ||
| for chunk in dest.chunks_mut(u32::max_value() as usize) { | ||
| let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::<c_void>(), chunk.len() as u32) }; | ||
| if ret != TRUE { | ||
| return Err(Error::WINDOWS_RTL_GEN_RANDOM); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.