| Index: chrome/browser/resources/cryptotoken/usbsignhelper.js
|
| diff --git a/chrome/browser/resources/cryptotoken/usbsignhelper.js b/chrome/browser/resources/cryptotoken/usbsignhelper.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a37ecb7dd391648e68a3f70fb3e8cb3b369fd135
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/cryptotoken/usbsignhelper.js
|
| @@ -0,0 +1,344 @@
|
| +// Copyright (c) 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview Implements a sign helper using USB gnubbies.
|
| + * @author juanlang@google.com (Juan Lang)
|
| + */
|
| +'use strict';
|
| +
|
| +var CORRUPT_sign = false;
|
| +
|
| +/**
|
| + * @param {!GnubbyFactory} factory
|
| + * @param {Countdown} timer Timer after whose expiration the caller is no longer
|
| + * interested in the result of a sign request.
|
| + * @param {function(number, boolean)} errorCb Called when a sign request fails
|
| + * with an error code and whether any gnubbies were found.
|
| + * @param {function(SignHelperChallenge, string)} successCb Called with the
|
| + * signature produced by a successful sign request.
|
| + * @param {(function(number, boolean)|undefined)} opt_progressCb Called with
|
| + * progress updates to the sign request.
|
| + * @param {string=} opt_logMsgUrl A URL to post log messages to.
|
| + * @constructor
|
| + * @implements {SignHelper}
|
| + */
|
| +function UsbSignHelper(factory, timer, errorCb, successCb, opt_progressCb,
|
| + opt_logMsgUrl) {
|
| + /** @private {!GnubbyFactory} */
|
| + this.factory_ = factory;
|
| + /** @private {Countdown} */
|
| + this.timer_ = timer;
|
| + /** @private {function(number, boolean)} */
|
| + this.errorCb_ = errorCb;
|
| + /** @private {function(SignHelperChallenge, string)} */
|
| + this.successCb_ = successCb;
|
| + /** @private {string|undefined} */
|
| + this.logMsgUrl_ = opt_logMsgUrl;
|
| +
|
| + /** @private {Array.<SignHelperChallenge>} */
|
| + this.pendingChallenges_ = [];
|
| + /** @private {Array.<usbGnubby>} */
|
| + this.waitingForTouchGnubbies_ = [];
|
| +
|
| + /** @private {boolean} */
|
| + this.notified_ = false;
|
| + /** @private {boolean} */
|
| + this.signerComplete_ = false;
|
| +}
|
| +
|
| +/**
|
| + * Attempts to sign the provided challenges.
|
| + * @param {Array.<SignHelperChallenge>} challenges
|
| + * @return {boolean} whether this set of challenges was accepted.
|
| + */
|
| +UsbSignHelper.prototype.doSign = function(challenges) {
|
| + if (!challenges.length) {
|
| + // Fail a sign request with an empty set of challenges, and pretend to have
|
| + // alerted the caller in case the enumerate is still pending.
|
| + this.notified_ = true;
|
| + return false;
|
| + } else {
|
| + this.pendingChallenges_ = challenges;
|
| + this.getSomeGnubbies_();
|
| + return true;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Enumerates gnubbies, and begins processing challenges upon enumeration if
|
| + * any gnubbies are found.
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.getSomeGnubbies_ = function() {
|
| + this.factory_.enumerate(this.enumerateCallback.bind(this));
|
| +};
|
| +
|
| +/**
|
| + * Called with the result of enumerating gnubbies.
|
| + * @param {number} rc the result of the enumerate.
|
| + * @param {Array.<llGnubbyDeviceId>} indexes
|
| + */
|
| +UsbSignHelper.prototype.enumerateCallback = function(rc, indexes) {
|
| + if (rc) {
|
| + this.notifyError_(rc, false);
|
| + return;
|
| + }
|
| + if (!indexes.length) {
|
| + this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
|
| + return;
|
| + }
|
| + if (this.timer_.expired()) {
|
| + this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS, true);
|
| + return;
|
| + }
|
| + this.gotSomeGnubbies_(indexes);
|
| +};
|
| +
|
| +/**
|
| + * Called with the result of enumerating gnubby indexes.
|
| + * @param {Array.<llGnubbyDeviceId>} indexes
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.gotSomeGnubbies_ = function(indexes) {
|
| + /** @private {MultipleGnubbySigner} */
|
| + this.signer_ = new MultipleGnubbySigner(
|
| + this.factory_,
|
| + indexes,
|
| + false /* forEnroll */,
|
| + this.signerCompleted_.bind(this),
|
| + this.signerFoundGnubby_.bind(this),
|
| + this.timer_,
|
| + this.logMsgUrl_);
|
| + this.signer_.addEncodedChallenges(this.pendingChallenges_, true);
|
| +};
|
| +
|
| +/**
|
| + * Called when a MultipleGnubbySigner completes its sign request.
|
| + * @param {boolean} anySucceeded whether any sign attempt completed
|
| + * successfully.
|
| + * @param {number=} errorCode an error code from a failing gnubby, if one was
|
| + * found.
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.signerCompleted_ = function(anySucceeded, errorCode) {
|
| + this.signerComplete_ = true;
|
| + // The signer is not created unless some gnubbies were enumerated, so
|
| + // anyGnubbies is mostly always true. The exception is when the last gnubby is
|
| + // removed, handled shortly.
|
| + var anyGnubbies = true;
|
| + if (!anySucceeded) {
|
| + if (!errorCode) {
|
| + errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
|
| + } else if (errorCode == -llGnubby.GONE) {
|
| + // If the last gnubby was removed, report as though no gnubbies were
|
| + // found.
|
| + errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
|
| + anyGnubbies = false;
|
| + }
|
| + this.notifyError_(errorCode, anyGnubbies);
|
| + } else if (this.anyTimeout_) {
|
| + // Some previously succeeding gnubby timed out: return its error code.
|
| + this.notifyError_(this.timeoutError_, anyGnubbies);
|
| + } else {
|
| + // Do nothing: signerFoundGnubby_ will have been called with each
|
| + // succeeding gnubby.
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Called when a MultipleGnubbySigner finds a gnubby that has successfully
|
| + * signed, or can successfully sign, one of the challenges.
|
| + * @param {number} code
|
| + * @param {MultipleSignerResult} signResult
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.signerFoundGnubby_ = function(code, signResult) {
|
| + var gnubby = signResult['gnubby'];
|
| + var challenge = signResult['challenge'];
|
| + var info = new Uint8Array(signResult['info']);
|
| + if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
|
| + this.notifySuccess_(gnubby, challenge, info);
|
| + } else {
|
| + this.waitingForTouchGnubbies_.push(gnubby);
|
| + this.retrySignIfNotTimedOut_(gnubby, challenge, code);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Reports the result of a successful sign operation.
|
| + * @param {usbGnubby} gnubby
|
| + * @param {SignHelperChallenge} challenge
|
| + * @param {Uint8Array} info
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.notifySuccess_ = function(gnubby, challenge, info) {
|
| + if (this.notified_)
|
| + return;
|
| + this.notified_ = true;
|
| +
|
| + gnubby.closeWhenIdle();
|
| + this.close();
|
| +
|
| + if (CORRUPT_sign) {
|
| + CORRUPT_sign = false;
|
| + info[info.length - 1] = info[info.length - 1] ^ 0xff;
|
| + }
|
| + var encodedChallenge = {};
|
| + encodedChallenge['challengeHash'] = B64_encode(challenge['challengeHash']);
|
| + encodedChallenge['appIdHash'] = B64_encode(challenge['appIdHash']);
|
| + encodedChallenge['keyHandle'] = B64_encode(challenge['keyHandle']);
|
| + this.successCb_(
|
| + /** @type {SignHelperChallenge} */ (encodedChallenge), B64_encode(info));
|
| +};
|
| +
|
| +/**
|
| + * Reports error to the caller.
|
| + * @param {number} code error to report
|
| + * @param {boolean} anyGnubbies
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.notifyError_ = function(code, anyGnubbies) {
|
| + if (this.notified_)
|
| + return;
|
| + this.notified_ = true;
|
| + this.close();
|
| + this.errorCb_(code, anyGnubbies);
|
| +};
|
| +
|
| +/**
|
| + * Retries signing a particular challenge on a gnubby.
|
| + * @param {usbGnubby} gnubby
|
| + * @param {SignHelperChallenge} challenge
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.retrySign_ = function(gnubby, challenge) {
|
| + var challengeHash = challenge['challengeHash'];
|
| + var appIdHash = challenge['appIdHash'];
|
| + var keyHandle = challenge['keyHandle'];
|
| + gnubby.sign(challengeHash, appIdHash, keyHandle,
|
| + this.signCallback_.bind(this, gnubby, challenge));
|
| +};
|
| +
|
| +/**
|
| + * Called when a gnubby completes a sign request.
|
| + * @param {usbGnubby} gnubby
|
| + * @param {SignHelperChallenge} challenge
|
| + * @param {number} code
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.retrySignIfNotTimedOut_ =
|
| + function(gnubby, challenge, code) {
|
| + if (this.timer_.expired()) {
|
| + // Store any timeout error code, to be returned from the complete
|
| + // callback if no other eligible gnubbies are found.
|
| + /** @private {boolean} */
|
| + this.anyTimeout_ = true;
|
| + /** @private {number} */
|
| + this.timeoutError_ = code;
|
| + this.removePreviouslyEligibleGnubby_(gnubby, code);
|
| + } else {
|
| + window.setTimeout(this.retrySign_.bind(this, gnubby, challenge), 200);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Removes a gnubby that was waiting for touch from the list, with the given
|
| + * error code. If this is the last gnubby, notifies the caller of the error.
|
| + * @param {usbGnubby} gnubby
|
| + * @param {number} code
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.removePreviouslyEligibleGnubby_ =
|
| + function(gnubby, code) {
|
| + // Close this gnubby.
|
| + gnubby.closeWhenIdle();
|
| + var index = this.waitingForTouchGnubbies_.indexOf(gnubby);
|
| + if (index >= 0) {
|
| + this.waitingForTouchGnubbies_.splice(index, 1);
|
| + }
|
| + if (!this.waitingForTouchGnubbies_.length && this.signerComplete_ &&
|
| + !this.notified_) {
|
| + // Last sign attempt is complete: return this error.
|
| + console.log(UTIL_fmt('timeout or error (' + code.toString(16) +
|
| + ') signing'));
|
| + // If the last device is gone, report as if no gnubbies were found.
|
| + if (code == -llGnubby.GONE) {
|
| + this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
|
| + return;
|
| + }
|
| + this.notifyError_(code, true);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Called when a gnubby completes a sign request.
|
| + * @param {usbGnubby} gnubby
|
| + * @param {SignHelperChallenge} challenge
|
| + * @param {number} code
|
| + * @param {ArrayBuffer=} infoArray
|
| + * @private
|
| + */
|
| +UsbSignHelper.prototype.signCallback_ =
|
| + function(gnubby, challenge, code, infoArray) {
|
| + if (this.notified_) {
|
| + // Individual sign completed after previous success or failure. Disregard.
|
| + return;
|
| + }
|
| + var info = new Uint8Array(infoArray || []);
|
| + if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
|
| + this.notifySuccess_(gnubby, challenge, info);
|
| + } else if (code == DeviceStatusCodes.OK_STATUS ||
|
| + code == DeviceStatusCodes.WAIT_TOUCH_STATUS ||
|
| + code == DeviceStatusCodes.BUSY_STATUS) {
|
| + this.retrySignIfNotTimedOut_(gnubby, challenge, code);
|
| + } else {
|
| + console.log(UTIL_fmt('got error ' + code.toString(16) + ' signing'));
|
| + this.removePreviouslyEligibleGnubby_(gnubby, code);
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Closes the MultipleGnubbySigner, if any.
|
| + */
|
| +UsbSignHelper.prototype.close = function() {
|
| + if (this.signer_) {
|
| + this.signer_.close();
|
| + this.signer_ = null;
|
| + }
|
| + for (var i = 0; i < this.waitingForTouchGnubbies_.length; i++) {
|
| + this.waitingForTouchGnubbies_[i].closeWhenIdle();
|
| + }
|
| + this.waitingForTouchGnubbies_ = [];
|
| +};
|
| +
|
| +/**
|
| + * @param {!GnubbyFactory} gnubbyFactory Factory to create gnubbies.
|
| + * @constructor
|
| + * @implements {SignHelperFactory}
|
| + */
|
| +function UsbSignHelperFactory(gnubbyFactory) {
|
| + /** @private {!GnubbyFactory} */
|
| + this.gnubbyFactory_ = gnubbyFactory;
|
| +}
|
| +
|
| +/**
|
| + * @param {Countdown} timer Timer after whose expiration the caller is no longer
|
| + * interested in the result of a sign request.
|
| + * @param {function(number, boolean)} errorCb Called when a sign request fails
|
| + * with an error code and whether any gnubbies were found.
|
| + * @param {function(SignHelperChallenge, string)} successCb Called with the
|
| + * signature produced by a successful sign request.
|
| + * @param {(function(number, boolean)|undefined)} opt_progressCb Called with
|
| + * progress updates to the sign request.
|
| + * @param {string=} opt_logMsgUrl A URL to post log messages to.
|
| + * @return {UsbSignHelper} the newly created helper.
|
| + */
|
| +UsbSignHelperFactory.prototype.createHelper =
|
| + function(timer, errorCb, successCb, opt_progressCb, opt_logMsgUrl) {
|
| + var helper =
|
| + new UsbSignHelper(this.gnubbyFactory_, timer, errorCb, successCb,
|
| + opt_progressCb, opt_logMsgUrl);
|
| + return helper;
|
| +};
|
|
|