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 }