Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / cryptotoken / multiplesigner.js
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 };