Home | History | Annotate | Download | only in cryptotoken
      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  */
     29 function 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  */
     71 MultipleGnubbySigner.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  */
     80 MultipleGnubbySigner.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  */
     96 MultipleGnubbySigner.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  */
    124 MultipleGnubbySigner.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  */
    154 MultipleGnubbySigner.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  */
    185 MultipleGnubbySigner.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  */
    209 MultipleGnubbySigner.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  */
    230 MultipleGnubbySigner.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  */
    259 MultipleGnubbySigner.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