123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * Copyright 2018 Scytl Secure Electronic Voting SA
- *
- * All rights reserved
- *
- * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
- */
- /* jshint node:true */
- 'use strict';
- var Tools = require('../tools');
- var AjaxCollector = require('./collectors/ajax');
- var PrivateKeyCollector = require('./collectors/privatekey');
- var JSExecutionCollector = require('./collectors/jsexecution');
- var NavigatorInfoCollector = require('./collectors/navigatorinfo');
- var MathRandomCollector = require('./collectors/mathrandom');
- var WebKitRandomCollector = require('./collectors/webkitrandom');
- var DeviceOrientationCollector = require('./events/deviceorientation');
- var DeviceMotionCollector = require('./events/devicemotion');
- var MouseMoveCollector = require('./events/mousemove');
- var MouseWheelCollector = require('./events/mousewheel');
- var MouseButtonCollector = require('./events/mousebutton');
- var TouchStartCollector = require('./events/touchstart');
- var TouchMoveCollector = require('./events/touchmove');
- var TouchEndCollector = require('./events/touchend');
- var KeyCollector = require('./events/key');
- var codec = require('scytl-codec');
- module.exports = EntropyManager;
- /**
- * Manager in charge of collecting the amount of entropy required to perform
- * secure random operations.
- *
- * @class EntropyManager
- * @private
- * @param {Object}
- * [options] An object containing optional arguments.
- *
- * <p>
- * This object can be used to override the default values of the entropy
- * manager, by making use of the following fields. For further explanation, see
- * the README.md file for this module.
- * <ul>
- * <li>maxEntropyCounter</li>
- * <li>hashUpdaterInterval</li>
- * <li>windowObject</li>
- * <li>cryptoApi</li>
- * <li>privateKeyData</li>
- * <li>noDefaultCollectors</li>
- * </ul>
- * <p>
- */
- function EntropyManager(options) {
- ////////////////////////////////// PRIVATE //////////////////////////////////
- var that = this;
- var tools_;
- var maxEntropyCounter_;
- var hashUpdaterInterval_;
- var windowObject_;
- var cryptoApi_;
- var privateKeyData_;
- var collectors_ = [];
- var events_ = [];
- var collectorHashUpdater_ = null;
- var entropyHashHex_ = '';
- var entropyCounter_ = 0;
- var isCollecting_ = false;
- var fullEntropyReached_ = false;
- var fullEntropyCallback_ = null;
- /**
- * Initializes the entropy manager.
- *
- * @function init
- * @memberof EntropyManager
- * @param {Object}
- * [options] The optional arguments passed by the constructor to
- * override the default values of the entropy manager
- */
- function init(options) {
- tools_ = new Tools();
- // Optional arguments.
- options = options || {};
- // Amount of entropy to accumulate before stopping collectors. Default is
- // 256.
- maxEntropyCounter_ = options.maxEntropyCounter || 256;
- // Interval (in milliseconds) at which entropy data is to be hashed. Default
- // is 1 second.
- hashUpdaterInterval_ = options.hashUpdaterInterval || 1000;
- // "window" object provided to collectors. Default is built-in "window"
- // object.
- windowObject_ =
- options.windowObject || (typeof window !== 'undefined') ? window : null;
- // Cryptographic library supplied to WebKit collector (if being used).
- // Default is user's built-in cryptographic library.
- cryptoApi_ = options.cryptoApi ||
- (windowObject_ ? (windowObject_.crypto || windowObject_.msCrypto) :
- null);
- // Private key collector data. By default, this collector is not used.
- privateKeyData_ = options.privateKeyData || null;
- // Set up standard set of collectors, unless specified not to.
- if (!options.noDefaultCollectors) {
- // Set up the default collectors.
- setupCollectors();
- }
- }
- /**
- * Sets up all of the entropy collectors to be used. If WebKit is available,
- * its entropy collector will be used exclusively. Otherwise, a combination
- * of other types of collectors will used, including the ajax calls
- * collector, JS calls execution collector, navigator information collector,
- * math random collector, private key collector, mouse events collectors,
- * key events collectors, load event collector and scroll collector.
- *
- * @function setupCollectors
- * @memberof EntropyManager
- */
- function setupCollectors() {
- if (!windowObject_) {
- addNonWindowCollectors();
- return;
- }
- if (cryptoApi_ && (cryptoApi_.getRandomValues)) {
- that.addCollector(new WebKitRandomCollector(
- collectEntropy, cryptoApi_, maxEntropyCounter_));
- } else {
- addNonWindowCollectors();
- // The API detects and delivers accelerometer data 50 times per
- // second
- if (window.DeviceOrientationEvent) {
- // Listen for the device orientation event and handle
- // DeviceOrientationEvent object
- that.addEvent(
- 'deviceorientation',
- new DeviceOrientationCollector(collectEntropy).handleEvent);
- } else if (window.OrientationEvent) {
- // Listen for the MozOrientation event and handle
- // OrientationData object
- that.addEvent(
- 'MozOrientation',
- new DeviceOrientationCollector(collectEntropy).handleEvent);
- }
- if (window.DeviceMotionEvent) {
- that.addEvent(
- 'devicemotion',
- new DeviceMotionCollector(collectEntropy).handleEvent);
- }
- }
- }
- /**
- * Registers the entropy collectors that do not need a window object to
- * work.
- *
- * @function addNonWindowCollectors
- * @memberof EntropyManager
- */
- function addNonWindowCollectors() {
- addCollectors([
- AjaxCollector, JSExecutionCollector, NavigatorInfoCollector,
- MathRandomCollector
- ]);
- if (privateKeyData_) {
- collectors_.push(new PrivateKeyCollector(
- collectEntropy, privateKeyData_.privateKeyPem,
- privateKeyData_.pinEntropy));
- }
- addEvents({
- 'mousemove': MouseMoveCollector,
- 'mousewheel': MouseWheelCollector,
- 'mouseup': MouseButtonCollector,
- 'mousedown': MouseButtonCollector,
- 'touchstart': TouchStartCollector,
- 'touchmove': TouchMoveCollector,
- 'touchend': TouchEndCollector,
- 'gesturestart': TouchStartCollector,
- 'gestureend': TouchEndCollector,
- 'keyup': KeyCollector,
- 'keydown': KeyCollector,
- 'load': KeyCollector,
- 'scroll': KeyCollector,
- 'beforeload': KeyCollector,
- });
- }
- /**
- * Registers a set of entropy collectors from an array of their
- * constructors.
- *
- * @function addCollectors
- * @memberof EntropyManager
- * @param {Array}
- * collectors The array of entropy collector constructors.
- */
- function addCollectors(collectors) {
- for (var i = 0; i < collectors.length; ++i) {
- collectors_.push(new collectors[i](collectEntropy));
- }
- }
- /**
- * Registers a set of entropy events from a map of their constructors, using
- * the event names as keys.
- *
- * @function addEvents
- * @memberof EntropyManager
- * @param {Object}
- * events The map of entropy event constructors, using the event
- * names as keys.
- */
- function addEvents(events) {
- for (var i = 0, eventNames = Object.keys(events), size = eventNames.length;
- i < size; i++) {
- that.addEvent(
- eventNames[i],
- (new events[eventNames[i]](collectEntropy)).handleEvent);
- }
- }
- /**
- * Registers an entropy collector. The "collector" argument can be an
- * object, in which case it will be added directly to the collector array,
- * or it can be a constructor, in which case it will be instantiated and the
- * resulting object will be added to the collector array.
- *
- * @function addCollector
- * @memberof EntropyManager
- * @param {Object}
- * collector An object or constructor that implements the
- * "startCollector" and "stopCollector" methods.
- * @returns {Object} The instantiation of the entropy collector that was
- * registered.
- */
- this.addCollector = function(Collector) {
- if (typeof Collector === 'function') {
- // If a constructor is passed, create a new object and link it to the
- // entropy collector.
- Collector = new Collector(collectEntropy);
- }
- collectors_.push(Collector);
- return Collector;
- };
- /**
- * Registers a new event.
- *
- * @function addEvent
- * @memberof EntropyManager
- * @param {string}
- * type The event type.
- * @param {function}
- * listener The event listener.
- */
- this.addEvent = function(type, listener) {
- events_.push({'type': type, 'listener': listener});
- };
- /**
- * Adds entropy to the entropy collected so far, and increments the entropy
- * counter. This method is usually called internally by the entropy collectors
- * and the events but is made public here for use as a callback function when
- * adding individual collectors via the secure random service.
- *
- * @function collectEntropy
- * @memberof EntropyManager
- * @param {string}
- * data The entropy hash data to be added.
- * @param {number}
- * increment The entropy counter increment to be added.
- */
- this.collectEntropy = function(data, increment) {
- return collectEntropy(data, increment);
- };
- /**
- * Adds entropy to the entropy collected so far, and increments the entropy
- * counter. This method is usually called by the entropy collectors and the
- * events.
- *
- * @function collectEntropy
- * @memberof EntropyManager
- * @param {string}
- * data The entropy hash data to be added.
- * @param {number}
- * increment The entropy counter increment to be added.
- */
- function collectEntropy(data, increment) {
- entropyHashHex_ += data;
- entropyCounter_ += (increment ? increment : 0);
- // if the entropy counter has reached the limit, update the hash and stop
- // the collectors
- if (isCollecting_ && (entropyCounter_ >= maxEntropyCounter_)) {
- // Full entropy reached.
- fullEntropyReached_ = true;
- // Stop collecting entropy.
- that.stopCollectors();
- // Make the full entropy callback, if present.
- if (typeof fullEntropyCallback_ === 'function') {
- fullEntropyCallback_();
- }
- }
- }
- /**
- * Updates the entropy hash, using the entropy collected so far.
- *
- * @function updateEntropyHash
- * @memberof EntropyManager
- */
- function updateEntropyHash() {
- var digester = tools_.getUpdateDigester();
- digester.update(entropyHashHex_);
- entropyHashHex_ =
- codec.hexEncode(codec.binaryDecode(digester.digest().getBytes()));
- }
- /////////////////////////////////// PUBLIC ///////////////////////////////////
- /**
- * Starts the entropy collectors.
- *
- * @function startCollectors
- * @memberof EntropyManager
- * @param {function}
- * fullEntropyCallback The function that is called when the
- * required amount of entropy has been collected.
- */
- this.startCollectors = function(fullEntropyCallback) {
- if (isCollecting_) {
- // Entropy collection is in progress -- nothing to do.
- return;
- } else {
- isCollecting_ = true;
- }
- if (fullEntropyCallback) {
- // Assign the full entropy callback.
- fullEntropyCallback_ = fullEntropyCallback;
- }
- var i = 0;
- // start all the collectors
- while (i < collectors_.length) {
- try {
- collectors_[i].startCollector();
- } catch (e) {
- // do not do anything about any exception that is thrown
- // by the execution of the collectors
- }
- i++;
- }
- i = 0;
- // start all the events
- while (i < events_.length) {
- try {
- if (window.addEventListener) {
- window.addEventListener(events_[i].type, events_[i].listener, false);
- } else if (document.attachEvent) {
- document.attachEvent('on' + events_[i].type, events_[i].listener);
- }
- } catch (e) {
- // do not do anything about any exception that is thrown
- // by the execution of the events
- }
- i++;
- }
- tools_.getUpdateDigester().start();
- collectorHashUpdater_ = setInterval(function() {
- updateEntropyHash();
- }, hashUpdaterInterval_);
- };
- /**
- * Stops the entropy collectors. This method will have no effect if the
- * required amount of entropy has already been collected.
- *
- * @function stopCollectors
- * @memberof EntropyManager
- * @returns {boolean} True if the required amount of entropy has been
- * collected, false otherwise.
- */
- this.stopCollectors = function() {
- if (!isCollecting_) {
- // Entropy collection is not in progress -- nothing to do.
- if (entropyCounter_ >= maxEntropyCounter_) {
- return true;
- } else {
- return false;
- }
- } else {
- isCollecting_ = false;
- }
- var i = 0;
- while (i < collectors_.length) {
- try {
- collectors_[i].stopCollector();
- } catch (e) {
- // do not do anything about any exception that is thrown
- // by the execution of the collectors
- }
- i++;
- }
- i = 0;
- while (i < events_.length) {
- try {
- if (window.removeEventListener) {
- window.removeEventListener(
- events_[i].type, events_[i].listener, false);
- } else if (document.detachEvent) {
- document.detachEvent('on' + events_[i].type, events_[i].listener);
- }
- } catch (e) {
- // do not do anything about any exception that is thrown
- // by the removal of the events
- }
- i++;
- }
- if (collectorHashUpdater_) {
- clearInterval(collectorHashUpdater_);
- }
- updateEntropyHash();
- return fullEntropyReached_;
- };
- /**
- * @function getCollectors.
- * @memberof EntropyManager
- * @returns {Array} The array of collectors currently in use.
- */
- this.getCollectors = function() {
- return collectors_;
- };
- /**
- * @function getEntropyHash
- * @memberof EntropyManager
- * @returns {Uint8Array} The hash of entropy that has been collected so far.
- */
- this.getEntropyHash = function() {
- return codec.hexDecode(entropyHashHex_);
- };
- /**
- * @function getEntropyPercentage returns {number} The percentage of the
- * required entropy that has been collected so far.
- * @memberof EntropyManager
- */
- this.getEntropyPercentage = function() {
- if (entropyCounter_ >= maxEntropyCounter_) {
- return 100;
- }
- return entropyCounter_ * 100 / maxEntropyCounter_;
- };
- /**
- * @function isCollecting
- * @memberof EntropyManager
- * @returns {boolean} True if entropy is still being collected, false
- * otherwise.
- */
- this.isCollecting = function() {
- return isCollecting_;
- };
- //////////////////////////////// CONSTRUCTOR ////////////////////////////////
- // Initialize the object.
- init(options);
- }
|