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