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

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