Index: chrome/browser/resources/cryptotoken/multiplesigner.js |
diff --git a/chrome/browser/resources/cryptotoken/multiplesigner.js b/chrome/browser/resources/cryptotoken/multiplesigner.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..86d5e1a8d95e623fad4a23667fcc631ec2551b42 |
--- /dev/null |
+++ b/chrome/browser/resources/cryptotoken/multiplesigner.js |
@@ -0,0 +1,276 @@ |
+// 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 A multiple gnubby signer wraps the process of opening a number |
+ * of gnubbies, signing each challenge in an array of challenges until a |
+ * success condition is satisfied, and yielding each succeeding gnubby. |
+ * |
+ * @author juanlang@google.com (Juan Lang) |
+ */ |
+'use strict'; |
+ |
+/** |
+ * Creates a new sign handler with an array of gnubby indexes. |
+ * @param {!GnubbyFactory} factory Used to create and open the gnubbies. |
+ * @param {Array.<llGnubbyDeviceId>} gnubbyIndexes Which gnubbies to open. |
+ * @param {boolean} forEnroll Whether this signer is signing for an attempted |
+ * enroll operation. |
+ * @param {function(boolean, (number|undefined))} completedCb Called when this |
+ * signer completes sign attempts, i.e. no further results should be |
+ * expected. |
+ * @param {function(number, MultipleSignerResult)} gnubbyFoundCb Called with |
+ * each gnubby/challenge that yields a successful result. |
+ * @param {Countdown=} opt_timer An advisory timer, beyond whose expiration the |
+ * signer will not attempt any new operations, assuming the caller is no |
+ * longer interested in the outcome. |
+ * @param {string=} opt_logMsgUrl A URL to post log messages to. |
+ * @constructor |
+ */ |
+function MultipleGnubbySigner(factory, gnubbyIndexes, forEnroll, completedCb, |
+ gnubbyFoundCb, opt_timer, opt_logMsgUrl) { |
+ /** @private {!GnubbyFactory} */ |
+ this.factory_ = factory; |
+ /** @private {Array.<llGnubbyDeviceId>} */ |
+ this.gnubbyIndexes_ = gnubbyIndexes; |
+ /** @private {boolean} */ |
+ this.forEnroll_ = forEnroll; |
+ /** @private {function(boolean, (number|undefined))} */ |
+ this.completedCb_ = completedCb; |
+ /** @private {function(number, MultipleSignerResult)} */ |
+ this.gnubbyFoundCb_ = gnubbyFoundCb; |
+ /** @private {Countdown|undefined} */ |
+ this.timer_ = opt_timer; |
+ /** @private {string|undefined} */ |
+ this.logMsgUrl_ = opt_logMsgUrl; |
+ |
+ /** @private {Array.<SignHelperChallenge>} */ |
+ this.challenges_ = []; |
+ /** @private {boolean} */ |
+ this.challengesFinal_ = false; |
+ |
+ // Create a signer for each gnubby. |
+ /** @private {boolean} */ |
+ this.anySucceeded_ = false; |
+ /** @private {number} */ |
+ this.numComplete_ = 0; |
+ /** @private {Array.<SingleGnubbySigner>} */ |
+ this.signers_ = []; |
+ /** @private {Array.<boolean>} */ |
+ this.stillGoing_ = []; |
+ /** @private {Array.<number>} */ |
+ this.errorStatus_ = []; |
+ for (var i = 0; i < gnubbyIndexes.length; i++) { |
+ this.addGnubby(gnubbyIndexes[i]); |
+ } |
+} |
+ |
+/** |
+ * Attempts to open this signer's gnubbies, if they're not already open. |
+ * (This is implicitly done by addChallenges.) |
+ */ |
+MultipleGnubbySigner.prototype.open = function() { |
+ for (var i = 0; i < this.signers_.length; i++) { |
+ this.signers_[i].open(); |
+ } |
+}; |
+ |
+/** |
+ * Closes this signer's gnubbies, if any are open. |
+ */ |
+MultipleGnubbySigner.prototype.close = function() { |
+ for (var i = 0; i < this.signers_.length; i++) { |
+ this.signers_[i].close(); |
+ } |
+}; |
+ |
+/** |
+ * Adds challenges to the set of challenges being tried by this signer. |
+ * The challenges are an array of challenge objects, where each challenge |
+ * object's values are base64-encoded. |
+ * If the signer is currently idle, begins signing the new challenges. |
+ * |
+ * @param {Array} challenges |
+ * @param {boolean} finalChallenges |
+ * @return {boolean} whether the challenges were successfully added. |
+ * @public |
+ */ |
+MultipleGnubbySigner.prototype.addEncodedChallenges = |
+ function(challenges, finalChallenges) { |
+ var decodedChallenges = []; |
+ if (challenges) { |
+ for (var i = 0; i < challenges.length; i++) { |
+ var decodedChallenge = {}; |
+ var challenge = challenges[i]; |
+ decodedChallenge['challengeHash'] = |
+ B64_decode(challenge['challengeHash']); |
+ decodedChallenge['appIdHash'] = B64_decode(challenge['appIdHash']); |
+ decodedChallenge['keyHandle'] = B64_decode(challenge['keyHandle']); |
+ if (challenge['version']) { |
+ decodedChallenge['version'] = challenge['version']; |
+ } |
+ decodedChallenges.push(decodedChallenge); |
+ } |
+ } |
+ return this.addChallenges(decodedChallenges, finalChallenges); |
+}; |
+ |
+/** |
+ * Adds challenges to the set of challenges being tried by this signer. |
+ * If the signer is currently idle, begins signing the new challenges. |
+ * |
+ * @param {Array.<SignHelperChallenge>} challenges |
+ * @param {boolean} finalChallenges |
+ * @return {boolean} whether the challenges were successfully added. |
+ * @public |
+ */ |
+MultipleGnubbySigner.prototype.addChallenges = |
+ function(challenges, finalChallenges) { |
+ if (this.challengesFinal_) { |
+ // Can't add new challenges once they're finalized. |
+ return false; |
+ } |
+ |
+ if (challenges) { |
+ for (var i = 0; i < challenges.length; i++) { |
+ this.challenges_.push(challenges[i]); |
+ } |
+ } |
+ this.challengesFinal_ = finalChallenges; |
+ |
+ for (var i = 0; i < this.signers_.length; i++) { |
+ this.stillGoing_[i] = |
+ this.signers_[i].addChallenges(challenges, finalChallenges); |
+ this.errorStatus_[i] = 0; |
+ } |
+ return true; |
+}; |
+ |
+/** |
+ * Adds a new gnubby to this signer's list of gnubbies. (Only possible while |
+ * this signer is still signing: without this restriction, the morePossible |
+ * indication in the callbacks could become violated.) If this signer has |
+ * challenges to sign, begins signing on the new gnubby with them. |
+ * @param {llGnubbyDeviceId} gnubbyIndex The index of the gnubby to add. |
+ * @return {boolean} Whether the gnubby was added successfully. |
+ * @public |
+ */ |
+MultipleGnubbySigner.prototype.addGnubby = function(gnubbyIndex) { |
+ if (this.numComplete_ && this.numComplete_ == this.signers_.length) |
+ return false; |
+ |
+ var index = this.signers_.length; |
+ this.signers_.push( |
+ new SingleGnubbySigner( |
+ this.factory_, |
+ gnubbyIndex, |
+ this.forEnroll_, |
+ this.signFailedCallback_.bind(this, index), |
+ this.signSucceededCallback_.bind(this, index), |
+ this.timer_ ? this.timer_.clone() : null, |
+ this.logMsgUrl_)); |
+ this.stillGoing_.push(false); |
+ |
+ if (this.challenges_.length) { |
+ this.stillGoing_[index] = |
+ this.signers_[index].addChallenges(this.challenges_, |
+ this.challengesFinal_); |
+ } |
+ return true; |
+}; |
+ |
+/** |
+ * Called by a SingleGnubbySigner upon failure, i.e. unsuccessful completion of |
+ * all its sign operations. |
+ * @param {number} index the index of the gnubby whose result this is |
+ * @param {number} code the result code of the sign operation |
+ * @private |
+ */ |
+MultipleGnubbySigner.prototype.signFailedCallback_ = function(index, code) { |
+ console.log( |
+ UTIL_fmt('failure. gnubby ' + index + ' got code ' + code.toString(16))); |
+ if (!this.stillGoing_[index]) { |
+ console.log(UTIL_fmt('gnubby ' + index + ' no longer running!')); |
+ // Shouldn't ever happen? Disregard. |
+ return; |
+ } |
+ this.stillGoing_[index] = false; |
+ this.errorStatus_[index] = code; |
+ this.numComplete_++; |
+ var morePossible = this.numComplete_ < this.signers_.length; |
+ if (!morePossible) |
+ this.notifyComplete_(); |
+}; |
+ |
+/** |
+ * Called by a SingleGnubbySigner upon success. |
+ * @param {number} index the index of the gnubby whose result this is |
+ * @param {usbGnubby} gnubby the underlying gnubby that succeded. |
+ * @param {number} code the result code of the sign operation |
+ * @param {SingleSignerResult=} signResult |
+ * @private |
+ */ |
+MultipleGnubbySigner.prototype.signSucceededCallback_ = |
+ function(index, gnubby, code, signResult) { |
+ console.log(UTIL_fmt('success! gnubby ' + index + ' got code ' + |
+ code.toString(16))); |
+ if (!this.stillGoing_[index]) { |
+ console.log(UTIL_fmt('gnubby ' + index + ' no longer running!')); |
+ // Shouldn't ever happen? Disregard. |
+ return; |
+ } |
+ this.anySucceeded_ = true; |
+ this.stillGoing_[index] = false; |
+ this.notifySuccess_(code, gnubby, index, signResult); |
+ this.numComplete_++; |
+ var morePossible = this.numComplete_ < this.signers_.length; |
+ if (!morePossible) |
+ this.notifyComplete_(); |
+}; |
+ |
+/** |
+ * @private |
+ */ |
+MultipleGnubbySigner.prototype.notifyComplete_ = function() { |
+ // See if any of the signers failed with a strange error. If so, report a |
+ // single error to the caller, partly as a diagnostic aid and partly to |
+ // distinguish real failures from wrong data. |
+ var funnyBusiness; |
+ for (var i = 0; i < this.errorStatus_.length; i++) { |
+ if (this.errorStatus_[i] && |
+ this.errorStatus_[i] != DeviceStatusCodes.WRONG_DATA_STATUS && |
+ this.errorStatus_[i] != DeviceStatusCodes.WAIT_TOUCH_STATUS) { |
+ funnyBusiness = this.errorStatus_[i]; |
+ break; |
+ } |
+ } |
+ if (funnyBusiness) { |
+ console.warn(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ', ' + |
+ 'funny error = ' + funnyBusiness + ')')); |
+ } else { |
+ console.log(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ')')); |
+ } |
+ this.completedCb_(this.anySucceeded_, funnyBusiness); |
+}; |
+ |
+/** |
+ * @param {number} code |
+ * @param {usbGnubby} gnubby |
+ * @param {number} gnubbyIndex |
+ * @param {SingleSignerResult=} singleSignerResult |
+ * @private |
+ */ |
+MultipleGnubbySigner.prototype.notifySuccess_ = |
+ function(code, gnubby, gnubbyIndex, singleSignerResult) { |
+ console.log(UTIL_fmt('success (' + code.toString(16) + ')')); |
+ var signResult = { |
+ 'gnubby': gnubby, |
+ 'gnubbyIndex': gnubbyIndex |
+ }; |
+ if (singleSignerResult && singleSignerResult['challenge']) |
+ signResult['challenge'] = singleSignerResult['challenge']; |
+ if (singleSignerResult && singleSignerResult['info']) |
+ signResult['info'] = singleSignerResult['info']; |
+ this.gnubbyFoundCb_(code, signResult); |
+}; |