| 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 Implements an enroll handler using USB gnubbies. | 6 * @fileoverview Implements an enroll handler using USB gnubbies. |
| 7 */ | 7 */ |
| 8 'use strict'; | 8 'use strict'; |
| 9 | 9 |
| 10 /** | 10 /** |
| (...skipping 19 matching lines...) Expand all Loading... |
| 30 * @const | 30 * @const |
| 31 */ | 31 */ |
| 32 UsbEnrollHandler.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; | 32 UsbEnrollHandler.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; |
| 33 | 33 |
| 34 /** | 34 /** |
| 35 * @param {RequestHandlerCallback} cb Called back with the result of the | 35 * @param {RequestHandlerCallback} cb Called back with the result of the |
| 36 * request, and an optional source for the result. | 36 * request, and an optional source for the result. |
| 37 * @return {boolean} Whether this handler could be run. | 37 * @return {boolean} Whether this handler could be run. |
| 38 */ | 38 */ |
| 39 UsbEnrollHandler.prototype.run = function(cb) { | 39 UsbEnrollHandler.prototype.run = function(cb) { |
| 40 var timeoutMillis = | 40 var timeoutMillis = this.request_.timeoutSeconds ? |
| 41 this.request_.timeoutSeconds ? | |
| 42 this.request_.timeoutSeconds * 1000 : | 41 this.request_.timeoutSeconds * 1000 : |
| 43 UsbEnrollHandler.DEFAULT_TIMEOUT_MILLIS; | 42 UsbEnrollHandler.DEFAULT_TIMEOUT_MILLIS; |
| 44 /** @private {Countdown} */ | 43 /** @private {Countdown} */ |
| 45 this.timer_ = DEVICE_FACTORY_REGISTRY.getCountdownFactory().createTimer( | 44 this.timer_ = |
| 46 timeoutMillis); | 45 DEVICE_FACTORY_REGISTRY.getCountdownFactory().createTimer(timeoutMillis); |
| 47 this.enrollChallenges = this.request_.enrollChallenges; | 46 this.enrollChallenges = this.request_.enrollChallenges; |
| 48 /** @private {RequestHandlerCallback} */ | 47 /** @private {RequestHandlerCallback} */ |
| 49 this.cb_ = cb; | 48 this.cb_ = cb; |
| 50 this.signer_ = new MultipleGnubbySigner( | 49 this.signer_ = new MultipleGnubbySigner( |
| 51 true /* forEnroll */, | 50 true /* forEnroll */, this.signerCompleted_.bind(this), |
| 52 this.signerCompleted_.bind(this), | 51 this.signerFoundGnubby_.bind(this), timeoutMillis, |
| 53 this.signerFoundGnubby_.bind(this), | |
| 54 timeoutMillis, | |
| 55 this.request_.logMsgUrl); | 52 this.request_.logMsgUrl); |
| 56 return this.signer_.doSign(this.request_.signData); | 53 return this.signer_.doSign(this.request_.signData); |
| 57 }; | 54 }; |
| 58 | 55 |
| 59 /** Closes this helper. */ | 56 /** Closes this helper. */ |
| 60 UsbEnrollHandler.prototype.close = function() { | 57 UsbEnrollHandler.prototype.close = function() { |
| 61 this.closed_ = true; | 58 this.closed_ = true; |
| 62 for (var i = 0; i < this.waitingForTouchGnubbies_.length; i++) { | 59 for (var i = 0; i < this.waitingForTouchGnubbies_.length; i++) { |
| 63 this.waitingForTouchGnubbies_[i].closeWhenIdle(); | 60 this.waitingForTouchGnubbies_[i].closeWhenIdle(); |
| 64 } | 61 } |
| (...skipping 19 matching lines...) Expand all Loading... |
| 84 } | 81 } |
| 85 }; | 82 }; |
| 86 | 83 |
| 87 /** | 84 /** |
| 88 * Called when a MultipleGnubbySigner finds a gnubby that can enroll. | 85 * Called when a MultipleGnubbySigner finds a gnubby that can enroll. |
| 89 * @param {MultipleSignerResult} signResult Signature results | 86 * @param {MultipleSignerResult} signResult Signature results |
| 90 * @param {boolean} moreExpected Whether the signer expects to report | 87 * @param {boolean} moreExpected Whether the signer expects to report |
| 91 * results from more gnubbies. | 88 * results from more gnubbies. |
| 92 * @private | 89 * @private |
| 93 */ | 90 */ |
| 94 UsbEnrollHandler.prototype.signerFoundGnubby_ = | 91 UsbEnrollHandler.prototype.signerFoundGnubby_ = function( |
| 95 function(signResult, moreExpected) { | 92 signResult, moreExpected) { |
| 96 if (!signResult.code) { | 93 if (!signResult.code) { |
| 97 // If the signer reports a gnubby can sign, report this immediately to the | 94 // If the signer reports a gnubby can sign, report this immediately to the |
| 98 // caller, as the gnubby is already enrolled. Map ok to WRONG_DATA, so the | 95 // caller, as the gnubby is already enrolled. Map ok to WRONG_DATA, so the |
| 99 // caller knows what to do. | 96 // caller knows what to do. |
| 100 this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS); | 97 this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS); |
| 101 } else if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle( | 98 } else if (SingleGnubbySigner.signErrorIndicatesInvalidKeyHandle( |
| 102 signResult.code)) { | 99 signResult.code)) { |
| 103 var gnubby = signResult['gnubby']; | 100 var gnubby = signResult['gnubby']; |
| 104 // A valid helper request contains at least one enroll challenge, so use | 101 // A valid helper request contains at least one enroll challenge, so use |
| 105 // the app id hash from the first challenge. | 102 // the app id hash from the first challenge. |
| 106 var appIdHash = this.request_.enrollChallenges[0].appIdHash; | 103 var appIdHash = this.request_.enrollChallenges[0].appIdHash; |
| 107 DEVICE_FACTORY_REGISTRY.getGnubbyFactory().notEnrolledPrerequisiteCheck( | 104 DEVICE_FACTORY_REGISTRY.getGnubbyFactory().notEnrolledPrerequisiteCheck( |
| 108 gnubby, appIdHash, this.gnubbyPrerequisitesChecked_.bind(this)); | 105 gnubby, appIdHash, this.gnubbyPrerequisitesChecked_.bind(this)); |
| 109 } else { | 106 } else { |
| 110 // Unexpected error in signing? Send this immediately to the caller. | 107 // Unexpected error in signing? Send this immediately to the caller. |
| 111 this.notifyError_(signResult.code); | 108 this.notifyError_(signResult.code); |
| 112 } | 109 } |
| 113 }; | 110 }; |
| 114 | 111 |
| 115 /** | 112 /** |
| 116 * Called with the result of a gnubby prerequisite check. | 113 * Called with the result of a gnubby prerequisite check. |
| 117 * @param {number} rc The result of the prerequisite check. | 114 * @param {number} rc The result of the prerequisite check. |
| 118 * @param {Gnubby=} opt_gnubby The gnubby whose prerequisites were checked. | 115 * @param {Gnubby=} opt_gnubby The gnubby whose prerequisites were checked. |
| 119 * @private | 116 * @private |
| 120 */ | 117 */ |
| 121 UsbEnrollHandler.prototype.gnubbyPrerequisitesChecked_ = | 118 UsbEnrollHandler.prototype.gnubbyPrerequisitesChecked_ = function( |
| 122 function(rc, opt_gnubby) { | 119 rc, opt_gnubby) { |
| 123 if (rc || this.timer_.expired()) { | 120 if (rc || this.timer_.expired()) { |
| 124 // Do nothing: | 121 // Do nothing: |
| 125 // If the timer is expired, the signerCompleted_ callback will indicate | 122 // If the timer is expired, the signerCompleted_ callback will indicate |
| 126 // timeout to the caller. | 123 // timeout to the caller. |
| 127 // If there's an error, this gnubby is ineligible, but there's nothing we | 124 // If there's an error, this gnubby is ineligible, but there's nothing we |
| 128 // can do about that here. | 125 // can do about that here. |
| 129 return; | 126 return; |
| 130 } | 127 } |
| 131 // If the callback succeeded, the gnubby is not null. | 128 // If the callback succeeded, the gnubby is not null. |
| 132 var gnubby = /** @type {Gnubby} */ (opt_gnubby); | 129 var gnubby = /** @type {Gnubby} */ (opt_gnubby); |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 205 * @private | 202 * @private |
| 206 */ | 203 */ |
| 207 UsbEnrollHandler.prototype.tryEnroll_ = function(gnubby, version) { | 204 UsbEnrollHandler.prototype.tryEnroll_ = function(gnubby, version) { |
| 208 var challenge = this.getChallengeOfVersion_(version); | 205 var challenge = this.getChallengeOfVersion_(version); |
| 209 if (!challenge) { | 206 if (!challenge) { |
| 210 this.removeWrongVersionGnubby_(gnubby); | 207 this.removeWrongVersionGnubby_(gnubby); |
| 211 return; | 208 return; |
| 212 } | 209 } |
| 213 var challengeValue = B64_decode(challenge['challengeHash']); | 210 var challengeValue = B64_decode(challenge['challengeHash']); |
| 214 var appIdHash = challenge['appIdHash']; | 211 var appIdHash = challenge['appIdHash']; |
| 215 var individualAttest = | 212 var individualAttest = DEVICE_FACTORY_REGISTRY.getIndividualAttestation() |
| 216 DEVICE_FACTORY_REGISTRY.getIndividualAttestation(). | 213 .requestIndividualAttestation(appIdHash); |
| 217 requestIndividualAttestation(appIdHash); | 214 gnubby.enroll( |
| 218 gnubby.enroll(challengeValue, B64_decode(appIdHash), | 215 challengeValue, B64_decode(appIdHash), |
| 219 this.enrollCallback_.bind(this, gnubby, version), individualAttest); | 216 this.enrollCallback_.bind(this, gnubby, version), individualAttest); |
| 220 }; | 217 }; |
| 221 | 218 |
| 222 /** | 219 /** |
| 223 * Finds the (first) challenge of the given version in this helper's challenges. | 220 * Finds the (first) challenge of the given version in this helper's challenges. |
| 224 * @param {string} version Protocol version | 221 * @param {string} version Protocol version |
| 225 * @return {Object} challenge, if found, or null if not. | 222 * @return {Object} challenge, if found, or null if not. |
| 226 * @private | 223 * @private |
| 227 */ | 224 */ |
| 228 UsbEnrollHandler.prototype.getChallengeOfVersion_ = function(version) { | 225 UsbEnrollHandler.prototype.getChallengeOfVersion_ = function(version) { |
| 229 for (var i = 0; i < this.enrollChallenges.length; i++) { | 226 for (var i = 0; i < this.enrollChallenges.length; i++) { |
| 230 if (this.enrollChallenges[i]['version'] == version) { | 227 if (this.enrollChallenges[i]['version'] == version) { |
| 231 return this.enrollChallenges[i]; | 228 return this.enrollChallenges[i]; |
| 232 } | 229 } |
| 233 } | 230 } |
| 234 return null; | 231 return null; |
| 235 }; | 232 }; |
| 236 | 233 |
| 237 /** | 234 /** |
| 238 * Called with the result of an enroll request to a gnubby. | 235 * Called with the result of an enroll request to a gnubby. |
| 239 * @param {Gnubby} gnubby Gnubby instance | 236 * @param {Gnubby} gnubby Gnubby instance |
| 240 * @param {string} version Protocol version | 237 * @param {string} version Protocol version |
| 241 * @param {number} code Status code | 238 * @param {number} code Status code |
| 242 * @param {ArrayBuffer=} infoArray Returned data | 239 * @param {ArrayBuffer=} infoArray Returned data |
| 243 * @private | 240 * @private |
| 244 */ | 241 */ |
| 245 UsbEnrollHandler.prototype.enrollCallback_ = | 242 UsbEnrollHandler.prototype.enrollCallback_ = function( |
| 246 function(gnubby, version, code, infoArray) { | 243 gnubby, version, code, infoArray) { |
| 247 if (this.notified_) { | 244 if (this.notified_) { |
| 248 // Enroll completed after previous success or failure. Disregard. | 245 // Enroll completed after previous success or failure. Disregard. |
| 249 return; | 246 return; |
| 250 } | 247 } |
| 251 switch (code) { | 248 switch (code) { |
| 252 case -GnubbyDevice.GONE: | 249 case -GnubbyDevice.GONE: |
| 253 // Close this gnubby. | 250 // Close this gnubby. |
| 254 this.removeWaitingGnubby_(gnubby); | 251 this.removeWaitingGnubby_(gnubby); |
| 255 if (!this.waitingForTouchGnubbies_.length) { | 252 if (!this.waitingForTouchGnubbies_.length) { |
| 256 // Last enroll attempt is complete and last gnubby is gone. | 253 // Last enroll attempt is complete and last gnubby is gone. |
| 257 this.anyGnubbiesFound_ = false; | 254 this.anyGnubbiesFound_ = false; |
| 258 if (this.timer_.expired()) { | 255 if (this.timer_.expired()) { |
| 259 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS); | 256 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS); |
| 260 } else if (this.signer_) { | 257 } else if (this.signer_) { |
| 261 this.signer_.reScanDevices(); | 258 this.signer_.reScanDevices(); |
| 262 } | |
| 263 } | 259 } |
| 260 } |
| 264 break; | 261 break; |
| 265 | 262 |
| 266 case DeviceStatusCodes.WAIT_TOUCH_STATUS: | 263 case DeviceStatusCodes.WAIT_TOUCH_STATUS: |
| 267 case DeviceStatusCodes.BUSY_STATUS: | 264 case DeviceStatusCodes.BUSY_STATUS: |
| 268 case DeviceStatusCodes.TIMEOUT_STATUS: | 265 case DeviceStatusCodes.TIMEOUT_STATUS: |
| 269 if (this.timer_.expired()) { | 266 if (this.timer_.expired()) { |
| 270 // Record that at least one gnubby timed out, to return a timeout status | 267 // Record that at least one gnubby timed out, to return a timeout status |
| 271 // from the complete callback if no other eligible gnubbies are found. | 268 // from the complete callback if no other eligible gnubbies are found. |
| 272 /** @private {boolean} */ | 269 /** @private {boolean} */ |
| 273 this.anyTimeout_ = true; | 270 this.anyTimeout_ = true; |
| 274 // Close this gnubby. | 271 // Close this gnubby. |
| 275 this.removeWaitingGnubby_(gnubby); | 272 this.removeWaitingGnubby_(gnubby); |
| 276 if (!this.waitingForTouchGnubbies_.length) { | 273 if (!this.waitingForTouchGnubbies_.length) { |
| 277 // Last enroll attempt is complete: return this error. | 274 // Last enroll attempt is complete: return this error. |
| 278 console.log(UTIL_fmt('timeout (' + code.toString(16) + | 275 console.log( |
| 279 ') enrolling')); | 276 UTIL_fmt('timeout (' + code.toString(16) + ') enrolling')); |
| 280 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS); | 277 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS); |
| 281 } | 278 } |
| 282 } else { | 279 } else { |
| 283 DEVICE_FACTORY_REGISTRY.getCountdownFactory().createTimer( | 280 DEVICE_FACTORY_REGISTRY.getCountdownFactory().createTimer( |
| 284 UsbEnrollHandler.ENUMERATE_DELAY_INTERVAL_MILLIS, | 281 UsbEnrollHandler.ENUMERATE_DELAY_INTERVAL_MILLIS, |
| 285 this.tryEnroll_.bind(this, gnubby, version)); | 282 this.tryEnroll_.bind(this, gnubby, version)); |
| 286 } | 283 } |
| 287 break; | 284 break; |
| 288 | 285 |
| 289 case DeviceStatusCodes.OK_STATUS: | 286 case DeviceStatusCodes.OK_STATUS: |
| (...skipping 17 matching lines...) Expand all Loading... |
| 307 /** | 304 /** |
| 308 * Notifies the callback with an error code. | 305 * Notifies the callback with an error code. |
| 309 * @param {number} code The error code to report. | 306 * @param {number} code The error code to report. |
| 310 * @private | 307 * @private |
| 311 */ | 308 */ |
| 312 UsbEnrollHandler.prototype.notifyError_ = function(code) { | 309 UsbEnrollHandler.prototype.notifyError_ = function(code) { |
| 313 if (this.notified_ || this.closed_) | 310 if (this.notified_ || this.closed_) |
| 314 return; | 311 return; |
| 315 this.notified_ = true; | 312 this.notified_ = true; |
| 316 this.close(); | 313 this.close(); |
| 317 var reply = { | 314 var reply = {'type': 'enroll_helper_reply', 'code': code}; |
| 318 'type': 'enroll_helper_reply', | |
| 319 'code': code | |
| 320 }; | |
| 321 this.cb_(reply); | 315 this.cb_(reply); |
| 322 }; | 316 }; |
| 323 | 317 |
| 324 /** | 318 /** |
| 325 * @param {string} version Protocol version | 319 * @param {string} version Protocol version |
| 326 * @param {string} info B64 encoded success data | 320 * @param {string} info B64 encoded success data |
| 327 * @private | 321 * @private |
| 328 */ | 322 */ |
| 329 UsbEnrollHandler.prototype.notifySuccess_ = function(version, info) { | 323 UsbEnrollHandler.prototype.notifySuccess_ = function(version, info) { |
| 330 if (this.notified_ || this.closed_) | 324 if (this.notified_ || this.closed_) |
| 331 return; | 325 return; |
| 332 this.notified_ = true; | 326 this.notified_ = true; |
| 333 this.close(); | 327 this.close(); |
| 334 var reply = { | 328 var reply = { |
| 335 'type': 'enroll_helper_reply', | 329 'type': 'enroll_helper_reply', |
| 336 'code': DeviceStatusCodes.OK_STATUS, | 330 'code': DeviceStatusCodes.OK_STATUS, |
| 337 'version': version, | 331 'version': version, |
| 338 'enrollData': info | 332 'enrollData': info |
| 339 }; | 333 }; |
| 340 this.cb_(reply); | 334 this.cb_(reply); |
| 341 }; | 335 }; |
| OLD | NEW |