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.
		
		
		
		
		
			
		
			
				
					
					
						
							419 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							419 lines
						
					
					
						
							12 KiB
						
					
					
				/** | 
						|
 * 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(String.fromCharCode(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; | 
						|
};
 | 
						|
 |