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 Handles web page requests for gnubby enrollment. |
| 7 * @author juanlang@google.com (Juan Lang) |
| 8 */ |
| 9 |
| 10 'use strict'; |
| 11 |
| 12 /** |
| 13 * Handles an enroll request. |
| 14 * @param {!EnrollHelperFactory} factory Factory to create an enroll helper. |
| 15 * @param {MessageSender} sender The sender of the message. |
| 16 * @param {Object} request The web page's enroll request. |
| 17 * @param {boolean} enforceAppIdValid Whether to enforce that the appId in the |
| 18 * request matches the sender's origin. |
| 19 * @param {Function} sendResponse Called back with the result of the enroll. |
| 20 * @param {boolean} toleratesMultipleResponses Whether the sendResponse |
| 21 * callback can be called more than once, e.g. for progress updates. |
| 22 * @return {Closeable} |
| 23 */ |
| 24 function handleEnrollRequest(factory, sender, request, enforceAppIdValid, |
| 25 sendResponse, toleratesMultipleResponses) { |
| 26 var sentResponse = false; |
| 27 function sendResponseOnce(r) { |
| 28 if (enroller) { |
| 29 enroller.close(); |
| 30 enroller = null; |
| 31 } |
| 32 if (!sentResponse) { |
| 33 sentResponse = true; |
| 34 try { |
| 35 // If the page has gone away or the connection has otherwise gone, |
| 36 // sendResponse fails. |
| 37 sendResponse(r); |
| 38 } catch (exception) { |
| 39 console.warn('sendResponse failed: ' + exception); |
| 40 } |
| 41 } else { |
| 42 console.warn(UTIL_fmt('Tried to reply more than once! Juan, FIX ME')); |
| 43 } |
| 44 } |
| 45 |
| 46 function sendErrorResponse(code) { |
| 47 console.log(UTIL_fmt('code=' + code)); |
| 48 var response = formatWebPageResponse(GnubbyMsgTypes.ENROLL_WEB_REPLY, code); |
| 49 if (request['requestId']) { |
| 50 response['requestId'] = request['requestId']; |
| 51 } |
| 52 sendResponseOnce(response); |
| 53 } |
| 54 |
| 55 var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); |
| 56 if (!origin) { |
| 57 sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST); |
| 58 return null; |
| 59 } |
| 60 |
| 61 if (!isValidEnrollRequest(request)) { |
| 62 sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST); |
| 63 return null; |
| 64 } |
| 65 |
| 66 var signData = request['signData']; |
| 67 var enrollChallenges = request['enrollChallenges']; |
| 68 var logMsgUrl = request['logMsgUrl']; |
| 69 var timeoutMillis = Enroller.DEFAULT_TIMEOUT_MILLIS; |
| 70 if (request['timeout']) { |
| 71 // Request timeout is in seconds. |
| 72 timeoutMillis = request['timeout'] * 1000; |
| 73 } |
| 74 |
| 75 function findChallengeOfVersion(enrollChallenges, version) { |
| 76 for (var i = 0; i < enrollChallenges.length; i++) { |
| 77 if (enrollChallenges[i]['version'] == version) { |
| 78 return enrollChallenges[i]; |
| 79 } |
| 80 } |
| 81 return null; |
| 82 } |
| 83 |
| 84 function sendSuccessResponse(u2fVersion, info, browserData) { |
| 85 var enrollChallenge = findChallengeOfVersion(enrollChallenges, u2fVersion); |
| 86 if (!enrollChallenge) { |
| 87 sendErrorResponse(GnubbyCodeTypes.UNKNOWN_ERROR); |
| 88 return; |
| 89 } |
| 90 var enrollUpdateData = {}; |
| 91 enrollUpdateData['enrollData'] = info; |
| 92 // Echo the used challenge back in the reply. |
| 93 for (var k in enrollChallenge) { |
| 94 enrollUpdateData[k] = enrollChallenge[k]; |
| 95 } |
| 96 if (u2fVersion == 'U2F_V2') { |
| 97 // For U2F_V2, the challenge sent to the gnubby is modified to be the |
| 98 // hash of the browser data. Include the browser data. |
| 99 enrollUpdateData['browserData'] = browserData; |
| 100 } |
| 101 var response = formatWebPageResponse( |
| 102 GnubbyMsgTypes.ENROLL_WEB_REPLY, GnubbyCodeTypes.OK, enrollUpdateData); |
| 103 sendResponseOnce(response); |
| 104 } |
| 105 |
| 106 function sendNotification(code) { |
| 107 console.log(UTIL_fmt('notification, code=' + code)); |
| 108 // Can the callback handle progress updates? If so, send one. |
| 109 if (toleratesMultipleResponses) { |
| 110 var response = formatWebPageResponse( |
| 111 GnubbyMsgTypes.ENROLL_WEB_NOTIFICATION, code); |
| 112 if (request['requestId']) { |
| 113 response['requestId'] = request['requestId']; |
| 114 } |
| 115 sendResponse(response); |
| 116 } |
| 117 } |
| 118 |
| 119 var timer = new CountdownTimer(timeoutMillis); |
| 120 var enroller = new Enroller(factory, timer, origin, sendErrorResponse, |
| 121 sendSuccessResponse, sendNotification, sender.tlsChannelId, logMsgUrl); |
| 122 enroller.doEnroll(enrollChallenges, signData, enforceAppIdValid); |
| 123 return /** @type {Closeable} */ (enroller); |
| 124 } |
| 125 |
| 126 /** |
| 127 * Returns whether the request appears to be a valid enroll request. |
| 128 * @param {Object} request the request. |
| 129 * @return {boolean} whether the request appears valid. |
| 130 */ |
| 131 function isValidEnrollRequest(request) { |
| 132 if (!request.hasOwnProperty('enrollChallenges')) |
| 133 return false; |
| 134 var enrollChallenges = request['enrollChallenges']; |
| 135 if (!enrollChallenges.length) |
| 136 return false; |
| 137 var seenVersions = {}; |
| 138 for (var i = 0; i < enrollChallenges.length; i++) { |
| 139 var enrollChallenge = enrollChallenges[i]; |
| 140 var version = enrollChallenge['version']; |
| 141 if (!version) { |
| 142 // Version is implicitly V1 if not specified. |
| 143 version = 'U2F_V1'; |
| 144 } |
| 145 if (version != 'U2F_V1' && version != 'U2F_V2') { |
| 146 return false; |
| 147 } |
| 148 if (seenVersions[version]) { |
| 149 // Each version can appear at most once. |
| 150 return false; |
| 151 } |
| 152 seenVersions[version] = version; |
| 153 if (!enrollChallenge['appId']) { |
| 154 return false; |
| 155 } |
| 156 if (!enrollChallenge['challenge']) { |
| 157 // The challenge is required. |
| 158 return false; |
| 159 } |
| 160 } |
| 161 var signData = request['signData']; |
| 162 // An empty signData is ok, in the case the user is not already enrolled. |
| 163 if (signData && !isValidSignData(signData)) |
| 164 return false; |
| 165 return true; |
| 166 } |
| 167 |
| 168 /** |
| 169 * Creates a new object to track enrolling with a gnubby. |
| 170 * @param {!EnrollHelperFactory} helperFactory factory to create an enroll |
| 171 * helper. |
| 172 * @param {!Countdown} timer Timer for enroll request. |
| 173 * @param {string} origin The origin making the request. |
| 174 * @param {function(number)} errorCb Called upon enroll failure with an error |
| 175 * code. |
| 176 * @param {function(string, string, (string|undefined))} successCb Called upon |
| 177 * enroll success with the version of the succeeding gnubby, the enroll |
| 178 * data, and optionally the browser data associated with the enrollment. |
| 179 * @param {(function(number)|undefined)} opt_progressCb Called with progress |
| 180 * updates to the enroll request. |
| 181 * @param {string=} opt_tlsChannelId the TLS channel ID, if any, of the origin |
| 182 * making the request. |
| 183 * @param {string=} opt_logMsgUrl The url to post log messages to. |
| 184 * @constructor |
| 185 */ |
| 186 function Enroller(helperFactory, timer, origin, errorCb, successCb, |
| 187 opt_progressCb, opt_tlsChannelId, opt_logMsgUrl) { |
| 188 /** @private {Countdown} */ |
| 189 this.timer_ = timer; |
| 190 /** @private {string} */ |
| 191 this.origin_ = origin; |
| 192 /** @private {function(number)} */ |
| 193 this.errorCb_ = errorCb; |
| 194 /** @private {function(string, string, (string|undefined))} */ |
| 195 this.successCb_ = successCb; |
| 196 /** @private {(function(number)|undefined)} */ |
| 197 this.progressCb_ = opt_progressCb; |
| 198 /** @private {string|undefined} */ |
| 199 this.tlsChannelId_ = opt_tlsChannelId; |
| 200 /** @private {string|undefined} */ |
| 201 this.logMsgUrl_ = opt_logMsgUrl; |
| 202 |
| 203 /** @private {boolean} */ |
| 204 this.done_ = false; |
| 205 /** @private {number|undefined} */ |
| 206 this.lastProgressUpdate_ = undefined; |
| 207 |
| 208 /** @private {Object.<string, string>} */ |
| 209 this.browserData_ = {}; |
| 210 /** @private {Array.<EnrollHelperChallenge>} */ |
| 211 this.encodedEnrollChallenges_ = []; |
| 212 /** @private {Array.<SignHelperChallenge>} */ |
| 213 this.encodedSignChallenges_ = []; |
| 214 // Allow http appIds for http origins. (Broken, but the caller deserves |
| 215 // what they get.) |
| 216 /** @private {boolean} */ |
| 217 this.allowHttp_ = this.origin_ ? this.origin_.indexOf('http://') == 0 : false; |
| 218 |
| 219 /** @private {EnrollHelper} */ |
| 220 this.helper_ = helperFactory.createHelper(timer, |
| 221 this.helperError_.bind(this), this.helperSuccess_.bind(this), |
| 222 this.helperProgress_.bind(this)); |
| 223 } |
| 224 |
| 225 /** |
| 226 * Default timeout value in case the caller never provides a valid timeout. |
| 227 */ |
| 228 Enroller.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; |
| 229 |
| 230 /** |
| 231 * Performs an enroll request with the given enroll and sign challenges. |
| 232 * @param {Array.<Object>} enrollChallenges |
| 233 * @param {Array.<Object>} signChallenges |
| 234 * @param {boolean} enforceAppIdValid |
| 235 */ |
| 236 Enroller.prototype.doEnroll = |
| 237 function(enrollChallenges, signChallenges, enforceAppIdValid) { |
| 238 this.setEnrollChallenges_(enrollChallenges); |
| 239 this.setSignChallenges_(signChallenges); |
| 240 |
| 241 if (!enforceAppIdValid) { |
| 242 // If not enforcing app id validity, begin enrolling right away. |
| 243 this.helper_.doEnroll(this.encodedEnrollChallenges_, |
| 244 this.encodedSignChallenges_); |
| 245 } |
| 246 // Whether or not enforcing app id validity, begin fetching/checking the |
| 247 // app ids. |
| 248 var enrollAppIds = []; |
| 249 for (var i = 0; i < enrollChallenges.length; i++) { |
| 250 enrollAppIds.push(enrollChallenges[i]['appId']); |
| 251 } |
| 252 var self = this; |
| 253 this.checkAppIds_(enrollAppIds, signChallenges, function(result) { |
| 254 if (!enforceAppIdValid) { |
| 255 // Nothing to do, move along. |
| 256 return; |
| 257 } |
| 258 if (result) { |
| 259 self.helper_.doEnroll(self.encodedEnrollChallenges_, |
| 260 self.encodedSignChallenges_); |
| 261 } else { |
| 262 self.notifyError_(GnubbyCodeTypes.BAD_APP_ID); |
| 263 } |
| 264 }); |
| 265 }; |
| 266 |
| 267 /** |
| 268 * Encodes the enroll challenges for use by an enroll helper. |
| 269 * @param {Array.<Object>} enrollChallenges |
| 270 * @return {Array.<EnrollHelperChallenge>} the encoded challenges. |
| 271 * @private |
| 272 */ |
| 273 Enroller.encodeEnrollChallenges_ = function(enrollChallenges) { |
| 274 var encodedChallenges = []; |
| 275 for (var i = 0; i < enrollChallenges.length; i++) { |
| 276 var enrollChallenge = enrollChallenges[i]; |
| 277 var encodedChallenge = {}; |
| 278 var version; |
| 279 if (enrollChallenge['version']) { |
| 280 version = enrollChallenge['version']; |
| 281 } else { |
| 282 // Version is implicitly V1 if not specified. |
| 283 version = 'U2F_V1'; |
| 284 } |
| 285 encodedChallenge['version'] = version; |
| 286 encodedChallenge['challenge'] = enrollChallenge['challenge']; |
| 287 encodedChallenge['appIdHash'] = |
| 288 B64_encode(sha256HashOfString(enrollChallenge['appId'])); |
| 289 encodedChallenges.push(encodedChallenge); |
| 290 } |
| 291 return encodedChallenges; |
| 292 }; |
| 293 |
| 294 /** |
| 295 * Sets this enroller's enroll challenges. |
| 296 * @param {Array.<Object>} enrollChallenges The enroll challenges. |
| 297 * @private |
| 298 */ |
| 299 Enroller.prototype.setEnrollChallenges_ = function(enrollChallenges) { |
| 300 var challenges = []; |
| 301 for (var i = 0; i < enrollChallenges.length; i++) { |
| 302 var enrollChallenge = enrollChallenges[i]; |
| 303 var version = enrollChallenge.version; |
| 304 if (!version) { |
| 305 // Version is implicitly V1 if not specified. |
| 306 version = 'U2F_V1'; |
| 307 } |
| 308 |
| 309 if (version == 'U2F_V2') { |
| 310 var modifiedChallenge = {}; |
| 311 for (var k in enrollChallenge) { |
| 312 modifiedChallenge[k] = enrollChallenge[k]; |
| 313 } |
| 314 // V2 enroll responses contain signatures over a browser data object, |
| 315 // which we're constructing here. The browser data object contains, among |
| 316 // other things, the server challenge. |
| 317 var serverChallenge = enrollChallenge['challenge']; |
| 318 var browserData = makeEnrollBrowserData( |
| 319 serverChallenge, this.origin_, this.tlsChannelId_); |
| 320 // Replace the challenge with the hash of the browser data. |
| 321 modifiedChallenge['challenge'] = |
| 322 B64_encode(sha256HashOfString(browserData)); |
| 323 this.browserData_[version] = |
| 324 B64_encode(UTIL_StringToBytes(browserData)); |
| 325 challenges.push(modifiedChallenge); |
| 326 } else { |
| 327 challenges.push(enrollChallenge); |
| 328 } |
| 329 } |
| 330 // Store the encoded challenges for use by the enroll helper. |
| 331 this.encodedEnrollChallenges_ = |
| 332 Enroller.encodeEnrollChallenges_(challenges); |
| 333 }; |
| 334 |
| 335 /** |
| 336 * Sets this enroller's sign data. |
| 337 * @param {Array=} signData the sign challenges to add. |
| 338 * @private |
| 339 */ |
| 340 Enroller.prototype.setSignChallenges_ = function(signData) { |
| 341 this.encodedSignChallenges_ = []; |
| 342 if (signData) { |
| 343 for (var i = 0; i < signData.length; i++) { |
| 344 var incomingChallenge = signData[i]; |
| 345 var serverChallenge = incomingChallenge['challenge']; |
| 346 var appId = incomingChallenge['appId']; |
| 347 var encodedKeyHandle = incomingChallenge['keyHandle']; |
| 348 |
| 349 var challenge = makeChallenge(serverChallenge, appId, encodedKeyHandle, |
| 350 incomingChallenge['version']); |
| 351 |
| 352 this.encodedSignChallenges_.push(challenge); |
| 353 } |
| 354 } |
| 355 }; |
| 356 |
| 357 /** |
| 358 * Checks the app ids associated with this enroll request, and calls a callback |
| 359 * with the result of the check. |
| 360 * @param {!Array.<string>} enrollAppIds The app ids in the enroll challenge |
| 361 * portion of the enroll request. |
| 362 * @param {SignData} signData The sign data associated with the request. |
| 363 * @param {function(boolean)} cb Called with the result of the check. |
| 364 * @private |
| 365 */ |
| 366 Enroller.prototype.checkAppIds_ = function(enrollAppIds, signData, cb) { |
| 367 if (!enrollAppIds || !enrollAppIds.length) { |
| 368 // Defensive programming check: the enroll request is required to contain |
| 369 // its own app ids, so if there aren't any, reject the request. |
| 370 cb(false); |
| 371 return; |
| 372 } |
| 373 |
| 374 /** @private {Array.<string>} */ |
| 375 this.distinctAppIds_ = |
| 376 UTIL_unionArrays(enrollAppIds, getDistinctAppIds(signData)); |
| 377 /** @private {boolean} */ |
| 378 this.anyInvalidAppIds_ = false; |
| 379 /** @private {boolean} */ |
| 380 this.appIdFailureReported_ = false; |
| 381 /** @private {number} */ |
| 382 this.fetchedAppIds_ = 0; |
| 383 |
| 384 for (var i = 0; i < this.distinctAppIds_.length; i++) { |
| 385 var appId = this.distinctAppIds_[i]; |
| 386 if (appId == this.origin_) { |
| 387 // Trivially allowed. |
| 388 this.fetchedAppIds_++; |
| 389 if (this.fetchedAppIds_ == this.distinctAppIds_.length && |
| 390 !this.anyInvalidAppIds_) { |
| 391 // Last app id was fetched, and they were all valid: we're done. |
| 392 // (Note that the case when anyInvalidAppIds_ is true doesn't need to |
| 393 // be handled here: the callback was already called with false at that |
| 394 // point, see fetchedAllowedOriginsForAppId_.) |
| 395 cb(true); |
| 396 } |
| 397 } else { |
| 398 var start = new Date(); |
| 399 fetchAllowedOriginsForAppId(appId, this.allowHttp_, |
| 400 this.fetchedAllowedOriginsForAppId_.bind(this, appId, start, cb)); |
| 401 } |
| 402 } |
| 403 }; |
| 404 |
| 405 /** |
| 406 * Called with the result of an app id fetch. |
| 407 * @param {string} appId the app id that was fetched. |
| 408 * @param {Date} start the time the fetch request started. |
| 409 * @param {function(boolean)} cb Called with the result of the app id check. |
| 410 * @param {number} rc The HTTP response code for the app id fetch. |
| 411 * @param {!Array.<string>} allowedOrigins The origins allowed for this app id. |
| 412 * @private |
| 413 */ |
| 414 Enroller.prototype.fetchedAllowedOriginsForAppId_ = |
| 415 function(appId, start, cb, rc, allowedOrigins) { |
| 416 var end = new Date(); |
| 417 this.fetchedAppIds_++; |
| 418 logFetchAppIdResult(appId, end - start, allowedOrigins, this.logMsgUrl_); |
| 419 if (rc != 200 && !(rc >= 400 && rc < 500)) { |
| 420 if (this.timer_.expired()) { |
| 421 // Act as though the helper timed out. |
| 422 this.helperError_(DeviceStatusCodes.TIMEOUT_STATUS, false); |
| 423 } else { |
| 424 start = new Date(); |
| 425 fetchAllowedOriginsForAppId(appId, this.allowHttp_, |
| 426 this.fetchedAllowedOriginsForAppId_.bind(this, appId, start, cb)); |
| 427 } |
| 428 return; |
| 429 } |
| 430 if (!isValidAppIdForOrigin(appId, this.origin_, allowedOrigins)) { |
| 431 logInvalidOriginForAppId(this.origin_, appId, this.logMsgUrl_); |
| 432 this.anyInvalidAppIds_ = true; |
| 433 if (!this.appIdFailureReported_) { |
| 434 // Only the failure case can happen more than once, so only report |
| 435 // it the first time. |
| 436 this.appIdFailureReported_ = true; |
| 437 cb(false); |
| 438 } |
| 439 } |
| 440 if (this.fetchedAppIds_ == this.distinctAppIds_.length && |
| 441 !this.anyInvalidAppIds_) { |
| 442 // Last app id was fetched, and they were all valid: we're done. |
| 443 cb(true); |
| 444 } |
| 445 }; |
| 446 |
| 447 /** Closes this enroller. */ |
| 448 Enroller.prototype.close = function() { |
| 449 if (this.helper_) this.helper_.close(); |
| 450 }; |
| 451 |
| 452 /** |
| 453 * Notifies the caller with the error code. |
| 454 * @param {number} code |
| 455 * @private |
| 456 */ |
| 457 Enroller.prototype.notifyError_ = function(code) { |
| 458 if (this.done_) |
| 459 return; |
| 460 this.close(); |
| 461 this.done_ = true; |
| 462 this.errorCb_(code); |
| 463 }; |
| 464 |
| 465 /** |
| 466 * Notifies the caller of success with the provided response data. |
| 467 * @param {string} u2fVersion |
| 468 * @param {string} info |
| 469 * @param {string|undefined} opt_browserData |
| 470 * @private |
| 471 */ |
| 472 Enroller.prototype.notifySuccess_ = |
| 473 function(u2fVersion, info, opt_browserData) { |
| 474 if (this.done_) |
| 475 return; |
| 476 this.close(); |
| 477 this.done_ = true; |
| 478 this.successCb_(u2fVersion, info, opt_browserData); |
| 479 }; |
| 480 |
| 481 /** |
| 482 * Notifies the caller of progress with the error code. |
| 483 * @param {number} code |
| 484 * @private |
| 485 */ |
| 486 Enroller.prototype.notifyProgress_ = function(code) { |
| 487 if (this.done_) |
| 488 return; |
| 489 if (code != this.lastProgressUpdate_) { |
| 490 this.lastProgressUpdate_ = code; |
| 491 // If there is no progress callback, treat it like an error and clean up. |
| 492 if (this.progressCb_) { |
| 493 this.progressCb_(code); |
| 494 } else { |
| 495 this.notifyError_(code); |
| 496 } |
| 497 } |
| 498 }; |
| 499 |
| 500 /** |
| 501 * Maps an enroll helper's error code namespace to the page's error code |
| 502 * namespace. |
| 503 * @param {number} code Error code from DeviceStatusCodes namespace. |
| 504 * @param {boolean} anyGnubbies Whether any gnubbies were found. |
| 505 * @return {number} A GnubbyCodeTypes error code. |
| 506 * @private |
| 507 */ |
| 508 Enroller.mapError_ = function(code, anyGnubbies) { |
| 509 var reportedError = GnubbyCodeTypes.UNKNOWN_ERROR; |
| 510 switch (code) { |
| 511 case DeviceStatusCodes.WRONG_DATA_STATUS: |
| 512 reportedError = anyGnubbies ? GnubbyCodeTypes.ALREADY_ENROLLED : |
| 513 GnubbyCodeTypes.NO_GNUBBIES; |
| 514 break; |
| 515 |
| 516 case DeviceStatusCodes.WAIT_TOUCH_STATUS: |
| 517 reportedError = GnubbyCodeTypes.WAIT_TOUCH; |
| 518 break; |
| 519 |
| 520 case DeviceStatusCodes.BUSY_STATUS: |
| 521 reportedError = GnubbyCodeTypes.BUSY; |
| 522 break; |
| 523 } |
| 524 return reportedError; |
| 525 }; |
| 526 |
| 527 /** |
| 528 * Called by the helper upon error. |
| 529 * @param {number} code |
| 530 * @param {boolean} anyGnubbies |
| 531 * @private |
| 532 */ |
| 533 Enroller.prototype.helperError_ = function(code, anyGnubbies) { |
| 534 var reportedError = Enroller.mapError_(code, anyGnubbies); |
| 535 console.log(UTIL_fmt('helper reported ' + code.toString(16) + |
| 536 ', returning ' + reportedError)); |
| 537 this.notifyError_(reportedError); |
| 538 }; |
| 539 |
| 540 /** |
| 541 * Called by helper upon success. |
| 542 * @param {string} u2fVersion gnubby version. |
| 543 * @param {string} info enroll data. |
| 544 * @private |
| 545 */ |
| 546 Enroller.prototype.helperSuccess_ = function(u2fVersion, info) { |
| 547 console.log(UTIL_fmt('Gnubby enrollment succeeded!!!!!')); |
| 548 |
| 549 var browserData; |
| 550 if (u2fVersion == 'U2F_V2') { |
| 551 // For U2F_V2, the challenge sent to the gnubby is modified to be the hash |
| 552 // of the browser data. Include the browser data. |
| 553 browserData = this.browserData_[u2fVersion]; |
| 554 } |
| 555 |
| 556 this.notifySuccess_(u2fVersion, info, browserData); |
| 557 }; |
| 558 |
| 559 /** |
| 560 * Called by helper to notify progress. |
| 561 * @param {number} code |
| 562 * @param {boolean} anyGnubbies |
| 563 * @private |
| 564 */ |
| 565 Enroller.prototype.helperProgress_ = function(code, anyGnubbies) { |
| 566 var reportedError = Enroller.mapError_(code, anyGnubbies); |
| 567 console.log(UTIL_fmt('helper notified ' + code.toString(16) + |
| 568 ', returning ' + reportedError)); |
| 569 this.notifyProgress_(reportedError); |
| 570 }; |
OLD | NEW |