[6a3a178] | 1 | /**
|
---|
| 2 | * A javascript implementation of a cryptographically-secure
|
---|
| 3 | * Pseudo Random Number Generator (PRNG). The Fortuna algorithm is followed
|
---|
| 4 | * here though the use of SHA-256 is not enforced; when generating an
|
---|
| 5 | * a PRNG context, the hashing algorithm and block cipher used for
|
---|
| 6 | * the generator are specified via a plugin.
|
---|
| 7 | *
|
---|
| 8 | * @author Dave Longley
|
---|
| 9 | *
|
---|
| 10 | * Copyright (c) 2010-2014 Digital Bazaar, Inc.
|
---|
| 11 | */
|
---|
| 12 | var forge = require('./forge');
|
---|
| 13 | require('./util');
|
---|
| 14 |
|
---|
| 15 | var _crypto = null;
|
---|
| 16 | if(forge.util.isNodejs && !forge.options.usePureJavaScript &&
|
---|
| 17 | !process.versions['node-webkit']) {
|
---|
| 18 | _crypto = require('crypto');
|
---|
| 19 | }
|
---|
| 20 |
|
---|
| 21 | /* PRNG API */
|
---|
| 22 | var prng = module.exports = forge.prng = forge.prng || {};
|
---|
| 23 |
|
---|
| 24 | /**
|
---|
| 25 | * Creates a new PRNG context.
|
---|
| 26 | *
|
---|
| 27 | * A PRNG plugin must be passed in that will provide:
|
---|
| 28 | *
|
---|
| 29 | * 1. A function that initializes the key and seed of a PRNG context. It
|
---|
| 30 | * will be given a 16 byte key and a 16 byte seed. Any key expansion
|
---|
| 31 | * or transformation of the seed from a byte string into an array of
|
---|
| 32 | * integers (or similar) should be performed.
|
---|
| 33 | * 2. The cryptographic function used by the generator. It takes a key and
|
---|
| 34 | * a seed.
|
---|
| 35 | * 3. A seed increment function. It takes the seed and returns seed + 1.
|
---|
| 36 | * 4. An api to create a message digest.
|
---|
| 37 | *
|
---|
| 38 | * For an example, see random.js.
|
---|
| 39 | *
|
---|
| 40 | * @param plugin the PRNG plugin to use.
|
---|
| 41 | */
|
---|
| 42 | prng.create = function(plugin) {
|
---|
| 43 | var ctx = {
|
---|
| 44 | plugin: plugin,
|
---|
| 45 | key: null,
|
---|
| 46 | seed: null,
|
---|
| 47 | time: null,
|
---|
| 48 | // number of reseeds so far
|
---|
| 49 | reseeds: 0,
|
---|
| 50 | // amount of data generated so far
|
---|
| 51 | generated: 0,
|
---|
| 52 | // no initial key bytes
|
---|
| 53 | keyBytes: ''
|
---|
| 54 | };
|
---|
| 55 |
|
---|
| 56 | // create 32 entropy pools (each is a message digest)
|
---|
| 57 | var md = plugin.md;
|
---|
| 58 | var pools = new Array(32);
|
---|
| 59 | for(var i = 0; i < 32; ++i) {
|
---|
| 60 | pools[i] = md.create();
|
---|
| 61 | }
|
---|
| 62 | ctx.pools = pools;
|
---|
| 63 |
|
---|
| 64 | // entropy pools are written to cyclically, starting at index 0
|
---|
| 65 | ctx.pool = 0;
|
---|
| 66 |
|
---|
| 67 | /**
|
---|
| 68 | * Generates random bytes. The bytes may be generated synchronously or
|
---|
| 69 | * asynchronously. Web workers must use the asynchronous interface or
|
---|
| 70 | * else the behavior is undefined.
|
---|
| 71 | *
|
---|
| 72 | * @param count the number of random bytes to generate.
|
---|
| 73 | * @param [callback(err, bytes)] called once the operation completes.
|
---|
| 74 | *
|
---|
| 75 | * @return count random bytes as a string.
|
---|
| 76 | */
|
---|
| 77 | ctx.generate = function(count, callback) {
|
---|
| 78 | // do synchronously
|
---|
| 79 | if(!callback) {
|
---|
| 80 | return ctx.generateSync(count);
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | // simple generator using counter-based CBC
|
---|
| 84 | var cipher = ctx.plugin.cipher;
|
---|
| 85 | var increment = ctx.plugin.increment;
|
---|
| 86 | var formatKey = ctx.plugin.formatKey;
|
---|
| 87 | var formatSeed = ctx.plugin.formatSeed;
|
---|
| 88 | var b = forge.util.createBuffer();
|
---|
| 89 |
|
---|
| 90 | // paranoid deviation from Fortuna:
|
---|
| 91 | // reset key for every request to protect previously
|
---|
| 92 | // generated random bytes should the key be discovered;
|
---|
| 93 | // there is no 100ms based reseeding because of this
|
---|
| 94 | // forced reseed for every `generate` call
|
---|
| 95 | ctx.key = null;
|
---|
| 96 |
|
---|
| 97 | generate();
|
---|
| 98 |
|
---|
| 99 | function generate(err) {
|
---|
| 100 | if(err) {
|
---|
| 101 | return callback(err);
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | // sufficient bytes generated
|
---|
| 105 | if(b.length() >= count) {
|
---|
| 106 | return callback(null, b.getBytes(count));
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | // if amount of data generated is greater than 1 MiB, trigger reseed
|
---|
| 110 | if(ctx.generated > 0xfffff) {
|
---|
| 111 | ctx.key = null;
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | if(ctx.key === null) {
|
---|
| 115 | // prevent stack overflow
|
---|
| 116 | return forge.util.nextTick(function() {
|
---|
| 117 | _reseed(generate);
|
---|
| 118 | });
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | // generate the random bytes
|
---|
| 122 | var bytes = cipher(ctx.key, ctx.seed);
|
---|
| 123 | ctx.generated += bytes.length;
|
---|
| 124 | b.putBytes(bytes);
|
---|
| 125 |
|
---|
| 126 | // generate bytes for a new key and seed
|
---|
| 127 | ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
|
---|
| 128 | ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
|
---|
| 129 |
|
---|
| 130 | forge.util.setImmediate(generate);
|
---|
| 131 | }
|
---|
| 132 | };
|
---|
| 133 |
|
---|
| 134 | /**
|
---|
| 135 | * Generates random bytes synchronously.
|
---|
| 136 | *
|
---|
| 137 | * @param count the number of random bytes to generate.
|
---|
| 138 | *
|
---|
| 139 | * @return count random bytes as a string.
|
---|
| 140 | */
|
---|
| 141 | ctx.generateSync = function(count) {
|
---|
| 142 | // simple generator using counter-based CBC
|
---|
| 143 | var cipher = ctx.plugin.cipher;
|
---|
| 144 | var increment = ctx.plugin.increment;
|
---|
| 145 | var formatKey = ctx.plugin.formatKey;
|
---|
| 146 | var formatSeed = ctx.plugin.formatSeed;
|
---|
| 147 |
|
---|
| 148 | // paranoid deviation from Fortuna:
|
---|
| 149 | // reset key for every request to protect previously
|
---|
| 150 | // generated random bytes should the key be discovered;
|
---|
| 151 | // there is no 100ms based reseeding because of this
|
---|
| 152 | // forced reseed for every `generateSync` call
|
---|
| 153 | ctx.key = null;
|
---|
| 154 |
|
---|
| 155 | var b = forge.util.createBuffer();
|
---|
| 156 | while(b.length() < count) {
|
---|
| 157 | // if amount of data generated is greater than 1 MiB, trigger reseed
|
---|
| 158 | if(ctx.generated > 0xfffff) {
|
---|
| 159 | ctx.key = null;
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | if(ctx.key === null) {
|
---|
| 163 | _reseedSync();
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | // generate the random bytes
|
---|
| 167 | var bytes = cipher(ctx.key, ctx.seed);
|
---|
| 168 | ctx.generated += bytes.length;
|
---|
| 169 | b.putBytes(bytes);
|
---|
| 170 |
|
---|
| 171 | // generate bytes for a new key and seed
|
---|
| 172 | ctx.key = formatKey(cipher(ctx.key, increment(ctx.seed)));
|
---|
| 173 | ctx.seed = formatSeed(cipher(ctx.key, ctx.seed));
|
---|
| 174 | }
|
---|
| 175 |
|
---|
| 176 | return b.getBytes(count);
|
---|
| 177 | };
|
---|
| 178 |
|
---|
| 179 | /**
|
---|
| 180 | * Private function that asynchronously reseeds a generator.
|
---|
| 181 | *
|
---|
| 182 | * @param callback(err) called once the operation completes.
|
---|
| 183 | */
|
---|
| 184 | function _reseed(callback) {
|
---|
| 185 | if(ctx.pools[0].messageLength >= 32) {
|
---|
| 186 | _seed();
|
---|
| 187 | return callback();
|
---|
| 188 | }
|
---|
| 189 | // not enough seed data...
|
---|
| 190 | var needed = (32 - ctx.pools[0].messageLength) << 5;
|
---|
| 191 | ctx.seedFile(needed, function(err, bytes) {
|
---|
| 192 | if(err) {
|
---|
| 193 | return callback(err);
|
---|
| 194 | }
|
---|
| 195 | ctx.collect(bytes);
|
---|
| 196 | _seed();
|
---|
| 197 | callback();
|
---|
| 198 | });
|
---|
| 199 | }
|
---|
| 200 |
|
---|
| 201 | /**
|
---|
| 202 | * Private function that synchronously reseeds a generator.
|
---|
| 203 | */
|
---|
| 204 | function _reseedSync() {
|
---|
| 205 | if(ctx.pools[0].messageLength >= 32) {
|
---|
| 206 | return _seed();
|
---|
| 207 | }
|
---|
| 208 | // not enough seed data...
|
---|
| 209 | var needed = (32 - ctx.pools[0].messageLength) << 5;
|
---|
| 210 | ctx.collect(ctx.seedFileSync(needed));
|
---|
| 211 | _seed();
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | /**
|
---|
| 215 | * Private function that seeds a generator once enough bytes are available.
|
---|
| 216 | */
|
---|
| 217 | function _seed() {
|
---|
| 218 | // update reseed count
|
---|
| 219 | ctx.reseeds = (ctx.reseeds === 0xffffffff) ? 0 : ctx.reseeds + 1;
|
---|
| 220 |
|
---|
| 221 | // goal is to update `key` via:
|
---|
| 222 | // key = hash(key + s)
|
---|
| 223 | // where 's' is all collected entropy from selected pools, then...
|
---|
| 224 |
|
---|
| 225 | // create a plugin-based message digest
|
---|
| 226 | var md = ctx.plugin.md.create();
|
---|
| 227 |
|
---|
| 228 | // consume current key bytes
|
---|
| 229 | md.update(ctx.keyBytes);
|
---|
| 230 |
|
---|
| 231 | // digest the entropy of pools whose index k meet the
|
---|
| 232 | // condition 'n mod 2^k == 0' where n is the number of reseeds
|
---|
| 233 | var _2powK = 1;
|
---|
| 234 | for(var k = 0; k < 32; ++k) {
|
---|
| 235 | if(ctx.reseeds % _2powK === 0) {
|
---|
| 236 | md.update(ctx.pools[k].digest().getBytes());
|
---|
| 237 | ctx.pools[k].start();
|
---|
| 238 | }
|
---|
| 239 | _2powK = _2powK << 1;
|
---|
| 240 | }
|
---|
| 241 |
|
---|
| 242 | // get digest for key bytes
|
---|
| 243 | ctx.keyBytes = md.digest().getBytes();
|
---|
| 244 |
|
---|
| 245 | // paranoid deviation from Fortuna:
|
---|
| 246 | // update `seed` via `seed = hash(key)`
|
---|
| 247 | // instead of initializing to zero once and only
|
---|
| 248 | // ever incrementing it
|
---|
| 249 | md.start();
|
---|
| 250 | md.update(ctx.keyBytes);
|
---|
| 251 | var seedBytes = md.digest().getBytes();
|
---|
| 252 |
|
---|
| 253 | // update state
|
---|
| 254 | ctx.key = ctx.plugin.formatKey(ctx.keyBytes);
|
---|
| 255 | ctx.seed = ctx.plugin.formatSeed(seedBytes);
|
---|
| 256 | ctx.generated = 0;
|
---|
| 257 | }
|
---|
| 258 |
|
---|
| 259 | /**
|
---|
| 260 | * The built-in default seedFile. This seedFile is used when entropy
|
---|
| 261 | * is needed immediately.
|
---|
| 262 | *
|
---|
| 263 | * @param needed the number of bytes that are needed.
|
---|
| 264 | *
|
---|
| 265 | * @return the random bytes.
|
---|
| 266 | */
|
---|
| 267 | function defaultSeedFile(needed) {
|
---|
| 268 | // use window.crypto.getRandomValues strong source of entropy if available
|
---|
| 269 | var getRandomValues = null;
|
---|
| 270 | var globalScope = forge.util.globalScope;
|
---|
| 271 | var _crypto = globalScope.crypto || globalScope.msCrypto;
|
---|
| 272 | if(_crypto && _crypto.getRandomValues) {
|
---|
| 273 | getRandomValues = function(arr) {
|
---|
| 274 | return _crypto.getRandomValues(arr);
|
---|
| 275 | };
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 278 | var b = forge.util.createBuffer();
|
---|
| 279 | if(getRandomValues) {
|
---|
| 280 | while(b.length() < needed) {
|
---|
| 281 | // max byte length is 65536 before QuotaExceededError is thrown
|
---|
| 282 | // http://www.w3.org/TR/WebCryptoAPI/#RandomSource-method-getRandomValues
|
---|
| 283 | var count = Math.max(1, Math.min(needed - b.length(), 65536) / 4);
|
---|
| 284 | var entropy = new Uint32Array(Math.floor(count));
|
---|
| 285 | try {
|
---|
| 286 | getRandomValues(entropy);
|
---|
| 287 | for(var i = 0; i < entropy.length; ++i) {
|
---|
| 288 | b.putInt32(entropy[i]);
|
---|
| 289 | }
|
---|
| 290 | } catch(e) {
|
---|
| 291 | /* only ignore QuotaExceededError */
|
---|
| 292 | if(!(typeof QuotaExceededError !== 'undefined' &&
|
---|
| 293 | e instanceof QuotaExceededError)) {
|
---|
| 294 | throw e;
|
---|
| 295 | }
|
---|
| 296 | }
|
---|
| 297 | }
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | // be sad and add some weak random data
|
---|
| 301 | if(b.length() < needed) {
|
---|
| 302 | /* Draws from Park-Miller "minimal standard" 31 bit PRNG,
|
---|
| 303 | implemented with David G. Carta's optimization: with 32 bit math
|
---|
| 304 | and without division (Public Domain). */
|
---|
| 305 | var hi, lo, next;
|
---|
| 306 | var seed = Math.floor(Math.random() * 0x010000);
|
---|
| 307 | while(b.length() < needed) {
|
---|
| 308 | lo = 16807 * (seed & 0xFFFF);
|
---|
| 309 | hi = 16807 * (seed >> 16);
|
---|
| 310 | lo += (hi & 0x7FFF) << 16;
|
---|
| 311 | lo += hi >> 15;
|
---|
| 312 | lo = (lo & 0x7FFFFFFF) + (lo >> 31);
|
---|
| 313 | seed = lo & 0xFFFFFFFF;
|
---|
| 314 |
|
---|
| 315 | // consume lower 3 bytes of seed
|
---|
| 316 | for(var i = 0; i < 3; ++i) {
|
---|
| 317 | // throw in more pseudo random
|
---|
| 318 | next = seed >>> (i << 3);
|
---|
| 319 | next ^= Math.floor(Math.random() * 0x0100);
|
---|
| 320 | b.putByte(String.fromCharCode(next & 0xFF));
|
---|
| 321 | }
|
---|
| 322 | }
|
---|
| 323 | }
|
---|
| 324 |
|
---|
| 325 | return b.getBytes(needed);
|
---|
| 326 | }
|
---|
| 327 | // initialize seed file APIs
|
---|
| 328 | if(_crypto) {
|
---|
| 329 | // use nodejs async API
|
---|
| 330 | ctx.seedFile = function(needed, callback) {
|
---|
| 331 | _crypto.randomBytes(needed, function(err, bytes) {
|
---|
| 332 | if(err) {
|
---|
| 333 | return callback(err);
|
---|
| 334 | }
|
---|
| 335 | callback(null, bytes.toString());
|
---|
| 336 | });
|
---|
| 337 | };
|
---|
| 338 | // use nodejs sync API
|
---|
| 339 | ctx.seedFileSync = function(needed) {
|
---|
| 340 | return _crypto.randomBytes(needed).toString();
|
---|
| 341 | };
|
---|
| 342 | } else {
|
---|
| 343 | ctx.seedFile = function(needed, callback) {
|
---|
| 344 | try {
|
---|
| 345 | callback(null, defaultSeedFile(needed));
|
---|
| 346 | } catch(e) {
|
---|
| 347 | callback(e);
|
---|
| 348 | }
|
---|
| 349 | };
|
---|
| 350 | ctx.seedFileSync = defaultSeedFile;
|
---|
| 351 | }
|
---|
| 352 |
|
---|
| 353 | /**
|
---|
| 354 | * Adds entropy to a prng ctx's accumulator.
|
---|
| 355 | *
|
---|
| 356 | * @param bytes the bytes of entropy as a string.
|
---|
| 357 | */
|
---|
| 358 | ctx.collect = function(bytes) {
|
---|
| 359 | // iterate over pools distributing entropy cyclically
|
---|
| 360 | var count = bytes.length;
|
---|
| 361 | for(var i = 0; i < count; ++i) {
|
---|
| 362 | ctx.pools[ctx.pool].update(bytes.substr(i, 1));
|
---|
| 363 | ctx.pool = (ctx.pool === 31) ? 0 : ctx.pool + 1;
|
---|
| 364 | }
|
---|
| 365 | };
|
---|
| 366 |
|
---|
| 367 | /**
|
---|
| 368 | * Collects an integer of n bits.
|
---|
| 369 | *
|
---|
| 370 | * @param i the integer entropy.
|
---|
| 371 | * @param n the number of bits in the integer.
|
---|
| 372 | */
|
---|
| 373 | ctx.collectInt = function(i, n) {
|
---|
| 374 | var bytes = '';
|
---|
| 375 | for(var x = 0; x < n; x += 8) {
|
---|
| 376 | bytes += String.fromCharCode((i >> x) & 0xFF);
|
---|
| 377 | }
|
---|
| 378 | ctx.collect(bytes);
|
---|
| 379 | };
|
---|
| 380 |
|
---|
| 381 | /**
|
---|
| 382 | * Registers a Web Worker to receive immediate entropy from the main thread.
|
---|
| 383 | * This method is required until Web Workers can access the native crypto
|
---|
| 384 | * API. This method should be called twice for each created worker, once in
|
---|
| 385 | * the main thread, and once in the worker itself.
|
---|
| 386 | *
|
---|
| 387 | * @param worker the worker to register.
|
---|
| 388 | */
|
---|
| 389 | ctx.registerWorker = function(worker) {
|
---|
| 390 | // worker receives random bytes
|
---|
| 391 | if(worker === self) {
|
---|
| 392 | ctx.seedFile = function(needed, callback) {
|
---|
| 393 | function listener(e) {
|
---|
| 394 | var data = e.data;
|
---|
| 395 | if(data.forge && data.forge.prng) {
|
---|
| 396 | self.removeEventListener('message', listener);
|
---|
| 397 | callback(data.forge.prng.err, data.forge.prng.bytes);
|
---|
| 398 | }
|
---|
| 399 | }
|
---|
| 400 | self.addEventListener('message', listener);
|
---|
| 401 | self.postMessage({forge: {prng: {needed: needed}}});
|
---|
| 402 | };
|
---|
| 403 | } else {
|
---|
| 404 | // main thread sends random bytes upon request
|
---|
| 405 | var listener = function(e) {
|
---|
| 406 | var data = e.data;
|
---|
| 407 | if(data.forge && data.forge.prng) {
|
---|
| 408 | ctx.seedFile(data.forge.prng.needed, function(err, bytes) {
|
---|
| 409 | worker.postMessage({forge: {prng: {err: err, bytes: bytes}}});
|
---|
| 410 | });
|
---|
| 411 | }
|
---|
| 412 | };
|
---|
| 413 | // TODO: do we need to remove the event listener when the worker dies?
|
---|
| 414 | worker.addEventListener('message', listener);
|
---|
| 415 | }
|
---|
| 416 | };
|
---|
| 417 |
|
---|
| 418 | return ctx;
|
---|
| 419 | };
|
---|