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