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 sign requests. |
| 7 * |
| 8 * @author juanlang@google.com (Juan Lang) |
| 9 */ |
| 10 |
| 11 'use strict'; |
| 12 |
| 13 var signRequestQueue = new OriginKeyedRequestQueue(); |
| 14 |
| 15 /** |
| 16 * Handles a sign request. |
| 17 * @param {!SignHelperFactory} factory Factory to create a sign helper. |
| 18 * @param {MessageSender} sender The sender of the message. |
| 19 * @param {Object} request The web page's sign request. |
| 20 * @param {boolean} enforceAppIdValid Whether to enforce that the app id in the |
| 21 * request matches the sender's origin. |
| 22 * @param {Function} sendResponse Called back with the result of the sign. |
| 23 * @param {boolean} toleratesMultipleResponses Whether the sendResponse |
| 24 * callback can be called more than once, e.g. for progress updates. |
| 25 * @return {Closeable} |
| 26 */ |
| 27 function handleSignRequest(factory, sender, request, enforceAppIdValid, |
| 28 sendResponse, toleratesMultipleResponses) { |
| 29 var sentResponse = false; |
| 30 function sendResponseOnce(r) { |
| 31 if (queuedSignRequest) { |
| 32 queuedSignRequest.close(); |
| 33 queuedSignRequest = null; |
| 34 } |
| 35 if (!sentResponse) { |
| 36 sentResponse = true; |
| 37 try { |
| 38 // If the page has gone away or the connection has otherwise gone, |
| 39 // sendResponse fails. |
| 40 sendResponse(r); |
| 41 } catch (exception) { |
| 42 console.warn('sendResponse failed: ' + exception); |
| 43 } |
| 44 } else { |
| 45 console.warn(UTIL_fmt('Tried to reply more than once! Juan, FIX ME')); |
| 46 } |
| 47 } |
| 48 |
| 49 function sendErrorResponse(code) { |
| 50 var response = formatWebPageResponse(GnubbyMsgTypes.SIGN_WEB_REPLY, code); |
| 51 sendResponseOnce(response); |
| 52 } |
| 53 |
| 54 function sendSuccessResponse(challenge, info, browserData) { |
| 55 var responseData = {}; |
| 56 for (var k in challenge) { |
| 57 responseData[k] = challenge[k]; |
| 58 } |
| 59 responseData['browserData'] = B64_encode(UTIL_StringToBytes(browserData)); |
| 60 responseData['signatureData'] = info; |
| 61 var response = formatWebPageResponse(GnubbyMsgTypes.SIGN_WEB_REPLY, |
| 62 GnubbyCodeTypes.OK, responseData); |
| 63 sendResponseOnce(response); |
| 64 } |
| 65 |
| 66 function sendNotification(code) { |
| 67 console.log(UTIL_fmt('notification, code=' + code)); |
| 68 // Can the callback handle progress updates? If so, send one. |
| 69 if (toleratesMultipleResponses) { |
| 70 var response = formatWebPageResponse( |
| 71 GnubbyMsgTypes.SIGN_WEB_NOTIFICATION, code); |
| 72 if (request['requestId']) { |
| 73 response['requestId'] = request['requestId']; |
| 74 } |
| 75 sendResponse(response); |
| 76 } |
| 77 } |
| 78 |
| 79 var origin = getOriginFromUrl(/** @type {string} */ (sender.url)); |
| 80 if (!origin) { |
| 81 sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST); |
| 82 return null; |
| 83 } |
| 84 // More closure type inference fail. |
| 85 var nonNullOrigin = /** @type {string} */ (origin); |
| 86 |
| 87 if (!isValidSignRequest(request)) { |
| 88 sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST); |
| 89 return null; |
| 90 } |
| 91 |
| 92 var signData = request['signData']; |
| 93 // A valid sign data has at least one challenge, so get the first appId from |
| 94 // the first challenge. |
| 95 var firstAppId = signData[0]['appId']; |
| 96 var timeoutMillis = Signer.DEFAULT_TIMEOUT_MILLIS; |
| 97 if (request['timeout']) { |
| 98 // Request timeout is in seconds. |
| 99 timeoutMillis = request['timeout'] * 1000; |
| 100 } |
| 101 var timer = new CountdownTimer(timeoutMillis); |
| 102 var logMsgUrl = request['logMsgUrl']; |
| 103 |
| 104 // Queue sign requests from the same origin, to protect against simultaneous |
| 105 // sign-out on many tabs resulting in repeated sign-in requests. |
| 106 var queuedSignRequest = new QueuedSignRequest(signData, factory, timer, |
| 107 nonNullOrigin, enforceAppIdValid, sendErrorResponse, sendSuccessResponse, |
| 108 sendNotification, sender.tlsChannelId, logMsgUrl); |
| 109 var requestToken = signRequestQueue.queueRequest(firstAppId, nonNullOrigin, |
| 110 queuedSignRequest.begin.bind(queuedSignRequest), timer); |
| 111 queuedSignRequest.setToken(requestToken); |
| 112 return queuedSignRequest; |
| 113 } |
| 114 |
| 115 /** |
| 116 * Returns whether the request appears to be a valid sign request. |
| 117 * @param {Object} request the request. |
| 118 * @return {boolean} whether the request appears valid. |
| 119 */ |
| 120 function isValidSignRequest(request) { |
| 121 if (!request.hasOwnProperty('signData')) |
| 122 return false; |
| 123 var signData = request['signData']; |
| 124 // If a sign request contains an empty array of challenges, it could never |
| 125 // be fulfilled. Fail. |
| 126 if (!signData.length) |
| 127 return false; |
| 128 return isValidSignData(signData); |
| 129 } |
| 130 |
| 131 /** |
| 132 * Adapter class representing a queued sign request. |
| 133 * @param {!SignData} signData |
| 134 * @param {!SignHelperFactory} factory |
| 135 * @param {Countdown} timer |
| 136 * @param {string} origin |
| 137 * @param {boolean} enforceAppIdValid |
| 138 * @param {function(number)} errorCb |
| 139 * @param {function(SignChallenge, string, string)} successCb |
| 140 * @param {(function(number)|undefined)} opt_progressCb |
| 141 * @param {string|undefined} opt_tlsChannelId |
| 142 * @param {string|undefined} opt_logMsgUrl |
| 143 * @constructor |
| 144 * @implements {Closeable} |
| 145 */ |
| 146 function QueuedSignRequest(signData, factory, timer, origin, enforceAppIdValid, |
| 147 errorCb, successCb, opt_progressCb, opt_tlsChannelId, opt_logMsgUrl) { |
| 148 /** @private {!SignData} */ |
| 149 this.signData_ = signData; |
| 150 /** @private {!SignHelperFactory} */ |
| 151 this.factory_ = factory; |
| 152 /** @private {Countdown} */ |
| 153 this.timer_ = timer; |
| 154 /** @private {string} */ |
| 155 this.origin_ = origin; |
| 156 /** @private {boolean} */ |
| 157 this.enforceAppIdValid_ = enforceAppIdValid; |
| 158 /** @private {function(number)} */ |
| 159 this.errorCb_ = errorCb; |
| 160 /** @private {function(SignChallenge, string, string)} */ |
| 161 this.successCb_ = successCb; |
| 162 /** @private {(function(number)|undefined)} */ |
| 163 this.progressCb_ = opt_progressCb; |
| 164 /** @private {string|undefined} */ |
| 165 this.tlsChannelId_ = opt_tlsChannelId; |
| 166 /** @private {string|undefined} */ |
| 167 this.logMsgUrl_ = opt_logMsgUrl; |
| 168 /** @private {boolean} */ |
| 169 this.begun_ = false; |
| 170 /** @private {boolean} */ |
| 171 this.closed_ = false; |
| 172 } |
| 173 |
| 174 /** Closes this sign request. */ |
| 175 QueuedSignRequest.prototype.close = function() { |
| 176 if (this.closed_) return; |
| 177 if (this.begun_ && this.signer_) { |
| 178 this.signer_.close(); |
| 179 } |
| 180 if (this.token_) { |
| 181 this.token_.complete(); |
| 182 } |
| 183 this.closed_ = true; |
| 184 }; |
| 185 |
| 186 /** |
| 187 * @param {QueuedRequestToken} token Token for this sign request. |
| 188 */ |
| 189 QueuedSignRequest.prototype.setToken = function(token) { |
| 190 /** @private {QueuedRequestToken} */ |
| 191 this.token_ = token; |
| 192 }; |
| 193 |
| 194 /** |
| 195 * Called when this sign request may begin work. |
| 196 * @param {QueuedRequestToken} token Token for this sign request. |
| 197 */ |
| 198 QueuedSignRequest.prototype.begin = function(token) { |
| 199 this.begun_ = true; |
| 200 this.setToken(token); |
| 201 this.signer_ = new Signer(this.factory_, this.timer_, this.origin_, |
| 202 this.enforceAppIdValid_, this.signerFailed_.bind(this), |
| 203 this.signerSucceeded_.bind(this), this.progressCb_, |
| 204 this.tlsChannelId_, this.logMsgUrl_); |
| 205 if (!this.signer_.setChallenges(this.signData_)) { |
| 206 token.complete(); |
| 207 this.errorCb_(GnubbyCodeTypes.BAD_REQUEST); |
| 208 } |
| 209 }; |
| 210 |
| 211 /** |
| 212 * Called when this request's signer fails. |
| 213 * @param {number} code The failure code reported by the signer. |
| 214 * @private |
| 215 */ |
| 216 QueuedSignRequest.prototype.signerFailed_ = function(code) { |
| 217 this.token_.complete(); |
| 218 this.errorCb_(code); |
| 219 }; |
| 220 |
| 221 /** |
| 222 * Called when this request's signer succeeds. |
| 223 * @param {SignChallenge} challenge The challenge that was signed. |
| 224 * @param {string} info The sign result. |
| 225 * @param {string} browserData |
| 226 * @private |
| 227 */ |
| 228 QueuedSignRequest.prototype.signerSucceeded_ = |
| 229 function(challenge, info, browserData) { |
| 230 this.token_.complete(); |
| 231 this.successCb_(challenge, info, browserData); |
| 232 }; |
| 233 |
| 234 /** |
| 235 * Creates an object to track signing with a gnubby. |
| 236 * @param {!SignHelperFactory} helperFactory Factory to create a sign helper. |
| 237 * @param {Countdown} timer Timer for sign request. |
| 238 * @param {string} origin The origin making the request. |
| 239 * @param {boolean} enforceAppIdValid Whether to enforce that the appId in the |
| 240 * request matches the sender's origin. |
| 241 * @param {function(number)} errorCb Called when the sign operation fails. |
| 242 * @param {function(SignChallenge, string, string)} successCb Called when the |
| 243 * sign operation succeeds. |
| 244 * @param {(function(number)|undefined)} opt_progressCb Called with progress |
| 245 * updates to the sign request. |
| 246 * @param {string=} opt_tlsChannelId the TLS channel ID, if any, of the origin |
| 247 * making the request. |
| 248 * @param {string=} opt_logMsgUrl The url to post log messages to. |
| 249 * @constructor |
| 250 */ |
| 251 function Signer(helperFactory, timer, origin, enforceAppIdValid, |
| 252 errorCb, successCb, opt_progressCb, opt_tlsChannelId, opt_logMsgUrl) { |
| 253 /** @private {Countdown} */ |
| 254 this.timer_ = timer; |
| 255 /** @private {string} */ |
| 256 this.origin_ = origin; |
| 257 /** @private {boolean} */ |
| 258 this.enforceAppIdValid_ = enforceAppIdValid; |
| 259 /** @private {function(number)} */ |
| 260 this.errorCb_ = errorCb; |
| 261 /** @private {function(SignChallenge, string, string)} */ |
| 262 this.successCb_ = successCb; |
| 263 /** @private {(function(number)|undefined)} */ |
| 264 this.progressCb_ = opt_progressCb; |
| 265 /** @private {string|undefined} */ |
| 266 this.tlsChannelId_ = opt_tlsChannelId; |
| 267 /** @private {string|undefined} */ |
| 268 this.logMsgUrl_ = opt_logMsgUrl; |
| 269 |
| 270 /** @private {boolean} */ |
| 271 this.challengesSet_ = false; |
| 272 /** @private {Array.<SignHelperChallenge>} */ |
| 273 this.pendingChallenges_ = []; |
| 274 /** @private {boolean} */ |
| 275 this.done_ = false; |
| 276 |
| 277 /** @private {Object.<string, string>} */ |
| 278 this.browserData_ = {}; |
| 279 /** @private {Object.<string, SignChallenge>} */ |
| 280 this.serverChallenges_ = {}; |
| 281 // Allow http appIds for http origins. (Broken, but the caller deserves |
| 282 // what they get.) |
| 283 /** @private {boolean} */ |
| 284 this.allowHttp_ = this.origin_ ? this.origin_.indexOf('http://') == 0 : false; |
| 285 |
| 286 // Protect against helper failure with a watchdog. |
| 287 this.createWatchdog_(timer); |
| 288 /** @private {SignHelper} */ |
| 289 this.helper_ = helperFactory.createHelper( |
| 290 timer, this.helperError_.bind(this), this.helperSuccess_.bind(this), |
| 291 this.helperProgress_.bind(this), this.logMsgUrl_); |
| 292 } |
| 293 |
| 294 /** |
| 295 * Creates a timer with an expiry greater than the expiration time of the given |
| 296 * timer. |
| 297 * @param {Countdown} timer |
| 298 * @private |
| 299 */ |
| 300 Signer.prototype.createWatchdog_ = function(timer) { |
| 301 var millis = timer.millisecondsUntilExpired(); |
| 302 millis += CountdownTimer.TIMER_INTERVAL_MILLIS; |
| 303 /** @private {Countdown|undefined} */ |
| 304 this.watchdogTimer_ = new CountdownTimer(millis, this.timeout_.bind(this)); |
| 305 }; |
| 306 |
| 307 /** |
| 308 * Default timeout value in case the caller never provides a valid timeout. |
| 309 */ |
| 310 Signer.DEFAULT_TIMEOUT_MILLIS = 30 * 1000; |
| 311 |
| 312 /** |
| 313 * Sets the challenges to be signed. |
| 314 * @param {SignData} signData The challenges to set. |
| 315 * @return {boolean} Whether the challenges could be set. |
| 316 */ |
| 317 Signer.prototype.setChallenges = function(signData) { |
| 318 if (this.challengesSet_ || this.done_) |
| 319 return false; |
| 320 /** @private {SignData} */ |
| 321 this.signData_ = signData; |
| 322 /** @private {boolean} */ |
| 323 this.challengesSet_ = true; |
| 324 |
| 325 // If app id enforcing isn't in effect, go ahead and start the helper with |
| 326 // all of the incoming challenges. |
| 327 var success = true; |
| 328 if (!this.enforceAppIdValid_) { |
| 329 success = this.addChallenges(signData, true /* finalChallenges */); |
| 330 } |
| 331 |
| 332 this.checkAppIds_(); |
| 333 return success; |
| 334 }; |
| 335 |
| 336 /** |
| 337 * Adds new challenges to the challenges being signed. |
| 338 * @param {SignData} signData Challenges to add. |
| 339 * @param {boolean} finalChallenges Whether these are the final challenges. |
| 340 * @return {boolean} Whether the challenge could be added. |
| 341 */ |
| 342 Signer.prototype.addChallenges = function(signData, finalChallenges) { |
| 343 var newChallenges = this.encodeSignChallenges_(signData); |
| 344 for (var i = 0; i < newChallenges.length; i++) { |
| 345 this.pendingChallenges_.push(newChallenges[i]); |
| 346 } |
| 347 if (!finalChallenges) { |
| 348 return true; |
| 349 } |
| 350 return this.helper_.doSign(this.pendingChallenges_); |
| 351 }; |
| 352 |
| 353 /** |
| 354 * Creates challenges for helper from challenges. |
| 355 * @param {Array.<SignChallenge>} challenges Challenges to add. |
| 356 * @return {Array.<SignHelperChallenge>} |
| 357 * @private |
| 358 */ |
| 359 Signer.prototype.encodeSignChallenges_ = function(challenges) { |
| 360 var newChallenges = []; |
| 361 for (var i = 0; i < challenges.length; i++) { |
| 362 var incomingChallenge = challenges[i]; |
| 363 var serverChallenge = incomingChallenge['challenge']; |
| 364 var appId = incomingChallenge['appId']; |
| 365 var encodedKeyHandle = incomingChallenge['keyHandle']; |
| 366 var version = incomingChallenge['version']; |
| 367 |
| 368 var browserData = |
| 369 makeSignBrowserData(serverChallenge, this.origin_, this.tlsChannelId_); |
| 370 var encodedChallenge = makeChallenge(browserData, appId, encodedKeyHandle, |
| 371 version); |
| 372 |
| 373 var key = encodedKeyHandle + encodedChallenge['challengeHash']; |
| 374 this.browserData_[key] = browserData; |
| 375 this.serverChallenges_[key] = incomingChallenge; |
| 376 |
| 377 newChallenges.push(encodedChallenge); |
| 378 } |
| 379 return newChallenges; |
| 380 }; |
| 381 |
| 382 /** |
| 383 * Checks the app ids of incoming requests, and, when this signer is enforcing |
| 384 * that app ids are valid, adds successful challenges to those being signed. |
| 385 * @private |
| 386 */ |
| 387 Signer.prototype.checkAppIds_ = function() { |
| 388 // Check the incoming challenges' app ids. |
| 389 /** @private {Array.<[string, Array.<Request>]>} */ |
| 390 this.orderedRequests_ = requestsByAppId(this.signData_); |
| 391 if (!this.orderedRequests_.length) { |
| 392 // Safety check: if the challenges are somehow empty, the helper will never |
| 393 // be fed any data, so the request could never be satisfied. You lose. |
| 394 this.notifyError_(GnubbyCodeTypes.BAD_REQUEST); |
| 395 return; |
| 396 } |
| 397 /** @private {number} */ |
| 398 this.fetchedAppIds_ = 0; |
| 399 /** @private {number} */ |
| 400 this.validAppIds_ = 0; |
| 401 for (var i = 0, appIdRequestsPair; i < this.orderedRequests_.length; i++) { |
| 402 var appIdRequestsPair = this.orderedRequests_[i]; |
| 403 var appId = appIdRequestsPair[0]; |
| 404 var requests = appIdRequestsPair[1]; |
| 405 if (appId == this.origin_) { |
| 406 // Trivially allowed. |
| 407 this.fetchedAppIds_++; |
| 408 this.validAppIds_++; |
| 409 // Only add challenges if in enforcing mode, i.e. they weren't added |
| 410 // earlier. |
| 411 if (this.enforceAppIdValid_) { |
| 412 this.addChallenges(requests, |
| 413 this.fetchedAppIds_ == this.orderedRequests_.length); |
| 414 } |
| 415 } else { |
| 416 var start = new Date(); |
| 417 fetchAllowedOriginsForAppId(appId, this.allowHttp_, |
| 418 this.fetchedAllowedOriginsForAppId_.bind(this, appId, start, |
| 419 requests)); |
| 420 } |
| 421 } |
| 422 }; |
| 423 |
| 424 /** |
| 425 * Called with the result of an app id fetch. |
| 426 * @param {string} appId the app id that was fetched. |
| 427 * @param {Date} start the time the fetch request started. |
| 428 * @param {Array.<SignChallenge>} challenges Challenges for this app id. |
| 429 * @param {number} rc The HTTP response code for the app id fetch. |
| 430 * @param {!Array.<string>} allowedOrigins The origins allowed for this app id. |
| 431 * @private |
| 432 */ |
| 433 Signer.prototype.fetchedAllowedOriginsForAppId_ = function(appId, start, |
| 434 challenges, rc, allowedOrigins) { |
| 435 var end = new Date(); |
| 436 logFetchAppIdResult(appId, end - start, allowedOrigins, this.logMsgUrl_); |
| 437 if (rc != 200 && !(rc >= 400 && rc < 500)) { |
| 438 if (this.timer_.expired()) { |
| 439 // Act as though the helper timed out. |
| 440 this.helperError_(DeviceStatusCodes.TIMEOUT_STATUS, false); |
| 441 } else { |
| 442 start = new Date(); |
| 443 fetchAllowedOriginsForAppId(appId, this.allowHttp_, |
| 444 this.fetchedAllowedOriginsForAppId_.bind(this, appId, start, |
| 445 challenges)); |
| 446 } |
| 447 return; |
| 448 } |
| 449 this.fetchedAppIds_++; |
| 450 var finalChallenges = (this.fetchedAppIds_ == this.orderedRequests_.length); |
| 451 if (isValidAppIdForOrigin(appId, this.origin_, allowedOrigins)) { |
| 452 this.validAppIds_++; |
| 453 // Only add challenges if in enforcing mode, i.e. they weren't added |
| 454 // earlier. |
| 455 if (this.enforceAppIdValid_) { |
| 456 this.addChallenges(challenges, finalChallenges); |
| 457 } |
| 458 } else { |
| 459 logInvalidOriginForAppId(this.origin_, appId, this.logMsgUrl_); |
| 460 // If in enforcing mode and this is the final request, sign the valid |
| 461 // challenges. |
| 462 if (this.enforceAppIdValid_ && finalChallenges) { |
| 463 if (!this.helper_.doSign(this.pendingChallenges_)) { |
| 464 this.notifyError_(GnubbyCodeTypes.BAD_REQUEST); |
| 465 return; |
| 466 } |
| 467 } |
| 468 } |
| 469 if (this.enforceAppIdValid_ && finalChallenges && !this.validAppIds_) { |
| 470 // If all app ids are invalid, notify the caller, otherwise implicitly |
| 471 // allow the helper to report whether any of the valid challenges succeeded. |
| 472 this.notifyError_(GnubbyCodeTypes.BAD_APP_ID); |
| 473 } |
| 474 }; |
| 475 |
| 476 /** |
| 477 * Called when the timeout expires on this signer. |
| 478 * @private |
| 479 */ |
| 480 Signer.prototype.timeout_ = function() { |
| 481 this.watchdogTimer_ = undefined; |
| 482 // The web page gets grumpy if it doesn't get WAIT_TOUCH within a reasonable |
| 483 // time. |
| 484 this.notifyError_(GnubbyCodeTypes.WAIT_TOUCH); |
| 485 }; |
| 486 |
| 487 /** Closes this signer. */ |
| 488 Signer.prototype.close = function() { |
| 489 if (this.helper_) this.helper_.close(); |
| 490 }; |
| 491 |
| 492 /** |
| 493 * Notifies the caller of error with the given error code. |
| 494 * @param {number} code |
| 495 * @private |
| 496 */ |
| 497 Signer.prototype.notifyError_ = function(code) { |
| 498 if (this.done_) |
| 499 return; |
| 500 this.close(); |
| 501 this.done_ = true; |
| 502 this.errorCb_(code); |
| 503 }; |
| 504 |
| 505 /** |
| 506 * Notifies the caller of success. |
| 507 * @param {SignChallenge} challenge The challenge that was signed. |
| 508 * @param {string} info The sign result. |
| 509 * @param {string} browserData |
| 510 * @private |
| 511 */ |
| 512 Signer.prototype.notifySuccess_ = function(challenge, info, browserData) { |
| 513 if (this.done_) |
| 514 return; |
| 515 this.close(); |
| 516 this.done_ = true; |
| 517 this.successCb_(challenge, info, browserData); |
| 518 }; |
| 519 |
| 520 /** |
| 521 * Notifies the caller of progress with the error code. |
| 522 * @param {number} code |
| 523 * @private |
| 524 */ |
| 525 Signer.prototype.notifyProgress_ = function(code) { |
| 526 if (this.done_) |
| 527 return; |
| 528 if (code != this.lastProgressUpdate_) { |
| 529 this.lastProgressUpdate_ = code; |
| 530 // If there is no progress callback, treat it like an error and clean up. |
| 531 if (this.progressCb_) { |
| 532 this.progressCb_(code); |
| 533 } else { |
| 534 this.notifyError_(code); |
| 535 } |
| 536 } |
| 537 }; |
| 538 |
| 539 /** |
| 540 * Maps a sign helper's error code namespace to the page's error code namespace. |
| 541 * @param {number} code Error code from DeviceStatusCodes namespace. |
| 542 * @param {boolean} anyGnubbies Whether any gnubbies were found. |
| 543 * @return {number} A GnubbyCodeTypes error code. |
| 544 * @private |
| 545 */ |
| 546 Signer.mapError_ = function(code, anyGnubbies) { |
| 547 var reportedError; |
| 548 switch (code) { |
| 549 case DeviceStatusCodes.WRONG_DATA_STATUS: |
| 550 reportedError = anyGnubbies ? GnubbyCodeTypes.NONE_PLUGGED_ENROLLED : |
| 551 GnubbyCodeTypes.NO_GNUBBIES; |
| 552 break; |
| 553 |
| 554 case DeviceStatusCodes.OK_STATUS: |
| 555 // If the error callback is called with OK, it means the signature was |
| 556 // empty, which we treat the same as... |
| 557 case DeviceStatusCodes.WAIT_TOUCH_STATUS: |
| 558 reportedError = GnubbyCodeTypes.WAIT_TOUCH; |
| 559 break; |
| 560 |
| 561 case DeviceStatusCodes.BUSY_STATUS: |
| 562 reportedError = GnubbyCodeTypes.BUSY; |
| 563 break; |
| 564 |
| 565 default: |
| 566 reportedError = GnubbyCodeTypes.UNKNOWN_ERROR; |
| 567 break; |
| 568 } |
| 569 return reportedError; |
| 570 }; |
| 571 |
| 572 /** |
| 573 * Called by the helper upon error. |
| 574 * @param {number} code |
| 575 * @param {boolean} anyGnubbies |
| 576 * @private |
| 577 */ |
| 578 Signer.prototype.helperError_ = function(code, anyGnubbies) { |
| 579 this.clearTimeout_(); |
| 580 var reportedError = Signer.mapError_(code, anyGnubbies); |
| 581 console.log(UTIL_fmt('helper reported ' + code.toString(16) + |
| 582 ', returning ' + reportedError)); |
| 583 this.notifyError_(reportedError); |
| 584 }; |
| 585 |
| 586 /** |
| 587 * Called by helper upon success. |
| 588 * @param {SignHelperChallenge} challenge The challenge that was signed. |
| 589 * @param {string} info The sign result. |
| 590 * @private |
| 591 */ |
| 592 Signer.prototype.helperSuccess_ = function(challenge, info) { |
| 593 // Got a good reply, kill timer. |
| 594 this.clearTimeout_(); |
| 595 |
| 596 var key = challenge['keyHandle'] + challenge['challengeHash']; |
| 597 var browserData = this.browserData_[key]; |
| 598 // Notify with server-provided challenge, not the encoded one: the |
| 599 // server-provided challenge contains additional fields it relies on. |
| 600 var serverChallenge = this.serverChallenges_[key]; |
| 601 this.notifySuccess_(serverChallenge, info, browserData); |
| 602 }; |
| 603 |
| 604 /** |
| 605 * Called by helper to notify progress. |
| 606 * @param {number} code |
| 607 * @param {boolean} anyGnubbies |
| 608 * @private |
| 609 */ |
| 610 Signer.prototype.helperProgress_ = function(code, anyGnubbies) { |
| 611 var reportedError = Signer.mapError_(code, anyGnubbies); |
| 612 console.log(UTIL_fmt('helper notified ' + code.toString(16) + |
| 613 ', returning ' + reportedError)); |
| 614 this.notifyProgress_(reportedError); |
| 615 }; |
| 616 |
| 617 /** |
| 618 * Clears the timeout for this signer. |
| 619 * @private |
| 620 */ |
| 621 Signer.prototype.clearTimeout_ = function() { |
| 622 if (this.watchdogTimer_) { |
| 623 this.watchdogTimer_.clearTimeout(); |
| 624 this.watchdogTimer_ = undefined; |
| 625 } |
| 626 }; |
OLD | NEW |