| 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 Handles web page requests for gnubby enrollment. | 6 * @fileoverview Handles web page requests for gnubby enrollment. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 'use strict'; | 9 'use strict'; |
| 10 | 10 |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 76 var responseData = | 76 var responseData = |
| 77 makeEnrollResponseData(enrollChallenge, u2fVersion, | 77 makeEnrollResponseData(enrollChallenge, u2fVersion, |
| 78 'registrationData', info, 'clientData', browserData); | 78 'registrationData', info, 'clientData', browserData); |
| 79 var response = makeU2fSuccessResponse(request, responseData); | 79 var response = makeU2fSuccessResponse(request, responseData); |
| 80 sendResponseOnce(sentResponse, closeable, response, sendResponse); | 80 sendResponseOnce(sentResponse, closeable, response, sendResponse); |
| 81 } | 81 } |
| 82 | 82 |
| 83 closeable = | 83 closeable = |
| 84 validateAndBeginEnrollRequest( | 84 validateAndBeginEnrollRequest( |
| 85 sender, request, 'registerRequests', 'signRequests', | 85 sender, request, 'registerRequests', 'signRequests', |
| 86 sendErrorResponse, sendSuccessResponse); | 86 sendErrorResponse, sendSuccessResponse, 'registeredKeys'); |
| 87 return closeable; | 87 return closeable; |
| 88 } | 88 } |
| 89 | 89 |
| 90 /** | 90 /** |
| 91 * Validates an enroll request using the given parameters, and, if valid, begins | 91 * Validates an enroll request using the given parameters, and, if valid, begins |
| 92 * handling the enroll request. | 92 * handling the enroll request. (The enroll request may be modified as a result |
| 93 * of handling it.) |
| 93 * @param {MessageSender} sender The sender of the message. | 94 * @param {MessageSender} sender The sender of the message. |
| 94 * @param {Object} request The web page's enroll request. | 95 * @param {Object} request The web page's enroll request. |
| 95 * @param {string} enrollChallengesName The name of the enroll challenges value | 96 * @param {string} enrollChallengesName The name of the enroll challenges value |
| 96 * in the request. | 97 * in the request. |
| 97 * @param {string} signChallengesName The name of the sign challenges value in | 98 * @param {string} signChallengesName The name of the sign challenges value in |
| 98 * the request. | 99 * the request. |
| 99 * @param {function(ErrorCodes)} errorCb Error callback. | 100 * @param {function(ErrorCodes)} errorCb Error callback. |
| 100 * @param {function(string, string, (string|undefined))} successCb Success | 101 * @param {function(string, string, (string|undefined))} successCb Success |
| 101 * callback. | 102 * callback. |
| 103 * @param {string=} opt_registeredKeysName The name of the registered keys |
| 104 * value in the request. |
| 102 * @return {Closeable} Request handler that should be closed when the browser | 105 * @return {Closeable} Request handler that should be closed when the browser |
| 103 * message channel is closed. | 106 * message channel is closed. |
| 104 */ | 107 */ |
| 105 function validateAndBeginEnrollRequest(sender, request, | 108 function validateAndBeginEnrollRequest(sender, request, |
| 106 enrollChallengesName, signChallengesName, errorCb, successCb) { | 109 enrollChallengesName, signChallengesName, errorCb, successCb, |
| 110 opt_registeredKeysName) { |
| 107 var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); | 111 var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); |
| 108 if (!origin) { | 112 if (!origin) { |
| 109 errorCb(ErrorCodes.BAD_REQUEST); | 113 errorCb(ErrorCodes.BAD_REQUEST); |
| 110 return null; | 114 return null; |
| 111 } | 115 } |
| 112 | 116 |
| 113 if (!isValidEnrollRequest(request, enrollChallengesName, | 117 if (!isValidEnrollRequest(request, enrollChallengesName, |
| 114 signChallengesName)) { | 118 signChallengesName, opt_registeredKeysName)) { |
| 115 errorCb(ErrorCodes.BAD_REQUEST); | 119 errorCb(ErrorCodes.BAD_REQUEST); |
| 116 return null; | 120 return null; |
| 117 } | 121 } |
| 118 | 122 |
| 119 var enrollChallenges = request[enrollChallengesName]; | 123 var enrollChallenges = request[enrollChallengesName]; |
| 120 var signChallenges = request[signChallengesName]; | 124 var signChallenges; |
| 125 if (opt_registeredKeysName && |
| 126 request.hasOwnProperty(opt_registeredKeysName)) { |
| 127 // Convert registered keys to sign challenges by adding a challenge value. |
| 128 signChallenges = request[opt_registeredKeysName]; |
| 129 for (var i = 0; i < signChallenges.length; i++) { |
| 130 // The actual value doesn't matter, as long as it's a string. |
| 131 signChallenges[i]['challenge'] = ''; |
| 132 } |
| 133 } else { |
| 134 signChallenges = request[signChallengesName]; |
| 135 } |
| 121 var logMsgUrl = request['logMsgUrl']; | 136 var logMsgUrl = request['logMsgUrl']; |
| 122 | 137 |
| 123 var timer = createTimerForRequest( | 138 var timer = createTimerForRequest( |
| 124 FACTORY_REGISTRY.getCountdownFactory(), request); | 139 FACTORY_REGISTRY.getCountdownFactory(), request); |
| 125 var enroller = new Enroller(timer, origin, errorCb, successCb, | 140 var enroller = new Enroller(timer, origin, errorCb, successCb, |
| 126 sender.tlsChannelId, logMsgUrl); | 141 sender.tlsChannelId, logMsgUrl); |
| 127 enroller.doEnroll(enrollChallenges, signChallenges); | 142 enroller.doEnroll(enrollChallenges, signChallenges, request['appId']); |
| 128 return /** @type {Closeable} */ (enroller); | 143 return /** @type {Closeable} */ (enroller); |
| 129 } | 144 } |
| 130 | 145 |
| 131 /** | 146 /** |
| 132 * Returns whether the request appears to be a valid enroll request. | 147 * Returns whether the request appears to be a valid enroll request. |
| 133 * @param {Object} request The request. | 148 * @param {Object} request The request. |
| 134 * @param {string} enrollChallengesName The name of the enroll challenges value | 149 * @param {string} enrollChallengesName The name of the enroll challenges value |
| 135 * in the request. | 150 * in the request. |
| 136 * @param {string} signChallengesName The name of the sign challenges value in | 151 * @param {string} signChallengesName The name of the sign challenges value in |
| 137 * the request. | 152 * the request. |
| 153 * @param {string=} opt_registeredKeysName The name of the registered keys |
| 154 * value in the request. |
| 138 * @return {boolean} Whether the request appears valid. | 155 * @return {boolean} Whether the request appears valid. |
| 139 */ | 156 */ |
| 140 function isValidEnrollRequest(request, enrollChallengesName, | 157 function isValidEnrollRequest(request, enrollChallengesName, |
| 141 signChallengesName) { | 158 signChallengesName, opt_registeredKeysName) { |
| 142 if (!request.hasOwnProperty(enrollChallengesName)) | 159 if (!request.hasOwnProperty(enrollChallengesName)) |
| 143 return false; | 160 return false; |
| 144 var enrollChallenges = request[enrollChallengesName]; | 161 var enrollChallenges = request[enrollChallengesName]; |
| 145 if (!enrollChallenges.length) | 162 if (!enrollChallenges.length) |
| 146 return false; | 163 return false; |
| 147 if (!isValidEnrollChallengeArray(enrollChallenges)) | 164 var hasAppId = request.hasOwnProperty('appId'); |
| 165 if (!isValidEnrollChallengeArray(enrollChallenges, !hasAppId)) |
| 148 return false; | 166 return false; |
| 149 var signChallenges = request[signChallengesName]; | 167 var signChallenges = request[signChallengesName]; |
| 150 // A missing sign challenge array is ok, in the case the user is not already | 168 // A missing sign challenge array is ok, in the case the user is not already |
| 151 // enrolled. | 169 // enrolled. |
| 152 if (signChallenges && !isValidSignChallengeArray(signChallenges)) | 170 if (signChallenges && !isValidSignChallengeArray(signChallenges, !hasAppId)) |
| 153 return false; | 171 return false; |
| 172 if (opt_registeredKeysName) { |
| 173 var registeredKeys = request[opt_registeredKeysName]; |
| 174 if (registeredKeys && |
| 175 !isValidRegisteredKeyArray(registeredKeys, !hasAppId)) { |
| 176 return false; |
| 177 } |
| 178 } |
| 154 return true; | 179 return true; |
| 155 } | 180 } |
| 156 | 181 |
| 157 /** | 182 /** |
| 158 * @typedef {{ | 183 * @typedef {{ |
| 159 * version: (string|undefined), | 184 * version: (string|undefined), |
| 160 * challenge: string, | 185 * challenge: string, |
| 161 * appId: string | 186 * appId: string |
| 162 * }} | 187 * }} |
| 163 */ | 188 */ |
| 164 var EnrollChallenge; | 189 var EnrollChallenge; |
| 165 | 190 |
| 166 /** | 191 /** |
| 167 * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to | 192 * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges to |
| 168 * validate. | 193 * validate. |
| 194 * @param {boolean} appIdRequired Whether the appId property is required on |
| 195 * each challenge. |
| 169 * @return {boolean} Whether the given array of challenges is a valid enroll | 196 * @return {boolean} Whether the given array of challenges is a valid enroll |
| 170 * challenges array. | 197 * challenges array. |
| 171 */ | 198 */ |
| 172 function isValidEnrollChallengeArray(enrollChallenges) { | 199 function isValidEnrollChallengeArray(enrollChallenges, appIdRequired) { |
| 173 var seenVersions = {}; | 200 var seenVersions = {}; |
| 174 for (var i = 0; i < enrollChallenges.length; i++) { | 201 for (var i = 0; i < enrollChallenges.length; i++) { |
| 175 var enrollChallenge = enrollChallenges[i]; | 202 var enrollChallenge = enrollChallenges[i]; |
| 176 var version = enrollChallenge['version']; | 203 var version = enrollChallenge['version']; |
| 177 if (!version) { | 204 if (!version) { |
| 178 // Version is implicitly V1 if not specified. | 205 // Version is implicitly V1 if not specified. |
| 179 version = 'U2F_V1'; | 206 version = 'U2F_V1'; |
| 180 } | 207 } |
| 181 if (version != 'U2F_V1' && version != 'U2F_V2') { | 208 if (version != 'U2F_V1' && version != 'U2F_V2') { |
| 182 return false; | 209 return false; |
| 183 } | 210 } |
| 184 if (seenVersions[version]) { | 211 if (seenVersions[version]) { |
| 185 // Each version can appear at most once. | 212 // Each version can appear at most once. |
| 186 return false; | 213 return false; |
| 187 } | 214 } |
| 188 seenVersions[version] = version; | 215 seenVersions[version] = version; |
| 189 if (!enrollChallenge['appId']) { | 216 if (appIdRequired && !enrollChallenge['appId']) { |
| 190 return false; | 217 return false; |
| 191 } | 218 } |
| 192 if (!enrollChallenge['challenge']) { | 219 if (!enrollChallenge['challenge']) { |
| 193 // The challenge is required. | 220 // The challenge is required. |
| 194 return false; | 221 return false; |
| 195 } | 222 } |
| 196 } | 223 } |
| 197 return true; | 224 return true; |
| 198 } | 225 } |
| 199 | 226 |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 293 /** | 320 /** |
| 294 * Default timeout value in case the caller never provides a valid timeout. | 321 * Default timeout value in case the caller never provides a valid timeout. |
| 295 */ | 322 */ |
| 296 Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; | 323 Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; |
| 297 | 324 |
| 298 /** | 325 /** |
| 299 * Performs an enroll request with the given enroll and sign challenges. | 326 * Performs an enroll request with the given enroll and sign challenges. |
| 300 * @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges. | 327 * @param {Array.<EnrollChallenge>} enrollChallenges A set of enroll challenges. |
| 301 * @param {Array.<SignChallenge>} signChallenges A set of sign challenges for | 328 * @param {Array.<SignChallenge>} signChallenges A set of sign challenges for |
| 302 * existing enrollments for this user and appId. | 329 * existing enrollments for this user and appId. |
| 330 * @param {string=} opt_appId The app id for the entire request. |
| 303 */ | 331 */ |
| 304 Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges) { | 332 Enroller.prototype.doEnroll = function(enrollChallenges, signChallenges, |
| 305 var encodedEnrollChallenges = this.encodeEnrollChallenges_(enrollChallenges); | 333 opt_appId) { |
| 306 var encodedSignChallenges = encodeSignChallenges(signChallenges); | 334 var encodedEnrollChallenges = |
| 335 this.encodeEnrollChallenges_(enrollChallenges, opt_appId); |
| 336 var encodedSignChallenges = encodeSignChallenges(signChallenges, opt_appId); |
| 307 var request = { | 337 var request = { |
| 308 type: 'enroll_helper_request', | 338 type: 'enroll_helper_request', |
| 309 enrollChallenges: encodedEnrollChallenges, | 339 enrollChallenges: encodedEnrollChallenges, |
| 310 signData: encodedSignChallenges, | 340 signData: encodedSignChallenges, |
| 311 logMsgUrl: this.logMsgUrl_ | 341 logMsgUrl: this.logMsgUrl_ |
| 312 }; | 342 }; |
| 313 if (!this.timer_.expired()) { | 343 if (!this.timer_.expired()) { |
| 314 request.timeout = this.timer_.millisecondsUntilExpired() / 1000.0; | 344 request.timeout = this.timer_.millisecondsUntilExpired() / 1000.0; |
| 315 request.timeoutSeconds = this.timer_.millisecondsUntilExpired() / 1000.0; | 345 request.timeoutSeconds = this.timer_.millisecondsUntilExpired() / 1000.0; |
| 316 } | 346 } |
| 317 | 347 |
| 318 // Begin fetching/checking the app ids. | 348 // Begin fetching/checking the app ids. |
| 319 var enrollAppIds = []; | 349 var enrollAppIds = []; |
| 350 if (opt_appId) { |
| 351 enrollAppIds.push(opt_appId); |
| 352 } |
| 320 for (var i = 0; i < enrollChallenges.length; i++) { | 353 for (var i = 0; i < enrollChallenges.length; i++) { |
| 321 enrollAppIds.push(enrollChallenges[i]['appId']); | 354 if (enrollChallenges[i].hasOwnProperty('appId')) { |
| 355 enrollAppIds.push(enrollChallenges[i]['appId']); |
| 356 } |
| 357 } |
| 358 // Sanity check |
| 359 if (!enrollAppIds.length) { |
| 360 console.warn(UTIL_fmt('empty enroll app ids?')); |
| 361 this.notifyError_(ErrorCodes.BAD_REQUEST); |
| 362 return; |
| 322 } | 363 } |
| 323 var self = this; | 364 var self = this; |
| 324 this.checkAppIds_(enrollAppIds, signChallenges, function(result) { | 365 this.checkAppIds_(enrollAppIds, signChallenges, function(result) { |
| 325 if (result) { | 366 if (result) { |
| 326 self.handler_ = FACTORY_REGISTRY.getRequestHelper().getHandler(request); | 367 self.handler_ = FACTORY_REGISTRY.getRequestHelper().getHandler(request); |
| 327 if (self.handler_) { | 368 if (self.handler_) { |
| 328 var helperComplete = | 369 var helperComplete = |
| 329 /** @type {function(HelperReply)} */ | 370 /** @type {function(HelperReply)} */ |
| 330 (self.helperComplete_.bind(self)); | 371 (self.helperComplete_.bind(self)); |
| 331 self.handler_.run(helperComplete); | 372 self.handler_.run(helperComplete); |
| 332 } else { | 373 } else { |
| 333 self.notifyError_(ErrorCodes.OTHER_ERROR); | 374 self.notifyError_(ErrorCodes.OTHER_ERROR); |
| 334 } | 375 } |
| 335 } else { | 376 } else { |
| 336 self.notifyError_(ErrorCodes.BAD_REQUEST); | 377 self.notifyError_(ErrorCodes.BAD_REQUEST); |
| 337 } | 378 } |
| 338 }); | 379 }); |
| 339 }; | 380 }; |
| 340 | 381 |
| 341 /** | 382 /** |
| 342 * Encodes the enroll challenge as an enroll helper challenge. | 383 * Encodes the enroll challenge as an enroll helper challenge. |
| 343 * @param {EnrollChallenge} enrollChallenge The enroll challenge to encode. | 384 * @param {EnrollChallenge} enrollChallenge The enroll challenge to encode. |
| 385 * @param {string=} opt_appId The app id for the entire request. |
| 344 * @return {EnrollHelperChallenge} The encoded challenge. | 386 * @return {EnrollHelperChallenge} The encoded challenge. |
| 345 * @private | 387 * @private |
| 346 */ | 388 */ |
| 347 Enroller.encodeEnrollChallenge_ = function(enrollChallenge) { | 389 Enroller.encodeEnrollChallenge_ = function(enrollChallenge, opt_appId) { |
| 348 var encodedChallenge = {}; | 390 var encodedChallenge = {}; |
| 349 var version; | 391 var version; |
| 350 if (enrollChallenge['version']) { | 392 if (enrollChallenge['version']) { |
| 351 version = enrollChallenge['version']; | 393 version = enrollChallenge['version']; |
| 352 } else { | 394 } else { |
| 353 // Version is implicitly V1 if not specified. | 395 // Version is implicitly V1 if not specified. |
| 354 version = 'U2F_V1'; | 396 version = 'U2F_V1'; |
| 355 } | 397 } |
| 356 encodedChallenge['version'] = version; | 398 encodedChallenge['version'] = version; |
| 399 // TODO: remove once external helpers look for challengeHash |
| 357 encodedChallenge['challenge'] = enrollChallenge['challenge']; | 400 encodedChallenge['challenge'] = enrollChallenge['challenge']; |
| 358 encodedChallenge['appIdHash'] = | 401 encodedChallenge['challengeHash'] = enrollChallenge['challenge']; |
| 359 B64_encode(sha256HashOfString(enrollChallenge['appId'])); | 402 var appId; |
| 403 if (enrollChallenge['appId']) { |
| 404 appId = enrollChallenge['appId']; |
| 405 } else { |
| 406 appId = opt_appId; |
| 407 } |
| 408 if (!appId) { |
| 409 // Sanity check. (Other code should fail if it's not set.) |
| 410 console.warn(UTIL_fmt('No appId?')); |
| 411 } |
| 412 encodedChallenge['appIdHash'] = B64_encode(sha256HashOfString(appId)); |
| 360 return /** @type {EnrollHelperChallenge} */ (encodedChallenge); | 413 return /** @type {EnrollHelperChallenge} */ (encodedChallenge); |
| 361 }; | 414 }; |
| 362 | 415 |
| 363 /** | 416 /** |
| 364 * Encodes the given enroll challenges using this enroller's state. | 417 * Encodes the given enroll challenges using this enroller's state. |
| 365 * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges. | 418 * @param {Array.<EnrollChallenge>} enrollChallenges The enroll challenges. |
| 419 * @param {string=} opt_appId The app id for the entire request. |
| 366 * @return {!Array.<EnrollHelperChallenge>} The encoded enroll challenges. | 420 * @return {!Array.<EnrollHelperChallenge>} The encoded enroll challenges. |
| 367 * @private | 421 * @private |
| 368 */ | 422 */ |
| 369 Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges) { | 423 Enroller.prototype.encodeEnrollChallenges_ = function(enrollChallenges, |
| 424 opt_appId) { |
| 370 var challenges = []; | 425 var challenges = []; |
| 371 for (var i = 0; i < enrollChallenges.length; i++) { | 426 for (var i = 0; i < enrollChallenges.length; i++) { |
| 372 var enrollChallenge = enrollChallenges[i]; | 427 var enrollChallenge = enrollChallenges[i]; |
| 373 var version = enrollChallenge.version; | 428 var version = enrollChallenge.version; |
| 374 if (!version) { | 429 if (!version) { |
| 375 // Version is implicitly V1 if not specified. | 430 // Version is implicitly V1 if not specified. |
| 376 version = 'U2F_V1'; | 431 version = 'U2F_V1'; |
| 377 } | 432 } |
| 378 | 433 |
| 379 if (version == 'U2F_V2') { | 434 if (version == 'U2F_V2') { |
| 380 var modifiedChallenge = {}; | 435 var modifiedChallenge = {}; |
| 381 for (var k in enrollChallenge) { | 436 for (var k in enrollChallenge) { |
| 382 modifiedChallenge[k] = enrollChallenge[k]; | 437 modifiedChallenge[k] = enrollChallenge[k]; |
| 383 } | 438 } |
| 384 // V2 enroll responses contain signatures over a browser data object, | 439 // V2 enroll responses contain signatures over a browser data object, |
| 385 // which we're constructing here. The browser data object contains, among | 440 // which we're constructing here. The browser data object contains, among |
| 386 // other things, the server challenge. | 441 // other things, the server challenge. |
| 387 var serverChallenge = enrollChallenge['challenge']; | 442 var serverChallenge = enrollChallenge['challenge']; |
| 388 var browserData = makeEnrollBrowserData( | 443 var browserData = makeEnrollBrowserData( |
| 389 serverChallenge, this.origin_, this.tlsChannelId_); | 444 serverChallenge, this.origin_, this.tlsChannelId_); |
| 390 // Replace the challenge with the hash of the browser data. | 445 // Replace the challenge with the hash of the browser data. |
| 391 modifiedChallenge['challenge'] = | 446 modifiedChallenge['challenge'] = |
| 392 B64_encode(sha256HashOfString(browserData)); | 447 B64_encode(sha256HashOfString(browserData)); |
| 393 this.browserData_[version] = | 448 this.browserData_[version] = |
| 394 B64_encode(UTIL_StringToBytes(browserData)); | 449 B64_encode(UTIL_StringToBytes(browserData)); |
| 395 challenges.push(Enroller.encodeEnrollChallenge_( | 450 challenges.push(Enroller.encodeEnrollChallenge_( |
| 396 /** @type {EnrollChallenge} */ (modifiedChallenge))); | 451 /** @type {EnrollChallenge} */ (modifiedChallenge), opt_appId)); |
| 397 } else { | 452 } else { |
| 398 challenges.push(Enroller.encodeEnrollChallenge_(enrollChallenge)); | 453 challenges.push( |
| 454 Enroller.encodeEnrollChallenge_(enrollChallenge, opt_appId)); |
| 399 } | 455 } |
| 400 } | 456 } |
| 401 return challenges; | 457 return challenges; |
| 402 }; | 458 }; |
| 403 | 459 |
| 404 /** | 460 /** |
| 405 * Checks the app ids associated with this enroll request, and calls a callback | 461 * Checks the app ids associated with this enroll request, and calls a callback |
| 406 * with the result of the check. | 462 * with the result of the check. |
| 407 * @param {!Array.<string>} enrollAppIds The app ids in the enroll challenge | 463 * @param {!Array.<string>} enrollAppIds The app ids in the enroll challenge |
| 408 * portion of the enroll request. | 464 * portion of the enroll request. |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 498 // For U2F_V2, the challenge sent to the gnubby is modified to be the hash | 554 // For U2F_V2, the challenge sent to the gnubby is modified to be the hash |
| 499 // of the browser data. Include the browser data. | 555 // of the browser data. Include the browser data. |
| 500 browserData = this.browserData_[reply.version]; | 556 browserData = this.browserData_[reply.version]; |
| 501 } | 557 } |
| 502 | 558 |
| 503 this.notifySuccess_(/** @type {string} */ (reply.version), | 559 this.notifySuccess_(/** @type {string} */ (reply.version), |
| 504 /** @type {string} */ (reply.enrollData), | 560 /** @type {string} */ (reply.enrollData), |
| 505 browserData); | 561 browserData); |
| 506 } | 562 } |
| 507 }; | 563 }; |
| OLD | NEW |