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.
191 lines
5.3 KiB
191 lines
5.3 KiB
/** |
|
* An API for getting cryptographically-secure random bytes. The bytes are |
|
* generated using the Fortuna algorithm devised by Bruce Schneier and |
|
* Niels Ferguson. |
|
* |
|
* Getting strong random bytes is not yet easy to do in javascript. The only |
|
* truish random entropy that can be collected is from the mouse, keyboard, or |
|
* from timing with respect to page loads, etc. This generator makes a poor |
|
* attempt at providing random bytes when those sources haven't yet provided |
|
* enough entropy to initially seed or to reseed the PRNG. |
|
* |
|
* @author Dave Longley |
|
* |
|
* Copyright (c) 2009-2014 Digital Bazaar, Inc. |
|
*/ |
|
var forge = require('./forge'); |
|
require('./aes'); |
|
require('./sha256'); |
|
require('./prng'); |
|
require('./util'); |
|
|
|
(function() { |
|
|
|
// forge.random already defined |
|
if(forge.random && forge.random.getBytes) { |
|
module.exports = forge.random; |
|
return; |
|
} |
|
|
|
(function(jQuery) { |
|
|
|
// the default prng plugin, uses AES-128 |
|
var prng_aes = {}; |
|
var _prng_aes_output = new Array(4); |
|
var _prng_aes_buffer = forge.util.createBuffer(); |
|
prng_aes.formatKey = function(key) { |
|
// convert the key into 32-bit integers |
|
var tmp = forge.util.createBuffer(key); |
|
key = new Array(4); |
|
key[0] = tmp.getInt32(); |
|
key[1] = tmp.getInt32(); |
|
key[2] = tmp.getInt32(); |
|
key[3] = tmp.getInt32(); |
|
|
|
// return the expanded key |
|
return forge.aes._expandKey(key, false); |
|
}; |
|
prng_aes.formatSeed = function(seed) { |
|
// convert seed into 32-bit integers |
|
var tmp = forge.util.createBuffer(seed); |
|
seed = new Array(4); |
|
seed[0] = tmp.getInt32(); |
|
seed[1] = tmp.getInt32(); |
|
seed[2] = tmp.getInt32(); |
|
seed[3] = tmp.getInt32(); |
|
return seed; |
|
}; |
|
prng_aes.cipher = function(key, seed) { |
|
forge.aes._updateBlock(key, seed, _prng_aes_output, false); |
|
_prng_aes_buffer.putInt32(_prng_aes_output[0]); |
|
_prng_aes_buffer.putInt32(_prng_aes_output[1]); |
|
_prng_aes_buffer.putInt32(_prng_aes_output[2]); |
|
_prng_aes_buffer.putInt32(_prng_aes_output[3]); |
|
return _prng_aes_buffer.getBytes(); |
|
}; |
|
prng_aes.increment = function(seed) { |
|
// FIXME: do we care about carry or signed issues? |
|
++seed[3]; |
|
return seed; |
|
}; |
|
prng_aes.md = forge.md.sha256; |
|
|
|
/** |
|
* Creates a new PRNG. |
|
*/ |
|
function spawnPrng() { |
|
var ctx = forge.prng.create(prng_aes); |
|
|
|
/** |
|
* Gets random bytes. If a native secure crypto API is unavailable, this |
|
* method tries to make the bytes more unpredictable by drawing from data that |
|
* can be collected from the user of the browser, eg: mouse movement. |
|
* |
|
* If a callback is given, this method will be called asynchronously. |
|
* |
|
* @param count the number of random bytes to get. |
|
* @param [callback(err, bytes)] called once the operation completes. |
|
* |
|
* @return the random bytes in a string. |
|
*/ |
|
ctx.getBytes = function(count, callback) { |
|
return ctx.generate(count, callback); |
|
}; |
|
|
|
/** |
|
* Gets random bytes asynchronously. If a native secure crypto API is |
|
* unavailable, this method tries to make the bytes more unpredictable by |
|
* drawing from data that can be collected from the user of the browser, |
|
* eg: mouse movement. |
|
* |
|
* @param count the number of random bytes to get. |
|
* |
|
* @return the random bytes in a string. |
|
*/ |
|
ctx.getBytesSync = function(count) { |
|
return ctx.generate(count); |
|
}; |
|
|
|
return ctx; |
|
} |
|
|
|
// create default prng context |
|
var _ctx = spawnPrng(); |
|
|
|
// add other sources of entropy only if window.crypto.getRandomValues is not |
|
// available -- otherwise this source will be automatically used by the prng |
|
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); |
|
}; |
|
} |
|
|
|
if(forge.options.usePureJavaScript || |
|
(!forge.util.isNodejs && !getRandomValues)) { |
|
// if this is a web worker, do not use weak entropy, instead register to |
|
// receive strong entropy asynchronously from the main thread |
|
if(typeof window === 'undefined' || window.document === undefined) { |
|
// FIXME: |
|
} |
|
|
|
// get load time entropy |
|
_ctx.collectInt(+new Date(), 32); |
|
|
|
// add some entropy from navigator object |
|
if(typeof(navigator) !== 'undefined') { |
|
var _navBytes = ''; |
|
for(var key in navigator) { |
|
try { |
|
if(typeof(navigator[key]) == 'string') { |
|
_navBytes += navigator[key]; |
|
} |
|
} catch(e) { |
|
/* Some navigator keys might not be accessible, e.g. the geolocation |
|
attribute throws an exception if touched in Mozilla chrome:// |
|
context. |
|
|
|
Silently ignore this and just don't use this as a source of |
|
entropy. */ |
|
} |
|
} |
|
_ctx.collect(_navBytes); |
|
_navBytes = null; |
|
} |
|
|
|
// add mouse and keyboard collectors if jquery is available |
|
if(jQuery) { |
|
// set up mouse entropy capture |
|
jQuery().mousemove(function(e) { |
|
// add mouse coords |
|
_ctx.collectInt(e.clientX, 16); |
|
_ctx.collectInt(e.clientY, 16); |
|
}); |
|
|
|
// set up keyboard entropy capture |
|
jQuery().keypress(function(e) { |
|
_ctx.collectInt(e.charCode, 8); |
|
}); |
|
} |
|
} |
|
|
|
/* Random API */ |
|
if(!forge.random) { |
|
forge.random = _ctx; |
|
} else { |
|
// extend forge.random with _ctx |
|
for(var key in _ctx) { |
|
forge.random[key] = _ctx[key]; |
|
} |
|
} |
|
|
|
// expose spawn PRNG |
|
forge.random.createInstance = spawnPrng; |
|
|
|
module.exports = forge.random; |
|
|
|
})(typeof(jQuery) !== 'undefined' ? jQuery : null); |
|
|
|
})();
|
|
|