1 module crypto.random;
2
3 import std.traits : isIntegral;
4 import std.random;
5
6 version (CRuntime_Bionic) version = SecureARC4Random; // ChaCha20
7 version (OSX) version = SecureARC4Random; // AES
8 version (OpenBSD) version = SecureARC4Random; // ChaCha20
9 version (NetBSD) version = SecureARC4Random; // ChaCha20
10 //version (FreeBSD) version = SecureARC4Random; // ARC4 before FreeBSD 12; ChaCha20 in FreeBSD 12.
11
12 version (SecureARC4Random)
13 {
14 private extern(C) uint arc4random() @nogc nothrow @safe;
15 private extern(C) uint arc4random_uniform(uint upperBound) @nogc nothrow @safe;
16
17 /++ Cryptographically secure source of random numbers. Not available on all platforms. +/
18 struct ARC4RandomGenerator
19 {
20 /++
21 Params:
22 min = min inclusive
23 max = max inclusive
24 Returns: `x` such that `min <= x <= max`
25 +/
26 T next(T = uint)(T min = T.min, T max = T.max) if (isIntegral!T)
27 {
28 if (min == T.min && max == T.max)
29 return cast(T) arc4random();
30 else
31 return cast(T) (min + arc4random_uniform(1U + max - min));
32 }
33 }
34
35 __gshared ARC4RandomGenerator rnd; // Can be global because it has no mutable state.
36 }
37 else version (Windows)
38 {
39 /++ Cryptographically secure source of random numbers. Not available on all platforms. +/
40 struct CryptGenRandomGenerator
41 {
42 import core.sys.windows.wincrypt;
43
44 __gshared private HCRYPTPROV hProvider;
45
46 private uint[32] buffer;
47 private uint pos = buffer.length;
48
49 static this()
50 {
51 if (!hProvider)
52 if (!CryptAcquireContextW(&hProvider, null, null, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
53 if (!CryptAcquireContextA(&hProvider, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_SILENT))
54 throw new Error("CryptAcquireContext failed");
55 }
56
57 /++
58 Params:
59 min = min inclusive
60 max = max inclusive
61 Returns: `x` such that `min <= x <= max`
62 +/
63 T next(T = uint)(T min = T.min, T max = T.max) if (isIntegral!T)
64 {
65 return uniform!("[]", T, T)(min, max, this);
66 }
67
68 // Stuff to make this work with std.random:
69 enum uint min = uint.min;
70 enum uint max = uint.max;
71 enum bool isUniformRandom = true;
72 enum bool empty = false;
73
74 uint front() @nogc nothrow @trusted
75 {
76 if (pos >= buffer.length)
77 {
78 while (!CryptGenRandom(hProvider, buffer.sizeof, cast(ubyte*) &buffer))
79 {
80 // Repeat until successful.
81 }
82 pos = 0;
83 }
84 return buffer[pos];
85 }
86
87 void popFront() @nogc nothrow @safe
88 {
89 pos++;
90 }
91 }
92
93 static CryptGenRandomGenerator rnd; // Thread-local because it has a mutable buffer.
94 }
95 else version (Posix)
96 {
97 version (linux)
98 {
99 // getentropy available on Linux 3.17 and later.
100 // http://www.man7.org/linux/man-pages/man3/getentropy.3.html
101 import core.sys.linux.dlfcn : dlsym, RTLD_DEFAULT;
102 private enum maybeHasGetEntropy = true;
103 }
104 else version (FreeBSD)
105 {
106 // getentropy available on FreeBSD 12 and later.
107 // https://www.freebsd.org/cgi/man.cgi?query=getentropy&sektion=3&manpath=FreeBSD+12.0-stable
108 import core.sys.freebsd.dlfcn : dlsym, RTLD_DEFAULT;
109 private enum maybeHasGetEntropy = true;
110 }
111 else version (Solaris)
112 {
113 // getentropy available on Solaris 11.3 and later.
114 // https://docs.oracle.com/cd/E86824_01/html/E54765/getentropy-2.html#REFMAN2getentropy-2
115 import core.sys.solaris.dlfcn : dlsym, RTLD_DEFAULT;
116 private enum maybeHasGetEntropy = true;
117 }
118 else
119 {
120 private enum maybeHasGetEntropy = false;
121 }
122
123 /++ Cryptographically secure source of random numbers. Not available on all platforms. +/
124 struct PosixRandomGenerator
125 {
126 private uint[32] buffer; // Fill using `getentropy` if possible. Otherwise fill using /dev/urandom.
127 private uint pos = buffer.length;
128
129 static if (maybeHasGetEntropy)
130 {
131 private alias GetEntropyFn = extern(C) int function(scope void*, size_t) @nogc nothrow @system;
132 private __gshared GetEntropyFn getentropy; // If unavailable, null.
133 shared static this()
134 {
135 getentropy = cast(GetEntropyFn) dlsym(RTLD_DEFAULT, "getentropy");
136 }
137 static assert(buffer.sizeof <= 256, "buffer must be no more than 256 bytes for use with getentropy");
138 }
139
140 /++
141 Params:
142 min = min inclusive
143 max = max inclusive
144 Returns: `x` such that `min <= x <= max`
145 +/
146 T next(T = uint)(T min = T.min, T max = T.max) if (isIntegral!T)
147 {
148 return uniform!("[]", T, T)(min, max, this);
149 }
150
151 // Stuff to make this work with std.random:
152 enum uint min = uint.min;
153 enum uint max = uint.max;
154 enum bool isUniformRandom = true;
155 enum bool empty = false;
156
157 uint front() @trusted
158 {
159 if (pos >= buffer.length)
160 {
161 static if (maybeHasGetEntropy)
162 {
163 if (getentropy is null || 0 != getentropy(buffer.ptr, buffer.sizeof))
164 fillBufferFromDevUrandom();
165 }
166 else
167 {
168 fillBufferFromDevUrandom();
169 }
170 pos = 0;
171 }
172 return buffer[pos];
173 }
174
175 void popFront() @nogc nothrow @safe
176 {
177 pos++;
178 }
179
180 private void fillBufferFromDevUrandom()
181 {
182 import core.stdc.errno : errno, EAGAIN, EINTR, EWOULDBLOCK;
183 import core.sys.posix.fcntl : open, O_RDONLY;
184 import core.sys.posix.unistd : close, read;
185
186 static if (maybeHasGetEntropy)
187 pragma(inline, false);
188
189 // Open file descriptor.
190 int fd;
191 while (-1 == (fd = open("/dev/urandom", O_RDONLY)))
192 {
193 import std.exception : ErrnoException;
194 if (errno != EAGAIN && errno != EINTR)
195 throw new ErrnoException("Error opening /dev/urandom");
196 }
197 scope(exit) close(fd);
198
199 // Read from /dev/urandom.
200 ubyte* ptr = cast(ubyte*) buffer.ptr;
201 size_t remaining = buffer.sizeof;
202 do {
203 const nread = read(fd, ptr, remaining);
204 if (nread >= 0)
205 {
206 remaining -= nread;
207 }
208 else
209 {
210 import std.exception : ErrnoException;
211 if (errno != EAGAIN && errno != EINTR && errno != EWOULDBLOCK)
212 throw new ErrnoException("Error reading from /dev/urandom");
213 }
214 } while (remaining > 0);
215 }
216 }
217
218 static PosixRandomGenerator rnd; // Thread-local because it has a mutable buffer.
219 }
220 else
221 {
222 /++ Fast but cryptographically insecure source of random numbers. +/
223 struct InsecureRandomGenerator
224 {
225 private static Mt19937 generator;
226
227 static this()
228 {
229 generator.seed(unpredictableSeed);
230 }
231
232 /++
233 Params:
234 min = min inclusive
235 max = max inclusive
236 Returns: `x` such that `min <= x <= max`
237 +/
238 T next(T = uint)(T min = T.min, T max = T.max) if (isIntegral!T)
239 {
240 return uniform!("[]", T, T, typeof(generator))(min, max, generator);
241 }
242 }
243
244 __gshared InsecureRandomGenerator rnd; // Can be global because it has no mutable state.
245 }