OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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. |
| 4 |
| 5 /** |
| 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. |
| 9 * |
| 10 * @author juanlang@google.com (Juan Lang) |
| 11 */ |
| 12 'use strict'; |
| 13 |
| 14 /** |
| 15 * Creates a new sign handler with an array of gnubby indexes. |
| 16 * @param {!GnubbyFactory} factory Used to create and open the gnubbies. |
| 17 * @param {Array.<llGnubbyDeviceId>} gnubbyIndexes Which gnubbies to open. |
| 18 * @param {boolean} forEnroll Whether this signer is signing for an attempted |
| 19 * enroll operation. |
| 20 * @param {function(boolean, (number|undefined))} completedCb Called when this |
| 21 * signer completes sign attempts, i.e. no further results should be |
| 22 * expected. |
| 23 * @param {function(number, MultipleSignerResult)} gnubbyFoundCb Called with |
| 24 * each gnubby/challenge that yields a successful result. |
| 25 * @param {Countdown=} opt_timer An advisory timer, beyond whose expiration the |
| 26 * signer will not attempt any new operations, assuming the caller is no |
| 27 * longer interested in the outcome. |
| 28 * @param {string=} opt_logMsgUrl A URL to post log messages to. |
| 29 * @constructor |
| 30 */ |
| 31 function MultipleGnubbySigner(factory, gnubbyIndexes, forEnroll, completedCb, |
| 32 gnubbyFoundCb, opt_timer, opt_logMsgUrl) { |
| 33 /** @private {!GnubbyFactory} */ |
| 34 this.factory_ = factory; |
| 35 /** @private {Array.<llGnubbyDeviceId>} */ |
| 36 this.gnubbyIndexes_ = gnubbyIndexes; |
| 37 /** @private {boolean} */ |
| 38 this.forEnroll_ = forEnroll; |
| 39 /** @private {function(boolean, (number|undefined))} */ |
| 40 this.completedCb_ = completedCb; |
| 41 /** @private {function(number, MultipleSignerResult)} */ |
| 42 this.gnubbyFoundCb_ = gnubbyFoundCb; |
| 43 /** @private {Countdown|undefined} */ |
| 44 this.timer_ = opt_timer; |
| 45 /** @private {string|undefined} */ |
| 46 this.logMsgUrl_ = opt_logMsgUrl; |
| 47 |
| 48 /** @private {Array.<SignHelperChallenge>} */ |
| 49 this.challenges_ = []; |
| 50 /** @private {boolean} */ |
| 51 this.challengesFinal_ = false; |
| 52 |
| 53 // Create a signer for each gnubby. |
| 54 /** @private {boolean} */ |
| 55 this.anySucceeded_ = false; |
| 56 /** @private {number} */ |
| 57 this.numComplete_ = 0; |
| 58 /** @private {Array.<SingleGnubbySigner>} */ |
| 59 this.signers_ = []; |
| 60 /** @private {Array.<boolean>} */ |
| 61 this.stillGoing_ = []; |
| 62 /** @private {Array.<number>} */ |
| 63 this.errorStatus_ = []; |
| 64 for (var i = 0; i < gnubbyIndexes.length; i++) { |
| 65 this.addGnubby(gnubbyIndexes[i]); |
| 66 } |
| 67 } |
| 68 |
| 69 /** |
| 70 * Attempts to open this signer's gnubbies, if they're not already open. |
| 71 * (This is implicitly done by addChallenges.) |
| 72 */ |
| 73 MultipleGnubbySigner.prototype.open = function() { |
| 74 for (var i = 0; i < this.signers_.length; i++) { |
| 75 this.signers_[i].open(); |
| 76 } |
| 77 }; |
| 78 |
| 79 /** |
| 80 * Closes this signer's gnubbies, if any are open. |
| 81 */ |
| 82 MultipleGnubbySigner.prototype.close = function() { |
| 83 for (var i = 0; i < this.signers_.length; i++) { |
| 84 this.signers_[i].close(); |
| 85 } |
| 86 }; |
| 87 |
| 88 /** |
| 89 * Adds challenges to the set of challenges being tried by this signer. |
| 90 * The challenges are an array of challenge objects, where each challenge |
| 91 * object's values are base64-encoded. |
| 92 * If the signer is currently idle, begins signing the new challenges. |
| 93 * |
| 94 * @param {Array} challenges |
| 95 * @param {boolean} finalChallenges |
| 96 * @return {boolean} whether the challenges were successfully added. |
| 97 * @public |
| 98 */ |
| 99 MultipleGnubbySigner.prototype.addEncodedChallenges = |
| 100 function(challenges, finalChallenges) { |
| 101 var decodedChallenges = []; |
| 102 if (challenges) { |
| 103 for (var i = 0; i < challenges.length; i++) { |
| 104 var decodedChallenge = {}; |
| 105 var challenge = challenges[i]; |
| 106 decodedChallenge['challengeHash'] = |
| 107 B64_decode(challenge['challengeHash']); |
| 108 decodedChallenge['appIdHash'] = B64_decode(challenge['appIdHash']); |
| 109 decodedChallenge['keyHandle'] = B64_decode(challenge['keyHandle']); |
| 110 if (challenge['version']) { |
| 111 decodedChallenge['version'] = challenge['version']; |
| 112 } |
| 113 decodedChallenges.push(decodedChallenge); |
| 114 } |
| 115 } |
| 116 return this.addChallenges(decodedChallenges, finalChallenges); |
| 117 }; |
| 118 |
| 119 /** |
| 120 * Adds challenges to the set of challenges being tried by this signer. |
| 121 * If the signer is currently idle, begins signing the new challenges. |
| 122 * |
| 123 * @param {Array.<SignHelperChallenge>} challenges |
| 124 * @param {boolean} finalChallenges |
| 125 * @return {boolean} whether the challenges were successfully added. |
| 126 * @public |
| 127 */ |
| 128 MultipleGnubbySigner.prototype.addChallenges = |
| 129 function(challenges, finalChallenges) { |
| 130 if (this.challengesFinal_) { |
| 131 // Can't add new challenges once they're finalized. |
| 132 return false; |
| 133 } |
| 134 |
| 135 if (challenges) { |
| 136 for (var i = 0; i < challenges.length; i++) { |
| 137 this.challenges_.push(challenges[i]); |
| 138 } |
| 139 } |
| 140 this.challengesFinal_ = finalChallenges; |
| 141 |
| 142 for (var i = 0; i < this.signers_.length; i++) { |
| 143 this.stillGoing_[i] = |
| 144 this.signers_[i].addChallenges(challenges, finalChallenges); |
| 145 this.errorStatus_[i] = 0; |
| 146 } |
| 147 return true; |
| 148 }; |
| 149 |
| 150 /** |
| 151 * Adds a new gnubby to this signer's list of gnubbies. (Only possible while |
| 152 * this signer is still signing: without this restriction, the morePossible |
| 153 * indication in the callbacks could become violated.) If this signer has |
| 154 * challenges to sign, begins signing on the new gnubby with them. |
| 155 * @param {llGnubbyDeviceId} gnubbyIndex The index of the gnubby to add. |
| 156 * @return {boolean} Whether the gnubby was added successfully. |
| 157 * @public |
| 158 */ |
| 159 MultipleGnubbySigner.prototype.addGnubby = function(gnubbyIndex) { |
| 160 if (this.numComplete_ && this.numComplete_ == this.signers_.length) |
| 161 return false; |
| 162 |
| 163 var index = this.signers_.length; |
| 164 this.signers_.push( |
| 165 new SingleGnubbySigner( |
| 166 this.factory_, |
| 167 gnubbyIndex, |
| 168 this.forEnroll_, |
| 169 this.signFailedCallback_.bind(this, index), |
| 170 this.signSucceededCallback_.bind(this, index), |
| 171 this.timer_ ? this.timer_.clone() : null, |
| 172 this.logMsgUrl_)); |
| 173 this.stillGoing_.push(false); |
| 174 |
| 175 if (this.challenges_.length) { |
| 176 this.stillGoing_[index] = |
| 177 this.signers_[index].addChallenges(this.challenges_, |
| 178 this.challengesFinal_); |
| 179 } |
| 180 return true; |
| 181 }; |
| 182 |
| 183 /** |
| 184 * Called by a SingleGnubbySigner upon failure, i.e. unsuccessful completion of |
| 185 * all its sign operations. |
| 186 * @param {number} index the index of the gnubby whose result this is |
| 187 * @param {number} code the result code of the sign operation |
| 188 * @private |
| 189 */ |
| 190 MultipleGnubbySigner.prototype.signFailedCallback_ = function(index, code) { |
| 191 console.log( |
| 192 UTIL_fmt('failure. gnubby ' + index + ' got code ' + code.toString(16))); |
| 193 if (!this.stillGoing_[index]) { |
| 194 console.log(UTIL_fmt('gnubby ' + index + ' no longer running!')); |
| 195 // Shouldn't ever happen? Disregard. |
| 196 return; |
| 197 } |
| 198 this.stillGoing_[index] = false; |
| 199 this.errorStatus_[index] = code; |
| 200 this.numComplete_++; |
| 201 var morePossible = this.numComplete_ < this.signers_.length; |
| 202 if (!morePossible) |
| 203 this.notifyComplete_(); |
| 204 }; |
| 205 |
| 206 /** |
| 207 * Called by a SingleGnubbySigner upon success. |
| 208 * @param {number} index the index of the gnubby whose result this is |
| 209 * @param {usbGnubby} gnubby the underlying gnubby that succeded. |
| 210 * @param {number} code the result code of the sign operation |
| 211 * @param {SingleSignerResult=} signResult |
| 212 * @private |
| 213 */ |
| 214 MultipleGnubbySigner.prototype.signSucceededCallback_ = |
| 215 function(index, gnubby, code, signResult) { |
| 216 console.log(UTIL_fmt('success! gnubby ' + index + ' got code ' + |
| 217 code.toString(16))); |
| 218 if (!this.stillGoing_[index]) { |
| 219 console.log(UTIL_fmt('gnubby ' + index + ' no longer running!')); |
| 220 // Shouldn't ever happen? Disregard. |
| 221 return; |
| 222 } |
| 223 this.anySucceeded_ = true; |
| 224 this.stillGoing_[index] = false; |
| 225 this.notifySuccess_(code, gnubby, index, signResult); |
| 226 this.numComplete_++; |
| 227 var morePossible = this.numComplete_ < this.signers_.length; |
| 228 if (!morePossible) |
| 229 this.notifyComplete_(); |
| 230 }; |
| 231 |
| 232 /** |
| 233 * @private |
| 234 */ |
| 235 MultipleGnubbySigner.prototype.notifyComplete_ = function() { |
| 236 // See if any of the signers failed with a strange error. If so, report a |
| 237 // single error to the caller, partly as a diagnostic aid and partly to |
| 238 // distinguish real failures from wrong data. |
| 239 var funnyBusiness; |
| 240 for (var i = 0; i < this.errorStatus_.length; i++) { |
| 241 if (this.errorStatus_[i] && |
| 242 this.errorStatus_[i] != DeviceStatusCodes.WRONG_DATA_STATUS && |
| 243 this.errorStatus_[i] != DeviceStatusCodes.WAIT_TOUCH_STATUS) { |
| 244 funnyBusiness = this.errorStatus_[i]; |
| 245 break; |
| 246 } |
| 247 } |
| 248 if (funnyBusiness) { |
| 249 console.warn(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ', ' + |
| 250 'funny error = ' + funnyBusiness + ')')); |
| 251 } else { |
| 252 console.log(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ')')); |
| 253 } |
| 254 this.completedCb_(this.anySucceeded_, funnyBusiness); |
| 255 }; |
| 256 |
| 257 /** |
| 258 * @param {number} code |
| 259 * @param {usbGnubby} gnubby |
| 260 * @param {number} gnubbyIndex |
| 261 * @param {SingleSignerResult=} singleSignerResult |
| 262 * @private |
| 263 */ |
| 264 MultipleGnubbySigner.prototype.notifySuccess_ = |
| 265 function(code, gnubby, gnubbyIndex, singleSignerResult) { |
| 266 console.log(UTIL_fmt('success (' + code.toString(16) + ')')); |
| 267 var signResult = { |
| 268 'gnubby': gnubby, |
| 269 'gnubbyIndex': gnubbyIndex |
| 270 }; |
| 271 if (singleSignerResult && singleSignerResult['challenge']) |
| 272 signResult['challenge'] = singleSignerResult['challenge']; |
| 273 if (singleSignerResult && singleSignerResult['info']) |
| 274 signResult['info'] = singleSignerResult['info']; |
| 275 this.gnubbyFoundCb_(code, signResult); |
| 276 }; |
OLD | NEW |