8
8
9
9
use crate :: Error ;
10
10
use core:: { convert:: TryInto , ffi:: c_void, mem:: MaybeUninit , num:: NonZeroU32 , ptr} ;
11
+ use once_cell:: sync:: OnceCell ;
11
12
12
13
const BCRYPT_USE_SYSTEM_PREFERRED_RNG : u32 = 0x00000002 ;
13
14
15
+ /// The kinds of RNG that may be available
16
+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
17
+ enum Rng {
18
+ Preferred ,
19
+ Fallback ,
20
+ }
21
+
14
22
#[ link( name = "bcrypt" ) ]
15
23
extern "system" {
16
24
fn BCryptGenRandom (
@@ -21,6 +29,14 @@ extern "system" {
21
29
) -> u32 ;
22
30
}
23
31
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
+
24
40
// BCryptGenRandom was introduced in Windows Vista. However, CNG Algorithm
25
41
// Pseudo-handles (specifically BCRYPT_RNG_ALG_HANDLE) weren't introduced
26
42
// 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> {
52
68
Err ( Error :: from ( code) )
53
69
}
54
70
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
+
55
87
pub fn getrandom_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
88
+ let rng_fn = get_rng ( ) ;
89
+
56
90
// Prevent overflow of u32
57
91
for chunk in dest. chunks_mut ( u32:: max_value ( ) as usize ) {
58
- bcrypt_random ( chunk) ?;
92
+ rng_fn ( chunk) ?;
59
93
}
60
94
Ok ( ( ) )
61
95
}
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