| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview A single gnubby signer wraps the process of opening a gnubby, | 6 * @fileoverview A single gnubby signer wraps the process of opening a gnubby, |
| 7 * signing each challenge in an array of challenges until a success condition | 7 * signing each challenge in an array of challenges until a success condition |
| 8 * is satisfied, and finally yielding the gnubby upon success. | 8 * is satisfied, and finally yielding the gnubby upon success. |
| 9 * | 9 * |
| 10 */ | 10 */ |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 43 * enroll operation. | 43 * enroll operation. |
| 44 * @param {function(SingleSignerResult)} | 44 * @param {function(SingleSignerResult)} |
| 45 * completeCb Called when this signer completes, i.e. no further results are | 45 * completeCb Called when this signer completes, i.e. no further results are |
| 46 * possible. | 46 * possible. |
| 47 * @param {Countdown} timer An advisory timer, beyond whose expiration the | 47 * @param {Countdown} timer An advisory timer, beyond whose expiration the |
| 48 * signer will not attempt any new operations, assuming the caller is no | 48 * signer will not attempt any new operations, assuming the caller is no |
| 49 * longer interested in the outcome. | 49 * longer interested in the outcome. |
| 50 * @param {string=} opt_logMsgUrl A URL to post log messages to. | 50 * @param {string=} opt_logMsgUrl A URL to post log messages to. |
| 51 * @constructor | 51 * @constructor |
| 52 */ | 52 */ |
| 53 function SingleGnubbySigner(gnubbyId, forEnroll, completeCb, timer, | 53 function SingleGnubbySigner( |
| 54 opt_logMsgUrl) { | 54 gnubbyId, forEnroll, completeCb, timer, opt_logMsgUrl) { |
| 55 /** @private {GnubbyDeviceId} */ | 55 /** @private {GnubbyDeviceId} */ |
| 56 this.gnubbyId_ = gnubbyId; | 56 this.gnubbyId_ = gnubbyId; |
| 57 /** @private {SingleGnubbySigner.State} */ | 57 /** @private {SingleGnubbySigner.State} */ |
| 58 this.state_ = SingleGnubbySigner.State.INIT; | 58 this.state_ = SingleGnubbySigner.State.INIT; |
| 59 /** @private {boolean} */ | 59 /** @private {boolean} */ |
| 60 this.forEnroll_ = forEnroll; | 60 this.forEnroll_ = forEnroll; |
| 61 /** @private {function(SingleSignerResult)} */ | 61 /** @private {function(SingleSignerResult)} */ |
| 62 this.completeCb_ = completeCb; | 62 this.completeCb_ = completeCb; |
| 63 /** @private {Countdown} */ | 63 /** @private {Countdown} */ |
| 64 this.timer_ = timer; | 64 this.timer_ = timer; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 108 | 108 |
| 109 /** | 109 /** |
| 110 * Closes this signer's gnubby, if it's held. | 110 * Closes this signer's gnubby, if it's held. |
| 111 */ | 111 */ |
| 112 SingleGnubbySigner.prototype.close = function() { | 112 SingleGnubbySigner.prototype.close = function() { |
| 113 if (this.state_ == SingleGnubbySigner.State.OPENING) { | 113 if (this.state_ == SingleGnubbySigner.State.OPENING) { |
| 114 if (this.openCanceller_) | 114 if (this.openCanceller_) |
| 115 this.openCanceller_(); | 115 this.openCanceller_(); |
| 116 } | 116 } |
| 117 | 117 |
| 118 if (!this.gnubby_) return; | 118 if (!this.gnubby_) |
| 119 return; |
| 119 this.state_ = SingleGnubbySigner.State.CLOSING; | 120 this.state_ = SingleGnubbySigner.State.CLOSING; |
| 120 this.gnubby_.closeWhenIdle(this.closed_.bind(this)); | 121 this.gnubby_.closeWhenIdle(this.closed_.bind(this)); |
| 121 }; | 122 }; |
| 122 | 123 |
| 123 /** | 124 /** |
| 124 * Called when this signer's gnubby is closed. | 125 * Called when this signer's gnubby is closed. |
| 125 * @private | 126 * @private |
| 126 */ | 127 */ |
| 127 SingleGnubbySigner.prototype.closed_ = function() { | 128 SingleGnubbySigner.prototype.closed_ = function() { |
| 128 this.gnubby_ = null; | 129 this.gnubby_ = null; |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 187 */ | 188 */ |
| 188 SingleGnubbySigner.prototype.open_ = function() { | 189 SingleGnubbySigner.prototype.open_ = function() { |
| 189 var appIdHash; | 190 var appIdHash; |
| 190 if (this.challenges_.length) { | 191 if (this.challenges_.length) { |
| 191 // Assume the first challenge's appId is representative of all of them. | 192 // Assume the first challenge's appId is representative of all of them. |
| 192 appIdHash = B64_encode(this.challenges_[0].appIdHash); | 193 appIdHash = B64_encode(this.challenges_[0].appIdHash); |
| 193 } | 194 } |
| 194 if (this.state_ == SingleGnubbySigner.State.INIT) { | 195 if (this.state_ == SingleGnubbySigner.State.INIT) { |
| 195 this.state_ = SingleGnubbySigner.State.OPENING; | 196 this.state_ = SingleGnubbySigner.State.OPENING; |
| 196 this.openCanceller_ = DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby( | 197 this.openCanceller_ = DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby( |
| 197 this.gnubbyId_, | 198 this.gnubbyId_, this.forEnroll_, this.openCallback_.bind(this), |
| 198 this.forEnroll_, | 199 appIdHash, this.logMsgUrl_, |
| 199 this.openCallback_.bind(this), | |
| 200 appIdHash, | |
| 201 this.logMsgUrl_, | |
| 202 'singlesigner.js:SingleGnubbySigner.prototype.open_'); | 200 'singlesigner.js:SingleGnubbySigner.prototype.open_'); |
| 203 } | 201 } |
| 204 }; | 202 }; |
| 205 | 203 |
| 206 /** | 204 /** |
| 207 * How long to delay retrying a failed open. | 205 * How long to delay retrying a failed open. |
| 208 */ | 206 */ |
| 209 SingleGnubbySigner.OPEN_DELAY_MILLIS = 200; | 207 SingleGnubbySigner.OPEN_DELAY_MILLIS = 200; |
| 210 | 208 |
| 211 /** | 209 /** |
| (...skipping 23 matching lines...) Expand all Loading... |
| 235 } | 233 } |
| 236 break; | 234 break; |
| 237 case DeviceStatusCodes.BUSY_STATUS: | 235 case DeviceStatusCodes.BUSY_STATUS: |
| 238 this.gnubby_ = gnubby; | 236 this.gnubby_ = gnubby; |
| 239 this.state_ = SingleGnubbySigner.State.BUSY; | 237 this.state_ = SingleGnubbySigner.State.BUSY; |
| 240 // If there's still time, retry the open. | 238 // If there's still time, retry the open. |
| 241 if (!this.timer_ || !this.timer_.expired()) { | 239 if (!this.timer_ || !this.timer_.expired()) { |
| 242 var self = this; | 240 var self = this; |
| 243 window.setTimeout(function() { | 241 window.setTimeout(function() { |
| 244 if (self.gnubby_) { | 242 if (self.gnubby_) { |
| 245 this.openCanceller_ = DEVICE_FACTORY_REGISTRY | 243 this.openCanceller_ = |
| 246 .getGnubbyFactory().openGnubby( | 244 DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby( |
| 247 self.gnubbyId_, | 245 self.gnubbyId_, self.forEnroll_, |
| 248 self.forEnroll_, | 246 self.openCallback_.bind(self), self.logMsgUrl_, |
| 249 self.openCallback_.bind(self), | 247 'singlesigner.js:SingleGnubbySigner.prototype.openCallback_'
); |
| 250 self.logMsgUrl_, | |
| 251 'singlesigner.js:SingleGnubbySigner.prototype.openCallback_'); | |
| 252 } | 248 } |
| 253 }, SingleGnubbySigner.OPEN_DELAY_MILLIS); | 249 }, SingleGnubbySigner.OPEN_DELAY_MILLIS); |
| 254 } else { | 250 } else { |
| 255 this.goToError_(DeviceStatusCodes.BUSY_STATUS); | 251 this.goToError_(DeviceStatusCodes.BUSY_STATUS); |
| 256 } | 252 } |
| 257 break; | 253 break; |
| 258 default: | 254 default: |
| 259 // TODO: This won't be confused with success, but should it be | 255 // TODO: This won't be confused with success, but should it be |
| 260 // part of the same namespace as the other error codes, which are | 256 // part of the same namespace as the other error codes, which are |
| 261 // always in DeviceStatusCodes.*? | 257 // always in DeviceStatusCodes.*? |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 var appIdHash = challenge.appIdHash; | 325 var appIdHash = challenge.appIdHash; |
| 330 var keyHandle = challenge.keyHandle; | 326 var keyHandle = challenge.keyHandle; |
| 331 if (this.cachedError_.hasOwnProperty(keyHandle)) { | 327 if (this.cachedError_.hasOwnProperty(keyHandle)) { |
| 332 // Cache hit: return wrong data again. | 328 // Cache hit: return wrong data again. |
| 333 this.signCallback_(challengeIndex, this.cachedError_[keyHandle]); | 329 this.signCallback_(challengeIndex, this.cachedError_[keyHandle]); |
| 334 } else if (challenge.version && challenge.version != this.version_) { | 330 } else if (challenge.version && challenge.version != this.version_) { |
| 335 // Sign challenge for a different version of gnubby: return wrong data. | 331 // Sign challenge for a different version of gnubby: return wrong data. |
| 336 this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS); | 332 this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS); |
| 337 } else { | 333 } else { |
| 338 var nowink = false; | 334 var nowink = false; |
| 339 this.gnubby_.sign(challengeHash, appIdHash, keyHandle, | 335 this.gnubby_.sign( |
| 340 this.signCallback_.bind(this, challengeIndex), | 336 challengeHash, appIdHash, keyHandle, |
| 341 nowink); | 337 this.signCallback_.bind(this, challengeIndex), nowink); |
| 342 } | 338 } |
| 343 }; | 339 }; |
| 344 | 340 |
| 345 /** | 341 /** |
| 346 * @param {number} code The result of a sign operation. | 342 * @param {number} code The result of a sign operation. |
| 347 * @return {boolean} Whether the error indicates the key handle is invalid | 343 * @return {boolean} Whether the error indicates the key handle is invalid |
| 348 * for this gnubby. | 344 * for this gnubby. |
| 349 */ | 345 */ |
| 350 SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle = function(code) { | 346 SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle = function(code) { |
| 351 return (code == DeviceStatusCodes.WRONG_DATA_STATUS || | 347 return ( |
| 348 code == DeviceStatusCodes.WRONG_DATA_STATUS || |
| 352 code == DeviceStatusCodes.WRONG_LENGTH_STATUS || | 349 code == DeviceStatusCodes.WRONG_LENGTH_STATUS || |
| 353 code == DeviceStatusCodes.INVALID_DATA_STATUS); | 350 code == DeviceStatusCodes.INVALID_DATA_STATUS); |
| 354 }; | 351 }; |
| 355 | 352 |
| 356 /** | 353 /** |
| 357 * Called with the result of a single sign operation. | 354 * Called with the result of a single sign operation. |
| 358 * @param {number} challengeIndex the index of the challenge just attempted | 355 * @param {number} challengeIndex the index of the challenge just attempted |
| 359 * @param {number} code the result of the sign operation | 356 * @param {number} code the result of the sign operation |
| 360 * @param {ArrayBuffer=} opt_info Optional result data | 357 * @param {ArrayBuffer=} opt_info Optional result data |
| 361 * @private | 358 * @private |
| 362 */ | 359 */ |
| 363 SingleGnubbySigner.prototype.signCallback_ = | 360 SingleGnubbySigner.prototype.signCallback_ = function( |
| 364 function(challengeIndex, code, opt_info) { | 361 challengeIndex, code, opt_info) { |
| 365 console.log(UTIL_fmt('gnubby ' + JSON.stringify(this.gnubbyId_) + | 362 console.log(UTIL_fmt( |
| 366 ', challenge ' + challengeIndex + ' yielded ' + code.toString(16))); | 363 'gnubby ' + JSON.stringify(this.gnubbyId_) + ', challenge ' + |
| 364 challengeIndex + ' yielded ' + code.toString(16))); |
| 367 if (this.state_ != SingleGnubbySigner.State.SIGNING) { | 365 if (this.state_ != SingleGnubbySigner.State.SIGNING) { |
| 368 console.log(UTIL_fmt('already done!')); | 366 console.log(UTIL_fmt('already done!')); |
| 369 // We're done, the caller's no longer interested. | 367 // We're done, the caller's no longer interested. |
| 370 return; | 368 return; |
| 371 } | 369 } |
| 372 | 370 |
| 373 // Cache certain idempotent errors, re-asking the gnubby to sign it | 371 // Cache certain idempotent errors, re-asking the gnubby to sign it |
| 374 // won't produce different results. | 372 // won't produce different results. |
| 375 if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) { | 373 if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) { |
| 376 if (challengeIndex < this.challenges_.length) { | 374 if (challengeIndex < this.challenges_.length) { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 392 break; | 390 break; |
| 393 | 391 |
| 394 case DeviceStatusCodes.BUSY_STATUS: | 392 case DeviceStatusCodes.BUSY_STATUS: |
| 395 this.doSign_(this.challengeIndex_); | 393 this.doSign_(this.challengeIndex_); |
| 396 break; | 394 break; |
| 397 | 395 |
| 398 case DeviceStatusCodes.OK_STATUS: | 396 case DeviceStatusCodes.OK_STATUS: |
| 399 // Lower bound on the minimum length, signature length can vary. | 397 // Lower bound on the minimum length, signature length can vary. |
| 400 var MIN_SIGNATURE_LENGTH = 7; | 398 var MIN_SIGNATURE_LENGTH = 7; |
| 401 if (!opt_info || opt_info.byteLength < MIN_SIGNATURE_LENGTH) { | 399 if (!opt_info || opt_info.byteLength < MIN_SIGNATURE_LENGTH) { |
| 402 console.error(UTIL_fmt('Got short response to sign request (' + | 400 console.error(UTIL_fmt( |
| 401 'Got short response to sign request (' + |
| 403 (opt_info ? opt_info.byteLength : 0) + ' bytes), WTF?')); | 402 (opt_info ? opt_info.byteLength : 0) + ' bytes), WTF?')); |
| 404 } | 403 } |
| 405 if (this.forEnroll_) { | 404 if (this.forEnroll_) { |
| 406 this.goToError_(code); | 405 this.goToError_(code); |
| 407 } else { | 406 } else { |
| 408 this.goToSuccess_(code, this.challenges_[challengeIndex], opt_info); | 407 this.goToSuccess_(code, this.challenges_[challengeIndex], opt_info); |
| 409 } | 408 } |
| 410 break; | 409 break; |
| 411 | 410 |
| 412 case DeviceStatusCodes.WAIT_TOUCH_STATUS: | 411 case DeviceStatusCodes.WAIT_TOUCH_STATUS: |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 455 /** | 454 /** |
| 456 * Switches to the error state, and notifies caller. | 455 * Switches to the error state, and notifies caller. |
| 457 * @param {number} code Error code | 456 * @param {number} code Error code |
| 458 * @param {boolean=} opt_warn Whether to warn in the console about the error. | 457 * @param {boolean=} opt_warn Whether to warn in the console about the error. |
| 459 * @private | 458 * @private |
| 460 */ | 459 */ |
| 461 SingleGnubbySigner.prototype.goToError_ = function(code, opt_warn) { | 460 SingleGnubbySigner.prototype.goToError_ = function(code, opt_warn) { |
| 462 this.state_ = SingleGnubbySigner.State.COMPLETE; | 461 this.state_ = SingleGnubbySigner.State.COMPLETE; |
| 463 var logFn = opt_warn ? console.warn.bind(console) : console.log.bind(console); | 462 var logFn = opt_warn ? console.warn.bind(console) : console.log.bind(console); |
| 464 logFn(UTIL_fmt('failed (' + code.toString(16) + ')')); | 463 logFn(UTIL_fmt('failed (' + code.toString(16) + ')')); |
| 465 var result = { code: code }; | 464 var result = {code: code}; |
| 466 if (!this.forEnroll_ && | 465 if (!this.forEnroll_ && |
| 467 SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) { | 466 SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle(code)) { |
| 468 // When a device yields an idempotent bad key handle error to all sign | 467 // When a device yields an idempotent bad key handle error to all sign |
| 469 // challenges, and this is a sign request, we don't want to yield to the | 468 // challenges, and this is a sign request, we don't want to yield to the |
| 470 // web page that it's not enrolled just yet: we want the user to tap the | 469 // web page that it's not enrolled just yet: we want the user to tap the |
| 471 // device first. We'll report the gnubby to the caller and let it close it | 470 // device first. We'll report the gnubby to the caller and let it close it |
| 472 // instead of closing it here. | 471 // instead of closing it here. |
| 473 result.gnubby = this.gnubby_; | 472 result.gnubby = this.gnubby_; |
| 474 } else { | 473 } else { |
| 475 // Since this gnubby can no longer produce a useful result, go ahead and | 474 // Since this gnubby can no longer produce a useful result, go ahead and |
| 476 // close it. | 475 // close it. |
| 477 this.close(); | 476 this.close(); |
| 478 } | 477 } |
| 479 this.completeCb_(result); | 478 this.completeCb_(result); |
| 480 }; | 479 }; |
| 481 | 480 |
| 482 /** | 481 /** |
| 483 * Switches to the success state, and notifies caller. | 482 * Switches to the success state, and notifies caller. |
| 484 * @param {number} code Status code | 483 * @param {number} code Status code |
| 485 * @param {SignHelperChallenge=} opt_challenge The challenge signed | 484 * @param {SignHelperChallenge=} opt_challenge The challenge signed |
| 486 * @param {ArrayBuffer=} opt_info Optional result data | 485 * @param {ArrayBuffer=} opt_info Optional result data |
| 487 * @private | 486 * @private |
| 488 */ | 487 */ |
| 489 SingleGnubbySigner.prototype.goToSuccess_ = | 488 SingleGnubbySigner.prototype.goToSuccess_ = function( |
| 490 function(code, opt_challenge, opt_info) { | 489 code, opt_challenge, opt_info) { |
| 491 this.state_ = SingleGnubbySigner.State.COMPLETE; | 490 this.state_ = SingleGnubbySigner.State.COMPLETE; |
| 492 console.log(UTIL_fmt('success (' + code.toString(16) + ')')); | 491 console.log(UTIL_fmt('success (' + code.toString(16) + ')')); |
| 493 var result = { code: code, gnubby: this.gnubby_ }; | 492 var result = {code: code, gnubby: this.gnubby_}; |
| 494 if (opt_challenge || opt_info) { | 493 if (opt_challenge || opt_info) { |
| 495 if (opt_challenge) { | 494 if (opt_challenge) { |
| 496 result['challenge'] = opt_challenge; | 495 result['challenge'] = opt_challenge; |
| 497 } | 496 } |
| 498 if (opt_info) { | 497 if (opt_info) { |
| 499 result['info'] = opt_info; | 498 result['info'] = opt_info; |
| 500 } | 499 } |
| 501 } | 500 } |
| 502 this.completeCb_(result); | 501 this.completeCb_(result); |
| 503 // this.gnubby_ is now owned by completeCb_. | 502 // this.gnubby_ is now owned by completeCb_. |
| 504 this.gnubby_ = null; | 503 this.gnubby_ = null; |
| 505 }; | 504 }; |
| OLD | NEW |