diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6847a4e7..53ebd05e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, windows-2022] - toolchain: [nightly, beta, stable, 1.38] + toolchain: [nightly, beta, stable, 1.56] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. @@ -159,6 +159,20 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test --features=std + windows7: + name: Test Windows 7 impl on Windows 10 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v3 + # Win7 targets are Tier3, so pin a nightly where libstd builds. + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-05-20 + components: rust-src + - uses: Swatinem/rust-cache@v2 + - run: cargo test --target=x86_64-win7-windows-msvc -Z build-std --features=std + - run: cargo test --target=i686-win7-windows-msvc -Z build-std --features=std + cross-tests: name: Cross Test runs-on: ubuntu-22.04 diff --git a/Cargo.toml b/Cargo.toml index 4de6dcfd..d7e65bfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "getrandom" version = "0.2.15" # Also update html_root_url in lib.rs when bumping this edition = "2018" +rust-version = "1.56" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "A small cross-platform library for retrieving random data from system source" @@ -23,6 +24,9 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] wasi = { version = "0.11", default-features = false } +[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies] +windows-targets = "0.52" + [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } js-sys = { version = "0.3", optional = true } diff --git a/README.md b/README.md index fcebbafa..2ca14a66 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ crate features, WASM support and Custom RNGs see the ## Minimum Supported Rust Version -This crate requires Rust 1.38.0 or later. +This crate requires Rust 1.56.0 or later. ## Platform Support diff --git a/src/error.rs b/src/error.rs index 5dc45110..3ba4ff6c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -53,6 +53,8 @@ impl Error { /// Called from an ES module on Node.js. This is unsupported, see: /// . pub const NODE_ES_MODULE: Error = internal_error(14); + /// Calling Windows ProcessPrng failed. + pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15); /// Codes below this point represent OS Errors (i.e. positive i32 values). /// Codes at or above this point, but below [`Error::CUSTOM_START`] are @@ -172,6 +174,7 @@ fn internal_desc(error: Error) -> Option<&'static str> { Error::NODE_CRYPTO => Some("Node.js crypto CommonJS module is unavailable"), Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"), Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"), + Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"), _ => None, } } diff --git a/src/lib.rs b/src/lib.rs index eb8c7fc5..e4f11006 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,8 @@ //! | Target | Target Triple | Implementation //! | ----------------- | ------------------ | -------------- //! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` -//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`] +//! | Windows 10+ | `*‑windows‑*` | [`ProcessPrng`] +//! | Windows 7 and 8 | `*-win7‑windows‑*` | [`RtlGenRandom`] //! | macOS | `*‑apple‑darwin` | [`getentropy`][3] //! | iOS, tvOS, watchOS | `*‑apple‑ios`, `*-apple-tvos`, `*-apple-watchos` | [`CCRandomGenerateBytes`] //! | FreeBSD | `*‑freebsd` | [`getrandom`][5] @@ -182,7 +183,8 @@ //! [17]: https://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getrandom //! [18]: https://github.com/rust3ds/shim-3ds/commit/b01d2568836dea2a65d05d662f8e5f805c64389d //! -//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom +//! [`ProcessPrng`]: https://learn.microsoft.com/en-us/windows/win32/seccng/processprng +//! [`RtlGenRandom`]: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom //! [`Crypto.getRandomValues`]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues //! [`RDRAND`]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide //! [`CCRandomGenerateBytes`]: https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html @@ -323,6 +325,8 @@ cfg_if! { #[path = "solid.rs"] mod imp; } else if #[cfg(target_os = "espidf")] { #[path = "espidf.rs"] mod imp; + } else if #[cfg(all(windows, target_vendor = "win7"))] { + #[path = "windows7.rs"] mod imp; } else if #[cfg(windows)] { #[path = "windows.rs"] mod imp; } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { diff --git a/src/windows.rs b/src/windows.rs index 0aa8dde2..e28c1e53 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,60 +1,38 @@ -//! 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 (in addition to bcryptprimitives.dll) +//! - Thin 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 (in addition to bcryptprimitives.dll) +//! - Requires using name "SystemFunction036" +//! - Thin 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; -} +// Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As +// bcryptprimitives.dll lacks an import library, we use the windows-targets +// crate to link to it. +windows_targets::link!("bcryptprimitives.dll" "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> BOOL); +pub type BOOL = i32; +pub const TRUE: BOOL = 1i32; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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::(), - 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::(), 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::(), dest.len()) } { + TRUE => Ok(()), + _ => Err(Error::WINDOWS_PROCESS_PRNG), } - Ok(()) } diff --git a/src/windows7.rs b/src/windows7.rs new file mode 100644 index 00000000..72f5695d --- /dev/null +++ b/src/windows7.rs @@ -0,0 +1,34 @@ +//! Legacy implementation for Windows XP and later +//! +//! 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 is secure. On Windows 10 and later, this +//! implementation behaves identically to the windows.rs implementation, except +//! that it forces the loading of an additonal DLL (advapi32.dll). +//! +//! This implementation will not work on UWP targets (which lack advapi32.dll), +//! but such targets require Windows 10, so can use the standard implementation. +use crate::Error; +use core::{ffi::c_void, mem::MaybeUninit}; + +// Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom +// API. Don't use windows-targets as it doesn't support Windows 7 targets. +#[link(name = "advapi32")] +extern "system" { + #[link_name = "SystemFunction036"] + fn RtlGenRandom(randombuffer: *mut c_void, randombufferlength: u32) -> BOOLEAN; +} +type BOOLEAN = u8; +const TRUE: BOOLEAN = 1u8; + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> 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::(), chunk.len() as u32) }; + if ret != TRUE { + return Err(Error::WINDOWS_RTL_GEN_RANDOM); + } + } + Ok(()) +}