Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(103)

Side by Side Diff: chrome/browser/resources/cryptotoken/usbsignhelper.js

Issue 249913002: FIDO U2F component extension (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge with HEAD Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 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 Implements a sign helper using USB gnubbies.
7 * @author juanlang@google.com (Juan Lang)
8 */
9 'use strict';
10
11 var CORRUPT_sign = false;
12
13 /**
14 * @param {!GnubbyFactory} factory
15 * @param {Countdown} timer Timer after whose expiration the caller is no longer
16 * interested in the result of a sign request.
17 * @param {function(number, boolean)} errorCb Called when a sign request fails
18 * with an error code and whether any gnubbies were found.
19 * @param {function(SignHelperChallenge, string)} successCb Called with the
20 * signature produced by a successful sign request.
21 * @param {(function(number, boolean)|undefined)} opt_progressCb Called with
22 * progress updates to the sign request.
23 * @param {string=} opt_logMsgUrl A URL to post log messages to.
24 * @constructor
25 * @implements {SignHelper}
26 */
27 function UsbSignHelper(factory, timer, errorCb, successCb, opt_progressCb,
28 opt_logMsgUrl) {
29 /** @private {!GnubbyFactory} */
30 this.factory_ = factory;
31 /** @private {Countdown} */
32 this.timer_ = timer;
33 /** @private {function(number, boolean)} */
34 this.errorCb_ = errorCb;
35 /** @private {function(SignHelperChallenge, string)} */
36 this.successCb_ = successCb;
37 /** @private {string|undefined} */
38 this.logMsgUrl_ = opt_logMsgUrl;
39
40 /** @private {Array.<SignHelperChallenge>} */
41 this.pendingChallenges_ = [];
42 /** @private {Array.<usbGnubby>} */
43 this.waitingForTouchGnubbies_ = [];
44
45 /** @private {boolean} */
46 this.notified_ = false;
47 /** @private {boolean} */
48 this.signerComplete_ = false;
49 }
50
51 /**
52 * Attempts to sign the provided challenges.
53 * @param {Array.<SignHelperChallenge>} challenges
54 * @return {boolean} whether this set of challenges was accepted.
55 */
56 UsbSignHelper.prototype.doSign = function(challenges) {
57 if (!challenges.length) {
58 // Fail a sign request with an empty set of challenges, and pretend to have
59 // alerted the caller in case the enumerate is still pending.
60 this.notified_ = true;
61 return false;
62 } else {
63 this.pendingChallenges_ = challenges;
64 this.getSomeGnubbies_();
65 return true;
66 }
67 };
68
69 /**
70 * Enumerates gnubbies, and begins processing challenges upon enumeration if
71 * any gnubbies are found.
72 * @private
73 */
74 UsbSignHelper.prototype.getSomeGnubbies_ = function() {
75 this.factory_.enumerate(this.enumerateCallback.bind(this));
76 };
77
78 /**
79 * Called with the result of enumerating gnubbies.
80 * @param {number} rc the result of the enumerate.
81 * @param {Array.<llGnubbyDeviceId>} indexes
82 */
83 UsbSignHelper.prototype.enumerateCallback = function(rc, indexes) {
84 if (rc) {
85 this.notifyError_(rc, false);
86 return;
87 }
88 if (!indexes.length) {
89 this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
90 return;
91 }
92 if (this.timer_.expired()) {
93 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS, true);
94 return;
95 }
96 this.gotSomeGnubbies_(indexes);
97 };
98
99 /**
100 * Called with the result of enumerating gnubby indexes.
101 * @param {Array.<llGnubbyDeviceId>} indexes
102 * @private
103 */
104 UsbSignHelper.prototype.gotSomeGnubbies_ = function(indexes) {
105 /** @private {MultipleGnubbySigner} */
106 this.signer_ = new MultipleGnubbySigner(
107 this.factory_,
108 indexes,
109 false /* forEnroll */,
110 this.signerCompleted_.bind(this),
111 this.signerFoundGnubby_.bind(this),
112 this.timer_,
113 this.logMsgUrl_);
114 this.signer_.addEncodedChallenges(this.pendingChallenges_, true);
115 };
116
117 /**
118 * Called when a MultipleGnubbySigner completes its sign request.
119 * @param {boolean} anySucceeded whether any sign attempt completed
120 * successfully.
121 * @param {number=} errorCode an error code from a failing gnubby, if one was
122 * found.
123 * @private
124 */
125 UsbSignHelper.prototype.signerCompleted_ = function(anySucceeded, errorCode) {
126 this.signerComplete_ = true;
127 // The signer is not created unless some gnubbies were enumerated, so
128 // anyGnubbies is mostly always true. The exception is when the last gnubby is
129 // removed, handled shortly.
130 var anyGnubbies = true;
131 if (!anySucceeded) {
132 if (!errorCode) {
133 errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
134 } else if (errorCode == -llGnubby.GONE) {
135 // If the last gnubby was removed, report as though no gnubbies were
136 // found.
137 errorCode = DeviceStatusCodes.WRONG_DATA_STATUS;
138 anyGnubbies = false;
139 }
140 this.notifyError_(errorCode, anyGnubbies);
141 } else if (this.anyTimeout_) {
142 // Some previously succeeding gnubby timed out: return its error code.
143 this.notifyError_(this.timeoutError_, anyGnubbies);
144 } else {
145 // Do nothing: signerFoundGnubby_ will have been called with each
146 // succeeding gnubby.
147 }
148 };
149
150 /**
151 * Called when a MultipleGnubbySigner finds a gnubby that has successfully
152 * signed, or can successfully sign, one of the challenges.
153 * @param {number} code
154 * @param {MultipleSignerResult} signResult
155 * @private
156 */
157 UsbSignHelper.prototype.signerFoundGnubby_ = function(code, signResult) {
158 var gnubby = signResult['gnubby'];
159 var challenge = signResult['challenge'];
160 var info = new Uint8Array(signResult['info']);
161 if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
162 this.notifySuccess_(gnubby, challenge, info);
163 } else {
164 this.waitingForTouchGnubbies_.push(gnubby);
165 this.retrySignIfNotTimedOut_(gnubby, challenge, code);
166 }
167 };
168
169 /**
170 * Reports the result of a successful sign operation.
171 * @param {usbGnubby} gnubby
172 * @param {SignHelperChallenge} challenge
173 * @param {Uint8Array} info
174 * @private
175 */
176 UsbSignHelper.prototype.notifySuccess_ = function(gnubby, challenge, info) {
177 if (this.notified_)
178 return;
179 this.notified_ = true;
180
181 gnubby.closeWhenIdle();
182 this.close();
183
184 if (CORRUPT_sign) {
185 CORRUPT_sign = false;
186 info[info.length - 1] = info[info.length - 1] ^ 0xff;
187 }
188 var encodedChallenge = {};
189 encodedChallenge['challengeHash'] = B64_encode(challenge['challengeHash']);
190 encodedChallenge['appIdHash'] = B64_encode(challenge['appIdHash']);
191 encodedChallenge['keyHandle'] = B64_encode(challenge['keyHandle']);
192 this.successCb_(
193 /** @type {SignHelperChallenge} */ (encodedChallenge), B64_encode(info));
194 };
195
196 /**
197 * Reports error to the caller.
198 * @param {number} code error to report
199 * @param {boolean} anyGnubbies
200 * @private
201 */
202 UsbSignHelper.prototype.notifyError_ = function(code, anyGnubbies) {
203 if (this.notified_)
204 return;
205 this.notified_ = true;
206 this.close();
207 this.errorCb_(code, anyGnubbies);
208 };
209
210 /**
211 * Retries signing a particular challenge on a gnubby.
212 * @param {usbGnubby} gnubby
213 * @param {SignHelperChallenge} challenge
214 * @private
215 */
216 UsbSignHelper.prototype.retrySign_ = function(gnubby, challenge) {
217 var challengeHash = challenge['challengeHash'];
218 var appIdHash = challenge['appIdHash'];
219 var keyHandle = challenge['keyHandle'];
220 gnubby.sign(challengeHash, appIdHash, keyHandle,
221 this.signCallback_.bind(this, gnubby, challenge));
222 };
223
224 /**
225 * Called when a gnubby completes a sign request.
226 * @param {usbGnubby} gnubby
227 * @param {SignHelperChallenge} challenge
228 * @param {number} code
229 * @private
230 */
231 UsbSignHelper.prototype.retrySignIfNotTimedOut_ =
232 function(gnubby, challenge, code) {
233 if (this.timer_.expired()) {
234 // Store any timeout error code, to be returned from the complete
235 // callback if no other eligible gnubbies are found.
236 /** @private {boolean} */
237 this.anyTimeout_ = true;
238 /** @private {number} */
239 this.timeoutError_ = code;
240 this.removePreviouslyEligibleGnubby_(gnubby, code);
241 } else {
242 window.setTimeout(this.retrySign_.bind(this, gnubby, challenge), 200);
243 }
244 };
245
246 /**
247 * Removes a gnubby that was waiting for touch from the list, with the given
248 * error code. If this is the last gnubby, notifies the caller of the error.
249 * @param {usbGnubby} gnubby
250 * @param {number} code
251 * @private
252 */
253 UsbSignHelper.prototype.removePreviouslyEligibleGnubby_ =
254 function(gnubby, code) {
255 // Close this gnubby.
256 gnubby.closeWhenIdle();
257 var index = this.waitingForTouchGnubbies_.indexOf(gnubby);
258 if (index >= 0) {
259 this.waitingForTouchGnubbies_.splice(index, 1);
260 }
261 if (!this.waitingForTouchGnubbies_.length && this.signerComplete_ &&
262 !this.notified_) {
263 // Last sign attempt is complete: return this error.
264 console.log(UTIL_fmt('timeout or error (' + code.toString(16) +
265 ') signing'));
266 // If the last device is gone, report as if no gnubbies were found.
267 if (code == -llGnubby.GONE) {
268 this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS, false);
269 return;
270 }
271 this.notifyError_(code, true);
272 }
273 };
274
275 /**
276 * Called when a gnubby completes a sign request.
277 * @param {usbGnubby} gnubby
278 * @param {SignHelperChallenge} challenge
279 * @param {number} code
280 * @param {ArrayBuffer=} infoArray
281 * @private
282 */
283 UsbSignHelper.prototype.signCallback_ =
284 function(gnubby, challenge, code, infoArray) {
285 if (this.notified_) {
286 // Individual sign completed after previous success or failure. Disregard.
287 return;
288 }
289 var info = new Uint8Array(infoArray || []);
290 if (code == DeviceStatusCodes.OK_STATUS && info.length > 0 && info[0]) {
291 this.notifySuccess_(gnubby, challenge, info);
292 } else if (code == DeviceStatusCodes.OK_STATUS ||
293 code == DeviceStatusCodes.WAIT_TOUCH_STATUS ||
294 code == DeviceStatusCodes.BUSY_STATUS) {
295 this.retrySignIfNotTimedOut_(gnubby, challenge, code);
296 } else {
297 console.log(UTIL_fmt('got error ' + code.toString(16) + ' signing'));
298 this.removePreviouslyEligibleGnubby_(gnubby, code);
299 }
300 };
301
302 /**
303 * Closes the MultipleGnubbySigner, if any.
304 */
305 UsbSignHelper.prototype.close = function() {
306 if (this.signer_) {
307 this.signer_.close();
308 this.signer_ = null;
309 }
310 for (var i = 0; i < this.waitingForTouchGnubbies_.length; i++) {
311 this.waitingForTouchGnubbies_[i].closeWhenIdle();
312 }
313 this.waitingForTouchGnubbies_ = [];
314 };
315
316 /**
317 * @param {!GnubbyFactory} gnubbyFactory Factory to create gnubbies.
318 * @constructor
319 * @implements {SignHelperFactory}
320 */
321 function UsbSignHelperFactory(gnubbyFactory) {
322 /** @private {!GnubbyFactory} */
323 this.gnubbyFactory_ = gnubbyFactory;
324 }
325
326 /**
327 * @param {Countdown} timer Timer after whose expiration the caller is no longer
328 * interested in the result of a sign request.
329 * @param {function(number, boolean)} errorCb Called when a sign request fails
330 * with an error code and whether any gnubbies were found.
331 * @param {function(SignHelperChallenge, string)} successCb Called with the
332 * signature produced by a successful sign request.
333 * @param {(function(number, boolean)|undefined)} opt_progressCb Called with
334 * progress updates to the sign request.
335 * @param {string=} opt_logMsgUrl A URL to post log messages to.
336 * @return {UsbSignHelper} the newly created helper.
337 */
338 UsbSignHelperFactory.prototype.createHelper =
339 function(timer, errorCb, successCb, opt_progressCb, opt_logMsgUrl) {
340 var helper =
341 new UsbSignHelper(this.gnubbyFactory_, timer, errorCb, successCb,
342 opt_progressCb, opt_logMsgUrl);
343 return helper;
344 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/cryptotoken/usbgnubbyfactory.js ('k') | chrome/browser/resources/cryptotoken/util.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698