1 /**
2 	Implements cryptographically secure random number generators.
3 
4 	Copyright: © 2013 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Ilya Shipunov
7 */
8 module vibe.crypto.cryptorand;
9 
10 import std.conv : text;
11 import std.digest.sha;
12 import vibe.core.stream;
13 
14 
15 /** Creates a cryptographically secure random number generator.
16 
17 	Note that the returned RNG will operate in a non-blocking mode, which means
18 	that if no sufficient entropy has been generated, new random numbers will be
19 	generated from previous state.
20 */
21 RandomNumberStream secureRNG()
22 @safe {
23 	static SystemRNG m_rng;
24 	if (!m_rng) m_rng = new SystemRNG;
25 	return m_rng;
26 }
27 
28 
29 /**
30 	Base interface for all cryptographically secure RNGs.
31 */
32 interface RandomNumberStream : InputStream {
33 	/**
34 		Fills the buffer new random numbers.
35 
36 		Params:
37 			dst = The buffer that will be filled with random numbers.
38 				It will contain buffer.length random ubytes.
39 				Supportes both heap-based and stack-based arrays.
40 			mode = The desired waiting mode for IO operations.
41 
42 		Throws:
43 			CryptoException on error.
44 	*/
45 	override size_t read(scope ubyte[] dst, IOMode mode) @safe;
46 
47 	alias read = InputStream.read;
48 }
49 
50 version(linux)
51 	enum bool LinuxMaybeHasGetrandom = __traits(compiles, {import mir.linux._asm.unistd : NR_getrandom;});
52 else
53 	enum bool LinuxMaybeHasGetrandom = false;
54 
55 static if (LinuxMaybeHasGetrandom)
56 {
57 	// getrandom was introduced in Linux 3.17
58     private enum GET_RANDOM {
59         UNINITIALIZED,
60         NOT_AVAILABLE,
61         AVAILABLE,
62     }
63 	private __gshared GET_RANDOM hasGetRandom = GET_RANDOM.UNINITIALIZED;
64 	private import core.sys.posix.sys.utsname : utsname;
65 	// druntime might not be properly annotated
66 	private extern(C) int uname(scope utsname* __name) @nogc nothrow;
67 	// checks whether the Linux kernel supports getRandom by looking at the
68 	// reported version
69 	private bool initHasGetRandom() @nogc @trusted nothrow
70 	{
71 		import core.stdc.string : strtok;
72 		import core.stdc.stdlib : atoi;
73 
74 		utsname uts;
75 		uname(&uts);
76 		char* p = uts.release.ptr;
77 
78 		// poor man's version check
79 		auto token = strtok(p, ".");
80 		int major = atoi(token);
81 		if (major > 3) return true;
82 
83 		if (major == 3)
84 		{
85 			token = strtok(p, ".");
86 			if (atoi(token) >= 17) return true;
87 		}
88 
89 		return false;
90 	}
91 	private extern(C) int syscall(size_t ident, size_t n, size_t arg1, size_t arg2) @nogc nothrow;
92 }
93 
94 version (CRuntime_Bionic)
95     version = secure_arc4random;//ChaCha20
96 version (OSX)
97     version = secure_arc4random;//AES
98 version (OpenBSD)
99     version = secure_arc4random;//ChaCha20
100 version (NetBSD)
101     version = secure_arc4random;//ChaCha20
102 version (secure_arc4random)
103 extern(C) @nogc nothrow private @system
104 {
105 	void arc4random_buf(scope void* buf, size_t nbytes);
106 }
107 
108 /**
109 	Operating system specific cryptography secure random number generator.
110 
111 	It uses the "CryptGenRandom" function for Windows; the "arc4random_buf"
112 	function (not based on RC4 but on a modern and cryptographically secure
113 	cipher) for macOS/OpenBSD/NetBSD; the "getrandom" syscall for Linux 3.17
114 	and later; and "/dev/urandom" for other Posix platforms.
115 	It's recommended to combine the output use additional processing generated random numbers
116 	via provided functions for systems where security matters.
117 
118 	Remarks:
119 		Windows "CryptGenRandom" RNG has known security vulnerabilities on
120 		Windows 2000 and Windows XP (assuming the attacker has control of the
121 		machine). Fixed for Windows XP Service Pack 3 and Windows Vista.
122 
123 	See_Also: $(LINK http://en.wikipedia.org/wiki/CryptGenRandom)
124 */
125 final class SystemRNG : RandomNumberStream {
126 @safe:
127 	import std.exception;
128 
129 	version(Windows)
130 	{
131 		//cryptographic service provider
132 		private HCRYPTPROV hCryptProv;
133 	}
134 	else version(secure_arc4random)
135 	{
136 		//Using arc4random does not involve any extra fields.
137 	}
138 	else version(Posix)
139 	{
140 		import core.stdc.errno : errno, EINTR;
141 		//cryptographic file descriptor
142 		private int m_fd = -1;
143 	}
144 	else
145 	{
146 		static assert(0, "OS is not supported");
147 	}
148 
149 	/**
150 		Creates new system random generator
151 	*/
152 	this()
153 	@trusted {
154 		version(Windows)
155 		{
156 			//init cryptographic service provider
157 			enforce!CryptoException(CryptAcquireContext(&this.hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0,
158 				text("Cannot init SystemRNG: Error id is ", GetLastError()));
159 		}
160 		else version(secure_arc4random)
161 		{
162 			//arc4random requires no setup or cleanup.
163 		}
164 		else version(Posix)
165 		{
166 			import core.sys.posix.fcntl : open, O_RDONLY;
167 			version (linux) static if (LinuxMaybeHasGetrandom)
168 			{
169 				import core.atomic : atomicLoad, atomicStore;
170 				GET_RANDOM p = atomicLoad(*cast(const shared GET_RANDOM*) &hasGetRandom);
171 				if (p == GET_RANDOM.UNINITIALIZED)
172 				{
173 					p = initHasGetRandom() ? GET_RANDOM.AVAILABLE
174 						: GET_RANDOM.NOT_AVAILABLE;
175 					// Benign race condition.
176 					atomicStore(*cast(shared GET_RANDOM*) &hasGetRandom, p);
177 				}
178 				if (p == GET_RANDOM.AVAILABLE)
179 					return;
180 			}
181 			//open file
182 			m_fd = open("/dev/urandom", O_RDONLY);
183 			enforce!CryptoException(m_fd != -1, "Failed to open /dev/urandom");
184 		}
185 	}
186 
187 	~this()
188 	@trusted {
189 		version(Windows)
190 		{
191 			CryptReleaseContext(this.hCryptProv, 0);
192 		}
193 		else version (secure_arc4random)
194 		{
195 			//arc4random requires no setup or cleanup.
196 		}
197 		else version (Posix)
198 		{
199 			import core.sys.posix.unistd : close;
200 			version (linux) static if (LinuxMaybeHasGetrandom)
201 			{
202 				if (m_fd == -1) return;
203 			}
204 			close(m_fd);
205 		}
206 	}
207 
208 	@property bool empty() { return false; }
209 	@property ulong leastSize() { return ulong.max; }
210 	@property bool dataAvailableForRead() { return true; }
211 	const(ubyte)[] peek() { return null; }
212 
213 	size_t read(scope ubyte[] buffer, IOMode mode) @trusted
214 	in
215 	{
216 		assert(buffer.length, "buffer length must be larger than 0");
217 		assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max");
218 	}
219 	do
220 	{
221 		version (Windows)
222 		{
223 			if(0 == CryptGenRandom(this.hCryptProv, cast(DWORD)buffer.length, buffer.ptr))
224 			{
225 				throw new CryptoException(text("Cannot get next random number: Error id is ", GetLastError()));
226 			}
227 		}
228 		else version (secure_arc4random)
229 		{
230 			arc4random_buf(buffer.ptr, buffer.length);//Cannot fail.
231 		}
232 		else version (Posix)
233 		{
234 			version (linux) static if (LinuxMaybeHasGetrandom)
235 			{
236 				if (hasGetRandom == GET_RANDOM.AVAILABLE)
237 				{
238 					/*
239 						http://man7.org/linux/man-pages/man2/getrandom.2.html
240 						If the urandom source has been initialized, reads of up to 256 bytes
241 						will always return as many bytes as requested and will not be
242 						interrupted by signals.  No such guarantees apply for larger buffer
243 						sizes.
244 					*/
245 					import mir.linux._asm.unistd : NR_getrandom;
246 					size_t len = buffer.length;
247 					size_t ptr = cast(size_t) buffer.ptr;
248 					while (len > 0)
249 					{
250 						auto res = syscall(NR_getrandom, ptr, len, 0);
251 						if (res >= 0)
252 						{
253 							len -= res;
254 							ptr += res;
255 						}
256 						else if (errno != EINTR)
257 						{
258 							throw new CryptoException(
259 								text("Failed to read next random number: ", errno));
260 						}
261 					}
262 					return buffer.length;
263 				}
264 			}
265 			import core.sys.posix.unistd : _read = read;
266 			enforce!CryptoException(_read(m_fd, buffer.ptr, buffer.length) == buffer.length,
267 				text("Failed to read next random number: ", errno));
268 		}
269 		return buffer.length;
270 	}
271 
272 	alias read = RandomNumberStream.read;
273 }
274 
275 //test heap-based arrays
276 unittest
277 {
278 	import std.algorithm;
279 	import std.range;
280 
281 	//number random bytes in the buffer
282 	enum uint bufferSize = 20;
283 
284 	//number of iteration counts
285 	enum iterationCount = 10;
286 
287 	auto rng = new SystemRNG();
288 
289 	//holds the random number
290 	ubyte[] rand = new ubyte[bufferSize];
291 
292 	//holds the previous random number after the creation of the next one
293 	ubyte[] prevRadn = new ubyte[bufferSize];
294 
295 	//create the next random number
296 	rng.read(prevRadn);
297 
298 	assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero");
299 
300 	//take "iterationCount" arrays with random bytes
301 	foreach(i; 0..iterationCount)
302 	{
303 		//create the next random number
304 		rng.read(rand);
305 
306 		assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero");
307 
308 		assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal");
309 
310 		//copy current random bytes for next iteration
311 		prevRadn[] = rand[];
312 	}
313 }
314 
315 //test stack-based arrays
316 unittest
317 {
318 	import std.algorithm;
319 	import std.range;
320 	import std.array;
321 
322 	//number random bytes in the buffer
323 	enum uint bufferSize = 20;
324 
325 	//number of iteration counts
326 	enum iterationCount = 10;
327 
328 	//array that contains only zeros
329 	ubyte[bufferSize] zeroArray;
330 	zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[];
331 
332 	auto rng = new SystemRNG();
333 
334 	//holds the random number
335 	ubyte[bufferSize] rand;
336 
337 	//holds the previous random number after the creation of the next one
338 	ubyte[bufferSize] prevRadn;
339 
340 	//create the next random number
341 	rng.read(prevRadn);
342 
343 	assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero");
344 
345 	//take "iterationCount" arrays with random bytes
346 	foreach(i; 0..iterationCount)
347 	{
348 		//create the next random number
349 		rng.read(rand);
350 
351 		assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero");
352 
353 		assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal");
354 
355 		//copy current random bytes for next iteration
356 		prevRadn[] = rand[];
357 	}
358 }
359 
360 
361 /**
362 	Hash-based cryptographically secure random number mixer.
363 
364 	This RNG uses a hash function to mix a specific amount of random bytes from the input RNG.
365 	Use only cryptographically secure hash functions like SHA-512, Whirlpool or SHA-256, but not MD5.
366 
367 	Params:
368 		Hash: The hash function used, for example SHA1
369 		factor: Determines how many times the hash digest length of input data
370 			is used as input to the hash function. Increase factor value if you
371 			need more security because it increases entropy level or decrease
372 			the factor value if you need more speed.
373 
374 */
375 final class HashMixerRNG(Hash, uint factor) : RandomNumberStream
376 	if(isDigest!Hash)
377 {
378 	static assert(factor, "factor must be larger than 0");
379 
380 	//random number generator
381 	SystemRNG rng;
382 
383 	/**
384 		Creates new hash-based mixer random generator.
385 	*/
386 	this()
387 	{
388 		//create random number generator
389 		this.rng = new SystemRNG();
390 	}
391 
392 	@property bool empty() { return false; }
393 	@property ulong leastSize() { return ulong.max; }
394 	@property bool dataAvailableForRead() { return true; }
395 	const(ubyte)[] peek() { return null; }
396 
397 	size_t read(scope ubyte[] buffer, IOMode mode)
398 	in
399 	{
400 		assert(buffer.length, "buffer length must be larger than 0");
401 		assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max");
402 	}
403 	do
404 	{
405 		auto len = buffer.length;
406 
407 		//use stack to allocate internal buffer
408 		ubyte[factor * digestLength!Hash] internalBuffer = void;
409 
410 		//init internal buffer
411 		this.rng.read(internalBuffer);
412 
413 		//create new random number on stack
414 		ubyte[digestLength!Hash] randomNumber = digest!Hash(internalBuffer);
415 
416 		//allows to fill buffers longer than hash digest length
417 		while(buffer.length > digestLength!Hash)
418 		{
419 			//fill the buffer's beginning
420 			buffer[0..digestLength!Hash] = randomNumber[0..$];
421 
422 			//receive the buffer's end
423 			buffer = buffer[digestLength!Hash..$];
424 
425 			//re-init internal buffer
426 			this.rng.read(internalBuffer);
427 
428 			//create next random number
429 			randomNumber = digest!Hash(internalBuffer);
430 		}
431 
432 		//fill the buffer's end
433 		buffer[0..$] = randomNumber[0..buffer.length];
434 
435 		return len;
436 	}
437 
438 	alias read = RandomNumberStream.read;
439 }
440 
441 /// A SHA-1 based mixing RNG. Alias for HashMixerRNG!(SHA1, 5).
442 alias SHA1HashMixerRNG = HashMixerRNG!(SHA1, 5);
443 
444 //test heap-based arrays
445 unittest
446 {
447 	import std.algorithm;
448 	import std.range;
449 	import std.typetuple;
450 	import std.digest.md;
451 
452 	//number of iteration counts
453 	enum iterationCount = 10;
454 
455 	enum uint factor = 5;
456 
457 	//tested hash functions
458 	foreach(Hash; TypeTuple!(SHA1, MD5))
459 	{
460 		//test for different number random bytes in the buffer from 10 to 80 inclusive
461 		foreach(bufferSize; iota(10, 81))
462 		{
463 			auto rng = new HashMixerRNG!(Hash, factor)();
464 
465 			//holds the random number
466 			ubyte[] rand = new ubyte[bufferSize];
467 
468 			//holds the previous random number after the creation of the next one
469 			ubyte[] prevRadn = new ubyte[bufferSize];
470 
471 			//create the next random number
472 			rng.read(prevRadn);
473 
474 			assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero");
475 
476 			//take "iterationCount" arrays with random bytes
477 			foreach(i; 0..iterationCount)
478 			{
479 				//create the next random number
480 				rng.read(rand);
481 
482 				assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero");
483 
484 				assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal");
485 
486 				//make sure that we have different random bytes in different hash digests
487 				if(bufferSize > digestLength!Hash)
488 				{
489 					//begin and end of random number array
490 					ubyte[] begin = rand[0..digestLength!Hash];
491 					ubyte[] end = rand[digestLength!Hash..$];
492 
493 					//compare all nearby hash digests
494 					while(end.length >= digestLength!Hash)
495 					{
496 						assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal");
497 
498 						//go to the next hash digests
499 						begin = end[0..digestLength!Hash];
500 						end = end[digestLength!Hash..$];
501 					}
502 				}
503 
504 				//copy current random bytes for next iteration
505 				prevRadn[] = rand[];
506 			}
507 		}
508 	}
509 }
510 
511 //test stack-based arrays
512 unittest
513 {
514 	import std.algorithm;
515 	import std.range;
516 	import std.array;
517 	import std.typetuple;
518 	import std.digest.md;
519 
520 	//number of iteration counts
521 	enum iterationCount = 10;
522 
523 	enum uint factor = 5;
524 
525 	//tested hash functions
526 	foreach(Hash; TypeTuple!(SHA1, MD5))
527 	{
528 		//test for different number random bytes in the buffer
529 		foreach(bufferSize; TypeTuple!(10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80))
530 		{
531 			//array that contains only zeros
532 			ubyte[bufferSize] zeroArray;
533 			zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[];
534 
535 			auto rng = new HashMixerRNG!(Hash, factor)();
536 
537 			//holds the random number
538 			ubyte[bufferSize] rand;
539 
540 			//holds the previous random number after the creation of the next one
541 			ubyte[bufferSize] prevRadn;
542 
543 			//create the next random number
544 			rng.read(prevRadn);
545 
546 			assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero");
547 
548 			//take "iterationCount" arrays with random bytes
549 			foreach(i; 0..iterationCount)
550 			{
551 				//create the next random number
552 				rng.read(rand);
553 
554 				assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero");
555 
556 				assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal");
557 
558 				//make sure that we have different random bytes in different hash digests
559 				static if(bufferSize > digestLength!Hash)
560 				{
561 					//begin and end of random number array
562 					ubyte[] begin = rand[0..digestLength!Hash];
563 					ubyte[] end = rand[digestLength!Hash..$];
564 
565 					//compare all nearby hash digests
566 					while(end.length >= digestLength!Hash)
567 					{
568 						assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal");
569 
570 						//go to the next hash digests
571 						begin = end[0..digestLength!Hash];
572 						end = end[digestLength!Hash..$];
573 					}
574 				}
575 
576 				//copy current random bytes for next iteration
577 				prevRadn[] = rand[];
578 			}
579 		}
580 	}
581 }
582 
583 
584 /**
585 	Thrown when an error occurs during random number generation.
586 */
587 class CryptoException : Exception
588 {
589 	this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow
590 	{
591 		super(msg, file, line, next);
592 	}
593 }
594 
595 
596 version(Windows)
597 {
598 	import core.sys.windows.windows;
599 
600 	private extern(Windows) nothrow
601 	{
602 		alias HCRYPTPROV = size_t;
603 
604 		enum LPCTSTR NULL = cast(LPCTSTR)0;
605 		enum DWORD PROV_RSA_FULL = 1;
606 		enum DWORD CRYPT_VERIFYCONTEXT = 0xF0000000;
607 
608 		BOOL CryptAcquireContextA(HCRYPTPROV *phProv, LPCTSTR pszContainer, LPCTSTR pszProvider, DWORD dwProvType, DWORD dwFlags);
609 		alias CryptAcquireContext = CryptAcquireContextA;
610 
611 		BOOL CryptReleaseContext(HCRYPTPROV hProv, DWORD dwFlags);
612 
613 		BOOL CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer);
614 	}
615 }