From 9876d9245c76056e190a12c9ed471f047e57a37e Mon Sep 17 00:00:00 2001 From: Nathan Sashihara <21227491+n8sh@users.noreply.github.com> Date: Mon, 19 Feb 2018 09:21:53 -0500 Subject: [PATCH] Implement PlatformEntropySource for macOS, Microsoft Windows, NetBSD, OpenBSD, Solaris --- dub.json | 3 +- source/tanya/math/random.d | 194 ++++++++++++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 5 deletions(-) diff --git a/dub.json b/dub.json index d3cdf03..f60f05b 100644 --- a/dub.json +++ b/dub.json @@ -28,5 +28,6 @@ "lflags": ["arch/tanya.a"], "versions": ["TanyaNative"] } - ] + ], + "libs-windows": ["advapi32"] } diff --git a/source/tanya/math/random.d b/source/tanya/math/random.d index 2275d98..6978f8d 100644 --- a/source/tanya/math/random.d +++ b/source/tanya/math/random.d @@ -94,6 +94,27 @@ abstract class EntropySource Nullable!ubyte poll(out ubyte[maxGather] output) @nogc; } +version (CRuntime_Bionic) +{ + version = SecureARC4Random; +} +else version (OSX) +{ + version = SecureARC4Random; +} +else version (OpenBSD) +{ + version = SecureARC4Random; +} +else version (NetBSD) +{ + version = SecureARC4Random; +} +else version (Solaris) +{ + version = SecureARC4Random; +} + version (linux) { extern (C) long syscall(long number, ...) nothrow @system @nogc; @@ -160,6 +181,173 @@ version (linux) } } } +else version (SecureARC4Random) +{ + private extern (C) void arc4random_buf(scope void* buf, size_t nbytes) nothrow @nogc @system; + + /** + * Uses arc4random_buf. + */ + class PlatformEntropySource : EntropySource + { + /** + * Returns: Minimum bytes required from the entropy source. + */ + override @property ubyte threshold() const pure nothrow @safe @nogc + { + return 32; + } + + /** + * Returns: Whether this entropy source is strong. + */ + override @property bool strong() const pure nothrow @safe @nogc + { + return true; + } + + /** + * Poll the entropy source. + * + * Params: + * output = Buffer to save the generate random sequence (the method will + * to fill the buffer). + * + * Returns: Number of bytes that were copied to the $(D_PARAM output) + * or $(D_PSYMBOL Nullable!ubyte.init) on error. + */ + override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow @nogc @safe + { + (() @trusted => arc4random_buf(output.ptr, output.length))(); + return Nullable!ubyte(cast(ubyte) (output.length)); + } + } + + @nogc @system unittest + { + auto entropy = defaultAllocator.make!Entropy(); + ubyte[blockSize] output; + output = entropy.random; + + defaultAllocator.dispose(entropy); + } +} +else version (Windows) +{ + import core.sys.windows.basetsd : ULONG_PTR; + import core.sys.windows.windef : BOOL, DWORD, PBYTE; + import core.sys.windows.winnt : LPCSTR, LPCWSTR; + import core.sys.windows.wincrypt; + private extern(Windows) @nogc nothrow + { + BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE); + BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD); + BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD); + BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR); + } + + private bool initCryptGenRandom(scope ref HCRYPTPROV hProvider) @nogc nothrow @trusted + { + import core.sys.windows.winbase : GetLastError; + import core.sys.windows.winerror : NTE_BAD_KEYSET; + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379886(v=vs.85).aspx + // For performance reasons, we recommend that you set the pszContainer + // parameter to NULL and the dwFlags parameter to CRYPT_VERIFYCONTEXT + // in all situations where you do not require a persisted key. + // CRYPT_SILENT is intended for use with applications for which the UI cannot be displayed by the CSP. + if (!CryptAcquireContextW(&hProvider, null, null, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + if (GetLastError() == NTE_BAD_KEYSET) + { + // Attempt to create default container + if (!CryptAcquireContextA(&hProvider, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_SILENT)) + return false; + } + else + { + return false; + } + } + + return true; + } + + class PlatformEntropySource : EntropySource + { + private HCRYPTPROV hProvider; + + /** + * Uses CryptGenRandom. + */ + this() @nogc + { + if (!initCryptGenRandom(hProvider)) + { + throw defaultAllocator.make!EntropyException("CryptAcquireContextW failed."); + } + assert(hProvider > 0, "hProvider not properly initialized."); + } + + ~this() @nogc nothrow @safe + { + if (hProvider > 0) + { + (() @trusted => CryptReleaseContext(hProvider, 0))(); + } + } + + /** + * Returns: Minimum bytes required from the entropy source. + */ + override @property ubyte threshold() const pure nothrow @safe @nogc + { + return 32; + } + + /** + * Returns: Whether this entropy source is strong. + */ + override @property bool strong() const pure nothrow @safe @nogc + { + return true; + } + + /** + * Poll the entropy source. + * + * Params: + * output = Buffer to save the generate random sequence (the method will + * to fill the buffer). + * + * Returns: Number of bytes that were copied to the $(D_PARAM output) + * or $(D_PSYMBOL Nullable!ubyte.init) on error. + */ + override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow @safe + in + { + assert(hProvider > 0, "hProvider not properly initialized."); + } + do + { + Nullable!ubyte ret; + if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))()) + { + ret = cast(ubyte) (output.length); + } + return ret; + } + } + + @nogc @system unittest + { + auto entropy = defaultAllocator.make!Entropy(); + ubyte[blockSize] output; + output = entropy.random; + + defaultAllocator.dispose(entropy); + } +} /** * Pseudorandom number generator. @@ -180,8 +368,6 @@ class Entropy private ubyte sourceCount_; - private shared Allocator allocator; - /// Entropy accumulator. protected SHA!(maxGather * 8, 512) accumulator; @@ -202,7 +388,7 @@ class Entropy { allocator.resize(sources, maxSources); - version (linux) + static if (is(PlatformEntropySource)) { this ~= allocator.make!PlatformEntropySource; } @@ -289,7 +475,7 @@ class Entropy if (!haveStrong) { - throw allocator.make!EntropyException("No strong entropy source defined."); + throw defaultAllocator.make!EntropyException("No strong entropy source defined."); } output = accumulator.finish();