1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview A multiple gnubby signer wraps the process of opening a number
7 * of gnubbies, signing each challenge in an array of challenges until a
8 * success condition is satisfied, and yielding each succeeding gnubby.
13 * Creates a new sign handler with an array of gnubby indexes.
14 * @param {!GnubbyFactory} factory Used to create and open the gnubbies.
15 * @param {Array.<llGnubbyDeviceId>} gnubbyIndexes Which gnubbies to open.
16 * @param {boolean} forEnroll Whether this signer is signing for an attempted
18 * @param {function(boolean, (number|undefined))} completedCb Called when this
19 * signer completes sign attempts, i.e. no further results should be
21 * @param {function(number, MultipleSignerResult)} gnubbyFoundCb Called with
22 * each gnubby/challenge that yields a successful result.
23 * @param {Countdown=} opt_timer An advisory timer, beyond whose expiration the
24 * signer will not attempt any new operations, assuming the caller is no
25 * longer interested in the outcome.
26 * @param {string=} opt_logMsgUrl A URL to post log messages to.
29 function MultipleGnubbySigner(factory, gnubbyIndexes, forEnroll, completedCb,
30 gnubbyFoundCb, opt_timer, opt_logMsgUrl) {
31 /** @private {!GnubbyFactory} */
32 this.factory_ = factory;
33 /** @private {Array.<llGnubbyDeviceId>} */
34 this.gnubbyIndexes_ = gnubbyIndexes;
35 /** @private {boolean} */
36 this.forEnroll_ = forEnroll;
37 /** @private {function(boolean, (number|undefined))} */
38 this.completedCb_ = completedCb;
39 /** @private {function(number, MultipleSignerResult)} */
40 this.gnubbyFoundCb_ = gnubbyFoundCb;
41 /** @private {Countdown|undefined} */
42 this.timer_ = opt_timer;
43 /** @private {string|undefined} */
44 this.logMsgUrl_ = opt_logMsgUrl;
46 /** @private {Array.<SignHelperChallenge>} */
47 this.challenges_ = [];
48 /** @private {boolean} */
49 this.challengesFinal_ = false;
51 // Create a signer for each gnubby.
52 /** @private {boolean} */
53 this.anySucceeded_ = false;
54 /** @private {number} */
55 this.numComplete_ = 0;
56 /** @private {Array.<SingleGnubbySigner>} */
58 /** @private {Array.<boolean>} */
59 this.stillGoing_ = [];
60 /** @private {Array.<number>} */
61 this.errorStatus_ = [];
62 for (var i = 0; i < gnubbyIndexes.length; i++) {
63 this.addGnubby(gnubbyIndexes[i]);
68 * Attempts to open this signer's gnubbies, if they're not already open.
69 * (This is implicitly done by addChallenges.)
71 MultipleGnubbySigner.prototype.open = function() {
72 for (var i = 0; i < this.signers_.length; i++) {
73 this.signers_[i].open();
78 * Closes this signer's gnubbies, if any are open.
80 MultipleGnubbySigner.prototype.close = function() {
81 for (var i = 0; i < this.signers_.length; i++) {
82 this.signers_[i].close();
87 * Adds challenges to the set of challenges being tried by this signer.
88 * The challenges are an array of challenge objects, where each challenge
89 * object's values are base64-encoded.
90 * If the signer is currently idle, begins signing the new challenges.
92 * @param {Array} challenges Encoded challenges
93 * @param {boolean} finalChallenges True iff there are no more challenges to add
94 * @return {boolean} whether the challenges were successfully added.
96 MultipleGnubbySigner.prototype.addEncodedChallenges =
97 function(challenges, finalChallenges) {
98 var decodedChallenges = [];
100 for (var i = 0; i < challenges.length; i++) {
101 var decodedChallenge = {};
102 var challenge = challenges[i];
103 decodedChallenge['challengeHash'] =
104 B64_decode(challenge['challengeHash']);
105 decodedChallenge['appIdHash'] = B64_decode(challenge['appIdHash']);
106 decodedChallenge['keyHandle'] = B64_decode(challenge['keyHandle']);
107 if (challenge['version']) {
108 decodedChallenge['version'] = challenge['version'];
110 decodedChallenges.push(decodedChallenge);
113 return this.addChallenges(decodedChallenges, finalChallenges);
117 * Adds challenges to the set of challenges being tried by this signer.
118 * If the signer is currently idle, begins signing the new challenges.
120 * @param {Array.<SignHelperChallenge>} challenges Challenges to add
121 * @param {boolean} finalChallenges True iff there are no more challnges to add
122 * @return {boolean} whether the challenges were successfully added.
124 MultipleGnubbySigner.prototype.addChallenges =
125 function(challenges, finalChallenges) {
126 if (this.challengesFinal_) {
127 // Can't add new challenges once they're finalized.
132 for (var i = 0; i < challenges.length; i++) {
133 this.challenges_.push(challenges[i]);
136 this.challengesFinal_ = finalChallenges;
138 for (var i = 0; i < this.signers_.length; i++) {
139 this.stillGoing_[i] =
140 this.signers_[i].addChallenges(challenges, finalChallenges);
141 this.errorStatus_[i] = 0;
147 * Adds a new gnubby to this signer's list of gnubbies. (Only possible while
148 * this signer is still signing: without this restriction, the morePossible
149 * indication in the callbacks could become violated.) If this signer has
150 * challenges to sign, begins signing on the new gnubby with them.
151 * @param {llGnubbyDeviceId} gnubbyIndex The index of the gnubby to add.
152 * @return {boolean} Whether the gnubby was added successfully.
154 MultipleGnubbySigner.prototype.addGnubby = function(gnubbyIndex) {
155 if (this.numComplete_ && this.numComplete_ == this.signers_.length)
158 var index = this.signers_.length;
160 new SingleGnubbySigner(
164 this.signFailedCallback_.bind(this, index),
165 this.signSucceededCallback_.bind(this, index),
166 this.timer_ ? this.timer_.clone() : null,
168 this.stillGoing_.push(false);
170 if (this.challenges_.length) {
171 this.stillGoing_[index] =
172 this.signers_[index].addChallenges(this.challenges_,
173 this.challengesFinal_);
179 * Called by a SingleGnubbySigner upon failure, i.e. unsuccessful completion of
180 * all its sign operations.
181 * @param {number} index the index of the gnubby whose result this is
182 * @param {number} code the result code of the sign operation
185 MultipleGnubbySigner.prototype.signFailedCallback_ = function(index, code) {
187 UTIL_fmt('failure. gnubby ' + index + ' got code ' + code.toString(16)));
188 if (!this.stillGoing_[index]) {
189 console.log(UTIL_fmt('gnubby ' + index + ' no longer running!'));
190 // Shouldn't ever happen? Disregard.
193 this.stillGoing_[index] = false;
194 this.errorStatus_[index] = code;
196 var morePossible = this.numComplete_ < this.signers_.length;
198 this.notifyComplete_();
202 * Called by a SingleGnubbySigner upon success.
203 * @param {number} index the index of the gnubby whose result this is
204 * @param {usbGnubby} gnubby the underlying gnubby that succeded.
205 * @param {number} code the result code of the sign operation
206 * @param {SingleSignerResult=} signResult Result object
209 MultipleGnubbySigner.prototype.signSucceededCallback_ =
210 function(index, gnubby, code, signResult) {
211 console.log(UTIL_fmt('success! gnubby ' + index + ' got code ' +
213 if (!this.stillGoing_[index]) {
214 console.log(UTIL_fmt('gnubby ' + index + ' no longer running!'));
215 // Shouldn't ever happen? Disregard.
218 this.anySucceeded_ = true;
219 this.stillGoing_[index] = false;
220 this.notifySuccess_(code, gnubby, index, signResult);
222 var morePossible = this.numComplete_ < this.signers_.length;
224 this.notifyComplete_();
230 MultipleGnubbySigner.prototype.notifyComplete_ = function() {
231 // See if any of the signers failed with a strange error. If so, report a
232 // single error to the caller, partly as a diagnostic aid and partly to
233 // distinguish real failures from wrong data.
235 for (var i = 0; i < this.errorStatus_.length; i++) {
236 if (this.errorStatus_[i] &&
237 this.errorStatus_[i] != DeviceStatusCodes.WRONG_DATA_STATUS &&
238 this.errorStatus_[i] != DeviceStatusCodes.WAIT_TOUCH_STATUS) {
239 funnyBusiness = this.errorStatus_[i];
244 console.warn(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ', ' +
245 'funny error = ' + funnyBusiness + ')'));
247 console.log(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ')'));
249 this.completedCb_(this.anySucceeded_, funnyBusiness);
253 * @param {number} code Success status code
254 * @param {usbGnubby} gnubby The gnubby that succeeded
255 * @param {number} gnubbyIndex The gnubby's index
256 * @param {SingleSignerResult=} singleSignerResult Result object
259 MultipleGnubbySigner.prototype.notifySuccess_ =
260 function(code, gnubby, gnubbyIndex, singleSignerResult) {
261 console.log(UTIL_fmt('success (' + code.toString(16) + ')'));
264 'gnubbyIndex': gnubbyIndex
266 if (singleSignerResult && singleSignerResult['challenge'])
267 signResult['challenge'] = singleSignerResult['challenge'];
268 if (singleSignerResult && singleSignerResult['info'])
269 signResult['info'] = singleSignerResult['info'];
270 this.gnubbyFoundCb_(code, signResult);