Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / cryptotoken / usbsignhandler.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 Implements a sign handler using USB gnubbies.
7  */
8 'use strict';
9
10 var CORRUPT_sign = false;
11
12 /**
13  * @param {!SignHelperRequest} request The sign request.
14  * @constructor
15  * @implements {RequestHandler}
16  */
17 function UsbSignHandler(request) {
18   /** @private {!SignHelperRequest} */
19   this.request_ = request;
20
21   /** @private {boolean} */
22   this.notified_ = false;
23   /** @private {boolean} */
24   this.anyGnubbiesFound_ = false;
25   /** @private {!Array.<!Gnubby>} */
26   this.notEnrolledGnubbies_ = [];
27 }
28
29 /**
30  * Default timeout value in case the caller never provides a valid timeout.
31  * @const
32  */
33 UsbSignHandler.DEFAULT_TIMEOUT_MILLIS = 30 * 1000;
34
35 /**
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.
40  */
41 UsbSignHandler.prototype.run = function(cb) {
42   if (this.cb_) {
43     // Can only handle one request.
44     return false;
45   }
46   /** @private {RequestHandlerCallback} */
47   this.cb_ = cb;
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);
51     return false;
52   }
53   var timeoutMillis =
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),
62       timeoutMillis,
63       this.request_.logMsgUrl);
64   return this.signer_.doSign(this.request_.signData);
65 };
66
67
68 /**
69  * Called when a MultipleGnubbySigner completes.
70  * @param {boolean} anyPending Whether any gnubbies are pending.
71  * @private
72  */
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_);
78   } else {
79     // Do nothing: signerFoundGnubby_ will have returned results from other
80     // gnubbies.
81   }
82 };
83
84 /**
85  * Called when a MultipleGnubbySigner finds a gnubby that has completed signing
86  * its challenges.
87  * @param {MultipleSignerResult} signResult Signer result object
88  * @param {boolean} moreExpected Whether the signer expects to produce more
89  *     results.
90  * @private
91  */
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
106     // the caller.
107     this.notifyError_(signResult.code);
108   } else {
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;
113   }
114 };
115
116 /** @const */
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
122 ];
123
124 /** @const */
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,
134     0xD3
135 ];
136
137 /** @const */
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
143 ];
144
145 /**
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.
149  * @private
150  */
151 UsbSignHandler.prototype.sendBogusEnroll_ = function(gnubby) {
152   var self = this;
153   gnubby.version(function(rc, opt_data) {
154     if (rc) {
155       self.notifyError_(rc);
156       return;
157     }
158     var enrollChallenge;
159     var version = UTIL_BytesToString(new Uint8Array(opt_data || []));
160     switch (version) {
161       case Gnubby.U2F_V1:
162         enrollChallenge = UsbSignHandler.BOGUS_CHALLENGE_V1;
163         break;
164       case Gnubby.U2F_V2:
165         enrollChallenge = UsbSignHandler.BOGUS_CHALLENGE_V2;
166         break;
167       default:
168         self.notifyError_(DeviceStatusCodes.INVALID_DATA_STATUS);
169     }
170     gnubby.enroll(
171         /** @type {Array.<number>} */ (enrollChallenge),
172         UsbSignHandler.BOGUS_APP_ID_HASH,
173         self.enrollCallback_.bind(self, gnubby));
174   });
175 };
176
177 /**
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.
182  * @private
183  */
184 UsbSignHandler.prototype.enrollCallback_ = function(gnubby, code, infoArray) {
185   if (this.notified_)
186     return;
187   switch (code) {
188     case DeviceStatusCodes.WAIT_TOUCH_STATUS:
189       this.sendBogusEnroll_(gnubby);
190       return;
191
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
195       // by notifyError_.)
196       this.notifyError_(DeviceStatusCodes.WRONG_DATA_STATUS);
197       return;
198   }
199 };
200
201 /**
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
206  * @private
207  */
208 UsbSignHandler.prototype.notifySuccess_ = function(gnubby, challenge, info) {
209   if (this.notified_)
210     return;
211   this.notified_ = true;
212
213   gnubby.closeWhenIdle();
214   this.close();
215
216   if (CORRUPT_sign) {
217     CORRUPT_sign = false;
218     info[info.length - 1] = info[info.length - 1] ^ 0xff;
219   }
220   var responseData = {
221     'appIdHash': B64_encode(challenge['appIdHash']),
222     'challengeHash': B64_encode(challenge['challengeHash']),
223     'keyHandle': B64_encode(challenge['keyHandle']),
224     'signatureData': B64_encode(info)
225   };
226   var reply = {
227     'type': 'sign_helper_reply',
228     'code': DeviceStatusCodes.OK_STATUS,
229     'responseData': responseData
230   };
231   this.cb_(reply, 'USB');
232 };
233
234 /**
235  * Reports error to the caller.
236  * @param {number} code error to report
237  * @private
238  */
239 UsbSignHandler.prototype.notifyError_ = function(code) {
240   if (this.notified_)
241     return;
242   this.notified_ = true;
243   this.close();
244   var reply = {
245     'type': 'sign_helper_reply',
246     'code': code
247   };
248   this.cb_(reply);
249 };
250
251 /**
252  * Closes the MultipleGnubbySigner, if any.
253  */
254 UsbSignHandler.prototype.close = function() {
255   while (this.notEnrolledGnubbies_.length != 0) {
256     var gnubby = this.notEnrolledGnubbies_.shift();
257     gnubby.closeWhenIdle();
258   }
259   if (this.signer_) {
260     this.signer_.close();
261     this.signer_ = null;
262   }
263 };