Botan 3.11.0
Crypto and TLS for C&
system_rng.cpp
Go to the documentation of this file.
1/*
2* System RNG
3* (C) 2014,2015,2017,2018,2022 Jack Lloyd
4* (C) 2021 Tom Crowley
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/system_rng.h>
10
11#include <botan/assert.h>
12#include <botan/exceptn.h>
13#include <botan/internal/target_info.h>
14
15#if defined(BOTAN_TARGET_OS_HAS_WIN32)
16 #define NOMINMAX 1
17 #define _WINSOCKAPI_ // stop windows.h including winsock.h
18 #include <windows.h>
19#endif
20
21#if defined(BOTAN_TARGET_OS_HAS_RTLGENRANDOM)
22 #include <botan/internal/dyn_load.h>
23 #include <limits>
24#elif defined(BOTAN_TARGET_OS_HAS_CRYPTO_NG)
25 #include <bcrypt.h>
26 #include <windows.h>
27#elif defined(BOTAN_TARGET_OS_HAS_CCRANDOM)
28 #include <CommonCrypto/CommonRandom.h>
29#elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
30 #include <stdlib.h>
31#elif defined(BOTAN_TARGET_OS_HAS_GETRANDOM)
32 #include <errno.h>
33 #include <sys/random.h>
34 #include <sys/syscall.h>
35 #include <unistd.h>
36#elif defined(BOTAN_TARGET_OS_HAS_DEV_RANDOM)
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40#endif
41
42namespace Botan {
43
44namespace {
45
46#if defined(BOTAN_TARGET_OS_HAS_RTLGENRANDOM)
47
48class System_RNG_Impl final : public RandomNumberGenerator {
49 public:
50 System_RNG_Impl() : m_advapi("advapi32.dll") {
51 // This throws if the function is not found
52 m_rtlgenrandom = m_advapi.resolve<RtlGenRandom_fptr>("SystemFunction036");
53 }
54
55 System_RNG_Impl(const System_RNG_Impl& other) = delete;
56 System_RNG_Impl(System_RNG_Impl&& other) = delete;
57 System_RNG_Impl& operator=(const System_RNG_Impl& other) = delete;
58 System_RNG_Impl& operator=(System_RNG_Impl&& other) = delete;
59
60 bool is_seeded() const override { return true; }
61
62 bool accepts_input() const override { return false; }
63
64 void clear() override { /* not possible */
65 }
66
67 std::string name() const override { return "RtlGenRandom"; }
68
69 private:
70 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
71 const size_t limit = std::numeric_limits<ULONG>::max();
72
73 uint8_t* pData = output.data();
74 size_t bytesLeft = output.size();
75 while(bytesLeft > 0) {
76 const ULONG blockSize = static_cast<ULONG>(std::min(bytesLeft, limit));
77
78 const bool success = m_rtlgenrandom(pData, blockSize) == TRUE;
79 if(!success) {
80 throw System_Error("RtlGenRandom failed");
81 }
82
83 BOTAN_ASSERT(bytesLeft >= blockSize, "Block is oversized");
84 bytesLeft -= blockSize;
85 pData += blockSize;
86 }
87 }
88
89 private:
90 using RtlGenRandom_fptr = BOOLEAN(NTAPI*)(PVOID, ULONG);
91
92 Dynamically_Loaded_Library m_advapi;
93 RtlGenRandom_fptr m_rtlgenrandom;
94};
95
96#elif defined(BOTAN_TARGET_OS_HAS_CRYPTO_NG)
97
98class System_RNG_Impl final : public RandomNumberGenerator {
99 public:
100 System_RNG_Impl() {
101 auto ret = ::BCryptOpenAlgorithmProvider(&m_prov, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
102 if(!BCRYPT_SUCCESS(ret)) {
103 throw System_Error("System_RNG failed to acquire crypto provider", ret);
104 }
105 }
106
107 System_RNG_Impl(const System_RNG_Impl& other) = delete;
108 System_RNG_Impl(System_RNG_Impl&& other) = delete;
109 System_RNG_Impl& operator=(const System_RNG_Impl& other) = delete;
110 System_RNG_Impl& operator=(System_RNG_Impl&& other) = delete;
111
112 ~System_RNG_Impl() override { ::BCryptCloseAlgorithmProvider(m_prov, 0); }
113
114 bool is_seeded() const override { return true; }
115
116 bool accepts_input() const override { return false; }
117
118 void clear() override { /* not possible */
119 }
120
121 std::string name() const override { return "crypto_ng"; }
122
123 private:
124 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
125 /*
126 There is a flag BCRYPT_RNG_USE_ENTROPY_IN_BUFFER to provide
127 entropy inputs, but it is ignored in Windows 8 and later.
128 */
129
130 const size_t limit = std::numeric_limits<ULONG>::max();
131
132 uint8_t* pData = output.data();
133 size_t bytesLeft = output.size();
134 while(bytesLeft > 0) {
135 const ULONG blockSize = static_cast<ULONG>(std::min(bytesLeft, limit));
136
137 auto ret = BCryptGenRandom(m_prov, static_cast<PUCHAR>(pData), blockSize, 0);
138 if(!BCRYPT_SUCCESS(ret)) {
139 throw System_Error("System_RNG call to BCryptGenRandom failed", ret);
140 }
141
142 BOTAN_ASSERT(bytesLeft >= blockSize, "Block is oversized");
143 bytesLeft -= blockSize;
144 pData += blockSize;
145 }
146 }
147
148 private:
149 BCRYPT_ALG_HANDLE m_prov;
150};
151
152#elif defined(BOTAN_TARGET_OS_HAS_CCRANDOM)
153
154class System_RNG_Impl final : public RandomNumberGenerator {
155 public:
156 bool accepts_input() const override { return false; }
157
158 bool is_seeded() const override { return true; }
159
160 void clear() override { /* not possible */
161 }
162
163 std::string name() const override { return "CCRandomGenerateBytes"; }
164
165 private:
166 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
167 if(::CCRandomGenerateBytes(output.data(), output.size()) != kCCSuccess) {
168 throw System_Error("System_RNG CCRandomGenerateBytes failed", errno);
169 }
170 }
171};
172
173#elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
174
175class System_RNG_Impl final : public RandomNumberGenerator {
176 public:
177 // No constructor or destructor needed as no userland state maintained
178
179 bool accepts_input() const override { return false; }
180
181 bool is_seeded() const override { return true; }
182
183 void clear() override { /* not possible */
184 }
185
186 std::string name() const override { return "arc4random"; }
187
188 private:
189 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
190 // macOS 10.15 arc4random crashes if called with buf == nullptr && len == 0
191 // however it uses ccrng_generate internally which returns a status, ignored
192 // to respect arc4random "no-fail" interface contract
193 if(!output.empty()) {
194 ::arc4random_buf(output.data(), output.size());
195 }
196 }
197};
198
199#elif defined(BOTAN_TARGET_OS_HAS_GETRANDOM)
200
201class System_RNG_Impl final : public RandomNumberGenerator {
202 public:
203 // No constructor or destructor needed as no userland state maintained
204
205 bool accepts_input() const override { return false; }
206
207 bool is_seeded() const override { return true; }
208
209 void clear() override { /* not possible */
210 }
211
212 std::string name() const override { return "getrandom"; }
213
214 private:
215 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override {
216 const unsigned int flags = 0;
217
218 uint8_t* buf = output.data();
219 size_t len = output.size();
220 while(len > 0) {
221 #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 25
222 const ssize_t got = ::syscall(SYS_getrandom, buf, len, flags);
223 #else
224 const ssize_t got = ::getrandom(buf, len, flags);
225 #endif
226
227 if(got < 0) {
228 if(errno == EINTR) {
229 continue;
230 }
231 throw System_Error("System_RNG getrandom failed", errno);
232 }
233
234 buf += got;
235 len -= got;
236 }
237 }
238};
239
240#elif defined(BOTAN_TARGET_OS_HAS_DEV_RANDOM)
241
242// Read a random device
243
244class System_RNG_Impl final : public RandomNumberGenerator {
245 public:
246 System_RNG_Impl() {
247 #ifndef O_NOCTTY
248 #define O_NOCTTY 0
249 #endif
250
251 /*
252 * First open /dev/random and read one byte. On old Linux kernels
253 * this blocks the RNG until we have been actually seeded.
254 */
255 m_fd = ::open("/dev/random", O_RDONLY | O_NOCTTY);
256 if(m_fd < 0)
257 throw System_Error("System_RNG failed to open RNG device", errno);
258
259 uint8_t b;
260 const size_t got = ::read(m_fd, &b, 1);
261 ::close(m_fd);
262
263 if(got != 1)
264 throw System_Error("System_RNG failed to read blocking RNG device");
265
266 m_fd = ::open("/dev/urandom", O_RDWR | O_NOCTTY);
267
268 if(m_fd >= 0) {
269 m_writable = true;
270 } else {
271 /*
272 Cannot open in read-write mode. Fall back to read-only,
273 calls to add_entropy will fail, but randomize will work
274 */
275 m_fd = ::open("/dev/urandom", O_RDONLY | O_NOCTTY);
276 m_writable = false;
277 }
278
279 if(m_fd < 0)
280 throw System_Error("System_RNG failed to open RNG device", errno);
281 }
282
283 System_RNG_Impl(const System_RNG_Impl& other) = delete;
284 System_RNG_Impl(System_RNG_Impl&& other) = delete;
285 System_RNG_Impl& operator=(const System_RNG_Impl& other) = delete;
286 System_RNG_Impl& operator=(System_RNG_Impl&& other) = delete;
287
288 ~System_RNG_Impl() override {
289 ::close(m_fd);
290 m_fd = -1;
291 }
292
293 bool is_seeded() const override { return true; }
294
295 bool accepts_input() const override { return m_writable; }
296
297 void clear() override { /* not possible */
298 }
299
300 std::string name() const override { return "urandom"; }
301
302 private:
303 void fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> /* ignored */) override;
304 void maybe_write_entropy(std::span<const uint8_t> input);
305
306 private:
307 int m_fd;
308 bool m_writable;
309};
310
311void System_RNG_Impl::fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) {
312 maybe_write_entropy(input);
313
314 uint8_t* buf = output.data();
315 size_t len = output.size();
316 while(len) {
317 ssize_t got = ::read(m_fd, buf, len);
318
319 if(got < 0) {
320 if(errno == EINTR)
321 continue;
322 throw System_Error("System_RNG read failed", errno);
323 }
324 if(got == 0)
325 throw System_Error("System_RNG EOF on device"); // ?!?
326
327 buf += got;
328 len -= got;
329 }
330}
331
332void System_RNG_Impl::maybe_write_entropy(std::span<const uint8_t> entropy_input) {
333 if(!m_writable || entropy_input.empty())
334 return;
335
336 const uint8_t* input = entropy_input.data();
337 size_t len = entropy_input.size();
338 while(len) {
339 ssize_t got = ::write(m_fd, input, len);
340
341 if(got < 0) {
342 if(errno == EINTR)
343 continue;
344
345 /*
346 * This is seen on OS X CI, despite the fact that the man page
347 * for macOS urandom explicitly states that writing to it is
348 * supported, and write(2) does not document EPERM at all.
349 * But in any case EPERM seems indicative of a policy decision
350 * by the OS or sysadmin that additional entropy is not wanted
351 * in the system pool, so we accept that and return here,
352 * since there is no corrective action possible.
353 *
354 * In Linux EBADF or EPERM is returned if m_fd is not opened for
355 * writing.
356 */
357 if(errno == EPERM || errno == EBADF)
358 return;
359
360 // maybe just ignore any failure here and return?
361 throw System_Error("System_RNG write failed", errno);
362 }
363
364 input += got;
365 len -= got;
366 }
367}
368
369#endif
370
371} // namespace
372
374 static System_RNG_Impl g_system_rng;
375 return g_system_rng;
376}
377
378} // namespace Botan
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:62
Public Header.
Flags flags(Flag flags)
Definition p11.h:1227
RandomNumberGenerator & system_rng()