xref: /freebsd/contrib/bearssl/src/rand/sysrng.c (revision a90b9d0159070121c221b966469c3e36d912bf82)
1 /*
2  * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
25 #define BR_ENABLE_INTRINSICS   1
26 #include "inner.h"
27 
28 #if BR_USE_GETENTROPY
29 #include <unistd.h>
30 #endif
31 
32 #if BR_USE_URANDOM
33 #include <sys/types.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #endif
38 
39 #if BR_USE_WIN32_RAND
40 #include <windows.h>
41 #include <wincrypt.h>
42 #pragma comment(lib, "advapi32")
43 #endif
44 
45 /*
46  * Seeder that uses the RDRAND opcodes (on x86 CPU).
47  */
48 #if BR_RDRAND
49 BR_TARGETS_X86_UP
50 BR_TARGET("rdrnd")
51 static int
52 seeder_rdrand(const br_prng_class **ctx)
53 {
54 	unsigned char tmp[32];
55 	size_t u;
56 
57 	for (u = 0; u < sizeof tmp; u += sizeof(uint32_t)) {
58 		int j;
59 		uint32_t x;
60 
61 		/*
62 		 * We use the 32-bit intrinsic so that code is compatible
63 		 * with both 32-bit and 64-bit architectures.
64 		 *
65 		 * Intel recommends trying at least 10 times in case of
66 		 * failure.
67 		 *
68 		 * AMD bug: there are reports that some AMD processors
69 		 * have a bug that makes them fail silently after a
70 		 * suspend/resume cycle, in which case RDRAND will report
71 		 * a success but always return 0xFFFFFFFF.
72 		 * see: https://bugzilla.kernel.org/show_bug.cgi?id=85911
73 		 *
74 		 * As a mitigation, if the 32-bit value is 0 or -1, then
75 		 * it is considered a failure and tried again. This should
76 		 * reliably detect the buggy case, at least. This also
77 		 * implies that the selected seed values can never be
78 		 * 0x00000000 or 0xFFFFFFFF, which is not a problem since
79 		 * we are generating a seed for a PRNG, and we overdo it
80 		 * a bit (we generate 32 bytes of randomness, and 256 bits
81 		 * of entropy are really overkill).
82 		 */
83 		for (j = 0; j < 10; j ++) {
84 			if (_rdrand32_step(&x) && x != 0 && x != (uint32_t)-1) {
85 				goto next_word;
86 			}
87 		}
88 		return 0;
89 	next_word:
90 		br_enc32le(tmp + u, x);
91 	}
92 	(*ctx)->update(ctx, tmp, sizeof tmp);
93 	return 1;
94 }
95 BR_TARGETS_X86_DOWN
96 
97 static int
98 rdrand_supported(void)
99 {
100 	/*
101 	 * The RDRND support is bit 30 of ECX, as returned by CPUID.
102 	 */
103 	return br_cpuid(0, 0, 0x40000000, 0);
104 }
105 #endif
106 
107 /*
108  * Seeder that uses /dev/urandom (on Unix-like systems).
109  */
110 #if BR_USE_URANDOM
111 static int
112 seeder_urandom(const br_prng_class **ctx)
113 {
114 	int f;
115 
116 	f = open("/dev/urandom", O_RDONLY);
117 	if (f >= 0) {
118 		unsigned char tmp[32];
119 		size_t u;
120 
121 		for (u = 0; u < sizeof tmp;) {
122 			ssize_t len;
123 
124 			len = read(f, tmp + u, (sizeof tmp) - u);
125 			if (len < 0) {
126 				if (errno == EINTR) {
127 					continue;
128 				}
129 				break;
130 			}
131 			u += (size_t)len;
132 		}
133 		close(f);
134 		if (u == sizeof tmp) {
135 			(*ctx)->update(ctx, tmp, sizeof tmp);
136 			return 1;
137 		}
138 	}
139 	return 0;
140 }
141 #endif
142 
143 /*
144  * Seeder that uses getentropy() (backed by getrandom() on some systems,
145  * e.g. Linux). On failure, it will use the /dev/urandom seeder (if
146  * enabled).
147  */
148 #if BR_USE_GETENTROPY
149 static int
150 seeder_getentropy(const br_prng_class **ctx)
151 {
152 	unsigned char tmp[32];
153 
154 	if (getentropy(tmp, sizeof tmp) == 0) {
155 		(*ctx)->update(ctx, tmp, sizeof tmp);
156 		return 1;
157 	}
158 #if BR_USE_URANDOM
159 	return seeder_urandom(ctx);
160 #else
161 	return 0;
162 #endif
163 }
164 #endif
165 
166 /*
167  * Seeder that uses CryptGenRandom() (on Windows).
168  */
169 #if BR_USE_WIN32_RAND
170 static int
171 seeder_win32(const br_prng_class **ctx)
172 {
173 	HCRYPTPROV hp;
174 
175 	if (CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL,
176 		CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
177 	{
178 		BYTE buf[32];
179 		BOOL r;
180 
181 		r = CryptGenRandom(hp, sizeof buf, buf);
182 		CryptReleaseContext(hp, 0);
183 		if (r) {
184 			(*ctx)->update(ctx, buf, sizeof buf);
185 			return 1;
186 		}
187 	}
188 	return 0;
189 }
190 #endif
191 
192 /*
193  * An aggregate seeder that uses RDRAND, and falls back to an OS-provided
194  * source if RDRAND fails.
195  */
196 #if BR_RDRAND && (BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND)
197 static int
198 seeder_rdrand_with_fallback(const br_prng_class **ctx)
199 {
200 	if (!seeder_rdrand(ctx)) {
201 #if BR_USE_GETENTROPY
202 		return seeder_getentropy(ctx);
203 #elif BR_USE_URANDOM
204 		return seeder_urandom(ctx);
205 #elif BR_USE_WIN32_RAND
206 		return seeder_win32(ctx);
207 #else
208 #error "macro selection has gone wrong"
209 #endif
210 	}
211 	return 1;
212 }
213 #endif
214 
215 /* see bearssl_rand.h */
216 br_prng_seeder
217 br_prng_seeder_system(const char **name)
218 {
219 #if BR_RDRAND
220 	if (rdrand_supported()) {
221 		if (name != NULL) {
222 			*name = "rdrand";
223 		}
224 #if BR_USE_GETENTROPY || BR_USE_URANDOM || BR_USE_WIN32_RAND
225 		return &seeder_rdrand_with_fallback;
226 #else
227 		return &seeder_rdrand;
228 #endif
229 	}
230 #endif
231 #if BR_USE_GETENTROPY
232 	if (name != NULL) {
233 		*name = "getentropy";
234 	}
235 	return &seeder_getentropy;
236 #elif BR_USE_URANDOM
237 	if (name != NULL) {
238 		*name = "urandom";
239 	}
240 	return &seeder_urandom;
241 #elif BR_USE_WIN32_RAND
242 	if (name != NULL) {
243 		*name = "win32";
244 	}
245 	return &seeder_win32;
246 #else
247 	if (name != NULL) {
248 		*name = "none";
249 	}
250 	return 0;
251 #endif
252 }
253