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

Revision 2573, 13.2 KB checked in by lowjoel, 14 months 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
RevLine 
[553]1/*
2 * $Id$
[2516]3 * Copyright 2008-2012 The Eraser Project
[553]4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
[611]5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net>
[553]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;
[1177]31
[2352]32using Eraser.Plugins;
33using Eraser.Plugins.ExtensionPoints;
[2572]34using Eraser.Util;
[2352]35
[553]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        {
[2571]49            //Create the pool and its complement.
[2566]50            Pool = new byte[sizeof(uint) << 7];
[2571]51            PoolInvert = new byte[Pool.Length];
52            for (uint i = 0, j = (uint)PoolInvert.Length; i < j; ++i)
53                PoolInvert[i] = byte.MaxValue;
[553]54
[2567]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
[553]62            //Then start the thread which maintains the pool.
[954]63            Thread = new Thread(Main);
[2573]64            Thread.Priority = ThreadPriority.Lowest;
[553]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        {
[2566]75            //Maintain the time we last provided entropy to the PRNGs. We will only
76            //provide entropy every 10 minutes.
[553]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)
[2443]83                    foreach (IEntropySource src in EntropySources)
[553]84                    {
85                        byte[] entropy = src.GetEntropy();
86                        AddEntropy(entropy);
87                    }
88
[2566]89                //Sleep for a "random" period between roughly [2, 5) seconds from now
90                Thread.Sleep(2000 + (int)(DateTime.Now.Ticks % 2999));
[553]91
[2567]92                //Send entropy to the PRNGs for new seeds.
[2566]93                DateTime now = DateTime.Now;
94                if (now - lastAddedEntropy > managerEntropySpan)
95                {
[2363]96                    Host.Instance.Prngs.AddEntropy(GetPool());
[2566]97                    lastAddedEntropy = now;
98                }
[553]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>
[2567]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>
[553]122        /// Adds a new Entropy Source to the Poller.
123        /// </summary>
124        /// <param name="source">The EntropySource object to add.</param>
[2443]125        public void AddEntropySource(IEntropySource source)
[553]126        {
127            lock (EntropySources)
128                EntropySources.Add(source);
129
130            AddEntropy(source.GetPrimer());
131            MixPool();
132
[2566]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.
[2572]138            HashAlgorithm secondaryHash = GetSecondaryHash();
139            if (secondaryHash != null)
140                MixPool(secondaryHash);
[553]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
[2566]154            lock (PoolLock)
[553]155            {
[2566]156                byte[] result = new byte[Pool.Length];
157                Pool.CopyTo(result, 0);
[553]158
159                return result;
160            }
161        }
162
163        /// <summary>
[2571]164        /// Inverts the contents of the pool.
[553]165        /// </summary>
166        private void InvertPool()
167        {
[2566]168            lock (PoolLock)
[2571]169            {
170                MemoryXor(PoolInvert, 0, Pool, 0, Pool.Length);
171            }
[553]172        }
173
174        /// <summary>
175        /// Mixes the contents of the pool.
176        /// </summary>
[2566]177        private void MixPool(HashAlgorithm hash)
[553]178        {
[2566]179            lock (PoolLock)
[553]180            {
181                //Mix the last 128 bytes first.
182                const int mixBlockSize = 128;
[2566]183                int hashSize = hash.HashSize / 8;
184                hash.ComputeHash(Pool, Pool.Length - mixBlockSize, mixBlockSize).CopyTo(Pool, 0);
[553]185
186                //Then mix the following bytes until wraparound is required
187                int i = 0;
[2566]188                for (; i < Pool.Length - hashSize; i += hashSize)
189                    Buffer.BlockCopy(hash.ComputeHash(Pool, i,
[2567]190                        Math.Min(mixBlockSize, Pool.Length - i)), 0, Pool, i,
191                        Math.Min(hashSize, Pool.Length - i));
[553]192
193                //Mix the remaining blocks which require copying from the front
194                byte[] combinedBuffer = new byte[mixBlockSize];
[2566]195                for (; i < Pool.Length; i += hashSize)
[553]196                {
[2567]197                    int remainder = Pool.Length - i;
198                    Buffer.BlockCopy(Pool, i, combinedBuffer, 0, remainder);
199                    Buffer.BlockCopy(Pool, 0, combinedBuffer, remainder,
200                        mixBlockSize - remainder);
[553]201
[2566]202                    Buffer.BlockCopy(hash.ComputeHash(combinedBuffer, 0, mixBlockSize), 0,
[2567]203                        Pool, i, Math.Min(hashSize, remainder));
[553]204                }
205            }
206        }
207
208        /// <summary>
[2566]209        /// Mixes the contents of the entropy pool using the currently specified default
210        /// algorithm.
211        /// </summary>
212        private void MixPool()
213        {
[2572]214            MixPool(GetPrimaryHash());
[2566]215        }
216
217        /// <summary>
[553]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>
[2569]222        public void AddEntropy(byte[] entropy)
[553]223        {
[2566]224            lock (PoolLock)
[2569]225            {
226                for (int i = entropy.Length, j = 0; i > 0; )
[553]227                {
[2569]228                    //Bring the pool position back to the front if we are at our end
229                    if (PoolPosition >= Pool.Length)
[553]230                    {
[2569]231                        PoolPosition = 0;
232                        MixPool();
233                    }
[553]234
[2569]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;
[553]240                }
[2569]241            }
[553]242        }
243
244        /// <summary>
[2569]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.
[553]247        /// </summary>
[2569]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)
[553]264        {
[2569]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();
[553]272           
[2569]273            unsafe
274            {
275                fixed (byte* pSrc = src)
276                fixed (byte* pDst = dst)
[2570]277                    MemoryXor64(pDst + dstOffset, pSrc + srcOffset, (uint)count);
[2569]278            }
279        }
[553]280
[2569]281        /// <summary>
[553]282
[2569]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
[553]294            {
[2569]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++;
[553]299            }
[2569]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            }
[553]309        }
310
311        /// <summary>
[2572]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>
[553]390        /// The pool of data which we currently maintain.
391        /// </summary>
[2566]392        private byte[] Pool;
[553]393
394        /// <summary>
[2571]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>
[553]401        /// The next position where entropy will be added to the pool.
402        /// </summary>
[2566]403        private int PoolPosition;
[553]404
405        /// <summary>
406        /// The lock guarding the pool array and the current entropy addition index.
407        /// </summary>
[2566]408        private object PoolLock = new object();
[553]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>
[2443]418        private List<IEntropySource> EntropySources = new List<IEntropySource>();
[2572]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;
[553]435    }
[1670]436}
Note: See TracBrowser for help on using the repository browser.