signer.js revision 010d83a9304c5a91596085d917d248abff47903a
15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved.
25c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Use of this source code is governed by a BSD-style license that can be
35c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// found in the LICENSE file.
45c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
55c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
65c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @fileoverview Handles web page requests for gnubby sign requests.
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu'use strict';
105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuvar signRequestQueue = new OriginKeyedRequestQueue();
125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Handles a sign request.
155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!SignHelperFactory} factory Factory to create a sign helper.
165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {MessageSender} sender The sender of the message.
175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object} request The web page's sign request.
185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} enforceAppIdValid Whether to enforce that the app id in the
195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     request matches the sender's origin.
205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Function} sendResponse Called back with the result of the sign.
215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} toleratesMultipleResponses Whether the sendResponse
225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     callback can be called more than once, e.g. for progress updates.
23010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @return {Closeable} Request handler that should be closed when the browser
24010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) *     message channel is closed.
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction handleSignRequest(factory, sender, request, enforceAppIdValid,
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sendResponse, toleratesMultipleResponses) {
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var sentResponse = false;
295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function sendResponseOnce(r) {
305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (queuedSignRequest) {
315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      queuedSignRequest.close();
325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      queuedSignRequest = null;
335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (!sentResponse) {
355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      sentResponse = true;
365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      try {
375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // If the page has gone away or the connection has otherwise gone,
385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // sendResponse fails.
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        sendResponse(r);
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } catch (exception) {
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        console.warn('sendResponse failed: ' + exception);
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else {
445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.warn(UTIL_fmt('Tried to reply more than once! Juan, FIX ME'));
455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function sendErrorResponse(code) {
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var response = formatWebPageResponse(GnubbyMsgTypes.SIGN_WEB_REPLY, code);
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sendResponseOnce(response);
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function sendSuccessResponse(challenge, info, browserData) {
545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var responseData = {};
555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    for (var k in challenge) {
565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      responseData[k] = challenge[k];
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    responseData['browserData'] = B64_encode(UTIL_StringToBytes(browserData));
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    responseData['signatureData'] = info;
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var response = formatWebPageResponse(GnubbyMsgTypes.SIGN_WEB_REPLY,
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        GnubbyCodeTypes.OK, responseData);
625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sendResponseOnce(response);
635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function sendNotification(code) {
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('notification, code=' + code));
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Can the callback handle progress updates? If so, send one.
685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (toleratesMultipleResponses) {
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var response = formatWebPageResponse(
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          GnubbyMsgTypes.SIGN_WEB_NOTIFICATION, code);
715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (request['requestId']) {
725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        response['requestId'] = request['requestId'];
735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      sendResponse(response);
755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var origin = getOriginFromUrl(/** @type {string} */ (sender.url));
795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!origin) {
805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST);
815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return null;
825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // More closure type inference fail.
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var nonNullOrigin = /** @type {string} */ (origin);
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!isValidSignRequest(request)) {
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    sendErrorResponse(GnubbyCodeTypes.BAD_REQUEST);
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return null;
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var signData = request['signData'];
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // A valid sign data has at least one challenge, so get the first appId from
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // the first challenge.
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var firstAppId = signData[0]['appId'];
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var timeoutMillis = Signer.DEFAULT_TIMEOUT_MILLIS;
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (request['timeout']) {
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Request timeout is in seconds.
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    timeoutMillis = request['timeout'] * 1000;
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var timer = new CountdownTimer(timeoutMillis);
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var logMsgUrl = request['logMsgUrl'];
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Queue sign requests from the same origin, to protect against simultaneous
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // sign-out on many tabs resulting in repeated sign-in requests.
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var queuedSignRequest = new QueuedSignRequest(signData, factory, timer,
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      nonNullOrigin, enforceAppIdValid, sendErrorResponse, sendSuccessResponse,
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      sendNotification, sender.tlsChannelId, logMsgUrl);
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var requestToken = signRequestQueue.queueRequest(firstAppId, nonNullOrigin,
1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      queuedSignRequest.begin.bind(queuedSignRequest), timer);
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  queuedSignRequest.setToken(requestToken);
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return queuedSignRequest;
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Returns whether the request appears to be a valid sign request.
1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object} request the request.
1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} whether the request appears valid.
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction isValidSignRequest(request) {
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!request.hasOwnProperty('signData'))
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return false;
1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var signData = request['signData'];
1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // If a sign request contains an empty array of challenges, it could never
1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // be fulfilled. Fail.
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!signData.length)
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return false;
1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return isValidSignData(signData);
1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Adapter class representing a queued sign request.
132010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {!SignData} signData Signature data
133010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {!SignHelperFactory} factory Factory for SignHelper instances
134010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Countdown} timer Timeout timer
135010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {string} origin Signature origin
136010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {boolean} enforceAppIdValid If to enforce appId validity
137010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {function(number)} errorCb Error callback
138010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {function(SignChallenge, string, string)} successCb Success callback
139010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {(function(number)|undefined)} opt_progressCb Progress callback
140010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {string|undefined} opt_tlsChannelId TLS Channel Id
141010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {string|undefined} opt_logMsgUrl Url to post log messages to
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @implements {Closeable}
1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction QueuedSignRequest(signData, factory, timer, origin, enforceAppIdValid,
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    errorCb, successCb, opt_progressCb, opt_tlsChannelId, opt_logMsgUrl) {
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {!SignData} */
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signData_ = signData;
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {!SignHelperFactory} */
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.factory_ = factory;
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Countdown} */
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.timer_ = timer;
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string} */
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.origin_ = origin;
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.enforceAppIdValid_ = enforceAppIdValid;
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {function(number)} */
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_ = errorCb;
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {function(SignChallenge, string, string)} */
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_ = successCb;
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {(function(number)|undefined)} */
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.progressCb_ = opt_progressCb;
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.tlsChannelId_ = opt_tlsChannelId;
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.logMsgUrl_ = opt_logMsgUrl;
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.begun_ = false;
1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.closed_ = false;
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** Closes this sign request. */
1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuQueuedSignRequest.prototype.close = function() {
1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.closed_) return;
1765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.begun_ && this.signer_) {
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.signer_.close();
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.token_) {
1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.token_.complete();
1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.closed_ = true;
1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {QueuedRequestToken} token Token for this sign request.
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuQueuedSignRequest.prototype.setToken = function(token) {
1895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {QueuedRequestToken} */
1905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.token_ = token;
1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when this sign request may begin work.
1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {QueuedRequestToken} token Token for this sign request.
1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuQueuedSignRequest.prototype.begin = function(token) {
1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.begun_ = true;
1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.setToken(token);
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signer_ = new Signer(this.factory_, this.timer_, this.origin_,
2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.enforceAppIdValid_, this.signerFailed_.bind(this),
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.signerSucceeded_.bind(this), this.progressCb_,
2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.tlsChannelId_, this.logMsgUrl_);
2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.signer_.setChallenges(this.signData_)) {
2055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    token.complete();
2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.errorCb_(GnubbyCodeTypes.BAD_REQUEST);
2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when this request's signer fails.
2125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} code The failure code reported by the signer.
2135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuQueuedSignRequest.prototype.signerFailed_ = function(code) {
2165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.token_.complete();
2175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_(code);
2185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when this request's signer succeeds.
2225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignChallenge} challenge The challenge that was signed.
2235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} info The sign result.
224010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {string} browserData Browser data JSON
2255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuQueuedSignRequest.prototype.signerSucceeded_ =
2285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(challenge, info, browserData) {
2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.token_.complete();
2305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_(challenge, info, browserData);
2315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates an object to track signing with a gnubby.
2355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!SignHelperFactory} helperFactory Factory to create a sign helper.
2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Countdown} timer Timer for sign request.
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} origin The origin making the request.
2385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} enforceAppIdValid Whether to enforce that the appId in the
2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     request matches the sender's origin.
2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(number)} errorCb Called when the sign operation fails.
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(SignChallenge, string, string)} successCb Called when the
2425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     sign operation succeeds.
2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {(function(number)|undefined)} opt_progressCb Called with progress
2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     updates to the sign request.
2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_tlsChannelId the TLS channel ID, if any, of the origin
2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     making the request.
2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl The url to post log messages to.
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction Signer(helperFactory, timer, origin, enforceAppIdValid,
2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    errorCb, successCb, opt_progressCb, opt_tlsChannelId, opt_logMsgUrl) {
2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Countdown} */
2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.timer_ = timer;
2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string} */
2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.origin_ = origin;
2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
2575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.enforceAppIdValid_ = enforceAppIdValid;
2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {function(number)} */
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_ = errorCb;
2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {function(SignChallenge, string, string)} */
2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_ = successCb;
2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {(function(number)|undefined)} */
2635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.progressCb_ = opt_progressCb;
2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
2655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.tlsChannelId_ = opt_tlsChannelId;
2665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
2675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.logMsgUrl_ = opt_logMsgUrl;
2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
2705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.challengesSet_ = false;
2715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Array.<SignHelperChallenge>} */
2725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.pendingChallenges_ = [];
2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
2745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.done_ = false;
2755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Object.<string, string>} */
2775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.browserData_ = {};
2785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Object.<string, SignChallenge>} */
2795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.serverChallenges_ = {};
2805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Allow http appIds for http origins. (Broken, but the caller deserves
2815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // what they get.)
2825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
2835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.allowHttp_ = this.origin_ ? this.origin_.indexOf('http://') == 0 : false;
2845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Protect against helper failure with a watchdog.
2865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.createWatchdog_(timer);
2875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {SignHelper} */
2885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.helper_ = helperFactory.createHelper(
2895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      timer, this.helperError_.bind(this), this.helperSuccess_.bind(this),
2905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          this.helperProgress_.bind(this), this.logMsgUrl_);
2915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a timer with an expiry greater than the expiration time of the given
2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * timer.
296010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Countdown} timer Timeout timer
2975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.createWatchdog_ = function(timer) {
3005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var millis = timer.millisecondsUntilExpired();
3015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  millis += CountdownTimer.TIMER_INTERVAL_MILLIS;
3025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Countdown|undefined} */
3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.watchdogTimer_ = new CountdownTimer(millis, this.timeout_.bind(this));
3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Default timeout value in case the caller never provides a valid timeout.
3085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.DEFAULT_TIMEOUT_MILLIS = 30 * 1000;
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Sets the challenges to be signed.
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignData} signData The challenges to set.
3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} Whether the challenges could be set.
3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.setChallenges = function(signData) {
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.challengesSet_ || this.done_)
3185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return false;
3195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {SignData} */
3205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signData_ = signData;
3215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.challengesSet_ = true;
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // If app id enforcing isn't in effect, go ahead and start the helper with
3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // all of the incoming challenges.
3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var success = true;
3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.enforceAppIdValid_) {
3285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    success = this.addChallenges(signData, true /* finalChallenges */);
3295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.checkAppIds_();
3325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return success;
3335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Adds new challenges to the challenges being signed.
3375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignData} signData Challenges to add.
3385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} finalChallenges Whether these are the final challenges.
3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} Whether the challenge could be added.
3405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.addChallenges = function(signData, finalChallenges) {
3425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var newChallenges = this.encodeSignChallenges_(signData);
3435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < newChallenges.length; i++) {
3445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.pendingChallenges_.push(newChallenges[i]);
3455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!finalChallenges) {
3475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return true;
3485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return this.helper_.doSign(this.pendingChallenges_);
3505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates challenges for helper from challenges.
3545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Array.<SignChallenge>} challenges Challenges to add.
355010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @return {Array.<SignHelperChallenge>} Encoded challenges
3565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.encodeSignChallenges_ = function(challenges) {
3595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var newChallenges = [];
3605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < challenges.length; i++) {
3615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var incomingChallenge = challenges[i];
3625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var serverChallenge = incomingChallenge['challenge'];
3635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var appId = incomingChallenge['appId'];
3645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var encodedKeyHandle = incomingChallenge['keyHandle'];
3655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var version = incomingChallenge['version'];
3665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var browserData =
3685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        makeSignBrowserData(serverChallenge, this.origin_, this.tlsChannelId_);
3695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var encodedChallenge = makeChallenge(browserData, appId, encodedKeyHandle,
3705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        version);
3715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var key = encodedKeyHandle + encodedChallenge['challengeHash'];
3735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.browserData_[key] = browserData;
3745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.serverChallenges_[key] = incomingChallenge;
3755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    newChallenges.push(encodedChallenge);
3775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return newChallenges;
3795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Checks the app ids of incoming requests, and, when this signer is enforcing
3835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * that app ids are valid, adds successful challenges to those being signed.
3845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.checkAppIds_ = function() {
3875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Check the incoming challenges' app ids.
3885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Array.<[string, Array.<Request>]>} */
3895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.orderedRequests_ = requestsByAppId(this.signData_);
3905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.orderedRequests_.length) {
3915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Safety check: if the challenges are somehow empty, the helper will never
3925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // be fed any data, so the request could never be satisfied. You lose.
3935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(GnubbyCodeTypes.BAD_REQUEST);
3945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
3955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {number} */
3975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.fetchedAppIds_ = 0;
3985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {number} */
3995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.validAppIds_ = 0;
4005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0, appIdRequestsPair; i < this.orderedRequests_.length; i++) {
4015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var appIdRequestsPair = this.orderedRequests_[i];
4025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var appId = appIdRequestsPair[0];
4035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var requests = appIdRequestsPair[1];
4045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (appId == this.origin_) {
4055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Trivially allowed.
4065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.fetchedAppIds_++;
4075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.validAppIds_++;
4085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Only add challenges if in enforcing mode, i.e. they weren't added
4095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // earlier.
4105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (this.enforceAppIdValid_) {
4115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.addChallenges(requests,
4125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            this.fetchedAppIds_ == this.orderedRequests_.length);
4135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
4145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else {
4155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var start = new Date();
4165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      fetchAllowedOriginsForAppId(appId, this.allowHttp_,
4175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          this.fetchedAllowedOriginsForAppId_.bind(this, appId, start,
4185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu              requests));
4195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
4215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
4245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called with the result of an app id fetch.
4255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} appId the app id that was fetched.
4265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Date} start the time the fetch request started.
4275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Array.<SignChallenge>} challenges Challenges for this app id.
4285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} rc The HTTP response code for the app id fetch.
4295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!Array.<string>} allowedOrigins The origins allowed for this app id.
4305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
4315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.fetchedAllowedOriginsForAppId_ = function(appId, start,
4335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    challenges, rc, allowedOrigins) {
4345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var end = new Date();
4355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  logFetchAppIdResult(appId, end - start, allowedOrigins, this.logMsgUrl_);
4365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (rc != 200 && !(rc >= 400 && rc < 500)) {
4375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.timer_.expired()) {
4385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Act as though the helper timed out.
4395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.helperError_(DeviceStatusCodes.TIMEOUT_STATUS, false);
4405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else {
4415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      start = new Date();
4425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      fetchAllowedOriginsForAppId(appId, this.allowHttp_,
4435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          this.fetchedAllowedOriginsForAppId_.bind(this, appId, start,
4445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu              challenges));
4455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
4475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
4485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.fetchedAppIds_++;
4495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var finalChallenges = (this.fetchedAppIds_ == this.orderedRequests_.length);
4505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (isValidAppIdForOrigin(appId, this.origin_, allowedOrigins)) {
4515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.validAppIds_++;
4525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Only add challenges if in enforcing mode, i.e. they weren't added
4535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // earlier.
4545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.enforceAppIdValid_) {
4555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.addChallenges(challenges, finalChallenges);
4565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
4585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    logInvalidOriginForAppId(this.origin_, appId, this.logMsgUrl_);
4595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // If in enforcing mode and this is the final request, sign the valid
4605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // challenges.
4615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.enforceAppIdValid_ && finalChallenges) {
4625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (!this.helper_.doSign(this.pendingChallenges_)) {
4635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.notifyError_(GnubbyCodeTypes.BAD_REQUEST);
4645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return;
4655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
4665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
4685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.enforceAppIdValid_ && finalChallenges && !this.validAppIds_) {
4695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // If all app ids are invalid, notify the caller, otherwise implicitly
4705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // allow the helper to report whether any of the valid challenges succeeded.
4715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(GnubbyCodeTypes.BAD_APP_ID);
4725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
4735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
4765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when the timeout expires on this signer.
4775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
4785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.timeout_ = function() {
4805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.watchdogTimer_ = undefined;
4815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // The web page gets grumpy if it doesn't get WAIT_TOUCH within a reasonable
4825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // time.
4835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notifyError_(GnubbyCodeTypes.WAIT_TOUCH);
4845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** Closes this signer. */
4875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.close = function() {
4885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.helper_) this.helper_.close();
4895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
4925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Notifies the caller of error with the given error code.
493010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Error code
4945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
4955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.notifyError_ = function(code) {
4975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.done_)
4985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
4995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.close();
5005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.done_ = true;
5015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_(code);
5025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
5035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
5055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Notifies the caller of success.
5065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignChallenge} challenge The challenge that was signed.
5075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} info The sign result.
508010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {string} browserData Browser data JSON
5095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
5105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
5115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.notifySuccess_ = function(challenge, info, browserData) {
5125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.done_)
5135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
5145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.close();
5155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.done_ = true;
5165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_(challenge, info, browserData);
5175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
5185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
5205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Notifies the caller of progress with the error code.
521010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Status code
5225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
5235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
5245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.notifyProgress_ = function(code) {
5255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.done_)
5265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
5275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (code != this.lastProgressUpdate_) {
5285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.lastProgressUpdate_ = code;
5295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // If there is no progress callback, treat it like an error and clean up.
5305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.progressCb_) {
5315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.progressCb_(code);
5325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else {
5335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.notifyError_(code);
5345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
5355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
5365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
5375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
5395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Maps a sign helper's error code namespace to the page's error code namespace.
5405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} code Error code from DeviceStatusCodes namespace.
5415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} anyGnubbies Whether any gnubbies were found.
5425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {number} A GnubbyCodeTypes error code.
5435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
5445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
5455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.mapError_ = function(code, anyGnubbies) {
5465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var reportedError;
5475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  switch (code) {
5485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.WRONG_DATA_STATUS:
5495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      reportedError = anyGnubbies ? GnubbyCodeTypes.NONE_PLUGGED_ENROLLED :
5505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          GnubbyCodeTypes.NO_GNUBBIES;
5515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
5525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.OK_STATUS:
5545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // If the error callback is called with OK, it means the signature was
5555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // empty, which we treat the same as...
5565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.WAIT_TOUCH_STATUS:
5575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      reportedError = GnubbyCodeTypes.WAIT_TOUCH;
5585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
5595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.BUSY_STATUS:
5615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      reportedError = GnubbyCodeTypes.BUSY;
5625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
5635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    default:
5655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      reportedError = GnubbyCodeTypes.UNKNOWN_ERROR;
5665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
5675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
5685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return reportedError;
5695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
5705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
5725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called by the helper upon error.
573010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Error code
574010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {boolean} anyGnubbies If any gnubbies were found
5755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
5765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
5775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.helperError_ = function(code, anyGnubbies) {
5785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.clearTimeout_();
5795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var reportedError = Signer.mapError_(code, anyGnubbies);
5805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  console.log(UTIL_fmt('helper reported ' + code.toString(16) +
5815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ', returning ' + reportedError));
5825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notifyError_(reportedError);
5835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
5845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
5865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called by helper upon success.
5875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignHelperChallenge} challenge The challenge that was signed.
5885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} info The sign result.
5895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
5905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
5915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.helperSuccess_ = function(challenge, info) {
5925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Got a good reply, kill timer.
5935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.clearTimeout_();
5945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
5955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var key = challenge['keyHandle'] + challenge['challengeHash'];
5965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var browserData = this.browserData_[key];
5975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Notify with server-provided challenge, not the encoded one: the
5985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // server-provided challenge contains additional fields it relies on.
5995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var serverChallenge = this.serverChallenges_[key];
6005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notifySuccess_(serverChallenge, info, browserData);
6015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
6025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
6035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
6045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called by helper to notify progress.
605010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Status code
606010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {boolean} anyGnubbies If any gnubbies were found
6075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
6085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
6095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.helperProgress_ = function(code, anyGnubbies) {
6105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var reportedError = Signer.mapError_(code, anyGnubbies);
6115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  console.log(UTIL_fmt('helper notified ' + code.toString(16) +
6125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ', returning ' + reportedError));
6135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notifyProgress_(reportedError);
6145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
6155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
6165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
6175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Clears the timeout for this signer.
6185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
6195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
6205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSigner.prototype.clearTimeout_ = function() {
6215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.watchdogTimer_) {
6225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.watchdogTimer_.clearTimeout();
6235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.watchdogTimer_ = undefined;
6245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
6255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
626