Coverage Report

Created: 2021-05-04 09:02

/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
}