1 module dcrypt.crypto.random.fortuna.fortuna;
2 
3 public import dcrypt.crypto.random.prng;
4 public import dcrypt.crypto.blockcipher;
5 public import dcrypt.crypto.digest;
6 
7 import dcrypt.crypto.random.fortuna.generator;
8 import dcrypt.crypto.random.fortuna.accumulator;
9 
10 import dcrypt.crypto.engines.aes;
11 import dcrypt.crypto.digests.sha2;
12 
13 import std.datetime;
14 
15 
16 /// OOP wrapper
17 public alias WrapperPRNG!(FortunaCore!(AES, SHA256)) FortunaRNG;
18 public alias FortunaCore!(AES, SHA256) Fortuna;
19 
20 /// Get some random bytes from Fortuna.
21 unittest {
22 	FortunaCore!(AES, SHA256) fortuna;
23 
24 	ubyte[61] buf1;
25 	ubyte[buf1.length] buf2;
26 	fortuna.addSeed([0,1,2,3]);
27 	foreach(i;0..10) {
28 		buf2 = buf1;
29 		fortuna.nextBytes(buf1);
30 
31 		if(i > 0) {
32 			assert(buf2 != buf1, "data is not random");
33 		}
34 	}
35 }
36 
37 
38 /// Add real entropy to the global accumulator.
39 /// 
40 /// Params:
41 /// sourceID =	The ID of the entropy source. Can actually be any number.
42 /// pool = The ID of the pool to add the entropy.
43 /// seed	=	Random data.
44 @safe
45 public void addEntropy(in ubyte sourceID, in size_t pool, in ubyte[] seed...) nothrow @nogc 
46 in {
47 	assert(pool < Accumulator.pools, "Pool ID out of range.");
48 }
49 body {
50 	assert(globalAcc !is null, "Accumulator not initialized!");
51 	globalAcc.addEntropy(sourceID, pool, seed);
52 	
53 }
54 
55 /// Get 32 bytes of unpredictable* seed from the global accumulator.
56 /// 
57 /// Note:
58 /// * The seed can only be unpredictable if the accumulator gets enough entropy from entropy sources.
59 /// 
60 /// Params:
61 /// buf	=	A buffer for exactly 32 bytes.
62 /// 
63 /// Throws:
64 /// Error = if buffer has wrong size.
65 @safe
66 private void getSeed(ubyte[] buf) nothrow @nogc
67 in {
68 	assert(buf.length == 32, "buf must be 32 bytes long.");
69 }
70 body {
71 	assert(globalAcc !is null, "Accumulator not initialized!");
72 	globalAcc.extractEntropy(buf);
73 }
74 
75 private shared Accumulator globalAcc;	/// The entropy accumulator is used globally.
76 
77 /// initialize and seed the global accumulator
78 private shared static this() {
79 	globalAcc = new shared Accumulator;
80 
81 	// seed the accumulator
82 	ubyte[32] buf;
83 	foreach(i;0..4096/buf.length) {
84 		import dcrypt.crypto.random.fortuna.sources.systemtick;
85 
86 		getTimingEntropy(buf);
87 		addEntropy(0, i%Accumulator.pools, buf);
88 
89 	}
90 }
91 
92 
93 static assert(isRNG!(FortunaCore!(AES, SHA256)), "Fortuna does not meet requirements for PRNGs.");
94 
95 /// FortunaCore is meant to be the mothership of the PRNGs. It should run as a singleton -
96 /// one instance per application that handles the accumulator and entropy sources.
97 /// 
98 /// Params:
99 /// Cipher = A block cipher.
100 /// Digest = A hash algorithm.
101 @safe
102 private struct FortunaCore(Cipher, Digest) if(isBlockCipher!Cipher && isDigest!Digest)  {
103 nothrow:
104 	
105 	public {
106 
107 		enum name = "FortunaCore";
108 
109 		/// Add entropy to generators state but not to the accumulator.
110 		@safe
111 		void addSeed(in ubyte[] seed) nothrow @nogc {
112 			// pass this call directly to the generator
113 			prng.addSeed(seed);
114 		}
115 
116 		/// Fill the buffer with random bytes.
117 		void nextBytes(ubyte[] buffer) nothrow @nogc {
118 			randomData(buffer);
119 		}
120 	}
121 
122 	
123 	private {
124 		enum minReseedInterval = 100; /// minimal time in ms between reseeds
125 
126 		FortunaGenerator!(Cipher, Digest) prng;
127 
128 		size_t reseedCount = 0; /// increment each time reseed() is called
129 		ulong lastReseed = 0; /// time of the last reseed in ms
130 
131 		@safe
132 		void randomData(ubyte[] buffer) nothrow @nogc
133 		{
134 
135 			if(
136 				//a.getLength() >= MINPOOLSIZE &&
137 				TickDuration.currSystemTick.msecs - lastReseed > minReseedInterval) {
138 				
139 				reseed();
140 
141 			}
142 
143 			if(lastReseed == 0 && reseedCount == 0) {
144 				assert(false, "PRNG not seeded yet");
145 			}
146 
147 			// ready to generate the random data
148 			prng.nextBytes(buffer);
149 		}
150 
151 		/// get entropy from accumulator
152 		@trusted
153 		private void reseed() nothrow @nogc {
154 			ubyte[32] buf;
155 
156 			getSeed(buf);
157 
158 			assert(std.algorithm.any!"a != 0"(buf[]), "Got only zeros from accumulator instead of noise!");
159 
160 			prng.addSeed(buf);
161 			
162 			lastReseed = TickDuration.currSystemTick.msecs;
163 			++reseedCount;
164 
165 		}
166 		
167 	}
168 }