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 }