]>
Commit | Line | Data |
---|---|---|
8c6ad4f6 WB |
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 || {}; | |
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; | |
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; | |
90 | ||
91 | ||
92 | /** | |
93 | * An error object for responses | |
94 | * @typedef {{ | |
95 | * errorCode: u2f.ErrorCodes, | |
96 | * errorMessage: ?string | |
97 | * }} | |
98 | */ | |
99 | u2f.Error; | |
100 | ||
101 | /** | |
102 | * Data object for a single sign request. | |
103 | * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} | |
104 | */ | |
105 | u2f.Transport; | |
106 | ||
107 | ||
108 | /** | |
109 | * Data object for a single sign request. | |
110 | * @typedef {Array<u2f.Transport>} | |
111 | */ | |
112 | u2f.Transports; | |
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; | |
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; | |
135 | ||
136 | ||
137 | /** | |
138 | * Data object for a registration request. | |
139 | * @typedef {{ | |
140 | * version: string, | |
141 | * challenge: string | |
142 | * }} | |
143 | */ | |
144 | u2f.RegisterRequest; | |
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; | |
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; | |
169 | ||
170 | ||
171 | /** | |
172 | * Data object for a get API register response. | |
173 | * @typedef {{ | |
174 | * js_api_version: number | |
175 | * }} | |
176 | */ | |
177 | u2f.GetJsApiVersionResponse; | |
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) { | |
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) { | |
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; | |
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; | |
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, | |
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++) { | |
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']; | |
449 | ||
450 | var errorCode = messageObject['errorCode']; | |
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) { | |
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(); | |
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) { | |
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_); | |
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); | |
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); | |
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 | }; |