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