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 }