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.
6 * @fileoverview Implements a sign handler using USB gnubbies.
10 var CORRUPT_sign = false;
13 * @param {!SignHelperRequest} request The sign request.
15 * @implements {RequestHandler}
17 function UsbSignHandler(request) {
18 /** @private {!SignHelperRequest} */
19 this.request_ = request;
21 /** @private {boolean} */
22 this.notified_ = false;
23 /** @private {boolean} */
24 this.anyGnubbiesFound_ = false;
25 /** @private {!Array.<!Gnubby>} */
26 this.notEnrolledGnubbies_ = [];
30 * Default timeout value in case the caller never provides a valid timeout.
33 UsbSignHandler.DEFAULT_TIMEOUT_MILLIS = 30 * 1000;
36 * Attempts to run this handler's request.
37 * @param {RequestHandlerCallback} cb Called with the result of the request and
38 * an optional source for the sign result.
39 * @return {boolean} whether this set of challenges was accepted.
41 UsbSignHandler.prototype.run = function(cb) {
43 // Can only handle one request.
46 /** @private {RequestHandlerCallback} */
48 if (!this.request_.signData || !this.request_.signData.length) {
49 // Fail a sign request with an empty set of challenges.
50 this.notifyError_(DeviceStatusCodes.INVALID_DATA_STATUS);
54 this.request_.timeoutSeconds ?
55 this.request_.timeoutSeconds * 1000 :
56 UsbSignHandler.DEFAULT_TIMEOUT_MILLIS;
57 /** @private {MultipleGnubbySigner} */
58 this.signer_ = new MultipleGnubbySigner(
59 false /* forEnroll */,
60 this.signerCompleted_.bind(this),
61 this.signerFoundGnubby_.bind(this),
63 this.request_.logMsgUrl);
64 return this.signer_.doSign(this.request_.signData);
69 * Called when a MultipleGnubbySigner completes.
70 * @param {boolean} anyPending Whether any gnubbies are pending.
73 UsbSignHandler.prototype.signerCompleted_ = function(anyPending) {
74 if (!this.anyGnubbiesFound_ || anyPending) {
75 this.notifyError_(DeviceStatusCodes.TIMEOUT_STATUS);
76 } else if (this.signerError_ !== undefined) {
77 this.notifyError_(this.signerError_);
79 // Do nothing: signerFoundGnubby_ will have returned results from other
85 * Called when a MultipleGnubbySigner finds a gnubby that has completed signing
87 * @param {MultipleSignerResult} signResult Signer result object
88 * @param {boolean} moreExpected Whether the signer expects to produce more
92 UsbSignHandler.prototype.signerFoundGnubby_ =
93 function(signResult, moreExpected) {
94 this.anyGnubbiesFound_ = true;
95 if (!signResult.code) {
96 var gnubby = signResult['gnubby'];
97 var challenge = signResult['challenge'];
98 var info = new Uint8Array(signResult['info']);
99 this.notifySuccess_(gnubby, challenge, info);
100 } else if (signResult.code == DeviceStatusCodes.WRONG_DATA_STATUS) {
101 var gnubby = signResult['gnubby'];
102 this.notEnrolledGnubbies_.push(gnubby);
103 this.sendBogusEnroll_(gnubby);
104 } else if (!moreExpected) {
105 // If the signer doesn't expect more results, return the error directly to
107 this.notifyError_(signResult.code);
109 // Record the last error, to report from the complete callback if no other
110 // eligible gnubbies are found.
111 /** @private {number} */
112 this.signerError_ = signResult.code;
117 UsbSignHandler.BOGUS_APP_ID_HASH = [
118 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
119 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
120 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
121 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41
125 UsbSignHandler.BOGUS_CHALLENGE_V1 = [
126 0x04, 0xA2, 0x24, 0x7D, 0x5C, 0x0B, 0x76, 0xF1,
127 0xDC, 0xCD, 0x44, 0xAF, 0x91, 0x9A, 0xA2, 0x3F,
128 0x3F, 0xBA, 0x65, 0x9F, 0x06, 0x78, 0x82, 0xFB,
129 0x93, 0x4B, 0xBF, 0x86, 0x55, 0x95, 0x66, 0x46,
130 0x76, 0x90, 0xDC, 0xE1, 0xE8, 0x6C, 0x86, 0x86,
131 0xC3, 0x03, 0x4E, 0x65, 0x52, 0x4C, 0x32, 0x6F,
132 0xB6, 0x44, 0x0D, 0x50, 0xF9, 0x16, 0xC0, 0xA3,
133 0xDA, 0x31, 0x4B, 0xD3, 0x3F, 0x94, 0xA5, 0xF1,
138 UsbSignHandler.BOGUS_CHALLENGE_V2 = [
139 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
140 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
141 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
142 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42
146 * Sends a bogus enroll command to the not-enrolled gnubby, to force the user
147 * to tap the gnubby before revealing its state to the caller.
148 * @param {Gnubby} gnubby The gnubby to "enroll" on.
151 UsbSignHandler.prototype.sendBogusEnroll_ = function(gnubby) {
153 gnubby.version(function(rc, opt_data) {
155 self.notifyError_(rc);
159 var version = UTIL_BytesToString(new Uint8Array(opt_data || []));
162 enrollChallenge = UsbSignHandler.BOGUS_CHALLENGE_V1;
165 enrollChallenge = UsbSignHandler.BOGUS_CHALLENGE_V2;
168 self.notifyError_(DeviceStatusCodes.INVALID_DATA_STATUS);
171 /** @type {Array.<number>} */ (enrollChallenge),
172 UsbSignHandler.BOGUS_APP_ID_HASH,
173 self.enrollCallback_.bind(self, gnubby));
178 * Called with the result of the (bogus, tap capturing) enroll command.
179 * @param {Gnubby} gnubby The gnubby "enrolled".
180 * @param {number} code The result of the enroll command.
181 * @param {ArrayBuffer=} infoArray Returned data.
184 UsbSignHandler.prototype.enrollCallback_ = function(gnubby, code, infoArray) {
188 case DeviceStatusCodes.WAIT_TOUCH_STATUS:
189 this.sendBogusEnroll_(gnubby);
192 case DeviceStatusCodes.OK_STATUS:
193 // Got a successful enroll => user tapped gnubby.
194 // Send a WRONG_DATA_STATUS finally. (The gnubby is implicitly closed
196 this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS);
202 * Reports the result of a successful sign operation.
203 * @param {Gnubby} gnubby Gnubby instance
204 * @param {SignHelperChallenge} challenge Challenge signed
205 * @param {Uint8Array} info Result data
208 UsbSignHandler.prototype.notifySuccess_ = function(gnubby, challenge, info) {
211 this.notified_ = true;
213 gnubby.closeWhenIdle();
217 CORRUPT_sign = false;
218 info[info.length - 1] = info[info.length - 1] ^ 0xff;
221 'appIdHash': B64_encode(challenge['appIdHash']),
222 'challengeHash': B64_encode(challenge['challengeHash']),
223 'keyHandle': B64_encode(challenge['keyHandle']),
224 'signatureData': B64_encode(info)
227 'type': 'sign_helper_reply',
228 'code': DeviceStatusCodes.OK_STATUS,
229 'responseData': responseData
231 this.cb_(reply, 'USB');
235 * Reports error to the caller.
236 * @param {number} code error to report
239 UsbSignHandler.prototype.notifyError_ = function(code) {
242 this.notified_ = true;
245 'type': 'sign_helper_reply',
252 * Closes the MultipleGnubbySigner, if any.
254 UsbSignHandler.prototype.close = function() {
255 while (this.notEnrolledGnubbies_.length != 0) {
256 var gnubby = this.notEnrolledGnubbies_.shift();
257 gnubby.closeWhenIdle();
260 this.signer_.close();