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