Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(29)

Unified Diff: chrome/browser/resources/cryptotoken/usbsignhelper.js

Issue 249913002: FIDO U2F component extension (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge with HEAD Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+};
« no previous file with comments | « chrome/browser/resources/cryptotoken/usbgnubbyfactory.js ('k') | chrome/browser/resources/cryptotoken/util.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698