1 | //Copyright 2014-2015 Google Inc. All rights reserved. |
||
2 | |||
3 | //Use of this source code is governed by a BSD-style |
||
4 | //license that can be found in the LICENSE file or at |
||
5 | //https://developers.google.com/open-source/licenses/bsd |
||
6 | |||
7 | /** |
||
8 | * @fileoverview The U2F api. |
||
9 | */ |
||
10 | 'use strict'; |
||
11 | |||
12 | |||
13 | /** |
||
14 | * Namespace for the U2F api. |
||
15 | * @type {Object} |
||
16 | */ |
||
17 | var u2f = u2f || {}; |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
18 | |||
19 | /** |
||
20 | * FIDO U2F Javascript API Version |
||
21 | * @number |
||
22 | */ |
||
23 | var js_api_version; |
||
24 | |||
25 | /** |
||
26 | * The U2F extension id |
||
27 | * @const {string} |
||
28 | */ |
||
29 | // The Chrome packaged app extension ID. |
||
30 | // Uncomment this if you want to deploy a server instance that uses |
||
31 | // the package Chrome app and does not require installing the U2F Chrome extension. |
||
32 | u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; |
||
33 | // The U2F Chrome extension ID. |
||
34 | // Uncomment this if you want to deploy a server instance that uses |
||
35 | // the U2F Chrome extension to authenticate. |
||
36 | // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; |
||
37 | |||
38 | |||
39 | /** |
||
40 | * Message types for messsages to/from the extension |
||
41 | * @const |
||
42 | * @enum {string} |
||
43 | */ |
||
44 | u2f.MessageTypes = { |
||
45 | 'U2F_REGISTER_REQUEST': 'u2f_register_request', |
||
46 | 'U2F_REGISTER_RESPONSE': 'u2f_register_response', |
||
47 | 'U2F_SIGN_REQUEST': 'u2f_sign_request', |
||
48 | 'U2F_SIGN_RESPONSE': 'u2f_sign_response', |
||
49 | 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', |
||
50 | 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' |
||
51 | }; |
||
52 | |||
53 | |||
54 | /** |
||
55 | * Response status codes |
||
56 | * @const |
||
57 | * @enum {number} |
||
58 | */ |
||
59 | u2f.ErrorCodes = { |
||
60 | 'OK': 0, |
||
61 | 'OTHER_ERROR': 1, |
||
62 | 'BAD_REQUEST': 2, |
||
63 | 'CONFIGURATION_UNSUPPORTED': 3, |
||
64 | 'DEVICE_INELIGIBLE': 4, |
||
65 | 'TIMEOUT': 5 |
||
66 | }; |
||
67 | |||
68 | |||
69 | /** |
||
70 | * A message for registration requests |
||
71 | * @typedef {{ |
||
72 | * type: u2f.MessageTypes, |
||
73 | * appId: ?string, |
||
74 | * timeoutSeconds: ?number, |
||
75 | * requestId: ?number |
||
76 | * }} |
||
77 | */ |
||
78 | u2f.U2fRequest; |
||
0 ignored issues
–
show
|
|||
79 | |||
80 | |||
81 | /** |
||
82 | * A message for registration responses |
||
83 | * @typedef {{ |
||
84 | * type: u2f.MessageTypes, |
||
85 | * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), |
||
86 | * requestId: ?number |
||
87 | * }} |
||
88 | */ |
||
89 | u2f.U2fResponse; |
||
0 ignored issues
–
show
|
|||
90 | |||
91 | |||
92 | /** |
||
93 | * An error object for responses |
||
94 | * @typedef {{ |
||
95 | * errorCode: u2f.ErrorCodes, |
||
96 | * errorMessage: ?string |
||
97 | * }} |
||
98 | */ |
||
99 | u2f.Error; |
||
0 ignored issues
–
show
|
|||
100 | |||
101 | /** |
||
102 | * Data object for a single sign request. |
||
103 | * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC, USB_INTERNAL}} |
||
104 | */ |
||
105 | u2f.Transport; |
||
0 ignored issues
–
show
|
|||
106 | |||
107 | |||
108 | /** |
||
109 | * Data object for a single sign request. |
||
110 | * @typedef {Array<u2f.Transport>} |
||
111 | */ |
||
112 | u2f.Transports; |
||
0 ignored issues
–
show
|
|||
113 | |||
114 | /** |
||
115 | * Data object for a single sign request. |
||
116 | * @typedef {{ |
||
117 | * version: string, |
||
118 | * challenge: string, |
||
119 | * keyHandle: string, |
||
120 | * appId: string |
||
121 | * }} |
||
122 | */ |
||
123 | u2f.SignRequest; |
||
0 ignored issues
–
show
|
|||
124 | |||
125 | |||
126 | /** |
||
127 | * Data object for a sign response. |
||
128 | * @typedef {{ |
||
129 | * keyHandle: string, |
||
130 | * signatureData: string, |
||
131 | * clientData: string |
||
132 | * }} |
||
133 | */ |
||
134 | u2f.SignResponse; |
||
0 ignored issues
–
show
|
|||
135 | |||
136 | |||
137 | /** |
||
138 | * Data object for a registration request. |
||
139 | * @typedef {{ |
||
140 | * version: string, |
||
141 | * challenge: string |
||
142 | * }} |
||
143 | */ |
||
144 | u2f.RegisterRequest; |
||
0 ignored issues
–
show
|
|||
145 | |||
146 | |||
147 | /** |
||
148 | * Data object for a registration response. |
||
149 | * @typedef {{ |
||
150 | * version: string, |
||
151 | * keyHandle: string, |
||
152 | * transports: Transports, |
||
153 | * appId: string |
||
154 | * }} |
||
155 | */ |
||
156 | u2f.RegisterResponse; |
||
0 ignored issues
–
show
|
|||
157 | |||
158 | |||
159 | /** |
||
160 | * Data object for a registered key. |
||
161 | * @typedef {{ |
||
162 | * version: string, |
||
163 | * keyHandle: string, |
||
164 | * transports: ?Transports, |
||
165 | * appId: ?string |
||
166 | * }} |
||
167 | */ |
||
168 | u2f.RegisteredKey; |
||
0 ignored issues
–
show
|
|||
169 | |||
170 | |||
171 | /** |
||
172 | * Data object for a get API register response. |
||
173 | * @typedef {{ |
||
174 | * js_api_version: number |
||
175 | * }} |
||
176 | */ |
||
177 | u2f.GetJsApiVersionResponse; |
||
0 ignored issues
–
show
|
|||
178 | |||
179 | |||
180 | //Low level MessagePort API support |
||
181 | |||
182 | /** |
||
183 | * Sets up a MessagePort to the U2F extension using the |
||
184 | * available mechanisms. |
||
185 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback |
||
186 | */ |
||
187 | u2f.getMessagePort = function(callback) { |
||
188 | if (typeof chrome != 'undefined' && chrome.runtime) { |
||
0 ignored issues
–
show
The variable
chrome seems to be never declared. If this is a global, consider adding a /** global: chrome */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
189 | // The actual message here does not matter, but we need to get a reply |
||
190 | // for the callback to run. Thus, send an empty signature request |
||
191 | // in order to get a failure response. |
||
192 | var msg = { |
||
193 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, |
||
194 | signRequests: [] |
||
195 | }; |
||
196 | chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { |
||
197 | if (!chrome.runtime.lastError) { |
||
0 ignored issues
–
show
The variable
chrome seems to be never declared. If this is a global, consider adding a /** global: chrome */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
198 | // We are on a whitelisted origin and can talk directly |
||
199 | // with the extension. |
||
200 | u2f.getChromeRuntimePort_(callback); |
||
201 | } else { |
||
202 | // chrome.runtime was available, but we couldn't message |
||
203 | // the extension directly, use iframe |
||
204 | u2f.getIframePort_(callback); |
||
205 | } |
||
206 | }); |
||
207 | } else if (u2f.isAndroidChrome_()) { |
||
208 | u2f.getAuthenticatorPort_(callback); |
||
209 | } else if (u2f.isIosChrome_()) { |
||
210 | u2f.getIosPort_(callback); |
||
211 | } else { |
||
212 | // chrome.runtime was not available at all, which is normal |
||
213 | // when this origin doesn't have access to any extensions. |
||
214 | u2f.getIframePort_(callback); |
||
215 | } |
||
216 | }; |
||
217 | |||
218 | /** |
||
219 | * Detect chrome running on android based on the browser's useragent. |
||
220 | * @private |
||
221 | */ |
||
222 | u2f.isAndroidChrome_ = function() { |
||
223 | var userAgent = navigator.userAgent; |
||
0 ignored issues
–
show
The variable
navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
224 | return userAgent.indexOf('Chrome') != -1 && |
||
225 | userAgent.indexOf('Android') != -1; |
||
226 | }; |
||
227 | |||
228 | /** |
||
229 | * Detect chrome running on iOS based on the browser's platform. |
||
230 | * @private |
||
231 | */ |
||
232 | u2f.isIosChrome_ = function() { |
||
233 | return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; |
||
0 ignored issues
–
show
The variable
navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
234 | }; |
||
235 | |||
236 | /** |
||
237 | * Connects directly to the extension via chrome.runtime.connect. |
||
238 | * @param {function(u2f.WrappedChromeRuntimePort_)} callback |
||
239 | * @private |
||
240 | */ |
||
241 | u2f.getChromeRuntimePort_ = function(callback) { |
||
242 | var port = chrome.runtime.connect(u2f.EXTENSION_ID, |
||
0 ignored issues
–
show
The variable
chrome seems to be never declared. If this is a global, consider adding a /** global: chrome */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
243 | {'includeTlsChannelId': true}); |
||
244 | setTimeout(function() { |
||
245 | callback(new u2f.WrappedChromeRuntimePort_(port)); |
||
246 | }, 0); |
||
247 | }; |
||
248 | |||
249 | /** |
||
250 | * Return a 'port' abstraction to the Authenticator app. |
||
251 | * @param {function(u2f.WrappedAuthenticatorPort_)} callback |
||
252 | * @private |
||
253 | */ |
||
254 | u2f.getAuthenticatorPort_ = function(callback) { |
||
255 | setTimeout(function() { |
||
256 | callback(new u2f.WrappedAuthenticatorPort_()); |
||
257 | }, 0); |
||
258 | }; |
||
259 | |||
260 | /** |
||
261 | * Return a 'port' abstraction to the iOS client app. |
||
262 | * @param {function(u2f.WrappedIosPort_)} callback |
||
263 | * @private |
||
264 | */ |
||
265 | u2f.getIosPort_ = function(callback) { |
||
266 | setTimeout(function() { |
||
267 | callback(new u2f.WrappedIosPort_()); |
||
268 | }, 0); |
||
269 | }; |
||
270 | |||
271 | /** |
||
272 | * A wrapper for chrome.runtime.Port that is compatible with MessagePort. |
||
273 | * @param {Port} port |
||
274 | * @constructor |
||
275 | * @private |
||
276 | */ |
||
277 | u2f.WrappedChromeRuntimePort_ = function(port) { |
||
278 | this.port_ = port; |
||
279 | }; |
||
280 | |||
281 | /** |
||
282 | * Format and return a sign request compliant with the JS API version supported by the extension. |
||
283 | * @param {Array<u2f.SignRequest>} signRequests |
||
284 | * @param {number} timeoutSeconds |
||
285 | * @param {number} reqId |
||
286 | * @return {Object} |
||
287 | */ |
||
288 | u2f.formatSignRequest_ = |
||
289 | function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { |
||
290 | if (js_api_version === undefined || js_api_version < 1.1) { |
||
291 | // Adapt request to the 1.0 JS API |
||
292 | var signRequests = []; |
||
293 | for (var i = 0; i < registeredKeys.length; i++) { |
||
294 | signRequests[i] = { |
||
295 | version: registeredKeys[i].version, |
||
296 | challenge: challenge, |
||
297 | keyHandle: registeredKeys[i].keyHandle, |
||
298 | appId: appId |
||
299 | }; |
||
300 | } |
||
301 | return { |
||
302 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, |
||
303 | signRequests: signRequests, |
||
304 | timeoutSeconds: timeoutSeconds, |
||
305 | requestId: reqId |
||
306 | }; |
||
307 | } |
||
308 | // JS 1.1 API |
||
309 | return { |
||
310 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, |
||
311 | appId: appId, |
||
312 | challenge: challenge, |
||
313 | registeredKeys: registeredKeys, |
||
314 | timeoutSeconds: timeoutSeconds, |
||
315 | requestId: reqId |
||
316 | }; |
||
317 | }; |
||
318 | |||
319 | /** |
||
320 | * Format and return a register request compliant with the JS API version supported by the extension.. |
||
321 | * @param {Array<u2f.SignRequest>} signRequests |
||
322 | * @param {Array<u2f.RegisterRequest>} signRequests |
||
323 | * @param {number} timeoutSeconds |
||
324 | * @param {number} reqId |
||
325 | * @return {Object} |
||
326 | */ |
||
327 | u2f.formatRegisterRequest_ = |
||
328 | function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { |
||
329 | if (js_api_version === undefined || js_api_version < 1.1) { |
||
330 | // Adapt request to the 1.0 JS API |
||
331 | for (var i = 0; i < registerRequests.length; i++) { |
||
332 | registerRequests[i].appId = appId; |
||
333 | } |
||
334 | var signRequests = []; |
||
335 | for (var i = 0; i < registeredKeys.length; i++) { |
||
0 ignored issues
–
show
Comprehensibility
Naming
Best Practice
introduced
by
The variable
i already seems to be declared on line 331 . Consider using another variable name or omitting the var keyword.
This check looks for variables that are declared in multiple lines. There may be several reasons for this. In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs. If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.
Loading history...
|
|||
336 | signRequests[i] = { |
||
337 | version: registeredKeys[i].version, |
||
338 | challenge: registerRequests[0], |
||
339 | keyHandle: registeredKeys[i].keyHandle, |
||
340 | appId: appId |
||
341 | }; |
||
342 | } |
||
343 | return { |
||
344 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, |
||
345 | signRequests: signRequests, |
||
346 | registerRequests: registerRequests, |
||
347 | timeoutSeconds: timeoutSeconds, |
||
348 | requestId: reqId |
||
349 | }; |
||
350 | } |
||
351 | // JS 1.1 API |
||
352 | return { |
||
353 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, |
||
354 | appId: appId, |
||
355 | registerRequests: registerRequests, |
||
356 | registeredKeys: registeredKeys, |
||
357 | timeoutSeconds: timeoutSeconds, |
||
358 | requestId: reqId |
||
359 | }; |
||
360 | }; |
||
361 | |||
362 | |||
363 | /** |
||
364 | * Posts a message on the underlying channel. |
||
365 | * @param {Object} message |
||
366 | */ |
||
367 | u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { |
||
368 | this.port_.postMessage(message); |
||
369 | }; |
||
370 | |||
371 | |||
372 | /** |
||
373 | * Emulates the HTML 5 addEventListener interface. Works only for the |
||
374 | * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. |
||
375 | * @param {string} eventName |
||
376 | * @param {function({data: Object})} handler |
||
377 | */ |
||
378 | u2f.WrappedChromeRuntimePort_.prototype.addEventListener = |
||
379 | function(eventName, handler) { |
||
380 | var name = eventName.toLowerCase(); |
||
381 | if (name == 'message' || name == 'onmessage') { |
||
382 | this.port_.onMessage.addListener(function(message) { |
||
383 | // Emulate a minimal MessageEvent object |
||
384 | handler({'data': message}); |
||
385 | }); |
||
386 | } else { |
||
387 | console.error('WrappedChromeRuntimePort only supports onMessage'); |
||
388 | } |
||
389 | }; |
||
390 | |||
391 | /** |
||
392 | * Wrap the Authenticator app with a MessagePort interface. |
||
393 | * @constructor |
||
394 | * @private |
||
395 | */ |
||
396 | u2f.WrappedAuthenticatorPort_ = function() { |
||
397 | this.requestId_ = -1; |
||
398 | this.requestObject_ = null; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Launch the Authenticator intent. |
||
403 | * @param {Object} message |
||
404 | */ |
||
405 | u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { |
||
406 | var intentUrl = |
||
407 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + |
||
408 | ';S.request=' + encodeURIComponent(JSON.stringify(message)) + |
||
409 | ';end'; |
||
410 | document.location = intentUrl; |
||
411 | }; |
||
412 | |||
413 | /** |
||
414 | * Tells what type of port this is. |
||
415 | * @return {String} port type |
||
416 | */ |
||
417 | u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { |
||
418 | return "WrappedAuthenticatorPort_"; |
||
419 | }; |
||
420 | |||
421 | |||
422 | /** |
||
423 | * Emulates the HTML 5 addEventListener interface. |
||
424 | * @param {string} eventName |
||
425 | * @param {function({data: Object})} handler |
||
426 | */ |
||
427 | u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { |
||
428 | var name = eventName.toLowerCase(); |
||
429 | if (name == 'message') { |
||
430 | var self = this; |
||
431 | /* Register a callback to that executes when |
||
432 | * chrome injects the response. */ |
||
433 | window.addEventListener( |
||
434 | 'message', self.onRequestUpdate_.bind(self, handler), false); |
||
435 | } else { |
||
436 | console.error('WrappedAuthenticatorPort only supports message'); |
||
437 | } |
||
438 | }; |
||
439 | |||
440 | /** |
||
441 | * Callback invoked when a response is received from the Authenticator. |
||
442 | * @param function({data: Object}) callback |
||
443 | * @param {Object} message message Object |
||
444 | */ |
||
445 | u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = |
||
446 | function(callback, message) { |
||
447 | var messageObject = JSON.parse(message.data); |
||
448 | var intentUrl = messageObject['intentURL']; |
||
0 ignored issues
–
show
|
|||
449 | |||
450 | var errorCode = messageObject['errorCode']; |
||
0 ignored issues
–
show
|
|||
451 | var responseObject = null; |
||
452 | if (messageObject.hasOwnProperty('data')) { |
||
453 | responseObject = /** @type {Object} */ ( |
||
454 | JSON.parse(messageObject['data'])); |
||
455 | } |
||
456 | |||
457 | callback({'data': responseObject}); |
||
458 | }; |
||
459 | |||
460 | /** |
||
461 | * Base URL for intents to Authenticator. |
||
462 | * @const |
||
463 | * @private |
||
464 | */ |
||
465 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = |
||
466 | 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; |
||
467 | |||
468 | /** |
||
469 | * Wrap the iOS client app with a MessagePort interface. |
||
470 | * @constructor |
||
471 | * @private |
||
472 | */ |
||
473 | u2f.WrappedIosPort_ = function() {}; |
||
474 | |||
475 | /** |
||
476 | * Launch the iOS client app request |
||
477 | * @param {Object} message |
||
478 | */ |
||
479 | u2f.WrappedIosPort_.prototype.postMessage = function(message) { |
||
480 | var str = JSON.stringify(message); |
||
481 | var url = "u2f://auth?" + encodeURI(str); |
||
482 | location.replace(url); |
||
483 | }; |
||
484 | |||
485 | /** |
||
486 | * Tells what type of port this is. |
||
487 | * @return {String} port type |
||
488 | */ |
||
489 | u2f.WrappedIosPort_.prototype.getPortType = function() { |
||
490 | return "WrappedIosPort_"; |
||
491 | }; |
||
492 | |||
493 | /** |
||
494 | * Emulates the HTML 5 addEventListener interface. |
||
495 | * @param {string} eventName |
||
496 | * @param {function({data: Object})} handler |
||
497 | */ |
||
498 | u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { |
||
0 ignored issues
–
show
|
|||
499 | var name = eventName.toLowerCase(); |
||
500 | if (name !== 'message') { |
||
501 | console.error('WrappedIosPort only supports message'); |
||
502 | } |
||
503 | }; |
||
504 | |||
505 | /** |
||
506 | * Sets up an embedded trampoline iframe, sourced from the extension. |
||
507 | * @param {function(MessagePort)} callback |
||
508 | * @private |
||
509 | */ |
||
510 | u2f.getIframePort_ = function(callback) { |
||
511 | // Create the iframe |
||
512 | var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; |
||
513 | var iframe = document.createElement('iframe'); |
||
514 | iframe.src = iframeOrigin + '/u2f-comms.html'; |
||
515 | iframe.setAttribute('style', 'display:none'); |
||
516 | document.body.appendChild(iframe); |
||
517 | |||
518 | var channel = new MessageChannel(); |
||
0 ignored issues
–
show
The variable
MessageChannel seems to be never declared. If this is a global, consider adding a /** global: MessageChannel */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN.
Loading history...
|
|||
519 | var ready = function(message) { |
||
520 | if (message.data == 'ready') { |
||
521 | channel.port1.removeEventListener('message', ready); |
||
522 | callback(channel.port1); |
||
523 | } else { |
||
524 | console.error('First event on iframe port was not "ready"'); |
||
525 | } |
||
526 | }; |
||
527 | channel.port1.addEventListener('message', ready); |
||
528 | channel.port1.start(); |
||
529 | |||
530 | iframe.addEventListener('load', function() { |
||
531 | // Deliver the port to the iframe and initialize |
||
532 | iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); |
||
533 | }); |
||
534 | }; |
||
535 | |||
536 | |||
537 | //High-level JS API |
||
538 | |||
539 | /** |
||
540 | * Default extension response timeout in seconds. |
||
541 | * @const |
||
542 | */ |
||
543 | u2f.EXTENSION_TIMEOUT_SEC = 30; |
||
544 | |||
545 | /** |
||
546 | * A singleton instance for a MessagePort to the extension. |
||
547 | * @type {MessagePort|u2f.WrappedChromeRuntimePort_} |
||
548 | * @private |
||
549 | */ |
||
550 | u2f.port_ = null; |
||
551 | |||
552 | /** |
||
553 | * Callbacks waiting for a port |
||
554 | * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>} |
||
555 | * @private |
||
556 | */ |
||
557 | u2f.waitingForPort_ = []; |
||
558 | |||
559 | /** |
||
560 | * A counter for requestIds. |
||
561 | * @type {number} |
||
562 | * @private |
||
563 | */ |
||
564 | u2f.reqCounter_ = 0; |
||
565 | |||
566 | /** |
||
567 | * A map from requestIds to client callbacks |
||
568 | * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse)) |
||
569 | * |function((u2f.Error|u2f.SignResponse)))>} |
||
570 | * @private |
||
571 | */ |
||
572 | u2f.callbackMap_ = {}; |
||
573 | |||
574 | /** |
||
575 | * Creates or retrieves the MessagePort singleton to use. |
||
576 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback |
||
577 | * @private |
||
578 | */ |
||
579 | u2f.getPortSingleton_ = function(callback) { |
||
580 | if (u2f.port_) { |
||
581 | callback(u2f.port_); |
||
582 | } else { |
||
583 | if (u2f.waitingForPort_.length == 0) { |
||
0 ignored issues
–
show
|
|||
584 | u2f.getMessagePort(function(port) { |
||
585 | u2f.port_ = port; |
||
586 | u2f.port_.addEventListener('message', |
||
587 | /** @type {function(Event)} */ (u2f.responseHandler_)); |
||
588 | |||
589 | // Careful, here be async callbacks. Maybe. |
||
590 | while (u2f.waitingForPort_.length) |
||
591 | u2f.waitingForPort_.shift()(u2f.port_); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed.
Loading history...
|
|||
592 | }); |
||
593 | } |
||
594 | u2f.waitingForPort_.push(callback); |
||
595 | } |
||
596 | }; |
||
597 | |||
598 | /** |
||
599 | * Handles response messages from the extension. |
||
600 | * @param {MessageEvent.<u2f.Response>} message |
||
601 | * @private |
||
602 | */ |
||
603 | u2f.responseHandler_ = function(message) { |
||
604 | var response = message.data; |
||
605 | var reqId = response['requestId']; |
||
606 | if (!reqId || !u2f.callbackMap_[reqId]) { |
||
607 | console.error('Unknown or missing requestId in response.'); |
||
608 | return; |
||
609 | } |
||
610 | var cb = u2f.callbackMap_[reqId]; |
||
611 | delete u2f.callbackMap_[reqId]; |
||
612 | cb(response['responseData']); |
||
613 | }; |
||
614 | |||
615 | /** |
||
616 | * Dispatches an array of sign requests to available U2F tokens. |
||
617 | * If the JS API version supported by the extension is unknown, it first sends a |
||
618 | * message to the extension to find out the supported API version and then it sends |
||
619 | * the sign request. |
||
620 | * @param {string=} appId |
||
621 | * @param {string=} challenge |
||
622 | * @param {Array<u2f.RegisteredKey>} registeredKeys |
||
623 | * @param {function((u2f.Error|u2f.SignResponse))} callback |
||
624 | * @param {number=} opt_timeoutSeconds |
||
625 | */ |
||
626 | u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { |
||
627 | if (js_api_version === undefined) { |
||
628 | // Send a message to get the extension to JS API version, then send the actual sign request. |
||
629 | u2f.getApiVersion( |
||
630 | function (response) { |
||
631 | js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; |
||
632 | console.log("Extension JS API Version: ", js_api_version); |
||
0 ignored issues
–
show
|
|||
633 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); |
||
634 | }); |
||
635 | } else { |
||
636 | // We know the JS API version. Send the actual sign request in the supported API version. |
||
637 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); |
||
638 | } |
||
639 | }; |
||
640 | |||
641 | /** |
||
642 | * Dispatches an array of sign requests to available U2F tokens. |
||
643 | * @param {string=} appId |
||
644 | * @param {string=} challenge |
||
645 | * @param {Array<u2f.RegisteredKey>} registeredKeys |
||
646 | * @param {function((u2f.Error|u2f.SignResponse))} callback |
||
647 | * @param {number=} opt_timeoutSeconds |
||
648 | */ |
||
649 | u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { |
||
650 | u2f.getPortSingleton_(function(port) { |
||
651 | var reqId = ++u2f.reqCounter_; |
||
652 | u2f.callbackMap_[reqId] = callback; |
||
653 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? |
||
654 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); |
||
655 | var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); |
||
656 | port.postMessage(req); |
||
657 | }); |
||
658 | }; |
||
659 | |||
660 | /** |
||
661 | * Dispatches register requests to available U2F tokens. An array of sign |
||
662 | * requests identifies already registered tokens. |
||
663 | * If the JS API version supported by the extension is unknown, it first sends a |
||
664 | * message to the extension to find out the supported API version and then it sends |
||
665 | * the register request. |
||
666 | * @param {string=} appId |
||
667 | * @param {Array<u2f.RegisterRequest>} registerRequests |
||
668 | * @param {Array<u2f.RegisteredKey>} registeredKeys |
||
669 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback |
||
670 | * @param {number=} opt_timeoutSeconds |
||
671 | */ |
||
672 | u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { |
||
673 | if (js_api_version === undefined) { |
||
674 | // Send a message to get the extension to JS API version, then send the actual register request. |
||
675 | u2f.getApiVersion( |
||
676 | function (response) { |
||
677 | js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; |
||
678 | console.log("Extension JS API Version: ", js_api_version); |
||
0 ignored issues
–
show
|
|||
679 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, |
||
680 | callback, opt_timeoutSeconds); |
||
681 | }); |
||
682 | } else { |
||
683 | // We know the JS API version. Send the actual register request in the supported API version. |
||
684 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, |
||
685 | callback, opt_timeoutSeconds); |
||
686 | } |
||
687 | }; |
||
688 | |||
689 | /** |
||
690 | * Dispatches register requests to available U2F tokens. An array of sign |
||
691 | * requests identifies already registered tokens. |
||
692 | * @param {string=} appId |
||
693 | * @param {Array<u2f.RegisterRequest>} registerRequests |
||
694 | * @param {Array<u2f.RegisteredKey>} registeredKeys |
||
695 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback |
||
696 | * @param {number=} opt_timeoutSeconds |
||
697 | */ |
||
698 | u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { |
||
699 | u2f.getPortSingleton_(function(port) { |
||
700 | var reqId = ++u2f.reqCounter_; |
||
701 | u2f.callbackMap_[reqId] = callback; |
||
702 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? |
||
703 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); |
||
704 | var req = u2f.formatRegisterRequest_( |
||
705 | appId, registeredKeys, registerRequests, timeoutSeconds, reqId); |
||
706 | port.postMessage(req); |
||
707 | }); |
||
708 | }; |
||
709 | |||
710 | |||
711 | /** |
||
712 | * Dispatches a message to the extension to find out the supported |
||
713 | * JS API version. |
||
714 | * If the user is on a mobile phone and is thus using Google Authenticator instead |
||
715 | * of the Chrome extension, don't send the request and simply return 0. |
||
716 | * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback |
||
717 | * @param {number=} opt_timeoutSeconds |
||
718 | */ |
||
719 | u2f.getApiVersion = function(callback, opt_timeoutSeconds) { |
||
720 | u2f.getPortSingleton_(function(port) { |
||
721 | // If we are using Android Google Authenticator or iOS client app, |
||
722 | // do not fire an intent to ask which JS API version to use. |
||
723 | if (port.getPortType) { |
||
724 | var apiVersion; |
||
725 | switch (port.getPortType()) { |
||
726 | case 'WrappedIosPort_': |
||
727 | case 'WrappedAuthenticatorPort_': |
||
728 | apiVersion = 1.1; |
||
729 | break; |
||
730 | |||
731 | default: |
||
732 | apiVersion = 0; |
||
733 | break; |
||
734 | } |
||
735 | callback({ 'js_api_version': apiVersion }); |
||
736 | return; |
||
737 | } |
||
738 | var reqId = ++u2f.reqCounter_; |
||
739 | u2f.callbackMap_[reqId] = callback; |
||
740 | var req = { |
||
741 | type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, |
||
742 | timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? |
||
743 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), |
||
744 | requestId: reqId |
||
745 | }; |
||
746 | port.postMessage(req); |
||
747 | }); |
||
748 | }; |
||
749 |