You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					420 lines
				
				12 KiB
			
		
		
			
		
	
	
					420 lines
				
				12 KiB
			| 
											2 years ago
										 | /** | ||
|  |  * A javascript implementation of a cryptographically-secure | ||
|  |  * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed | ||
|  |  * here though the use of SHA-256 is not enforced; when generating an | ||
|  |  * a PRNG context, the hashing algorithm and block cipher used for | ||
|  |  * the generator are specified via a plugin. | ||
|  |  * | ||
|  |  * @author Dave Longley | ||
|  |  * | ||
|  |  * Copyright (c) 2010-2014 Digital Bazaar, Inc. | ||
|  |  */ | ||
|  | var forge = require('./forge'); | ||
|  | require('./util'); | ||
|  | 
 | ||
|  | var _crypto = null; | ||
|  | if(forge.util.isNodejs && !forge.options.usePureJavaScript && | ||
|  |   !process.versions['node-webkit']) { | ||
|  |   _crypto = require('crypto'); | ||
|  | } | ||
|  | 
 | ||
|  | /* PRNG API */ | ||
|  | var prng = module.exports = forge.prng = forge.prng || {}; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Creates a new PRNG context. | ||
|  |  * | ||
|  |  * A PRNG plugin must be passed in that will provide: | ||
|  |  * | ||
|  |  * 1. A function that initializes the key and seed of a PRNG context. It | ||
|  |  *   will be given a 16 byte key and a 16 byte seed. Any key expansion | ||
|  |  *   or transformation of the seed from a byte string into an array of | ||
|  |  *   integers (or similar) should be performed. | ||
|  |  * 2. The cryptographic function used by the generator. It takes a key and | ||
|  |  *   a seed. | ||
|  |  * 3. A seed increment function. It takes the seed and returns seed + 1. | ||
|  |  * 4. An api to create a message digest. | ||
|  |  * | ||
|  |  * For an example, see random.js. | ||
|  |  * | ||
|  |  * @param plugin the PRNG plugin to use. | ||
|  |  */ | ||
|  | prng.create = function(plugin) { | ||
|  |   var ctx = { | ||
|  |     plugin: plugin, | ||
|  |     key: null, | ||
|  |     seed: null, | ||
|  |     time: null, | ||
|  |     // number of reseeds so far
 | ||
|  |     reseeds: 0, | ||
|  |     // amount of data generated so far
 | ||
|  |     generated: 0, | ||
|  |     // no initial key bytes
 | ||
|  |     keyBytes: '' | ||
|  |   }; | ||
|  | 
 | ||
|  |   // create 32 entropy pools (each is a message digest)
 | ||
|  |   var md = plugin.md; | ||
|  |   var pools = new Array(32); | ||
|  |   for(var i = 0; i < 32; ++i) { | ||
|  |     pools[i] = md.create(); | ||
|  |   } | ||
|  |   ctx.pools = pools; | ||
|  | 
 | ||
|  |   // entropy pools are written to cyclically, starting at index 0
 | ||
|  |   ctx.pool = 0; | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Generates random bytes. The bytes may be generated synchronously or | ||
|  |    * asynchronously. Web workers must use the asynchronous interface or | ||
|  |    * else the behavior is undefined. | ||
|  |    * | ||
|  |    * @param count the number of random bytes to generate. | ||
|  |    * @param [callback(err, bytes)] called once the operation completes. | ||
|  |    * | ||
|  |    * @return count random bytes as a string. | ||
|  |    */ | ||
|  |   ctx.generate = function(count, callback) { | ||
|  |     // do synchronously
 | ||
|  |     if(!callback) { | ||
|  |       return ctx.generateSync(count); | ||
|  |     } | ||
|  | 
 | ||
|  |     // simple generator using counter-based CBC
 | ||
|  |     var cipher = ctx.plugin.cipher; | ||
|  |     var increment = ctx.plugin.increment; | ||
|  |     var formatKey = ctx.plugin.formatKey; | ||
|  |     var formatSeed = ctx.plugin.formatSeed; | ||
|  |     var b = forge.util.createBuffer(); | ||
|  | 
 | ||
|  |     // paranoid deviation from Fortuna:
 | ||
|  |     // reset key for every request to protect previously
 | ||
|  |     // generated random bytes should the key be discovered;
 | ||
|  |     // there is no 100ms based reseeding because of this
 | ||
|  |     // forced reseed for every `generate` call
 | ||
|  |     ctx.key = null; | ||
|  | 
 | ||
|  |     generate(); | ||
|  | 
 | ||
|  |     function generate(err) { | ||
|  |       if(err) { | ||
|  |         return callback(err); | ||
|  |       } | ||
|  | 
 | ||
|  |       // sufficient bytes generated
 | ||
|  |       if(b.length() >= count) { | ||
|  |         return callback(null, b.getBytes(count)); | ||
|  |       } | ||
|  | 
 | ||
|  |       // if amount of data generated is greater than 1 MiB, trigger reseed
 | ||
|  |       if(ctx.generated > 0xfffff) { | ||
|  |         ctx.key = null; | ||
|  |       } | ||
|  | 
 | ||
|  |       if(ctx.key === null) { | ||
|  |         // prevent stack overflow
 | ||
|  |         return forge.util.nextTick(function() { | ||
|  |           _reseed(generate); | ||
|  |         }); | ||
|  |       } | ||
|  | 
 | ||
|  |       // generate the random bytes
 | ||
|  |       var bytes = cipher(ctx.key, ctx.seed); | ||
|  |       ctx.generated += bytes.length; | ||
|  |       b.putBytes(bytes); | ||
|  | 
 | ||
|  |       // generate bytes for a new key and seed
 | ||
|  |       ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed))); | ||
|  |       ctx.seed = formatSeed(cipher(ctx.key, ctx.seed)); | ||
|  | 
 | ||
