1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Random number generator. 7 * 8 * Copyright: Eugene Wissner 2016-2020. 9 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 10 * Mozilla Public License, v. 2.0). 11 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 12 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/random.d, 13 * tanya/math/random.d) 14 */ 15 module tanya.math.random; 16 17 import std.typecons; 18 import tanya.memory.allocator; 19 import tanya.typecons; 20 21 /// Maximum amount gathered from the entropy sources. 22 enum maxGather = 128; 23 24 /** 25 * Exception thrown if random number generating fails. 26 */ 27 class EntropyException : Exception 28 { 29 /** 30 * Params: 31 * msg = Message to output. 32 * file = The file where the exception occurred. 33 * line = The line number where the exception occurred. 34 * next = The previous exception in the chain of exceptions, if any. 35 */ 36 this(string msg, 37 string file = __FILE__, 38 size_t line = __LINE__, 39 Throwable next = null) const @nogc nothrow pure @safe 40 { 41 super(msg, file, line, next); 42 } 43 } 44 45 /** 46 * Interface for implementing entropy sources. 47 */ 48 abstract class EntropySource 49 { 50 /// Amount of already generated entropy. 51 protected ushort size_; 52 53 /** 54 * Returns: Minimum bytes required from the entropy source. 55 */ 56 @property ubyte threshold() const @nogc nothrow pure @safe; 57 58 /** 59 * Returns: Whether this entropy source is strong. 60 */ 61 @property bool strong() const @nogc nothrow pure @safe; 62 63 /** 64 * Returns: Amount of already generated entropy. 65 */ 66 @property ushort size() const @nogc nothrow pure @safe 67 { 68 return size_; 69 } 70 71 /** 72 * Params: 73 * size = Amount of already generated entropy. Cannot be smaller than the 74 * already set value. 75 */ 76 @property void size(ushort size) @nogc nothrow pure @safe 77 { 78 size_ = size; 79 } 80 81 /** 82 * Poll the entropy source. 83 * 84 * Params: 85 * output = Buffer to save the generate random sequence (the method will 86 * to fill the buffer). 87 * 88 * Returns: Number of bytes that were copied to the $(D_PARAM output) 89 * or nothing on error. 90 * 91 * Postcondition: Returned length is less than or equal to 92 * $(D_PARAM output) length. 93 */ 94 Nullable!ubyte poll(out ubyte[maxGather] output) @nogc; 95 } 96 97 version (CRuntime_Bionic) 98 { 99 version = SecureARC4Random; 100 } 101 else version (OSX) 102 { 103 version = SecureARC4Random; 104 } 105 else version (OpenBSD) 106 { 107 version = SecureARC4Random; 108 } 109 else version (NetBSD) 110 { 111 version = SecureARC4Random; 112 } 113 else version (Solaris) 114 { 115 version = SecureARC4Random; 116 } 117 118 version (linux) 119 { 120 import core.stdc.config : c_long; 121 private extern(C) c_long syscall(c_long number, ...) @nogc nothrow @system; 122 123 /** 124 * Uses getrandom system call. 125 */ 126 class PlatformEntropySource : EntropySource 127 { 128 /** 129 * Returns: Minimum bytes required from the entropy source. 130 */ 131 override @property ubyte threshold() const @nogc nothrow pure @safe 132 { 133 return 32; 134 } 135 136 /** 137 * Returns: Whether this entropy source is strong. 138 */ 139 override @property bool strong() const @nogc nothrow pure @safe 140 { 141 return true; 142 } 143 144 /** 145 * Poll the entropy source. 146 * 147 * Params: 148 * output = Buffer to save the generate random sequence (the method will 149 * to fill the buffer). 150 * 151 * Returns: Number of bytes that were copied to the $(D_PARAM output) 152 * or nothing on error. 153 */ 154 override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow 155 out (length) 156 { 157 assert(length.isNull || length.get <= maxGather); 158 } 159 do 160 { 161 // int getrandom(void *buf, size_t buflen, unsigned int flags); 162 import mir.linux._asm.unistd : NR_getrandom; 163 auto length = syscall(NR_getrandom, output.ptr, output.length, 0); 164 Nullable!ubyte ret; 165 166 if (length >= 0) 167 { 168 ret = cast(ubyte) length; 169 } 170 return ret; 171 } 172 } 173 } 174 else version (SecureARC4Random) 175 { 176 private extern(C) void arc4random_buf(scope void* buf, size_t nbytes) 177 @nogc nothrow @system; 178 179 /** 180 * Uses arc4random_buf. 181 */ 182 class PlatformEntropySource : EntropySource 183 { 184 /** 185 * Returns: Minimum bytes required from the entropy source. 186 */ 187 override @property ubyte threshold() const @nogc nothrow pure @safe 188 { 189 return 32; 190 } 191 192 /** 193 * Returns: Whether this entropy source is strong. 194 */ 195 override @property bool strong() const @nogc nothrow pure @safe 196 { 197 return true; 198 } 199 200 /** 201 * Poll the entropy source. 202 * 203 * Params: 204 * output = Buffer to save the generate random sequence (the method will 205 * to fill the buffer). 206 * 207 * Returns: Number of bytes that were copied to the $(D_PARAM output) 208 * or nothing on error. 209 */ 210 override Nullable!ubyte poll(out ubyte[maxGather] output) 211 @nogc nothrow @safe 212 out (length) 213 { 214 assert(length.isNull || length.get <= maxGather); 215 } 216 do 217 { 218 (() @trusted => arc4random_buf(output.ptr, output.length))(); 219 return Nullable!ubyte(cast(ubyte) (output.length)); 220 } 221 } 222 } 223 else version (Windows) 224 { 225 import core.sys.windows.basetsd : ULONG_PTR; 226 import core.sys.windows.winbase : GetLastError; 227 import core.sys.windows.wincrypt; 228 import core.sys.windows.windef : BOOL, DWORD, PBYTE; 229 import core.sys.windows.winerror : NTE_BAD_KEYSET; 230 import core.sys.windows.winnt : LPCSTR, LPCWSTR; 231 232 private extern(Windows) @nogc nothrow 233 { 234 BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE); 235 BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD); 236 BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD); 237 BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR); 238 } 239 240 private bool initCryptGenRandom(scope ref HCRYPTPROV hProvider) 241 @nogc nothrow @trusted 242 { 243 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379886(v=vs.85).aspx 244 // For performance reasons, we recommend that you set the pszContainer 245 // parameter to NULL and the dwFlags parameter to CRYPT_VERIFYCONTEXT 246 // in all situations where you do not require a persisted key. 247 // CRYPT_SILENT is intended for use with applications for which the UI 248 // cannot be displayed by the CSP. 249 if (!CryptAcquireContextW(&hProvider, 250 null, 251 null, 252 PROV_RSA_FULL, 253 CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) 254 { 255 if (GetLastError() != NTE_BAD_KEYSET) 256 { 257 return false; 258 } 259 // Attempt to create default container 260 if (!CryptAcquireContextA(&hProvider, 261 null, 262 null, 263 PROV_RSA_FULL, 264 CRYPT_NEWKEYSET | CRYPT_SILENT)) 265 { 266 return false; 267 } 268 } 269 270 return true; 271 } 272 273 class PlatformEntropySource : EntropySource 274 { 275 private HCRYPTPROV hProvider; 276 277 /** 278 * Uses CryptGenRandom. 279 */ 280 this() @nogc 281 { 282 if (!initCryptGenRandom(hProvider)) 283 { 284 throw defaultAllocator.make!EntropyException("CryptAcquireContextW failed."); 285 } 286 assert(hProvider > 0, "hProvider not properly initialized."); 287 } 288 289 ~this() @nogc nothrow @safe 290 { 291 if (hProvider > 0) 292 { 293 (() @trusted => CryptReleaseContext(hProvider, 0))(); 294 } 295 } 296 297 /** 298 * Returns: Minimum bytes required from the entropy source. 299 */ 300 override @property ubyte threshold() const @nogc nothrow pure @safe 301 { 302 return 32; 303 } 304 305 /** 306 * Returns: Whether this entropy source is strong. 307 */ 308 override @property bool strong() const @nogc nothrow pure @safe 309 { 310 return true; 311 } 312 313 /** 314 * Poll the entropy source. 315 * 316 * Params: 317 * output = Buffer to save the generate random sequence (the method will 318 * to fill the buffer). 319 * 320 * Returns: Number of bytes that were copied to the $(D_PARAM output) 321 * or nothing on error. 322 */ 323 override Nullable!ubyte poll(out ubyte[maxGather] output) 324 @nogc nothrow @safe 325 out (length) 326 { 327 assert(length.isNull || length.get <= maxGather); 328 } 329 do 330 { 331 Nullable!ubyte ret; 332 333 assert(hProvider > 0, "hProvider not properly initialized"); 334 if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))()) 335 { 336 ret = cast(ubyte) (output.length); 337 } 338 return ret; 339 } 340 } 341 }