source: trunk/eraser/Eraser.Manager/EntropyPoller.cs @ 2573

Revision 2573, 13.2 KB checked in by lowjoel, 2 years ago (diff)

Our entropy polling thread should have the lowest thread priority to reduce overhead when other tasks are running.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net>
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25
26using System.Threading;
27using System.Security.Cryptography;
28using System.Runtime.InteropServices;
29using System.Diagnostics;
30using System.Reflection;
31
32using Eraser.Plugins;
33using Eraser.Plugins.ExtensionPoints;
34using Eraser.Util;
35
36namespace Eraser.Manager
37{
38    /// <summary>
39    /// A class which uses EntropyPoll class to fetch system data as a source of
40    /// randomness at "regular" but "random" intervals
41    /// </summary>
42    public class EntropyPoller
43    {
44        /// <summary>
45        /// Constructor.
46        /// </summary>
47        public EntropyPoller()
48        {
49            //Create the pool and its complement.
50            Pool = new byte[sizeof(uint) << 7];
51            PoolInvert = new byte[Pool.Length];
52            for (uint i = 0, j = (uint)PoolInvert.Length; i < j; ++i)
53                PoolInvert[i] = byte.MaxValue;
54
55            //Handle the Entropy Source Registered event.
56            Host.Instance.EntropySources.Registered += OnEntropySourceRegistered;
57
58            //Meanwhile, add all entropy sources already registered.
59            foreach (IEntropySource source in Host.Instance.EntropySources)
60                AddEntropySource(source);
61
62            //Then start the thread which maintains the pool.
63            Thread = new Thread(Main);
64            Thread.Priority = ThreadPriority.Lowest;
65            Thread.Start();
66        }
67
68        /// <summary>
69        /// The PRNG entropy thread. This thread will run in the background, getting
70        /// random data to be used for entropy. This will maintain the integrity
71        /// of generated data from the PRNGs.
72        /// </summary>
73        private void Main()
74        {
75            //Maintain the time we last provided entropy to the PRNGs. We will only
76            //provide entropy every 10 minutes.
77            DateTime lastAddedEntropy = DateTime.Now;
78            TimeSpan managerEntropySpan = new TimeSpan(0, 10, 0);
79
80            while (Thread.ThreadState != System.Threading.ThreadState.AbortRequested)
81            {
82                lock (EntropySources)
83                    foreach (IEntropySource src in EntropySources)
84                    {
85                        byte[] entropy = src.GetEntropy();
86                        AddEntropy(entropy);
87                    }
88
89                //Sleep for a "random" period between roughly [2, 5) seconds from now
90                Thread.Sleep(2000 + (int)(DateTime.Now.Ticks % 2999));
91
92                //Send entropy to the PRNGs for new seeds.
93                DateTime now = DateTime.Now;
94                if (now - lastAddedEntropy > managerEntropySpan)
95                {
96                    Host.Instance.Prngs.AddEntropy(GetPool());
97                    lastAddedEntropy = now;
98                }
99            }
100        }
101
102        /// <summary>
103        /// Stops the execution of the thread.
104        /// </summary>
105        public void Abort()
106        {
107            Thread.Abort();
108        }
109
110        /// <summary>
111        /// Handles the OnEntropySourceRegistered event so we can register them with
112        /// ourselves.
113        /// </summary>
114        /// <param name="sender">The object which was registered.</param>
115        /// <param name="e">Event argument.</param>
116        private void OnEntropySourceRegistered(object sender, EventArgs e)
117        {
118            AddEntropySource((IEntropySource)sender);
119        }
120
121        /// <summary>
122        /// Adds a new Entropy Source to the Poller.
123        /// </summary>
124        /// <param name="source">The EntropySource object to add.</param>
125        public void AddEntropySource(IEntropySource source)
126        {
127            lock (EntropySources)
128                EntropySources.Add(source);
129
130            AddEntropy(source.GetPrimer());
131            MixPool();
132
133            //Apply "whitening" effect. Try to mix the pool using RIPEMD-160 to strengthen
134            //the cryptographic strength of the pool.
135            //There is a need to catch the InvalidOperationException because if Eraser is
136            //running under an OS with FIPS-compliance mode the RIPEMD-160 algorithm cannot
137            //be used.
138            HashAlgorithm secondaryHash = GetSecondaryHash();
139            if (secondaryHash != null)
140                MixPool(secondaryHash);
141        }
142
143        /// <summary>
144        /// Retrieves the current contents of the entropy pool.
145        /// </summary>
146        /// <returns>A byte array containing all the randomness currently found.</returns>
147        public byte[] GetPool()
148        {
149            //Mix and invert the pool
150            MixPool();
151            InvertPool();
152
153            //Return a safe copy
154            lock (PoolLock)
155            {
156                byte[] result = new byte[Pool.Length];
157                Pool.CopyTo(result, 0);
158
159                return result;
160            }
161        }
162
163        /// <summary>
164        /// Inverts the contents of the pool.
165        /// </summary>
166        private void InvertPool()
167        {
168            lock (PoolLock)
169            {
170                MemoryXor(PoolInvert, 0, Pool, 0, Pool.Length);
171            }
172        }
173
174        /// <summary>
175        /// Mixes the contents of the pool.
176        /// </summary>
177        private void MixPool(HashAlgorithm hash)
178        {
179            lock (PoolLock)
180            {
181                //Mix the last 128 bytes first.
182                const int mixBlockSize = 128;
183                int hashSize = hash.HashSize / 8;
184                hash.ComputeHash(Pool, Pool.Length - mixBlockSize, mixBlockSize).CopyTo(Pool, 0);
185
186                //Then mix the following bytes until wraparound is required
187                int i = 0;
188                for (; i < Pool.Length - hashSize; i += hashSize)
189                    Buffer.BlockCopy(hash.ComputeHash(Pool, i,
190                        Math.Min(mixBlockSize, Pool.Length - i)), 0, Pool, i,
191                        Math.Min(hashSize, Pool.Length - i));
192
193                //Mix the remaining blocks which require copying from the front
194                byte[] combinedBuffer = new byte[mixBlockSize];
195                for (; i < Pool.Length; i += hashSize)
196                {
197                    int remainder = Pool.Length - i;
198                    Buffer.BlockCopy(Pool, i, combinedBuffer, 0, remainder);
199                    Buffer.BlockCopy(Pool, 0, combinedBuffer, remainder,
200                        mixBlockSize - remainder);
201
202                    Buffer.BlockCopy(hash.ComputeHash(combinedBuffer, 0, mixBlockSize), 0,
203                        Pool, i, Math.Min(hashSize, remainder));
204                }
205            }
206        }
207
208        /// <summary>
209        /// Mixes the contents of the entropy pool using the currently specified default
210        /// algorithm.
211        /// </summary>
212        private void MixPool()
213        {
214            MixPool(GetPrimaryHash());
215        }
216
217        /// <summary>
218        /// Adds data which is random to the pool
219        /// </summary>
220        /// <param name="entropy">An array of data which will be XORed with pool
221        /// contents.</param>
222        public void AddEntropy(byte[] entropy)
223        {
224            lock (PoolLock)
225            {
226                for (int i = entropy.Length, j = 0; i > 0; )
227                {
228                    //Bring the pool position back to the front if we are at our end
229                    if (PoolPosition >= Pool.Length)
230                    {
231                        PoolPosition = 0;
232                        MixPool();
233                    }
234
235                    int amountToMix = Math.Min(i, Pool.Length - PoolPosition);
236                    MemoryXor(entropy, j, Pool, PoolPosition, amountToMix);
237                    i -= amountToMix;
238                    j += amountToMix;
239                    PoolPosition += amountToMix;
240                }
241            }
242        }
243
244        /// <summary>
245        /// Copies a specified number of bytes from a source array starting at a particular
246        /// offset to a destination array starting at a particular offset.
247        /// </summary>
248        /// <param name="src">The source buffer.</param>
249        /// <param name="srcOffset">The zero-based byte offset into src.</param>
250        /// <param name="dst">The destination buffer.</param>
251        /// <param name="dstOffset">The zero-based byte offset into dst.</param>
252        /// <param name="count">The number of bytes to copy.</param>
253        ///
254        /// <exception cref="System.ArgumentNullException"><paramref name="src"/> or
255        /// <paramref name="dst"/> is null.</exception>
256        /// <exception cref="System.ArgumentException"><paramref name="src"/> or
257        /// <paramref name="dst"/> is not an array of primitives or the length of
258        /// <paramref name="src"/> is less than <paramref name="srcOffset"/> +
259        /// <paramref name="count"/> or the length of <paramref name="dst"/>
260        /// is less than <paramref name="dstOffset"/> + <paramref name="count"/>.</exception>
261        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="srcOffset"/>,
262        /// <paramref name="dstOffset"/>, or <paramref name="count"/> is less than 0.</exception>
263        private static void MemoryXor(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
264        {
265            if (src == null || dst == null)
266                throw new ArgumentNullException();
267            if (src.Length < srcOffset + count ||
268                dst.Length < dstOffset + count)
269                throw new ArgumentException();
270            if (srcOffset < 0 || dstOffset < 0 || count < 0)
271                throw new ArgumentOutOfRangeException();
272           
273            unsafe
274            {
275                fixed (byte* pSrc = src)
276                fixed (byte* pDst = dst)
277                    MemoryXor64(pDst + dstOffset, pSrc + srcOffset, (uint)count);
278            }
279        }
280
281        /// <summary>
282
283        /// XOR's <paramref name="source"/> onto <paramref name="destination"/>, at the
284        /// natural word alignment of the current processor.
285        /// </summary>
286        /// <typeparam name="T">An integral type indicating the natural word of the
287        /// processor.</typeparam>
288        /// <param name="destination">The destination buffer to XOR to.</param>
289        /// <param name="source">The source buffer to XOR with.</param>
290        /// <param name="length">The amount of data, in bytes, to XOR.</param>
291        private static unsafe void MemoryXor64(byte* destination, byte* source, uint length)
292        {
293            //XOR the buffers using a processor word
294            {
295                ulong* wDestination = (ulong*)destination;
296                ulong* wSource = (ulong*)source;
297                for (uint i = 0, j = (uint)(length / sizeof(ulong)); i < j; ++i)
298                    *wDestination++ ^= *wSource++;
299            }
300
301            //XOR the remaining bytes
302            {
303                uint i = length - (length % sizeof(ulong));
304                destination += i;
305                source += i;
306                for (; i < length; ++i)
307                    *destination++ ^= *source++;
308            }
309        }
310
311        /// <summary>
312        /// Gets the primary hash algorithm used for pool mixing.
313        /// </summary>
314        /// <returns>A hash algorithm suitable for the current platform serving as the
315        /// primary hash algorithm for pool mixing.</returns>
316        /// <remarks>The instance returned need not be freed as it is cached.</remarks>
317        private static HashAlgorithm GetPrimaryHash()
318        {
319            if (PrimaryHashAlgorithmCache != null)
320                return PrimaryHashAlgorithmCache;
321
322            HashFactoryDelegate[] priorityList = new HashFactoryDelegate[] {
323                delegate() { return new SHA512Cng(); },
324                delegate() { return new SHA512CryptoServiceProvider(); },
325                delegate() { return new SHA512Managed(); },
326                delegate() { return new SHA256Cng(); },
327                delegate() { return new SHA256CryptoServiceProvider(); },
328                delegate() { return new SHA256Managed(); },
329                delegate() { return new SHA1Cng(); },
330                delegate() { return new SHA1CryptoServiceProvider(); },
331                delegate() { return new SHA1Managed(); }
332            };
333
334            foreach (HashFactoryDelegate func in priorityList)
335            {
336                try
337                {
338                    return PrimaryHashAlgorithmCache = func();
339                }
340                catch (InvalidOperationException)
341                {
342                }
343            }
344
345            throw new InvalidOperationException(S._("No suitable hash algorithms were found " +
346                "on this computer."));
347        }
348
349        /// <summary>
350        /// Gets the secondary hash algorithm used for pool mixing, serving roughly analogous
351        /// to key whitening.
352        /// </summary>
353        /// <returns>A hash algorithm suitable for the current platform serving as the
354        /// secondary hash algorithm for pool mixing, or null if no secondary hash
355        /// algorithm can be used (e.g. due to FIPS algorithm restrictions)</returns>
356        /// <remarks>The instance returned need not be freed as it is cached.</remarks>
357        private static HashAlgorithm GetSecondaryHash()
358        {
359            if (HasSecondaryHashAlgorithm)
360                return SecondaryHashAlgorithmCache;
361
362            HashFactoryDelegate[] priorityList = new HashFactoryDelegate[] {
363                delegate() { return new RIPEMD160Managed(); }
364            };
365
366            foreach (HashFactoryDelegate func in priorityList)
367            {
368                try
369                {
370                    SecondaryHashAlgorithmCache = func();
371                    HasSecondaryHashAlgorithm = true;
372                    return SecondaryHashAlgorithmCache;
373                }
374                catch (InvalidOperationException)
375                {
376                }
377            }
378
379            return null;
380        }
381
382        /// <summary>
383        /// The function prototype for factory delegates in the Primary and Secondary hash
384        /// priority lists.
385        /// </summary>
386        /// <returns></returns>
387        private delegate HashAlgorithm HashFactoryDelegate();
388
389        /// <summary>
390        /// The pool of data which we currently maintain.
391        /// </summary>
392        private byte[] Pool;
393
394        /// <summary>
395        /// A pool, the same size as <see cref="Pool"/>, but containing all bitwise 1's
396        /// for XOR for pool inversion
397        /// </summary>
398        private byte[] PoolInvert;
399
400        /// <summary>
401        /// The next position where entropy will be added to the pool.
402        /// </summary>
403        private int PoolPosition;
404
405        /// <summary>
406        /// The lock guarding the pool array and the current entropy addition index.
407        /// </summary>
408        private object PoolLock = new object();
409
410        /// <summary>
411        /// The thread object.
412        /// </summary>
413        private Thread Thread;
414
415        /// <summary>
416        /// The list of entropy sources registered with the Poller.
417        /// </summary>
418        private List<IEntropySource> EntropySources = new List<IEntropySource>();
419
420        /// <summary>
421        /// Cache object for <see cref="GetPrimaryHash"/>
422        /// </summary>
423        private static HashAlgorithm PrimaryHashAlgorithmCache;
424
425        /// <summary>
426        /// Cache object for <see cref="GetSecondaryHash"/>
427        /// </summary>
428        private static HashAlgorithm SecondaryHashAlgorithmCache;
429
430        /// <summary>
431        /// Cache for whether construction for a <see cref="SecondaryHashAlgorithmCache"/>
432        /// has been attempted.
433        /// </summary>
434        private static bool HasSecondaryHashAlgorithm;
435    }
436}
Note: See TracBrowser for help on using the repository browser.