1 module dcrypt.crypto.random.fortuna.fortuna;
2 
3 public import dcrypt.crypto.random.drng;
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!Fortuna FortunaRNG;
18 public alias FortunaCore!(FortunaGenerator!(AES, SHA256)) Fortuna;
19 
20 /// Get some random bytes from Fortuna.
21 unittest {
22 	FortunaCore!(FortunaGenerator!(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(isRNGWithInput!(FortunaCore!(FortunaGenerator!(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(RNGWithInput) if(isRNGWithInput!RNGWithInput)  {
103 nothrow:
104 	
105 	public {
106 
107 		enum name = "FortunaCore";
108 		enum isDeterministic = true;
109 
110 		/// Add entropy to generators state but not to the accumulator.
111 		@safe
112 		void addSeed(in ubyte[] seed...) nothrow @nogc {
113 			// pass this call directly to the generator
114 			prng.addSeed(seed);
115 		}
116 
117 		/// Fill the buffer with random bytes.
118 		void nextBytes(ubyte[] buffer) nothrow @nogc {
119 			randomData(buffer);
120 		}
121 	}
122 
123 	
124 	private {
125 		enum minReseedInterval = 100; /// minimal time in ms between reseeds
126 
127 		RNGWithInput prng;
128 
129 		size_t reseedCount = 0; /// increment each time reseed() is called
130 		ulong lastReseed = 0; /// time of the last reseed in ms
131 
132 		@safe
133 		void randomData(ubyte[] buffer) nothrow @nogc
134 		{
135 
136 			if(
137 				//a.getLength() >= MINPOOLSIZE &&
138 				TickDuration.currSystemTick.msecs - lastReseed > minReseedInterval) {
139 				
140 				reseed();
141 
142 			}
143 
144 			if(lastReseed == 0 && reseedCount == 0) {
145 				assert(false, "PRNG not seeded yet");
146 			}
147 
148 			// ready to generate the random data
149 			prng.nextBytes(buffer);
150 		}
151 
152 		/// get entropy from accumulator
153 		@trusted
154 		private void reseed() nothrow @nogc {
155 			ubyte[32] buf;
156 
157 			getSeed(buf);
158 
159 			assert(std.algorithm.any!"a != 0"(buf[]), "Got only zeros from accumulator instead of noise!");
160 
161 			prng.addSeed(buf);
162 			
163 			lastReseed = TickDuration.currSystemTick.msecs;
164 			++reseedCount;
165 
166 		}
167 		
168 	}
169 }