| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 | /** * 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;};
 |