1 /**
2     A Cryptographically-Secure Pseudo-Random Number Generator that uses system
3     APIs as its source of secure random bytes. In the case of Windows, it uses
4     the $(MONO BCryptGenRandom), $(MONO CryptGenRandom), and $(MONO RtlGenRandom)
5     libraries in that order of fallback. On POSIX-ish systems, it uses the
6     pseudo-device, $(MONO /dev/random), as its source of secure random bytes.
7 
8     "ARC4" is a trademark of $(LINK https://www.rsa.com/, RSA Laboratories).
9 
10     Authors:
11     $(UL
12         $(LI $(PERSON Jonathan M. Wilbur, jonathan@wilbur.space, http://jonathan.wilbur.space))
13     )
14     Copyright: Copyright (C) Jonathan M. Wilbur
15     License: $(LINK https://mit-license.org/, MIT License)
16 */
17 module csprng.system;
18 private import std.conv : text;
19 
20 version (CRuntime_Bionic)
21 {
22     version = SecureARC4Random;
23 }
24 
25 /* NOTE:
26     Why not use $(D arc4random_buf) on FreeBSD or DragonFlyBSD?
27 
28     To quote $(LINK https://github.com/n8sh, Nathan Sashihara)
29     ($(LINK https://github.com/n8sh, @n8sh)), who implemented the use of
30     $(D arc4random_buf) in this library:
31 
32     $(BLOCKQUOTE
33         FreeBSD's arc4random_buf implementation actually uses ARC4.
34         It's also historically had some implementation issues with seeding.
35         DragonFlyBSD uses the arc4random code from FreeBSD.
36     )
37 
38     I have confirmed that FreeBSD uses the insecure ARC4 cipher
39     $(LINK https://www.unix.com/man-page/freebsd/3/arc4random/, here).
40 
41     I could not find confirmation that DragonFlyBSD uses the insecure ARC4
42     cipher, but I did find confirmation that DragonFlyBSD is a fork of
43     FreeBSD on
44     $(LINK https://en.wikipedia.org/wiki/DragonFly_BSD, this wiki), so it's
45     believable.
46 
47     I have confirmed that $(D arc4random_buf) is in the Bionic C Library
48     and in the uClibc Library, but I can't easily find a link that I believe
49     will be permanent.
50 */
51 version (OSX)
52 {
53     version = SecureARC4Random;
54 }
55 else version (OpenBSD)
56 {
57     version = SecureARC4Random;
58 }
59 else version (NetBSD)
60 {
61     version = SecureARC4Random;
62 }
63 
64 /* NOTE:
65     It is important to distinguish between secure and insecure implementations
66     of $(D arc4random_buf). The name, $(D arc4random_buf), comes from the fact
67     that the stream of random bytes was, at one point, generated by an RC4
68     cipher--formerly called ARC4--which is no longer considered secure.
69 
70     Secure implementations of $(D arc4random_buf) keep the name, but change the
71     underlying code to generate cryptographically-secure pseudo-random bytes.
72 */
73 version (SecureARC4Random)
74 {
75     private extern (C) void arc4random_buf(scope void* buf, size_t nbytes) @nogc nothrow @system;
76 }
77 
78 ///
79 public alias CSPRNGException = CryptographicallySecurePseudoRandomNumberGeneratorException;
80 /// A generic CSPRNG exception
81 public
82 class CryptographicallySecurePseudoRandomNumberGeneratorException : Exception
83 {
84     import std.exception : basicExceptionCtors;
85     mixin basicExceptionCtors;
86 }
87 
88 ///
89 public alias CSPRNG = CryptographicallySecurePseudoRandomNumberGenerator;
90 /**
91     The class that wraps the system's CSPRNG APIs, making it easy to
92     retrieve cryptographically-secure pseudo-random bytes.
93 */
94 public
95 class CryptographicallySecurePseudoRandomNumberGenerator
96 {
97     import std.traits : ForeachType, isNumeric, isStaticArray, Unqual;
98 
99     version (Windows)
100     {
101         import core.sys.windows.windows;
102 
103         private static size_t openInstances;
104         /* NOTE:
105             Question: Should I call malloc() before using these pointers to store data?
106             Answer: I don't believe so, because the memory _should be_ allocated by the
107                 loader already. All of the pointers are pointers to functions within the
108                 loaded library, so that should not cause a problem, either.
109         */
110         private alias BCRYPT_ALG_HANDLE = void*;
111         private alias HCRYPTPROV = void*;
112         private alias NTSTATUS = uint;
113 
114         // Used by the Cryptography: Next Generation (CNG) API
115         private static HMODULE bcrypt; // A pointer to the loaded Bcrypt library (Bcrypt.dll)
116         private static BCRYPT_ALG_HANDLE cngProviderHandle; // A pointer to the CNG Provider Handle, which is used by BCryptGenRandomAddress()
117         private static FARPROC bCryptOpenAlgorithmProviderAddress; // A pointer to the BCryptOpenAlgorithmProvider() function, as obtained from this.bcrypt
118         private static FARPROC bCryptCloseAlgorithmProviderAddress; // A pointer to the BCryptCloseAlgorithmProvider() function, as obtained from this.bcrypt
119         private static FARPROC bCryptGenRandomAddress; // A pointer to the BCryptGenRandom() function, as obtained from this.bcrypt
120 
121         // Used by the CryptoAPI and the legacy cryptography API
122         private static HMODULE advapi32; // A pointer to the loaded Windows Advanced API (advapi32.dll)
123         private static HCRYPTPROV cryptographicServiceProviderHandle; // A pointer to the CSP, which is obtained with CryptoAcquireContext(), and used by CryptGenRandom()
124         private static FARPROC cryptAcquireContextAddress; // A pointer to CryptAcquireContext(), as obtained from this.advapi32
125         private static FARPROC cryptReleaseContextAddress; // A pointer to CryptReleaseContext(), as obtained from this.advapi32
126         private static FARPROC cryptGenRandomAddress; // A pointer to CryptGenRandom(), as obtained from this.advapi32
127         private static FARPROC rtlGenRandomAddress; // A pointer to RtlGenRandom(), as obtained from this.advapi32
128 
129         ///
130         public alias isUsingCNGAPI = isUsingCryptographyNextGenerationApplicationProgrammingInterface;
131         ///
132         public alias isUsingCryptographyNextGenerationAPI = isUsingCryptographyNextGenerationApplicationProgrammingInterface;
133         ///
134         public alias isUsingCNGApplicationProgrammingInterface = isUsingCryptographyNextGenerationApplicationProgrammingInterface;
135         /**
136             Returns boolean indicating whether this library is using the Windows
137             $(B Cryptography: Next Generation) API to generate random bytes from
138             the $(MONO BCryptGenRandom) API function.
139 
140             More specifically, this library returns true if $(MONO Bcrypt.dll) was found,
141             loaded, and all three requisite functions could be loaded from it,
142             which are:
143             $(UL
144                 $(LI $(MONO BCryptOpenAlgorithmProvider))
145                 $(LI $(MONO BCryptCloseAlgorithmProvider))
146                 $(LI $(MONO BCryptGenRandom))
147             )
148         */
149         public @property @safe @nogc nothrow
150         bool isUsingCryptographyNextGenerationApplicationProgrammingInterface()
151         {
152             return
153             (
154                 this.bcrypt != NULL &&
155                 this.cngProviderHandle != NULL &&
156                 this.bCryptOpenAlgorithmProviderAddress != NULL &&
157                 this.bCryptCloseAlgorithmProviderAddress != NULL &&
158                 this.bCryptGenRandomAddress != NULL
159             );
160         }
161 
162         ///
163         public alias isUsingCryptoAPI = isUsingCryptoApplicationProgrammingInterface;
164         /**
165             Returns boolean indicating whether this library is using the Windows
166             $(B Crypto API) to generate random bytes from
167             the $(MONO CryptGenRandom) API function.
168 
169             More specifically, this library returns true if $(MONO advapi32.dll) was found,
170             loaded, and all three requisite functions could be loaded from it,
171             which are:
172             $(UL
173                 $(LI $(MONO CryptAcquireContext))
174                 $(LI $(MONO CryptReleaseContext))
175                 $(LI $(MONO CryptGenRandom))
176             )
177         */
178         public @property @safe @nogc nothrow
179         bool isUsingCryptoApplicationProgrammingInterface()
180         {
181             return
182             (
183                 this.advapi32 != NULL &&
184                 this.cryptographicServiceProviderHandle != NULL &&
185                 this.cryptAcquireContextAddress != NULL &&
186                 this.cryptReleaseContextAddress != NULL &&
187                 this.cryptGenRandomAddress != NULL
188             );
189         }
190 
191         /**
192             Returns a boolean indicating whether this library was able to load
193             $(MONO advapi32.dll) and $(MONO RtlGenRandom) from it, and will
194             use $(MONO RtlGenRandom) to obtain secure random bytes.
195         */
196         public @property @safe @nogc nothrow
197         bool isUsingRtlGenRandom()
198         {
199             return
200             (
201                 this.advapi32 != NULL &&
202                 this.rtlGenRandomAddress != NULL
203             );
204         }
205 
206         /**
207             Returns the specified number of cryptographically-secure
208             pseudo-random bytes, using one of the system APIs.
209 
210             Throws:
211             $(UL
212                 $(LI $(D CSPRNGException) if one of the functions from the
213                     automatically-select cryptography API could not be loaded.)
214             )
215         */
216         public @system
217         void[] getBytes (in size_t length)
218         {
219             if (this.isUsingCNGAPI)
220             {
221                 ubyte[] bytes;
222                 bytes.length = length;
223                 extern (Windows) NTSTATUS function (BCRYPT_ALG_HANDLE hAlgorithm, PUCHAR pbBuffer, ULONG cbBuffer, ULONG dwFlags) BCryptGenRandom =
224                     cast(NTSTATUS function (BCRYPT_ALG_HANDLE hAlgorithm, PUCHAR pbBuffer, ULONG cbBuffer, ULONG dwFlags)) this.bCryptGenRandomAddress;
225 
226                 if (BCryptGenRandom(this.cngProviderHandle, bytes.ptr, bytes.length, 0u))
227                     return bytes;
228             }
229 
230             if (this.isUsingCryptoAPI)
231             {
232                 ubyte[] bytes;
233                 bytes.length = length;
234                 extern (Windows) BOOL function(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer) CryptGenRandom
235                     = cast(BOOL function(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer)) this.cryptGenRandomAddress;
236 
237                 if (CryptGenRandom(this.cryptographicServiceProviderHandle, bytes.length, bytes.ptr))
238                     return cast(void[]) bytes;
239             }
240 
241             if (this.isUsingRtlGenRandom)
242             {
243                 void[] bytes;
244                 bytes.length = length;
245                 extern (Windows) BOOLEAN function(PVOID RandomBuffer, ULONG RandomBufferLength) RtlGenRandom
246                     = cast(BOOLEAN function(PVOID RandomBuffer, ULONG RandomBufferLength)) rtlGenRandomAddress;
247 
248                 version (unittest) assert(bytes.length == length);
249                 if (RtlGenRandom(bytes.ptr, length))
250                     return bytes;
251             }
252 
253             throw new CSPRNGException
254             (
255                 "This exception was thrown because you attempted to generate " ~
256                 "cryptographically secure random bytes from the csprng library, " ~
257                 "but none of the Windows cryptography APIs could be loaded. " ~
258                 "Ensure that either Bcrypt.dll or advapi32.dll is either in " ~
259                 "the executable's directory, current directory, the Windows " ~
260                 "directory, the System directory, or in one of the directories " ~
261                 "in the PATH environment variable. If you believe you have " ~
262                 "received this error by mistake, please report this as a bug " ~
263                 "to https://github.com/JonathanWilbur/csprng-d/issues."
264             );
265         }
266 
267         /**
268             The constructor for a CSPRNG. When a $(D CSPRNG) is created with
269             this constructor, the relevant libraries and functions from them are
270             loaded. If they cannot be loaded, the constructor throws a
271             $(D CSPRNGException) and the $(D CSPRNG) will not be created.
272 
273             This constructor first attempts to load the Windows
274             Cryptography: Next Generation API. If that cannot be loaded, it
275             attempts to load the CryptoAPI. If that cannot be loaded, it attempts
276             to load the $(MONO RtlGenRandom) function. If all of those fail,
277             the $(D CSPRNGException) is thrown, since no source of
278             cryptographically-secure pseudo-random bytes can be accessed.
279         */
280         public @system
281         this ()
282         {
283             scope(success) this.openInstances++;
284 
285             firstTryToUseTheNextGenCryptoAPI:
286                 this.bcrypt = LoadLibrary("Bcrypt.dll");
287                 if (this.bcrypt == NULL)
288                     goto fallBackOnAdvapi32;
289 
290                 this.bCryptOpenAlgorithmProviderAddress = GetProcAddress(this.bcrypt, "BCryptOpenAlgorithmProvider");
291                 if (this.bCryptOpenAlgorithmProviderAddress == NULL)
292                     goto fallBackOnAdvapi32;
293 
294                 this.bCryptCloseAlgorithmProviderAddress = GetProcAddress(this.bcrypt, "BCryptCloseAlgorithmProvider");
295                 if (this.bCryptCloseAlgorithmProviderAddress == NULL)
296                     goto fallBackOnAdvapi32;
297 
298                 this.bCryptGenRandomAddress = GetProcAddress(this.bcrypt, "BCryptGenRandom");
299                 if (this.bCryptGenRandomAddress == NULL)
300                     goto fallBackOnAdvapi32;
301 
302                 {
303                     extern (Windows) NTSTATUS function(BCRYPT_ALG_HANDLE phAlgorithm, LPCWSTR pszAlgId, typeof(null) pszImplementation, DWORD dwFlags) BCryptOpenAlgorithmProvider =
304                         cast(NTSTATUS function(BCRYPT_ALG_HANDLE phAlgorithm, LPCWSTR pszAlgId, typeof(null) pszImplementation, DWORD dwFlags)) this.bCryptOpenAlgorithmProviderAddress;
305 
306                     if (!BCryptOpenAlgorithmProvider(&(this.cngProviderHandle), "RNG", NULL, 0u))
307                         goto fallBackOnAdvapi32;
308                 }
309 
310                 if (this.cngProviderHandle != NULL)
311                     goto fallBackOnAdvapi32;
312                 return; // If we have a viable CSPRNG function, then we're done.
313 
314             // Since the Next-Gen Crypto API failed to load, fall back on either CryptGenRandom or RtlGenRandom from advapi32.dll
315             fallBackOnAdvapi32:
316                 this.advapi32 = LoadLibrary("advapi32.dll");
317                 if (this.advapi32 == NULL)
318                     throw new CSPRNGException
319                     (
320                         "This exception was thrown because you attempted to generate " ~
321                         "cryptographically secure random bytes from the csprng library, " ~
322                         "but none of the Windows cryptography libraries could be loaded. " ~
323                         "Ensure that either Bcrypt.dll or advapi32.dll is either in " ~
324                         "the executable's directory, current directory, the Windows " ~
325                         "directory, the System directory, or in one of the directories " ~
326                         "in the PATH environment variable. If you believe you have " ~
327                         "received this error by mistake, please report this as a bug " ~
328                         "to https://github.com/JonathanWilbur/csprng-d/issues. The " ~
329                         "Windows error code associated with this particular failure is " ~
330                         text(GetLastError()) ~ "."
331                     );
332 
333             // This label is never used, but I want it here for visual consistency, anyway.
334             fallBackOnCryptGenRandom:
335 
336                 // For some reason, CryptAcquireContextW and CryptAcquireContextA existed, but not CryptAcquireContext
337                 this.cryptAcquireContextAddress = GetProcAddress(this.advapi32, "CryptAcquireContextW");
338                 if (this.cryptAcquireContextAddress == NULL)
339                     this.cryptAcquireContextAddress = GetProcAddress(this.advapi32, "CryptAcquireContextA");
340                 if (this.cryptAcquireContextAddress == NULL)
341                     goto fallBackOnRtlGenRandom;
342                 if (this.cryptReleaseContextAddress == NULL)
343                     this.cryptReleaseContextAddress = GetProcAddress(this.advapi32, "CryptReleaseContext");
344 
345                 this.cryptGenRandomAddress = GetProcAddress(this.advapi32, "CryptGenRandom");
346                 if (this.cryptGenRandomAddress == NULL)
347                     goto fallBackOnRtlGenRandom;
348 
349                 {
350                     // REVIEW: Make sure this does not leave behind a bunch of keys in your profile.
351                     extern (Windows) BOOL function (HCRYPTPROV *phProv, typeof(null) pszContainer, typeof(null) pszProvider, DWORD dwProvType, DWORD dwFlags) CryptAcquireContext
352                         = cast(BOOL function (HCRYPTPROV *phProv, typeof(null) pszContainer, typeof(null) pszProvider, DWORD dwProvType, DWORD dwFlags)) this.cryptAcquireContextAddress;
353                     if (!CryptAcquireContext(&(this.cryptographicServiceProviderHandle), NULL, NULL, 1u, 0u))
354                         goto fallBackOnRtlGenRandom;
355                 }
356 
357                 if (this.cryptographicServiceProviderHandle != NULL)
358                     goto fallBackOnRtlGenRandom;
359                 return; // If we have a viable CSPRNG function, then we're done.
360 
361             // Otherwise, fall back on the really old RtlGenRandom API
362             fallBackOnRtlGenRandom:
363                 this.rtlGenRandomAddress = GetProcAddress(this.advapi32, "SystemFunction036");
364                 if (this.rtlGenRandomAddress == NULL)
365                     throw new CSPRNGException
366                     (
367                         "This exception was thrown because you attempted to generate " ~
368                         "cryptographically secure random bytes from the csprng library, " ~
369                         "but none of the Windows cryptography functions could be loaded " ~
370                         "from advapi32.dll, which was the backup plan, since loading " ~
371                         "Bcrypt.dll failed. " ~
372                         "Ensure that either Bcrypt.dll or advapi32.dll is either in " ~
373                         "the executable's directory, current directory, the Windows " ~
374                         "directory, the System directory, or in one of the directories " ~
375                         "in the PATH environment variable. If you believe you have " ~
376                         "received this error by mistake, please report this as a bug " ~
377                         "to https://github.com/JonathanWilbur/csprng-d/issues. The " ~
378                         "Windows error code associated with this particular failure is " ~
379                         text(GetLastError()) ~ "."
380                     );
381         }
382 
383         /**
384             Upon deleting the $(D CSPRNG) object, the libraries used are unloaded,
385             and any relevant cryptographic constructs used by those libraries
386             are released.
387         */
388         public @system
389         ~this ()
390         {
391             scope(exit) this.openInstances--;
392             if (this.openInstances == 1u)
393             {
394                 if (this.isUsingCNGAPI)
395                 {
396                     extern (Windows) NTSTATUS function (BCRYPT_ALG_HANDLE hAlgorithm, ULONG dwFlags) BCryptCloseAlgorithmProvider =
397                         cast(NTSTATUS function (BCRYPT_ALG_HANDLE hAlgorithm, ULONG dwFlags)) this.bCryptCloseAlgorithmProviderAddress;
398                     BCryptCloseAlgorithmProvider(this.cngProviderHandle, 0u);
399                     FreeLibrary(this.bcrypt);
400                 }
401                 else if (this.isUsingCryptoAPI)
402                 {
403                     extern (Windows) BOOL function (HCRYPTPROV hProv, DWORD dwFlags) CryptReleaseContext =
404                         cast(BOOL function (HCRYPTPROV hProv, DWORD dwFlags)) this.cryptReleaseContextAddress;
405                     CryptReleaseContext(this.cryptographicServiceProviderHandle, 0u);
406                     FreeLibrary(this.advapi32);
407                 }
408                 else if (this.isUsingRtlGenRandom)
409                 {
410                     FreeLibrary(this.advapi32);
411                 }
412             }
413         }
414     }
415     else version (SecureARC4Random) // Not a built-in version flag. Defined above.
416     {
417         /**
418             Returns the specified number of cryptographically-secure
419             pseudo-random bytes, using one of the system APIs.
420         */
421         public @system
422         void[] getBytes (in size_t length)
423         {
424             void[] ret = new void[length];
425             arc4random_buf(ret.ptr, length);
426             return ret;
427         }
428     }
429     else version (Posix)
430     {
431         import std.stdio : File;
432 
433         /// The size of the buffer used to read from $(MONO /dev/random)
434         public static immutable size_t readBufferSize = 128u;
435         private static File randomFile;
436         private static size_t openInstances;
437 
438         /**
439             Returns the specified number of cryptographically-secure
440             pseudo-random bytes, using one of the system APIs.
441         */
442         public @system
443         void[] getBytes (in size_t length)
444         {
445             void[] ret = new void[length];
446             size_t pos = 0;
447             while (pos < length)
448             {
449                 size_t n = length - pos;
450                 if (n > this.readBufferSize)
451                     n = this.readBufferSize;
452                 pos += this.randomFile.rawRead(ret[pos .. pos + n]).length;
453             }
454             return ret;
455         }
456 
457         /**
458             The constructor for a CSPRNG. When a $(D CSPRNG) is created with
459             this constructor, the pseudo-device, $(MONO /dev/random) is opened
460             for reading (not writing). If it cannot be opened, the constructor
461             throws a $(D CSPRNGException) and the $(D CSPRNG) will not be created.
462         */
463         public @safe
464         this ()
465         {
466             scope(success) this.openInstances++;
467             if (!this.randomFile.isOpen())
468                 this.randomFile = File("/dev/random", "r");
469             if (this.randomFile.error())
470                 throw new CSPRNGException
471                 (
472                     "This exception was thrown because you attempted to generate " ~
473                     "cryptographically secure random bytes from the csprng library, " ~
474                     "but the CSPRNG file, /dev/random, could not be opened for " ~
475                     "reading. If you believe you have received this error by mistake, " ~
476                     "please report this as a bug to " ~
477                     "https://github.com/JonathanWilbur/csprng-d/issues."
478                 );
479         }
480 
481         /**
482             Upon deleting the $(D CSPRNG) object, the file descriptor for the
483             pseudo-device, $(MONO /dev/random) is closed. Note that it is closed
484             in such a way that should not cause problems if it is closed multiple
485             times.
486         */
487         public @safe
488         ~this ()
489         {
490             scope(exit) this.openInstances--;
491             if (this.openInstances == 1u)
492                 this.randomFile.detach();
493         }
494 
495         invariant
496         {
497             assert(this.openInstances > 0u);
498             assert(this.randomFile.isOpen());
499         }
500     }
501     else
502     {
503         static assert
504         (
505             0u,
506             "The csprng library cannot be compiled, because your operating system " ~
507             "is unsupported. The csprng library, currently, can only compile on " ~
508             "Windows, Mac OS X, Linux, and possibly Solaris and the BSDs."
509         );
510     }
511 
512     public @system
513     T get(T)()
514     if (isNumeric!(Unqual!T) || (isStaticArray!T && isNumeric!(Unqual!(ForeachType!T))))
515     {
516         ubyte[] ret = cast(ubyte[]) this.getBytes(T.sizeof);
517         return *(cast(T*) ret.ptr);
518     }
519 
520     @system
521     unittest
522     {
523         assert(__traits(compiles, (new CSPRNG()).get!byte()));
524         assert(__traits(compiles, (new CSPRNG()).get!ubyte()));
525         assert(__traits(compiles, (new CSPRNG()).get!short()));
526         assert(__traits(compiles, (new CSPRNG()).get!ushort()));
527         assert(__traits(compiles, (new CSPRNG()).get!int()));
528         assert(__traits(compiles, (new CSPRNG()).get!uint()));
529         assert(__traits(compiles, (new CSPRNG()).get!long()));
530         assert(__traits(compiles, (new CSPRNG()).get!ulong()));
531         assert(__traits(compiles, (new CSPRNG()).get!size_t()));
532         assert(__traits(compiles, (new CSPRNG()).get!ptrdiff_t()));
533         assert(__traits(compiles, (new CSPRNG()).get!float()));
534         assert(__traits(compiles, (new CSPRNG()).get!double()));
535         assert(__traits(compiles, (new CSPRNG()).get!real()));
536         assert(__traits(compiles, (new CSPRNG()).get!(ubyte[4])()));
537         assert(__traits(compiles, (new CSPRNG()).get!(float[6])()));
538     }
539 }
540 
541 /*
542     Test that one CSPRNG instance being destroyed doesn't adversely affect
543     other instances.
544 */
545 @system
546 unittest
547 {
548     CSPRNG csprng1 = new CSPRNG();
549     CSPRNG csprng2 = new CSPRNG();
550     csprng2.destroy();
551 
552     /*
553         If csprng.randomFile is closed here, the program will crash with a
554         segmentation fault. I don't know of a better way to test this.
555     */
556     ubyte[] bytes = cast(ubyte[]) csprng1.getBytes(16);
557 
558     // Ensure the CSPRNG did not just silently fail and output insufficient bytes.
559     assert(bytes.length == 16);
560 
561     // Ensure the output bytes are actually random, and not just null bytes.
562     bool anySetBits = false;
563     foreach (b; bytes)
564         if (b)
565         {
566             anySetBits = true;
567             break;
568         }
569     assert(anySetBits, "Either the buffer was not filled or an event of likelihood 2^^-128 occurred.");
570 }
571 
572 // Test that multi-threaded use of CSPRNG does not crash or cause errors.
573 @system
574 unittest
575 {
576     void multithreadedTest (in size_t threadsToUseInTest, in size_t bytesToAppendInEachThread)
577     {
578         import std.concurrency : spawn;
579         import std.algorithm.searching : all;
580         shared ubyte[] output = [];
581         shared bool[] threadsDone = [];
582         threadsDone.length = threadsToUseInTest;
583 
584         for (size_t i = 0u; i < threadsToUseInTest; i++)
585         {
586             spawn
587             (
588                 function void
589                 (
590                     size_t _threadIndex,
591                     shared bool[]* _threadsDone,
592                     shared ubyte[]* _output,
593                     size_t _bytesToAppendInEachThread,
594                 )
595                 {
596                     CSPRNG c = new CSPRNG();
597                     synchronized {
598                         *_output ~= cast(ubyte[]) c.getBytes(_bytesToAppendInEachThread);
599                         (*_threadsDone)[_threadIndex] = true;
600                     }
601 
602                     /* NOTE
603                         I tried to make this test destroy every other CSPRNG manually,
604                         but for some reason, that caused an InvalidMemoryOperationError.
605                         So for now, this has to destroy every CSPRNG.
606                     */
607                     // if (_threadIndex % 2u)
608                     c.destroy();
609                 },
610                 i, &threadsDone, &output, bytesToAppendInEachThread
611             );
612         }
613 
614         while (!all(threadsDone)) {}
615 
616         // Ensure the CSPRNG did not just silently fail and output insufficient bytes.
617         assert(output.length == (threadsToUseInTest * bytesToAppendInEachThread));
618 
619         // Ensure the output bytes are actually random, and not just null bytes.
620         bool anySetBits = false;
621         foreach (b; output)
622             if (b)
623             {
624                 anySetBits = true;
625                 break;
626             }
627         assert(anySetBits, "Buffer was not filled with random bytes!");
628 
629         /*
630             Ensure that there are not peculiar repeats of the same bytes.
631             This could--hypothetically--be a problem when multiple
632             threads are reading from the same single source of random
633             bytes.
634         */
635         if (bytesToAppendInEachThread > 4u)
636         {
637             import std.algorithm.searching : canFind;
638             for (int i = 0; i < (output.length - 4u); i++)
639             {
640                 assert(!canFind(output[i+1 .. $], output[i .. i+4]));
641             }
642         }
643     }
644 
645     version (SecureARC4Random)
646     {
647         multithreadedTest(10u, 5u); // Small number of threads, small reads
648         multithreadedTest(10u, 500u); // Small number of threads, large reads
649         multithreadedTest(100u, 5u); // Large number of threads, small reads
650     }
651     else version (Posix)
652     {
653         multithreadedTest(10u, CSPRNG.readBufferSize / 2u); // Reads smaller than readBufferSize
654         multithreadedTest(10u, CSPRNG.readBufferSize * 5u); // Reads larger than readBufferSize
655         multithreadedTest(100u, CSPRNG.readBufferSize / 2u); // Large number of threads, small reads
656     }
657     else
658     {
659         multithreadedTest(10u, 5u); // Small number of threads, small reads
660         multithreadedTest(10u, 500u); // Small number of threads, large reads
661         multithreadedTest(100u, 5u); // Large number of threads, small reads
662     }
663 }