• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview A multiple gnubby signer wraps the process of opening a number
7 * of gnubbies, signing each challenge in an array of challenges until a
8 * success condition is satisfied, and yielding each succeeding gnubby.
9 */
10'use strict';
11
12/**
13 * Creates a new sign handler with an array of gnubby indexes.
14 * @param {!GnubbyFactory} factory Used to create and open the gnubbies.
15 * @param {Array.<llGnubbyDeviceId>} gnubbyIndexes Which gnubbies to open.
16 * @param {boolean} forEnroll Whether this signer is signing for an attempted
17 *     enroll operation.
18 * @param {function(boolean, (number|undefined))} completedCb Called when this
19 *     signer completes sign attempts, i.e. no further results should be
20 *     expected.
21 * @param {function(number, MultipleSignerResult)} gnubbyFoundCb Called with
22 *     each gnubby/challenge that yields a successful result.
23 * @param {Countdown=} opt_timer An advisory timer, beyond whose expiration the
24 *     signer will not attempt any new operations, assuming the caller is no
25 *     longer interested in the outcome.
26 * @param {string=} opt_logMsgUrl A URL to post log messages to.
27 * @constructor
28 */
29function MultipleGnubbySigner(factory, gnubbyIndexes, forEnroll, completedCb,
30    gnubbyFoundCb, opt_timer, opt_logMsgUrl) {
31  /** @private {!GnubbyFactory} */
32  this.factory_ = factory;
33  /** @private {Array.<llGnubbyDeviceId>} */
34  this.gnubbyIndexes_ = gnubbyIndexes;
35  /** @private {boolean} */
36  this.forEnroll_ = forEnroll;
37  /** @private {function(boolean, (number|undefined))} */
38  this.completedCb_ = completedCb;
39  /** @private {function(number, MultipleSignerResult)} */
40  this.gnubbyFoundCb_ = gnubbyFoundCb;
41  /** @private {Countdown|undefined} */
42  this.timer_ = opt_timer;
43  /** @private {string|undefined} */
44  this.logMsgUrl_ = opt_logMsgUrl;
45
46  /** @private {Array.<SignHelperChallenge>} */
47  this.challenges_ = [];
48  /** @private {boolean} */
49  this.challengesFinal_ = false;
50
51  // Create a signer for each gnubby.
52  /** @private {boolean} */
53  this.anySucceeded_ = false;
54  /** @private {number} */
55  this.numComplete_ = 0;
56  /** @private {Array.<SingleGnubbySigner>} */
57  this.signers_ = [];
58  /** @private {Array.<boolean>} */
59  this.stillGoing_ = [];
60  /** @private {Array.<number>} */
61  this.errorStatus_ = [];
62  for (var i = 0; i < gnubbyIndexes.length; i++) {
63    this.addGnubby(gnubbyIndexes[i]);
64  }
65}
66
67/**
68 * Attempts to open this signer's gnubbies, if they're not already open.
69 * (This is implicitly done by addChallenges.)
70 */
71MultipleGnubbySigner.prototype.open = function() {
72  for (var i = 0; i < this.signers_.length; i++) {
73    this.signers_[i].open();
74  }
75};
76
77/**
78 * Closes this signer's gnubbies, if any are open.
79 */
80MultipleGnubbySigner.prototype.close = function() {
81  for (var i = 0; i < this.signers_.length; i++) {
82    this.signers_[i].close();
83  }
84};
85
86/**
87 * Adds challenges to the set of challenges being tried by this signer.
88 * The challenges are an array of challenge objects, where each challenge
89 * object's values are base64-encoded.
90 * If the signer is currently idle, begins signing the new challenges.
91 *
92 * @param {Array} challenges Encoded challenges
93 * @param {boolean} finalChallenges True iff there are no more challenges to add
94 * @return {boolean} whether the challenges were successfully added.
95 */
96MultipleGnubbySigner.prototype.addEncodedChallenges =
97    function(challenges, finalChallenges) {
98  var decodedChallenges = [];
99  if (challenges) {
100    for (var i = 0; i < challenges.length; i++) {
101      var decodedChallenge = {};
102      var challenge = challenges[i];
103      decodedChallenge['challengeHash'] =
104          B64_decode(challenge['challengeHash']);
105      decodedChallenge['appIdHash'] = B64_decode(challenge['appIdHash']);
106      decodedChallenge['keyHandle'] = B64_decode(challenge['keyHandle']);
107      if (challenge['version']) {
108        decodedChallenge['version'] = challenge['version'];
109      }
110      decodedChallenges.push(decodedChallenge);
111    }
112  }
113  return this.addChallenges(decodedChallenges, finalChallenges);
114};
115
116/**
117 * Adds challenges to the set of challenges being tried by this signer.
118 * If the signer is currently idle, begins signing the new challenges.
119 *
120 * @param {Array.<SignHelperChallenge>} challenges Challenges to add
121 * @param {boolean} finalChallenges True iff there are no more challnges to add
122 * @return {boolean} whether the challenges were successfully added.
123 */
124MultipleGnubbySigner.prototype.addChallenges =
125    function(challenges, finalChallenges) {
126  if (this.challengesFinal_) {
127    // Can't add new challenges once they're finalized.
128    return false;
129  }
130
131  if (challenges) {
132    for (var i = 0; i < challenges.length; i++) {
133      this.challenges_.push(challenges[i]);
134    }
135  }
136  this.challengesFinal_ = finalChallenges;
137
138  for (var i = 0; i < this.signers_.length; i++) {
139    this.stillGoing_[i] =
140        this.signers_[i].addChallenges(challenges, finalChallenges);
141    this.errorStatus_[i] = 0;
142  }
143  return true;
144};
145
146/**
147 * Adds a new gnubby to this signer's list of gnubbies. (Only possible while
148 * this signer is still signing: without this restriction, the morePossible
149 * indication in the callbacks could become violated.) If this signer has
150 * challenges to sign, begins signing on the new gnubby with them.
151 * @param {llGnubbyDeviceId} gnubbyIndex The index of the gnubby to add.
152 * @return {boolean} Whether the gnubby was added successfully.
153 */
154MultipleGnubbySigner.prototype.addGnubby = function(gnubbyIndex) {
155  if (this.numComplete_ && this.numComplete_ == this.signers_.length)
156    return false;
157
158  var index = this.signers_.length;
159  this.signers_.push(
160      new SingleGnubbySigner(
161          this.factory_,
162          gnubbyIndex,
163          this.forEnroll_,
164          this.signFailedCallback_.bind(this, index),
165          this.signSucceededCallback_.bind(this, index),
166          this.timer_ ? this.timer_.clone() : null,
167          this.logMsgUrl_));
168  this.stillGoing_.push(false);
169
170  if (this.challenges_.length) {
171    this.stillGoing_[index] =
172        this.signers_[index].addChallenges(this.challenges_,
173            this.challengesFinal_);
174  }
175  return true;
176};
177
178/**
179 * Called by a SingleGnubbySigner upon failure, i.e. unsuccessful completion of
180 * all its sign operations.
181 * @param {number} index the index of the gnubby whose result this is
182 * @param {number} code the result code of the sign operation
183 * @private
184 */
185MultipleGnubbySigner.prototype.signFailedCallback_ = function(index, code) {
186  console.log(
187      UTIL_fmt('failure. gnubby ' + index + ' got code ' + code.toString(16)));
188  if (!this.stillGoing_[index]) {
189    console.log(UTIL_fmt('gnubby ' + index + ' no longer running!'));
190    // Shouldn't ever happen? Disregard.
191    return;
192  }
193  this.stillGoing_[index] = false;
194  this.errorStatus_[index] = code;
195  this.numComplete_++;
196  var morePossible = this.numComplete_ < this.signers_.length;
197  if (!morePossible)
198    this.notifyComplete_();
199};
200
201/**
202 * Called by a SingleGnubbySigner upon success.
203 * @param {number} index the index of the gnubby whose result this is
204 * @param {usbGnubby} gnubby the underlying gnubby that succeded.
205 * @param {number} code the result code of the sign operation
206 * @param {SingleSignerResult=} signResult Result object
207 * @private
208 */
209MultipleGnubbySigner.prototype.signSucceededCallback_ =
210    function(index, gnubby, code, signResult) {
211  console.log(UTIL_fmt('success! gnubby ' + index + ' got code ' +
212      code.toString(16)));
213  if (!this.stillGoing_[index]) {
214    console.log(UTIL_fmt('gnubby ' + index + ' no longer running!'));
215    // Shouldn't ever happen? Disregard.
216    return;
217  }
218  this.anySucceeded_ = true;
219  this.stillGoing_[index] = false;
220  this.notifySuccess_(code, gnubby, index, signResult);
221  this.numComplete_++;
222  var morePossible = this.numComplete_ < this.signers_.length;
223  if (!morePossible)
224    this.notifyComplete_();
225};
226
227/**
228 * @private
229 */
230MultipleGnubbySigner.prototype.notifyComplete_ = function() {
231  // See if any of the signers failed with a strange error. If so, report a
232  // single error to the caller, partly as a diagnostic aid and partly to
233  // distinguish real failures from wrong data.
234  var funnyBusiness;
235  for (var i = 0; i < this.errorStatus_.length; i++) {
236    if (this.errorStatus_[i] &&
237        this.errorStatus_[i] != DeviceStatusCodes.WRONG_DATA_STATUS &&
238        this.errorStatus_[i] != DeviceStatusCodes.WAIT_TOUCH_STATUS) {
239      funnyBusiness = this.errorStatus_[i];
240      break;
241    }
242  }
243  if (funnyBusiness) {
244    console.warn(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ', ' +
245        'funny error = ' + funnyBusiness + ')'));
246  } else {
247    console.log(UTIL_fmt('all done (success: ' + this.anySucceeded_ + ')'));
248  }
249  this.completedCb_(this.anySucceeded_, funnyBusiness);
250};
251
252/**
253 * @param {number} code Success status code
254 * @param {usbGnubby} gnubby The gnubby that succeeded
255 * @param {number} gnubbyIndex The gnubby's index
256 * @param {SingleSignerResult=} singleSignerResult Result object
257 * @private
258 */
259MultipleGnubbySigner.prototype.notifySuccess_ =
260    function(code, gnubby, gnubbyIndex, singleSignerResult) {
261  console.log(UTIL_fmt('success (' + code.toString(16) + ')'));
262  var signResult = {
263    'gnubby': gnubby,
264    'gnubbyIndex': gnubbyIndex
265  };
266  if (singleSignerResult && singleSignerResult['challenge'])
267    signResult['challenge'] = singleSignerResult['challenge'];
268  if (singleSignerResult && singleSignerResult['info'])
269    signResult['info'] = singleSignerResult['info'];
270  this.gnubbyFoundCb_(code, signResult);
271};
272