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 }