88
99use crate :: Error ;
1010use core:: { convert:: TryInto , ffi:: c_void, mem:: MaybeUninit , num:: NonZeroU32 , ptr} ;
11+ use once_cell:: sync:: OnceCell ;
1112
1213const BCRYPT_USE_SYSTEM_PREFERRED_RNG : u32 = 0x00000002 ;
1314
15+ /// The kinds of RNG that may be available
16+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
17+ enum Rng {
18+ Preferred ,
19+ Fallback ,
20+ }
21+
1422#[ link( name = "bcrypt" ) ]
1523extern "system" {
1624 fn BCryptGenRandom (
@@ -21,6 +29,14 @@ extern "system" {
2129 ) -> u32 ;
2230}
2331
32+ #[ cfg( not( target_vendor = "uwp" ) ) ]
33+ #[ link( name = "advapi32" ) ]
34+ extern "system" {
35+ // Forbidden when targeting UWP
36+ #[ link_name = "SystemFunction036" ]
37+ pub fn RtlGenRandom ( RandomBuffer : * mut u8 , RandomBufferLength : u32 ) -> u8 ;
38+ }
39+
2440// BCryptGenRandom was introduced in Windows Vista. However, CNG Algorithm
2541// Pseudo-handles (specifically BCRYPT_RNG_ALG_HANDLE) weren't introduced
2642// until Windows 10, so we cannot use them yet. Note that on older systems
@@ -52,10 +68,65 @@ fn bcrypt_random(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
5268 Err ( Error :: from ( code) )
5369}
5470
71+ /// Generate random numbers using the fallback RNG function (RtlGenRandom)
72+ #[ cfg( not( target_vendor = "uwp" ) ) ]
73+ fn fallback_rng ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
74+ let ret = unsafe { RtlGenRandom ( dest. as_mut_ptr ( ) as * mut u8 , dest. len ( ) as u32 ) } ;
75+ if ret == 0 {
76+ return Err ( Error :: WINDOWS_RTL_GEN_RANDOM ) ;
77+ }
78+ Ok ( ( ) )
79+ }
80+
81+ /// We can't use RtlGenRandom with UWP, so there is no fallback
82+ #[ cfg( target_vendor = "uwp" ) ]
83+ fn fallback_rng ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
84+ Err ( Error :: UNSUPPORTED )
85+ }
86+
5587pub fn getrandom_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
88+ let rng_fn = get_rng ( ) ;
89+
5690 // Prevent overflow of u32
5791 for chunk in dest. chunks_mut ( u32:: max_value ( ) as usize ) {
58- bcrypt_random ( chunk) ?;
92+ rng_fn ( chunk) ?;
5993 }
6094 Ok ( ( ) )
6195}
96+
97+ /// Returns the RNG that should be used
98+ ///
99+ /// Panics if they are both broken
100+ fn get_rng ( ) -> fn ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
101+ // Assume that if the preferred RNG is broken the first time we use it, it likely means
102+ // that: the DLL has failed to load, there is no point to calling it over-and-over again,
103+ // and we should cache the result
104+ static VALUE : OnceCell < Rng > = OnceCell :: new ( ) ;
105+ match VALUE . get_or_init ( choose_rng) {
106+ Rng :: Preferred => bcrypt_random,
107+ Rng :: Fallback => fallback_rng,
108+ }
109+ }
110+
111+ /// Test whether we should use the preferred or fallback RNG
112+ ///
113+ /// If the preferred RNG is successful, we choose it. Otherwise, if the fallback RNG is successful,
114+ /// we choose that
115+ ///
116+ /// Panics if both the preferred and the fallback RNG are both non-functional
117+ fn choose_rng ( ) -> Rng {
118+ let mut dest = [ MaybeUninit :: uninit ( ) ; 1 ] ;
119+
120+ let preferred_error = match bcrypt_random ( & mut dest) {
121+ Ok ( _) => return Rng :: Preferred ,
122+ Err ( e) => e,
123+ } ;
124+
125+ match fallback_rng ( & mut dest) {
126+ Ok ( _) => return Rng :: Fallback ,
127+ Err ( fallback_error) => panic ! (
128+ "preferred RNG broken: `{}`, fallback RNG broken: `{}`" ,
129+ preferred_error, fallback_error
130+ ) ,
131+ }
132+ }
0 commit comments