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 Implements a sign helper using USB gnubbies.
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu'use strict';
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuvar CORRUPT_sign = false;
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
13010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {!GnubbyFactory} factory Factory for gnubby instances
145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Countdown} timer Timer after whose expiration the caller is no longer
155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     interested in the result of a sign request.
165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(number, boolean)} errorCb Called when a sign request fails
175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     with an error code and whether any gnubbies were found.
1846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) * @param {function(SignHelperChallenge, string, string=)} successCb Called with
1946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) *     the signature produced by a successful sign request.
205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl A URL to post log messages to.
215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @implements {SignHelper}
235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)function UsbSignHelper(factory, timer, errorCb, successCb, opt_logMsgUrl) {
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {!GnubbyFactory} */
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.factory_ = factory;
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Countdown} */
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.timer_ = timer;
295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {function(number, boolean)} */
305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_ = errorCb;
3146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  /** @private {function(SignHelperChallenge, string, string=)} */
325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_ = successCb;
335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.logMsgUrl_ = opt_logMsgUrl;
355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Array.<SignHelperChallenge>} */
375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.pendingChallenges_ = [];
385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Array.<usbGnubby>} */
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.waitingForTouchGnubbies_ = [];
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notified_ = false;
435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signerComplete_ = false;
455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Attempts to sign the provided challenges.
49010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Array.<SignHelperChallenge>} challenges Challenges to sign
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} whether this set of challenges was accepted.
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.doSign = function(challenges) {
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!challenges.length) {
545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Fail a sign request with an empty set of challenges, and pretend to have
555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // alerted the caller in case the enumerate is still pending.
565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notified_ = true;
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return false;
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.pendingChallenges_ = challenges;
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.getSomeGnubbies_();
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return true;
625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Enumerates gnubbies, and begins processing challenges upon enumeration if
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * any gnubbies are found.
685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.getSomeGnubbies_ = function() {
715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.factory_.enumerate(this.enumerateCallback.bind(this));
725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called with the result of enumerating gnubbies.
765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} rc the result of the enumerate.
77010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Array.<llGnubbyDeviceId>} indexes Indexes of found gnubbies
785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.enumerateCallback = function(rc, indexes) {
805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (rc) {
815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(rc, false);
825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!indexes.length) {
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.timer_.expired()) {
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS, true);
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gotSomeGnubbies_(indexes);
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called with the result of enumerating gnubby indexes.
97010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Array.<llGnubbyDeviceId>} indexes Indexes of found gnubbies
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.gotSomeGnubbies_ = function(indexes) {
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {MultipleGnubbySigner} */
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signer_ = new MultipleGnubbySigner(
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.factory_,
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      indexes,
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      false /* forEnroll */,
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.signerCompleted_.bind(this),
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.signerFoundGnubby_.bind(this),
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.timer_,
1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.logMsgUrl_);
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signer_.addEncodedChallenges(this.pendingChallenges_, true);
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when a MultipleGnubbySigner completes its sign request.
1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} anySucceeded whether any sign attempt completed
1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     successfully.
1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number=} errorCode an error code from a failing gnubby, if one was
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     found.
1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.signerCompleted_ = function(anySucceeded, errorCode) {
1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.signerComplete_ = true;
1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // The signer is not created unless some gnubbies were enumerated, so
1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // anyGnubbies is mostly always true. The exception is when the last gnubby is
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // removed, handled shortly.
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var anyGnubbies = true;
1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!anySucceeded) {
1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (!errorCode) {
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else if (errorCode == -llGnubby.GONE) {
1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // If the last gnubby was removed, report as though no gnubbies were
1325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // found.
1335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
1345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      anyGnubbies = false;
1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(errorCode, anyGnubbies);
1375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else if (this.anyTimeout_) {
1385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Some previously succeeding gnubby timed out: return its error code.
1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(this.timeoutError_, anyGnubbies);
1405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
1415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Do nothing: signerFoundGnubby_ will have been called with each
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // succeeding gnubby.
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when a MultipleGnubbySigner finds a gnubby that has successfully
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * signed, or can successfully sign, one of the challenges.
149010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Status code
150010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {MultipleSignerResult} signResult Signer result object
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.signerFoundGnubby_ = function(code, signResult) {
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var gnubby = signResult['gnubby'];
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var challenge = signResult['challenge'];
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var info = new Uint8Array(signResult['info']);
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifySuccess_(gnubby, challenge, info);
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.waitingForTouchGnubbies_.push(gnubby);
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.retrySignIfNotTimedOut_(gnubby, challenge, code);
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Reports the result of a successful sign operation.
167010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {usbGnubby} gnubby Gnubby instance
168010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {SignHelperChallenge} challenge Challenge signed
169010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Uint8Array} info Result data
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.notifySuccess_ = function(gnubby, challenge, info) {
1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.notified_)
1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notified_ = true;
1765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  gnubby.closeWhenIdle();
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.close();
1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (CORRUPT_sign) {
1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    CORRUPT_sign = false;
1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    info[info.length - 1] = info[info.length - 1] ^ 0xff;
1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var encodedChallenge = {};
1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  encodedChallenge['challengeHash'] = B64_encode(challenge['challengeHash']);
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  encodedChallenge['appIdHash'] = B64_encode(challenge['appIdHash']);
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  encodedChallenge['keyHandle'] = B64_encode(challenge['keyHandle']);
1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.successCb_(
18946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      /** @type {SignHelperChallenge} */ (encodedChallenge), B64_encode(info),
19046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      'USB');
1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Reports error to the caller.
1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} code error to report
196010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {boolean} anyGnubbies If any gnubbies were found
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.notifyError_ = function(code, anyGnubbies) {
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.notified_)
2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.notified_ = true;
2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.close();
2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.errorCb_(code, anyGnubbies);
2055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Retries signing a particular challenge on a gnubby.
209010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {usbGnubby} gnubby Gnubby instance
210010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {SignHelperChallenge} challenge Challenge to retry
2115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.retrySign_ = function(gnubby, challenge) {
2145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var challengeHash = challenge['challengeHash'];
2155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var appIdHash = challenge['appIdHash'];
2165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var keyHandle = challenge['keyHandle'];
2175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  gnubby.sign(challengeHash, appIdHash, keyHandle,
2185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.signCallback_.bind(this, gnubby, challenge));
2195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when a gnubby completes a sign request.
223010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {usbGnubby} gnubby Gnubby instance
224010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {SignHelperChallenge} challenge Challenge to retry
225010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Previous status code
2265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.retrySignIfNotTimedOut_ =
2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(gnubby, challenge, code) {
2305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.timer_.expired()) {
2315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Store any timeout error code, to be returned from the complete
2325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // callback if no other eligible gnubbies are found.
2335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /** @private {boolean} */
2345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.anyTimeout_ = true;
2355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    /** @private {number} */
2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.timeoutError_ = code;
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.removePreviouslyEligibleGnubby_(gnubby, code);
2385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    window.setTimeout(this.retrySign_.bind(this, gnubby, challenge), 200);
2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Removes a gnubby that was waiting for touch from the list, with the given
2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * error code. If this is the last gnubby, notifies the caller of the error.
246010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {usbGnubby} gnubby Gnubby instance
247010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Previous status code
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.removePreviouslyEligibleGnubby_ =
2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(gnubby, code) {
2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Close this gnubby.
2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  gnubby.closeWhenIdle();
2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var index = this.waitingForTouchGnubbies_.indexOf(gnubby);
2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (index >= 0) {
2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.waitingForTouchGnubbies_.splice(index, 1);
2575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.waitingForTouchGnubbies_.length && this.signerComplete_ &&
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      !this.notified_) {
2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Last sign attempt is complete: return this error.
2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('timeout or error (' + code.toString(16) +
2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        ') signing'));
2635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // If the last device is gone, report as if no gnubbies were found.
2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (code == -llGnubby.GONE) {
2655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
2665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return;
2675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifyError_(code, true);
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when a gnubby completes a sign request.
274010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {usbGnubby} gnubby Gnubby instance
275010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {SignHelperChallenge} challenge Challenge signed
276010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Status code
277010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {ArrayBuffer=} infoArray Result data
2785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.signCallback_ =
2815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(gnubby, challenge, code, infoArray) {
2825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.notified_) {
2835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Individual sign completed after previous success or failure. Disregard.
2845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var info = new Uint8Array(infoArray || []);
2875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
2885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.notifySuccess_(gnubby, challenge, info);
2895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else if (code == DeviceStatusCodes.OK_STATUS ||
2905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      code == DeviceStatusCodes.WAIT_TOUCH_STATUS ||
2915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      code == DeviceStatusCodes.BUSY_STATUS) {
2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.retrySignIfNotTimedOut_(gnubby, challenge, code);
2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('got error ' + code.toString(16) + ' signing'));
2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.removePreviouslyEligibleGnubby_(gnubby, code);
2965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Closes the MultipleGnubbySigner, if any.
3015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelper.prototype.close = function() {
3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.signer_) {
3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.signer_.close();
3055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.signer_ = null;
3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < this.waitingForTouchGnubbies_.length; i++) {
3085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.waitingForTouchGnubbies_[i].closeWhenIdle();
3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.waitingForTouchGnubbies_ = [];
3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!GnubbyFactory} gnubbyFactory Factory to create gnubbies.
3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @implements {SignHelperFactory}
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction UsbSignHelperFactory(gnubbyFactory) {
3195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {!GnubbyFactory} */
3205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gnubbyFactory_ = gnubbyFactory;
3215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Countdown} timer Timer after whose expiration the caller is no longer
3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     interested in the result of a sign request.
3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(number, boolean)} errorCb Called when a sign request fails
3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     with an error code and whether any gnubbies were found.
3285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(SignHelperChallenge, string)} successCb Called with the
3295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     signature produced by a successful sign request.
3305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl A URL to post log messages to.
3315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {UsbSignHelper} the newly created helper.
3325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuUsbSignHelperFactory.prototype.createHelper =
33446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    function(timer, errorCb, successCb, opt_logMsgUrl) {
3355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var helper =
3365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      new UsbSignHelper(this.gnubbyFactory_, timer, errorCb, successCb,
33746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          opt_logMsgUrl);
3385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return helper;
3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
340