Botan  2.1.0
Crypto and TLS for C++11
system_rng.cpp
Go to the documentation of this file.
1 /*
2 * System RNG
3 * (C) 2014,2015 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7 
8 #include <botan/system_rng.h>
9 #include <botan/build.h>
10 
11 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
12 
13 #include <windows.h>
14 #define NOMINMAX 1
15 #include <wincrypt.h>
16 
17 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
18 
19 #include <stdlib.h>
20 
21 #else
22 
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <errno.h>
28 
29 #endif
30 
31 namespace Botan {
32 
33 namespace {
34 
35 class System_RNG_Impl final : public RandomNumberGenerator
36  {
37  public:
38  System_RNG_Impl();
39  ~System_RNG_Impl();
40 
41  bool is_seeded() const override { return true; }
42 
43  void clear() override {}
44 
45  void randomize(uint8_t out[], size_t len) override;
46 
47  void add_entropy(const uint8_t in[], size_t length) override;
48 
49  std::string name() const override;
50 
51  private:
52 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
53  HCRYPTPROV m_prov;
54 #else
55  int m_fd;
56 #endif
57  };
58 
59 std::string System_RNG_Impl::name() const
60  {
61 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
62  return "cryptoapi";
63 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
64  return "arc4random";
65 #else
66  return BOTAN_SYSTEM_RNG_DEVICE;
67 #endif
68  }
69 
70 System_RNG_Impl::System_RNG_Impl()
71  {
72 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
73 
74  if(!CryptAcquireContext(&m_prov, 0, 0, BOTAN_SYSTEM_RNG_CRYPTOAPI_PROV_TYPE, CRYPT_VERIFYCONTEXT))
75  throw Exception("System_RNG failed to acquire crypto provider");
76 
77 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
78  // Nothing to do, arc4random(3) works from the beginning.
79 #else
80 
81 #ifndef O_NOCTTY
82  #define O_NOCTTY 0
83 #endif
84 
85  m_fd = ::open(BOTAN_SYSTEM_RNG_DEVICE, O_RDWR | O_NOCTTY);
86 
87  // Cannot open in read-write mode. Fall back to read-only
88  // Calls to add_entropy will fail, but randomize will work
89  if(m_fd < 0)
90  m_fd = ::open(BOTAN_SYSTEM_RNG_DEVICE, O_RDONLY | O_NOCTTY);
91 
92  if(m_fd < 0)
93  throw Exception("System_RNG failed to open RNG device");
94 #endif
95  }
96 
97 System_RNG_Impl::~System_RNG_Impl()
98  {
99 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
100  ::CryptReleaseContext(m_prov, 0);
101 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
102  // Nothing to do.
103 #else
104  ::close(m_fd);
105  m_fd = -1;
106 #endif
107  }
108 
109 void System_RNG_Impl::add_entropy(const uint8_t input[], size_t len)
110  {
111 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
112  /*
113  There is no explicit ConsumeRandom, but all values provided in
114  the call are incorporated into the state.
115 
116  TODO: figure out a way to avoid this copy. Byte at a time updating
117  seems worse than the allocation.
118 
119  for(size_t i = 0; i != len; ++i)
120  {
121  uint8_t b = input[i];
122  ::CryptGenRandom(m_prov, 1, &b);
123  }
124  */
125 
126  if(len > 0)
127  {
128  secure_vector<uint8_t> buf(input, input + len);
129  ::CryptGenRandom(m_prov, static_cast<DWORD>(buf.size()), buf.data());
130  }
131 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
132  // arc4random(3) reseeds itself from the OpenBSD kernel, not from user land.
133 #else
134  while(len)
135  {
136  ssize_t got = ::write(m_fd, input, len);
137 
138  if(got < 0)
139  {
140  if(errno == EINTR)
141  continue;
142 
143  /*
144  * This is seen on OS X CI, despite the fact that the man page
145  * for Darwin urandom explicitly states that writing to it is
146  * supported, and write(2) does not document EPERM at all.
147  * But in any case EPERM seems indicative of a policy decision
148  * by the OS or sysadmin that additional entropy is not wanted
149  * in the system pool, so we accept that and return here,
150  * since there is no corrective action possible.
151  *
152  * In Linux EBADF or EPERM is returned if m_fd is not opened for
153  * writing.
154  */
155  if(errno == EPERM || errno == EBADF)
156  return;
157 
158  // maybe just ignore any failure here and return?
159  throw Exception("System_RNG write failed error " + std::to_string(errno));
160  }
161 
162  input += got;
163  len -= got;
164  }
165 #endif
166  }
167 
168 void System_RNG_Impl::randomize(uint8_t buf[], size_t len)
169  {
170 #if defined(BOTAN_TARGET_OS_HAS_CRYPTGENRANDOM)
171  ::CryptGenRandom(m_prov, static_cast<DWORD>(len), buf);
172 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
173  ::arc4random_buf(buf, len);
174 #else
175  while(len)
176  {
177  ssize_t got = ::read(m_fd, buf, len);
178 
179  if(got < 0)
180  {
181  if(errno == EINTR)
182  continue;
183  throw Exception("System_RNG read failed error " + std::to_string(errno));
184  }
185  if(got == 0)
186  throw Exception("System_RNG EOF on device"); // ?!?
187 
188  buf += got;
189  len -= got;
190  }
191 #endif
192  }
193 
194 }
195 
197  {
198  static System_RNG_Impl g_system_rng;
199  return g_system_rng;
200  }
201 
202 }
std::string to_string(const BER_Object &obj)
Definition: asn1_obj.cpp:47
Definition: alg_id.cpp:13
#define O_NOCTTY
RandomNumberGenerator & system_rng()
Definition: system_rng.cpp:196
int m_fd
Definition: system_rng.cpp:55