|  |       forge.util.setImmediate(generate); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Generates random bytes synchronously. | ||
|  |    * | ||
|  |    * @param count the number of random bytes to generate. | ||
|  |    * | ||
|  |    * @return count random bytes as a string. | ||
|  |    */ | ||
|  |   ctx.generateSync = function(count) { | ||
|  |     // simple generator using counter-based CBC
 | ||
|  |     var cipher = ctx.plugin.cipher; | ||
|  |     var increment = ctx.plugin.increment; | ||
|  |     var formatKey = ctx.plugin.formatKey; | ||
|  |     var formatSeed = ctx.plugin.formatSeed; | ||
|  | 
 | ||
|  |     // paranoid deviation from Fortuna:
 | ||
|  |     // reset key for every request to protect previously
 | ||
|  |     // generated random bytes should the key be discovered;
 | ||
|  |     // there is no 100ms based reseeding because of this
 | ||
|  |     // forced reseed for every `generateSync` call
 | ||
|  |     ctx.key = null; | ||
|  | 
 | ||
|  |     var b = forge.util.createBuffer(); | ||
|  |     while(b.length() < count) { | ||
|  |       // if amount of data generated is greater than 1 MiB, trigger reseed
 | ||
|  |       if(ctx.generated > 0xfffff) { | ||
|  |         ctx.key = null; | ||
|  |       } | ||
|  | 
 | ||
|  |       if(ctx.key === null) { | ||
|  |         _reseedSync(); | ||
|  |       } | ||
|  | 
 | ||
|  |       // generate the random bytes
 | ||
|  |       var bytes = cipher(ctx.key, ctx.seed); | ||
|  |       ctx.generated += bytes.length; | ||
|  |       b.putBytes(bytes); | ||
|  | 
 | ||
|  |       // generate bytes for a new key and seed
 | ||
|  |       ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed))); | ||
|  |       ctx.seed = formatSeed(cipher(ctx.key, ctx.seed)); | ||
|  |     } | ||
|  | 
 | ||
