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 |