/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Random number generator. * * Copyright: Eugene Wissner 2016-2020. * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * Mozilla Public License, v. 2.0). * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/random.d, * tanya/math/random.d) */ module tanya.math.random; import std.typecons; import tanya.memory.allocator; import tanya.typecons; /// Maximum amount gathered from the entropy sources. enum maxGather = 128; /** * Exception thrown if random number generating fails. */ class EntropyException : Exception { /** * Params: * msg = Message to output. * file = The file where the exception occurred. * line = The line number where the exception occurred. * next = The previous exception in the chain of exceptions, if any. */ this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) const @nogc nothrow pure @safe { super(msg, file, line, next); } } /** * Interface for implementing entropy sources. */ abstract class EntropySource { /// Amount of already generated entropy. protected ushort size_; /** * Returns: Minimum bytes required from the entropy source. */ @property ubyte threshold() const @nogc nothrow pure @safe; /** * Returns: Whether this entropy source is strong. */ @property bool strong() const @nogc nothrow pure @safe; /** * Returns: Amount of already generated entropy. */ @property ushort size() const @nogc nothrow pure @safe { return size_; } /** * Params: * size = Amount of already generated entropy. Cannot be smaller than the * already set value. */ @property void size(ushort size) @nogc nothrow pure @safe { size_ = size; } /** * 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 nothing on error. * * Postcondition: Returned length is less than or equal to * $(D_PARAM output) length. */ 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) { import core.stdc.config : c_long; private extern(C) c_long syscall(c_long number, ...) @nogc nothrow @system; /** * Uses getrandom system call. */ class PlatformEntropySource : EntropySource { /** * Returns: Minimum bytes required from the entropy source. */ override @property ubyte threshold() const @nogc nothrow pure @safe { return 32; } /** * Returns: Whether this entropy source is strong. */ override @property bool strong() const @nogc nothrow pure @safe { 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 nothing on error. */ override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow out (length) { assert(length.isNull || length.get <= maxGather); } do { // int getrandom(void *buf, size_t buflen, unsigned int flags); import mir.linux._asm.unistd : NR_getrandom; auto length = syscall(NR_getrandom, output.ptr, output.length, 0); Nullable!ubyte ret; if (length >= 0) { ret = cast(ubyte) length; } return ret; } } } else version (SecureARC4Random) { private extern(C) void arc4random_buf(scope void* buf, size_t nbytes) @nogc nothrow @system; /** * Uses arc4random_buf. */ class PlatformEntropySource : EntropySource { /** * Returns: Minimum bytes required from the entropy source. */ override @property ubyte threshold() const @nogc nothrow pure @safe { return 32; } /** * Returns: Whether this entropy source is strong. */ override @property bool strong() const @nogc nothrow pure @safe { 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 nothing on error. */ override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow @safe out (length) { assert(length.isNull || length.get <= maxGather); } do { (() @trusted => arc4random_buf(output.ptr, output.length))(); return Nullable!ubyte(cast(ubyte) (output.length)); } } } else version (Windows) { import core.sys.windows.basetsd : ULONG_PTR; import core.sys.windows.winbase : GetLastError; import core.sys.windows.wincrypt; import core.sys.windows.windef : BOOL, DWORD, PBYTE; import core.sys.windows.winerror : NTE_BAD_KEYSET; import core.sys.windows.winnt : LPCSTR, LPCWSTR; 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 { // 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) { return false; } // Attempt to create default container if (!CryptAcquireContextA(&hProvider, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_SILENT)) { 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 @nogc nothrow pure @safe { return 32; } /** * Returns: Whether this entropy source is strong. */ override @property bool strong() const @nogc nothrow pure @safe { 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 nothing on error. */ override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow @safe out (length) { assert(length.isNull || length.get <= maxGather); } do { Nullable!ubyte ret; assert(hProvider > 0, "hProvider not properly initialized"); if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))()) { ret = cast(ubyte) (output.length); } return ret; } } }