327 lines
9.3 KiB
D
327 lines
9.3 KiB
D
/* 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-2019.
|
|
* 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 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.
|
|
*/
|
|
Option!ubyte poll(out ubyte[maxGather] output) @nogc
|
|
out (length; length.isNothing || length.get <= maxGather);
|
|
}
|
|
|
|
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 Option!ubyte poll(out ubyte[maxGather] output) @nogc nothrow
|
|
{
|
|
// 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);
|
|
Option!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 Option!ubyte poll(out ubyte[maxGather] output)
|
|
@nogc nothrow @safe
|
|
{
|
|
(() @trusted => arc4random_buf(output.ptr, output.length))();
|
|
return Option!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 Option!ubyte poll(out ubyte[maxGather] output)
|
|
@nogc nothrow @safe
|
|
{
|
|
Option!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;
|
|
}
|
|
}
|
|
}
|