|  |     return b.getBytes(count); | ||
|  |   }; | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Private function that asynchronously reseeds a generator. | ||
|  |    * | ||
|  |    * @param callback(err) called once the operation completes. | ||
|  |    */ | ||
|  |   function _reseed(callback) { | ||
|  |     if(ctx.pools[0].messageLength >= 32) { | ||
|  |       _seed(); | ||
|  |       return callback(); | ||
|  |     } | ||
|  |     // not enough seed data...
 | ||
|  |     var needed = (32 - ctx.pools[0].messageLength) << 5; | ||
|  |     ctx.seedFile(needed, function(err, bytes) { | ||
|  |       if(err) { | ||
|  |         return callback(err); | ||
|  |       } | ||
|  |       ctx.collect(bytes); | ||
|  |       _seed(); | ||
|  |       callback(); | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Private function that synchronously reseeds a generator. | ||
|  |    */ | ||
|  |   function _reseedSync() { | ||
|  |     if(ctx.pools[0].messageLength >= 32) { | ||
|  |       return _seed(); | ||
|  |     } | ||
|  |     // not enough seed data...
 | ||
|  |     var needed = (32 - ctx.pools[0].messageLength) << 5; | ||
|  |     ctx.collect(ctx.seedFileSync(needed)); | ||
|  |     _seed(); | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Private function that seeds a generator once enough bytes are available. | ||
|  |    */ | ||
|  |   function _seed() { | ||
|  |     // update reseed count
 | ||
|  |     ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1; | ||
|  | 
 | ||
|  |     // goal is to update `key` via:
 | ||
|  |     // key = hash(key + s)
 | ||
|  |     //   where 's' is all collected entropy from selected pools, then...
 | ||
|  | 
 | ||
|  |     // create a plugin-based message digest
 | ||
|  |     var md = ctx.plugin.md.create(); | ||
|  | 
 | ||
|  |     // consume current key bytes
 | ||
|  |     md.update(ctx.keyBytes); | ||
|  | 
 | ||
|  |     // digest the entropy of pools whose index k meet the
 | ||
|  |     // condition 'n mod 2^k == 0' where n is the number of reseeds
 | ||
|  |     var _2powK = 1; | ||
|  |     for(var k = 0; k < 32; ++k) { | ||
|  |       if(ctx.reseeds % _2powK === 0) { | ||
|  |         md.update(ctx.pools[k].digest().getBytes()); | ||
|  |         ctx.pools[k].start(); | ||
|  |       } | ||
|  |       _2powK = _2powK << 1; | ||
|  |     } | ||
|  | 
 | ||
|  |     // get digest for key bytes
 | ||
|  |     ctx.keyBytes = md.digest().getBytes(); | ||
|  | 
 | ||
|  |     // paranoid deviation from Fortuna:
 | ||
|  |     // update `seed` via `seed = hash(key)`
 | ||
|  |     // instead of initializing to zero once and only
 | ||
|  |     // ever incrementing it
 | ||
|  |     md.start(); | ||
|  |     md.update(ctx.keyBytes); | ||
|  |     var seedBytes = md.digest().getBytes(); | ||
|  | 
 | ||
|  |     // update state
 | ||
|  |     ctx.key = ctx.plugin.formatKey(ctx.keyBytes); | ||
|  |     ctx.seed = ctx.plugin.formatSeed(seedBytes); | ||
|  |     ctx.generated = 0; | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * The built-in default seedFile. This seedFile is used when entropy | ||
|  |    * is needed immediately. | ||
|  |    * | ||
|  |    * @param needed the number of bytes that are needed. | ||
|  |    * | ||
|  |    * @return the random bytes. | ||
|  |    */ | ||
|  |   function defaultSeedFile(needed) { | ||
|  |     // use window.crypto.getRandomValues strong source of entropy if available
 | ||
|  |     var getRandomValues = null; | ||
|  |     var globalScope = forge.util.globalScope; | ||
|  |     var _crypto = globalScope.crypto || globalScope.msCrypto; | ||
|  |     if(_crypto && _crypto.getRandomValues) { | ||
|  |       getRandomValues = function(arr) { | ||
|  |         return _crypto.getRandomValues(arr); | ||
|  |       }; | ||
|  |     } | ||
|  | 
 | ||
|  |     var b = forge.util.createBuffer(); | ||
|  |     if(getRandomValues) { | ||
|  |       while(b.length() < needed) { | ||
|  |         // max byte length is 65536 before QuotaExceededError is thrown
 | ||
|  |         // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
 | ||
|  |         var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4); | ||
|  |         var entropy = new Uint32Array(Math.floor(count)); | ||
|  |         try { | ||
|  |           getRandomValues(entropy); | ||
|  |           for(var i = 0; i < entropy.length; ++i) { | ||
|  |             b.putInt32(entropy[i]); | ||
|  |           } | ||
|  |         } catch(e) { | ||
|  |           /* only ignore QuotaExceededError */ | ||
|  |           if(!(typeof QuotaExceededError !== 'undefined' && | ||
|  |             e instanceof QuotaExceededError)) { | ||
|  |             throw e; | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // be sad and add some weak random data
 | ||
|  |     if(b.length() < needed) { | ||
|  |       /* Draws from Park-Miller "minimal standard" 31 bit PRNG, | ||
|  |       implemented with David G. Carta's optimization: with 32 bit math | ||
|  |       and without division (Public Domain). */ | ||
|  |       var hi, lo, next; | ||
|  |       var seed = Math.floor(Math.random() * 0x010000); | ||
|  |       while(b.length() < needed) { | ||
|  |         lo = 16807 * (seed & 0xFFFF); | ||
|  |         hi = 16807 * (seed >> 16); | ||
|  |         lo += (hi & 0x7FFF) << 16; | ||
|  |         lo += hi >> 15; | ||
|  |         lo = (lo & 0x7FFFFFFF) + (lo >> 31); | ||
|  |         seed = lo & 0xFFFFFFFF; | ||
|  | 
 | ||
|  |         // consume lower 3 bytes of seed
 | ||
|  |         for(var i = 0; i < 3; ++i) { | ||
|  |           // throw in more pseudo random
 | ||
|  |           next = seed >>> (i << 3); | ||
|  |           next ^= Math.floor(Math.random() * 0x0100); | ||
|  |           b.putByte(next & 0xFF); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     return b.getBytes(needed); | ||
|  |   } | ||
|  |   // initialize seed file APIs
 | ||
|  |   if(_crypto) { | ||
|  |     // use nodejs async API
 | ||
|  |     ctx.seedFile = function(needed, callback) { | ||
|  |       _crypto.randomBytes(needed, function(err, bytes) { | ||
|  |         if(err) { | ||
|  |           return callback(err); | ||
|  |         } | ||
|  |         callback(null, bytes.toString()); | ||
|  |       }); | ||
|  |     }; | ||
|  |     // use nodejs sync API
 | ||
|  |     ctx.seedFileSync = function(needed) { | ||
|  |       return _crypto.randomBytes(needed).toString(); | ||
|  |     }; | ||
|  |   } else { | ||
|  |     ctx.seedFile = function(needed, callback) { | ||
|  |       try { | ||
|  |         callback(null, defaultSeedFile(needed)); | ||
|  |       } catch(e) { | ||
|  |         callback(e); | ||
|  |       } | ||
|  |     }; | ||
|  |     ctx.seedFileSync = defaultSeedFile; | ||
|  |   } | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Adds entropy to a prng ctx's accumulator. | ||
|  |    * | ||
|  |    * @param bytes the bytes of entropy as a string. | ||
|  |    */ | ||
|  |   ctx.collect = function(bytes) { | ||
|  |     // iterate over pools distributing entropy cyclically
 | ||
|  |     var count = bytes.length; | ||
|  |     for(var i = 0; i < count; ++i) { | ||
|  |       ctx.pools[ctx.pool].update(bytes.substr(i, 1)); | ||
|  |       ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1; | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Collects an integer of n bits. | ||
|  |    * | ||
|  |    * @param i the integer entropy. | ||
|  |    * @param n the number of bits in the integer. | ||
|  |    */ | ||
|  |   ctx.collectInt = function(i, n) { | ||
|  |     var bytes = ''; | ||
|  |     for(var x = 0; x < n; x += 8) { | ||
|  |       bytes += String.fromCharCode((i >> x) & 0xFF); | ||
|  |     } | ||
|  |     ctx.collect(bytes); | ||
|  |   }; | ||
|  | 
 | ||
|  |   /** | ||
|  |    * Registers a Web Worker to receive immediate entropy from the main thread. | ||
|  |    * This method is required until Web Workers can access the native crypto | ||
|  |    * API. This method should be called twice for each created worker, once in | ||
|  |    * the main thread, and once in the worker itself. | ||
|  |    * | ||
|  |    * @param worker the worker to register. | ||
|  |    */ | ||
|  |   ctx.registerWorker = function(worker) { | ||
|  |     // worker receives random bytes
 | ||
|  |     if(worker === self) { | ||
|  |       ctx.seedFile = function(needed, callback) { | ||
|  |         function listener(e) { | ||
|  |           var data = e.data; | ||
|  |           if(data.forge && data.forge.prng) { | ||
|  |             self.removeEventListener('message', listener); | ||
|  |             callback(data.forge.prng.err, data.forge.prng.bytes); | ||
|  |           } | ||
|  |         } | ||
|  |         self.addEventListener('message', listener); | ||
|  |         self.postMessage({forge: {prng: {needed: needed}}}); | ||
|  |       }; | ||
|  |     } else { | ||
|  |       // main thread sends random bytes upon request
 | ||
|  |       var listener = function(e) { | ||
|  |         var data = e.data; | ||
|  |         if(data.forge && data.forge.prng) { | ||
|  |           ctx.seedFile(data.forge.prng.needed, function(err, bytes) { | ||
|  |             worker.postMessage({forge: {prng: {err: err, bytes: bytes}}}); | ||
|  |           }); | ||
|  |         } | ||
|  |       }; | ||
|  |       // TODO: do we need to remove the event listener when the worker dies?
 | ||
|  |       worker.addEventListener('message', listener); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ctx; | ||
|  | }; |