| // Written in the D programming language. | |
| /+ | |
| CSPRNG library prototype. | |
| This code has not been audited. | |
| Do not use for cryptographic purposes. | |
| The terms $(I entropy) and $(I entropy sources) here do refer to | |
| cryptographically-safe random numbers and higher-level generators of such | |
| — typically powered by an entropy pool provided by the operating system. | |
| An example of similar usage of said terminology would be the `getentropy()` | |
| function provided by | |
| $(LINK2 https://man.freebsd.org/cgi/man.cgi?query=getentropy&apropos=0&sektion=3&manpath=FreeBSD+14.2-RELEASE&arch=default&format=html, | |
| FreeBSD). | |
| This library does not interact with any actual low-level entropy sources | |
| by itself. Instead it interfaces with system-provided CSPRNGs that are | |
| typically seeded through aforementioned entropy sources by the operating | |
| system as needed. | |
| See_also: | |
| $(LINK https://blog.cr.yp.to/20140205-entropy.html), | |
| $(LINK https://cr.yp.to/talks/2014.10.18/slides-djb-20141018-a4.pdf) | |
| License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| Authors: Elias Batek | |
| Source: $(PHOBOSSRC std/internal/entropy.d) | |
| +/ | |
| module std.internal.entropy; | |
| import std.meta; | |
| version (OSX) | |
| version = Darwin; | |
| else version (iOS) | |
| version = Darwin; | |
| else version (TVOS) | |
| version = Darwin; | |
| else version (WatchOS) | |
| version = Darwin; | |
| // Self-test: Detect potentially unsuitable default entropy source. | |
| @safe unittest | |
| { | |
| auto buffer = new ubyte[](32); | |
| forceEntropySource(defaultEntropySource); | |
| const result = getEntropy(buffer); | |
| assert( | |
| !result.isUnavailable, | |
| "The default entropy source for the target platform" | |
| ~ " is unavailable on this machine. Please consider" | |
| ~ " patching it to accommodate to your environment." | |
| ); | |
| assert(result.isOK); | |
| } | |
| // Self-test: Detect faulty implementation. | |
| @system unittest | |
| { | |
| forceEntropySource(defaultEntropySource); | |
| bool test() @system | |
| { | |
| static immutable pattern = 0xDEAD_BEEF_1337_0000; | |
| long number = pattern; | |
| const result = getEntropy(&number, number.sizeof); | |
| assert(result.isOK); | |
| return number != pattern; | |
| } | |
| size_t timesFailed = 0; | |
| foreach (n; 0 .. 3) | |
| if (!test()) | |
| ++timesFailed; | |
| assert( | |
| timesFailed <= 1, | |
| "Suspicious random data: Potential security issue or really unlucky; please retry." | |
| ); | |
| } | |
| // Self-test: Detect faulty implementation. | |
| @safe unittest | |
| { | |
| forceEntropySource(defaultEntropySource); | |
| bool test() @safe | |
| { | |
| ubyte[32] data; | |
| data[] = 0; | |
| const result = getEntropy(data[]); | |
| assert(result.isOK); | |
| size_t zeros = 0; | |
| foreach (b; data) | |
| if (b == 0) | |
| ++zeros; | |
| enum threshold = 24; | |
| return zeros < threshold; | |
| } | |
| size_t timesFailed = 0; | |
| foreach (n; 0 .. 3) | |
| if (!test()) | |
| ++timesFailed; | |
| assert( | |
| timesFailed <= 1, | |
| "Suspicious random data: Potential security issue or really unlucky; please retry." | |
| ); | |
| } | |
| @nogc nothrow: | |
| // Flagship function | |
| /++ | |
| Retrieves random data from an applicable system CSPRNG. | |
| Params: | |
| buffer = An output buffer to store the retrieved entropy in. | |
| The length of it will determine the amount of random data to | |
| be obtained. | |
| This function (and all overloads) always attempt to fill | |
| the entire buffer. Therefore, they can block, spin or report | |
| an error. | |
| Returns: | |
| An `EntropyResult` that either reports success | |
| or the type of error that has occurred. | |
| In case of an error, the data in `buffer` MUST NOT be used. | |
| The recommended way to check for success is through the `isOK()` | |
| helper function. | |
| +/ | |
| EntropyResult getEntropy(scope void[] buffer) @safe | |
| { | |
| return getEntropyImpl(buffer); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| int[4] bytes; | |
| if (getEntropy(cast(void[]) bytes).isOK) | |
| { | |
| // Success; data in `bytes` may be used. | |
| } | |
| assert((cast(void[]) bytes).length == bytes.sizeof); | |
| } | |
| // Convenience overload | |
| /// ditto | |
| EntropyResult getEntropy(scope ubyte[] buffer) @safe | |
| { | |
| return getEntropy(cast(void[]) buffer); | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| ubyte[16] bytes; | |
| if (getEntropy(bytes).isOK) | |
| { | |
| // Success; data in `bytes` may be used. | |
| } | |
| } | |
| // Convenience wrapper | |
| /// ditto | |
| /++ | |
| Retrieves random data from an applicable system CSPRNG. | |
| Params: | |
| buffer = An output buffer to store the retrieved entropy in. | |
| length = Length of the provided `buffer`. | |
| Specifying a wrong value here, will lead to memory corruption. | |
| Returns: | |
| An `EntropyResult` that either reports success | |
| or the type of error that has occurred. | |
| In case of an error, the data in `buffer` MUST NOT be used. | |
| The recommended way to check for success is through the `isOK()` | |
| helper function. | |
| +/ | |
| EntropyResult getEntropy(scope void* buffer, size_t length) @system | |
| { | |
| return getEntropy(buffer[0 .. length]); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| ubyte[16] bytes; | |
| if (getEntropy(cast(void*) bytes.ptr, bytes.length).isOK) | |
| { | |
| // Success; data in `bytes` may be used. | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| int number = void; | |
| if (getEntropy(&number, number.sizeof).isOK) | |
| { | |
| // Success; value of `number` may be used. | |
| } | |
| } | |
| /++ | |
| Manually set the entropy source to use for the current thread. | |
| As a rule of thumb, this SHOULD NOT be done. | |
| It might be useful in cases where the default entropy source — as chosen by | |
| the maintainer of the used compiler package — is unavailable on a system. | |
| Usually, `EntropySource.tryAll` will be the most reasonable option | |
| in such cases. | |
| Params: | |
| source = The requested default entropy source to use for the current thread. | |
| Examples: | |
| --- | |
| // Using `forceEntropySource` almost always is a bad idea. | |
| // As a rule of thumb, this SHOULD NOT be done. | |
| forceEntropySource(EntropySource.none); | |
| --- | |
| +/ | |
| void forceEntropySource(EntropySource source) @safe | |
| { | |
| _entropySource = source; | |
| } | |
| // (In-)Convenience wrapper | |
| /++ | |
| Retrieves random data from the requested entropy source. | |
| In general, it’s a $(B bad idea) to let users pick sources themselves. | |
| A sane option should be used by default instead. | |
| This overload only exists because its used by Phobos. | |
| See_also: | |
| Use `forceEntropySource` instead. | |
| Params: | |
| buffer = An output buffer to store the retrieved entropy in. | |
| The length of it will determine the amount of entropy to be | |
| obtained. | |
| length = Length of the provided `buffer`. | |
| Specifying a wrong value here, will lead to memory corruption. | |
| source = The entropy source to use for the operation. | |
| Returns: | |
| An `EntropyResult` that either reports success | |
| or the type of error that has occurred. | |
| In case of an error, the data in `buffer` MUST NOT be used. | |
| The recommended way to check for success is through the `isOK()` | |
| helper function. | |
| +/ | |
| EntropyResult getEntropy(scope void* buffer, size_t length, EntropySource source) @system | |
| { | |
| const sourcePrevious = _entropySource; | |
| scope (exit) _entropySource = sourcePrevious; | |
| _entropySource = source; | |
| return getEntropy(buffer[0 .. length]); | |
| } | |
| /// | |
| @system unittest | |
| { | |
| ubyte[4] bytes; | |
| // `EntropySource.none` always fails. | |
| assert(!getEntropy(bytes.ptr, bytes.length, EntropySource.none).isOK); | |
| } | |
| /++ | |
| A CSPRNG suitable to retrieve cryptographically-secure random data from. | |
| (No actual low-level entropy sources are provided on purpose.) | |
| +/ | |
| enum EntropySource | |
| { | |
| /// Implements a $(I hunting) strategy for finding an entropy source that | |
| /// is available at runtime. | |
| /// | |
| /// Try supported sources one-by-one until one is available. | |
| /// This exists to enable the use of this the entropy library | |
| /// in a backwards compatibility way. | |
| /// | |
| /// It is recommended against using this in places that do not strictly | |
| /// have to to meet compatibility requirements. | |
| /// Like any kind of crypto-agility, this approach may suffer from | |
| /// practical issues. | |
| /// | |
| /// See_also: | |
| /// While the following article focuses on cipher agility in protocols, | |
| /// it elaborates why agility can lead to problems: | |
| /// $(LINK https://web.archive.org/web/20191102211148/https://paragonie.com/blog/2019/10/against-agility-in-cryptography-protocols) | |
| tryAll = -1, | |
| /// Always fail. | |
| none = 0, | |
| /// `/dev/urandom` | |
| charDevURandom = 1, | |
| /// `/dev/random` | |
| charDevRandom = 2, | |
| /// `getrandom` syscall or wrapper | |
| getrandom = 3, | |
| /// `arc4random` | |
| arc4random = 4, | |
| // `getentropy` | |
| getentropy = 5, | |
| /// Windows legacy CryptoAPI | |
| cryptGenRandom = 6, | |
| /// Windows Cryptography API: Next Generation (“BCrypt”) | |
| bcryptGenRandom = 7, | |
| } | |
| /// | |
| enum EntropyStatus | |
| { | |
| /// success | |
| ok = 0, | |
| /// catch-all error | |
| unknownError = 1, | |
| /// An entropy source was unavailable. | |
| unavailable, | |
| /// A dependency providing the entropy source turned out unavailable. | |
| unavailableLibrary, | |
| /// The requested entropy source is not supported on this platform. | |
| unavailablePlatform, | |
| /// Could not retrieve entropy from the selected source. | |
| readError, | |
| } | |
| /++ | |
| Status report returned by `getEntropy` functions. | |
| Use the `isOK` helper function to test for success. | |
| +/ | |
| struct EntropyResult | |
| { | |
| /// | |
| EntropyStatus status; | |
| /// | |
| EntropySource source; | |
| /++ | |
| Returns: | |
| A human-readable status message. | |
| +/ | |
| string toString() const @nogc nothrow pure @safe | |
| { | |
| if (status == EntropyStatus.ok) | |
| return "getEntropy(): OK."; | |
| if (source == EntropySource.none) | |
| { | |
| if (status == EntropyStatus.unavailable) | |
| return "getEntropy(): Error - No suitable entropy source was available."; | |
| } | |
| else if (source == EntropySource.getrandom) | |
| { | |
| if (status == EntropyStatus.unavailableLibrary) | |
| return "getEntropy(): `dlopen(\"libc\")` failed."; | |
| if (status == EntropyStatus.unavailable) | |
| return "getEntropy(): `dlsym(\"libc\", \"getrandom\")` failed."; | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): `getrandom()` failed."; | |
| } | |
| else if (source == EntropySource.getentropy) | |
| { | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): `getentropy()` failed."; | |
| } | |
| else if (source == EntropySource.charDevURandom) | |
| { | |
| if (status == EntropyStatus.unavailable) | |
| return "getEntropy(): `/dev/urandom` is unavailable."; | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): Reading from `/dev/urandom` failed."; | |
| } | |
| else if (source == EntropySource.charDevURandom) | |
| { | |
| if (status == EntropyStatus.unavailable) | |
| return "getEntropy(): `/dev/random` is unavailable."; | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): Reading from `/dev/random` failed."; | |
| } | |
| else if (source == EntropySource.bcryptGenRandom) | |
| { | |
| if (status == EntropyStatus.unavailableLibrary) | |
| return "getEntropy(): `LoadLibraryA(\"Bcrypt.dll\")` failed."; | |
| if (status == EntropyStatus.unavailable) | |
| return "getEntropy(): `GetProcAddress(hBcrypt , \"BCryptGenRandom\")` failed."; | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): `BCryptGenRandom()` failed."; | |
| } | |
| // generic errors | |
| { | |
| if (status == EntropyStatus.unavailable || | |
| status == EntropyStatus.unavailableLibrary) | |
| return "getEntropy(): An entropy source was unavailable."; | |
| if (status == EntropyStatus.unavailablePlatform) | |
| return "getEntropy(): The requested entropy source is not supported on this platform."; | |
| if (status == EntropyStatus.readError) | |
| return "getEntropy(): Could not retrieve entropy from the selected source."; | |
| return "getEntropy(): An unknown error occurred."; | |
| } | |
| } | |
| } | |
| /// | |
| @safe unittest | |
| { | |
| ubyte[4] data; | |
| EntropyResult result = getEntropy(data[]); | |
| if (result.isOK) | |
| { | |
| // Success; data in `bytes` may be used. | |
| } | |
| else | |
| { | |
| // Failure | |
| if (result.isUnavailable) | |
| { | |
| // System’s entropy source was unavailable. | |
| } | |
| // Call `toString` to obtain a user-readable error message. | |
| assert(result.toString() !is null); | |
| assert(result.toString().length > 0); | |
| } | |
| } | |
| /++ | |
| Determines whether an `EntropyResult` reports the success of an operation. | |
| Params: | |
| value = test subject | |
| Returns: | |
| `true` on success | |
| +/ | |
| pragma(inline, true) bool isOK(const EntropyResult value) pure @safe | |
| { | |
| return value.status == EntropyStatus.ok; | |
| } | |
| /++ | |
| Determines whether an `EntropyResult` reports the unvailability of the | |
| requested entropy source. | |
| Params: | |
| value = test subject | |
| Returns: | |
| `true` if entropy source requested to use with the operation was unavailable. | |
| +/ | |
| pragma(inline, true) bool isUnavailable(const EntropyResult value) pure @safe | |
| { | |
| return ( | |
| value.status == EntropyStatus.unavailable || | |
| value.status == EntropyStatus.unavailableLibrary || | |
| value.status == EntropyStatus.unavailablePlatform | |
| ); | |
| } | |
| package(std): | |
| // If the system let us down, we'll let the system down. | |
| pragma(inline, true) void crashOnError(const EntropyResult value) pure @safe | |
| { | |
| if (value.isOK) | |
| return; | |
| assert(false, value.toString()); | |
| } | |
| /+ | |
| Building blocks and implementation helpers | |
| +/ | |
| private | |
| { | |
| /++ | |
| A “Chunks” implementation that works with `void[]`. | |
| +/ | |
| struct VoidChunks | |
| { | |
| void[] _data; | |
| size_t _chunkSize; | |
| @nogc nothrow pure @safe: | |
| this(void[] data, size_t chunkSize) | |
| { | |
| _data = data; | |
| _chunkSize = chunkSize; | |
| } | |
| bool empty() const | |
| { | |
| return _data.length == 0; | |
| } | |
| inout(void)[] front() inout | |
| { | |
| if (_data.length < _chunkSize) | |
| return _data; | |
| return _data[0 .. _chunkSize]; | |
| } | |
| void popFront() | |
| { | |
| if (_data.length <= _chunkSize) | |
| { | |
| _data = null; | |
| return; | |
| } | |
| _data = _data[_chunkSize .. $]; | |
| } | |
| } | |
| struct SrcFunPair(EntropySource source, alias func) | |
| { | |
| enum src = source; | |
| alias fun = func; | |
| } | |
| template isValidSupportedSource(SupportedSource) | |
| { | |
| enum isValidSupportedSource = ( | |
| is(SupportedSource == SrcFunPair!Args, Args...) && | |
| SupportedSource.src != EntropySource.tryAll && | |
| SupportedSource.src != EntropySource.none | |
| ); | |
| } | |
| /++ | |
| `getEntropyImpl()` implementation helper. | |
| To be instantiated and mixed in with platform-specific configuration. | |
| Params: | |
| defaultSource = Default entropy source of the platform | |
| SupportedSources = Sequence of `SrcFunPair` | |
| representing the supported sources of the platform | |
| +/ | |
| mixin template entropyImpl(EntropySource defaultSource, SupportedSources...) | |
| if (allSatisfy!(isValidSupportedSource, SupportedSources)) | |
| { | |
| private: | |
| /// Preconfigured entropy source preset of the platform. | |
| enum defaultEntropySource = defaultSource; | |
| EntropyResult getEntropyImpl(scope void[] buffer) @safe | |
| { | |
| switch (_entropySource) | |
| { | |
| static foreach (source; SupportedSources) | |
| { | |
| case source.src: | |
| return source.fun(buffer); | |
| } | |
| case EntropySource.tryAll: | |
| { | |
| const result = _tryEntropySources(buffer); | |
| result.saveSourceForNextUse(); | |
| return result; | |
| } | |
| case EntropySource.none: | |
| return getEntropyViaNone(buffer); | |
| default: | |
| return EntropyResult(EntropyStatus.unavailablePlatform, _entropySource); | |
| } | |
| } | |
| EntropyResult _tryEntropySources(scope void[] buffer) @safe | |
| { | |
| EntropyResult result; | |
| static foreach (source; SupportedSources) | |
| { | |
| result = source.fun(buffer); | |
| if (!result.isUnavailable) | |
| return result; | |
| } | |
| result = EntropyResult( | |
| EntropyStatus.unavailable, | |
| EntropySource.none, | |
| ); | |
| return result; | |
| } | |
| } | |
| } | |
| version (Darwin) mixin entropyImpl!( | |
| EntropySource.arc4random, | |
| SrcFunPair!(EntropySource.arc4random, getEntropyViaARC4Random), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (DragonFlyBSD) mixin entropyImpl!( | |
| EntropySource.getentropy, | |
| SrcFunPair!(EntropySource.getentropy, getEntropyViaGetentropy), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (FreeBSD) mixin entropyImpl!( | |
| EntropySource.getentropy, | |
| SrcFunPair!(EntropySource.getentropy, getEntropyViaGetentropy), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (linux) mixin entropyImpl!( | |
| EntropySource.getrandom, | |
| SrcFunPair!(EntropySource.getrandom, getEntropyViaGetrandom), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (NetBSD) mixin entropyImpl!( | |
| EntropySource.arc4random, | |
| SrcFunPair!(EntropySource.arc4random, getEntropyViaARC4Random), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (OpenBSD) mixin entropyImpl!( | |
| EntropySource.arc4random, | |
| SrcFunPair!(EntropySource.arc4random, getEntropyViaARC4Random), | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (Posix) mixin entropyImpl!( | |
| EntropySource.charDevURandom, | |
| SrcFunPair!(EntropySource.charDevURandom, getEntropyViaCharDevURandom), | |
| SrcFunPair!(EntropySource.charDevRandom, getEntropyViaCharDevRandom), | |
| ); | |
| else version (Windows) mixin entropyImpl!( | |
| EntropySource.bcryptGenRandom, | |
| SrcFunPair!(EntropySource.bcryptGenRandom, getEntropyViaBCryptGenRandom), | |
| ); | |
| else mixin entropyImpl!( | |
| EntropySource.none, | |
| ); | |
| private | |
| { | |
| static EntropySource _entropySource = defaultEntropySource; | |
| void saveSourceForNextUse(const EntropyResult result) @safe | |
| { | |
| if (!result.isOK) | |
| return; | |
| _entropySource = result.source; | |
| } | |
| } | |
| version (all) | |
| { | |
| private: | |
| EntropyResult getEntropyViaNone(scope void[]) @safe | |
| { | |
| return EntropyResult(EntropyStatus.unavailable, EntropySource.none); | |
| } | |
| } | |
| version (Posix) | |
| { | |
| private: | |
| EntropyResult getEntropyViaCharDevURandom(scope void[] buffer) @trusted | |
| { | |
| const status = getEntropyViaCharDev(buffer, "/dev/urandom".ptr); | |
| return EntropyResult(status, EntropySource.charDevURandom); | |
| } | |
| EntropyResult getEntropyViaCharDevRandom(scope void[] buffer) @trusted | |
| { | |
| const status = getEntropyViaCharDev(buffer, "/dev/random".ptr); | |
| return EntropyResult(status, EntropySource.charDevRandom); | |
| } | |
| EntropyStatus getEntropyViaCharDev(scope void[] buffer, const(char)* charDevName) @system | |
| { | |
| import core.stdc.stdio : fclose, fopen, fread; | |
| auto charDev = fopen(charDevName, "r"); | |
| if (charDev is null) | |
| return EntropyStatus.unavailable; | |
| scope (exit) | |
| fclose(charDev); | |
| const bytesRead = fread(buffer.ptr, 1, buffer.length, charDev); | |
| if (bytesRead != buffer.length) | |
| return EntropyStatus.readError; | |
| return EntropyStatus.ok; | |
| } | |
| } | |
| version (linux) | |
| { | |
| private: | |
| EntropyResult getEntropyViaGetrandom(scope void[] buffer) @trusted | |
| { | |
| const status = syscallGetrandom(buffer, 0); | |
| return EntropyResult(status, EntropySource.getrandom); | |
| } | |
| EntropyStatus syscallGetrandom(scope void[] buffer, uint flags) @system | |
| { | |
| import core.sys.linux.errno : EINTR, ENOSYS, errno; | |
| import core.sys.linux.sys.syscall : SYS_getrandom; | |
| import core.sys.linux.unistd : syscall; | |
| while (buffer.length > 0) | |
| { | |
| const got = syscall(SYS_getrandom, buffer.ptr, buffer.length, flags); | |
| if (got == -1) | |
| { | |
| switch (errno) | |
| { | |
| case EINTR: | |
| break; // That’s fine. | |
| case ENOSYS: | |
| return EntropyStatus.unavailable; | |
| default: | |
| return EntropyStatus.readError; | |
| } | |
| } | |
| if (got > 0) | |
| buffer = buffer[got .. $]; | |
| } | |
| return EntropyStatus.ok; | |
| } | |
| } | |
| // BSD | |
| private | |
| { | |
| version (Darwin) | |
| version = SecureARC4Random; | |
| version (DragonFlyBSD) | |
| version = UseGetentropy; | |
| version (FreeBSD) | |
| version = UseGetentropy; | |
| version (NetBSD) | |
| version = SecureARC4Random; | |
| version (OpenBSD) | |
| version = SecureARC4Random; | |
| version (SecureARC4Random) | |
| { | |
| EntropyResult getEntropyViaARC4Random(scope void[] buffer) @trusted | |
| { | |
| arc4random_buf(buffer.ptr, buffer.length); | |
| return EntropyResult(EntropyStatus.ok, EntropySource.arc4random); | |
| } | |
| private extern(C) void arc4random_buf(scope void* buf, size_t nbytes) @system; | |
| } | |
| version (UseGetentropy) | |
| { | |
| EntropyResult getEntropyViaGetentropy(scope void[] buffer) @trusted | |
| { | |
| const status = callGetentropy(buffer); | |
| return EntropyResult(status, EntropySource.getentropy); | |
| } | |
| private EntropyStatus callGetentropy(scope void[] buffer) @system | |
| { | |
| /+ | |
| genentropy(3): | |
| The maximum buflen permitted is 256 bytes. | |
| +/ | |
| foreach (chunk; VoidChunks(buffer, 256)) | |
| { | |
| const status = getentropy(buffer.ptr, buffer.length); | |
| if (status != 0) | |
| return EntropyStatus.readError; | |
| } | |
| return EntropyStatus.ok; | |
| } | |
| private extern(C) int getentropy(scope void* buf, size_t buflen) @system; | |
| } | |
| } | |
| version (Windows) | |
| { | |
| import core.sys.windows.bcrypt : BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG; | |
| import core.sys.windows.windef : HMODULE, PUCHAR, ULONG; | |
| import core.sys.windows.ntdef : NT_SUCCESS; | |
| private: | |
| EntropyResult getEntropyViaBCryptGenRandom(scope void[] buffer) @trusted | |
| { | |
| const loaded = loadBcrypt(); | |
| if (loaded != EntropyStatus.ok) | |
| return EntropyResult(loaded, EntropySource.bcryptGenRandom); | |
| const status = callBcryptGenRandom(buffer); | |
| return EntropyResult(status, EntropySource.bcryptGenRandom); | |
| } | |
| EntropyStatus callBcryptGenRandom(scope void[] buffer) @system | |
| { | |
| foreach (chunk; VoidChunks(buffer, ULONG.max)) | |
| { | |
| assert(chunk.length <= ULONG.max, "Bad chunk length."); | |
| const gotRandom = ptrBCryptGenRandom( | |
| null, | |
| cast(PUCHAR) buffer.ptr, | |
| cast(ULONG) buffer.length, | |
| BCRYPT_USE_SYSTEM_PREFERRED_RNG, | |
| ); | |
| if (!NT_SUCCESS(gotRandom)) | |
| return EntropyStatus.readError; | |
| } | |
| return EntropyStatus.ok; | |
| } | |
| static | |
| { | |
| HMODULE hBcrypt = null; | |
| typeof(BCryptGenRandom)* ptrBCryptGenRandom; | |
| } | |
| EntropyStatus loadBcrypt() @system | |
| { | |
| import core.sys.windows.winbase : GetProcAddress, LoadLibraryA; | |
| if (hBcrypt !is null) | |
| return EntropyStatus.ok; | |
| hBcrypt = LoadLibraryA("Bcrypt.dll"); | |
| if (!hBcrypt) | |
| return EntropyStatus.unavailableLibrary; | |
| ptrBCryptGenRandom = cast(typeof(ptrBCryptGenRandom)) GetProcAddress(hBcrypt, "BCryptGenRandom"); | |
| if (!ptrBCryptGenRandom) | |
| return EntropyStatus.unavailable; | |
| return EntropyStatus.ok; | |
| } | |
| // Will free `Bcrypt.dll`. | |
| void freeBcrypt() @system | |
| { | |
| import core.sys.windows.winbase : FreeLibrary; | |
| if (hBcrypt is null) | |
| return; | |
| if (!FreeLibrary(hBcrypt)) | |
| { | |
| return; // Error | |
| } | |
| hBcrypt = null; | |
| ptrBCryptGenRandom = null; | |
| } | |
| static ~this() @system | |
| { | |
| freeBcrypt(); | |
| } | |
| } |