├── COPYRIGHT ├── LICENSE ├── README.md ├── package.json └── u2f-api-polyfill.js /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2014 Google Inc. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, Google Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # U2F API 2 | 3 | This is a JavaScript polyfill, implementing the high-level U2F JavaScript API 4 | for Chrome. 5 | 6 | Google wrote and owns this code. It's just a pain to manually pull it from their 7 | [u2f-ref-code](https://github.com/google/u2f-ref-code) repo, so I made a NPM 8 | package. 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "u2f-api-polyfill", 3 | "main": "u2f-api-polyfill.js", 4 | "version": "0.4.4", 5 | "authors": [ 6 | "arnar" 7 | ], 8 | "description": "High level JavaScript API for interacting with FIDO U2F devices in Chrome", 9 | "keywords": [ 10 | "fido", 11 | "u2f" 12 | ], 13 | "license": "BSD-3-Clause", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/mastahyeti/u2f-api" 17 | }, 18 | "homepage": "https://fidoalliance.org/", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /u2f-api-polyfill.js: -------------------------------------------------------------------------------- 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 | // NOTE FROM MAINTAINER: This file is copied from google/u2f-ref-code with as 8 | // few alterations as possible. Any changes that were necessary are annotated 9 | // with "NECESSARY CHANGE". These changes, as well as this note, should be 10 | // preserved when updating this file from the source. 11 | 12 | /** 13 | * @fileoverview The U2F api. 14 | */ 15 | 'use strict'; 16 | 17 | // NECESSARY CHANGE: wrap the whole file in a closure 18 | (function (){ 19 | // NECESSARY CHANGE: detect UA to avoid clobbering other browser's U2F API. 20 | var isChrome = 'chrome' in window && window.navigator.userAgent.indexOf('Edge') < 0; 21 | if ('u2f' in window || !isChrome) { 22 | return; 23 | } 24 | 25 | /** 26 | * Namespace for the U2F api. 27 | * @type {Object} 28 | */ 29 | // NECESSARY CHANGE: define the window.u2f API. 30 | var u2f = window.u2f = {}; 31 | 32 | /** 33 | * FIDO U2F Javascript API Version 34 | * @number 35 | */ 36 | var js_api_version; 37 | 38 | /** 39 | * The U2F extension id 40 | * @const {string} 41 | */ 42 | // The Chrome packaged app extension ID. 43 | // Uncomment this if you want to deploy a server instance that uses 44 | // the package Chrome app and does not require installing the U2F Chrome extension. 45 | u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; 46 | // The U2F Chrome extension ID. 47 | // Uncomment this if you want to deploy a server instance that uses 48 | // the U2F Chrome extension to authenticate. 49 | // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; 50 | 51 | 52 | /** 53 | * Message types for messsages to/from the extension 54 | * @const 55 | * @enum {string} 56 | */ 57 | u2f.MessageTypes = { 58 | 'U2F_REGISTER_REQUEST': 'u2f_register_request', 59 | 'U2F_REGISTER_RESPONSE': 'u2f_register_response', 60 | 'U2F_SIGN_REQUEST': 'u2f_sign_request', 61 | 'U2F_SIGN_RESPONSE': 'u2f_sign_response', 62 | 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', 63 | 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' 64 | }; 65 | 66 | 67 | /** 68 | * Response status codes 69 | * @const 70 | * @enum {number} 71 | */ 72 | u2f.ErrorCodes = { 73 | 'OK': 0, 74 | 'OTHER_ERROR': 1, 75 | 'BAD_REQUEST': 2, 76 | 'CONFIGURATION_UNSUPPORTED': 3, 77 | 'DEVICE_INELIGIBLE': 4, 78 | 'TIMEOUT': 5 79 | }; 80 | 81 | 82 | /** 83 | * A message for registration requests 84 | * @typedef {{ 85 | * type: u2f.MessageTypes, 86 | * appId: ?string, 87 | * timeoutSeconds: ?number, 88 | * requestId: ?number 89 | * }} 90 | */ 91 | u2f.U2fRequest; 92 | 93 | 94 | /** 95 | * A message for registration responses 96 | * @typedef {{ 97 | * type: u2f.MessageTypes, 98 | * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), 99 | * requestId: ?number 100 | * }} 101 | */ 102 | u2f.U2fResponse; 103 | 104 | 105 | /** 106 | * An error object for responses 107 | * @typedef {{ 108 | * errorCode: u2f.ErrorCodes, 109 | * errorMessage: ?string 110 | * }} 111 | */ 112 | u2f.Error; 113 | 114 | /** 115 | * Data object for a single sign request. 116 | * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC, USB_INTERNAL}} 117 | */ 118 | u2f.Transport; 119 | 120 | 121 | /** 122 | * Data object for a single sign request. 123 | * @typedef {Array} 124 | */ 125 | u2f.Transports; 126 | 127 | /** 128 | * Data object for a single sign request. 129 | * @typedef {{ 130 | * version: string, 131 | * challenge: string, 132 | * keyHandle: string, 133 | * appId: string 134 | * }} 135 | */ 136 | u2f.SignRequest; 137 | 138 | 139 | /** 140 | * Data object for a sign response. 141 | * @typedef {{ 142 | * keyHandle: string, 143 | * signatureData: string, 144 | * clientData: string 145 | * }} 146 | */ 147 | u2f.SignResponse; 148 | 149 | 150 | /** 151 | * Data object for a registration request. 152 | * @typedef {{ 153 | * version: string, 154 | * challenge: string 155 | * }} 156 | */ 157 | u2f.RegisterRequest; 158 | 159 | 160 | /** 161 | * Data object for a registration response. 162 | * @typedef {{ 163 | * version: string, 164 | * keyHandle: string, 165 | * transports: Transports, 166 | * appId: string 167 | * }} 168 | */ 169 | u2f.RegisterResponse; 170 | 171 | 172 | /** 173 | * Data object for a registered key. 174 | * @typedef {{ 175 | * version: string, 176 | * keyHandle: string, 177 | * transports: ?Transports, 178 | * appId: ?string 179 | * }} 180 | */ 181 | u2f.RegisteredKey; 182 | 183 | 184 | /** 185 | * Data object for a get API register response. 186 | * @typedef {{ 187 | * js_api_version: number 188 | * }} 189 | */ 190 | u2f.GetJsApiVersionResponse; 191 | 192 | 193 | //Low level MessagePort API support 194 | 195 | /** 196 | * Sets up a MessagePort to the U2F extension using the 197 | * available mechanisms. 198 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback 199 | */ 200 | u2f.getMessagePort = function(callback) { 201 | if (typeof chrome != 'undefined' && chrome.runtime) { 202 | // The actual message here does not matter, but we need to get a reply 203 | // for the callback to run. Thus, send an empty signature request 204 | // in order to get a failure response. 205 | var msg = { 206 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 207 | signRequests: [] 208 | }; 209 | chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { 210 | if (!chrome.runtime.lastError) { 211 | // We are on a whitelisted origin and can talk directly 212 | // with the extension. 213 | u2f.getChromeRuntimePort_(callback); 214 | } else { 215 | // chrome.runtime was available, but we couldn't message 216 | // the extension directly, use iframe 217 | u2f.getIframePort_(callback); 218 | } 219 | }); 220 | } else if (u2f.isAndroidChrome_()) { 221 | u2f.getAuthenticatorPort_(callback); 222 | } else if (u2f.isIosChrome_()) { 223 | u2f.getIosPort_(callback); 224 | } else { 225 | // chrome.runtime was not available at all, which is normal 226 | // when this origin doesn't have access to any extensions. 227 | u2f.getIframePort_(callback); 228 | } 229 | }; 230 | 231 | /** 232 | * Detect chrome running on android based on the browser's useragent. 233 | * @private 234 | */ 235 | u2f.isAndroidChrome_ = function() { 236 | var userAgent = navigator.userAgent; 237 | return userAgent.indexOf('Chrome') != -1 && 238 | userAgent.indexOf('Android') != -1; 239 | }; 240 | 241 | /** 242 | * Detect chrome running on iOS based on the browser's platform. 243 | * @private 244 | */ 245 | u2f.isIosChrome_ = function() { 246 | return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; 247 | }; 248 | 249 | /** 250 | * Connects directly to the extension via chrome.runtime.connect. 251 | * @param {function(u2f.WrappedChromeRuntimePort_)} callback 252 | * @private 253 | */ 254 | u2f.getChromeRuntimePort_ = function(callback) { 255 | var port = chrome.runtime.connect(u2f.EXTENSION_ID, 256 | {'includeTlsChannelId': true}); 257 | setTimeout(function() { 258 | callback(new u2f.WrappedChromeRuntimePort_(port)); 259 | }, 0); 260 | }; 261 | 262 | /** 263 | * Return a 'port' abstraction to the Authenticator app. 264 | * @param {function(u2f.WrappedAuthenticatorPort_)} callback 265 | * @private 266 | */ 267 | u2f.getAuthenticatorPort_ = function(callback) { 268 | setTimeout(function() { 269 | callback(new u2f.WrappedAuthenticatorPort_()); 270 | }, 0); 271 | }; 272 | 273 | /** 274 | * Return a 'port' abstraction to the iOS client app. 275 | * @param {function(u2f.WrappedIosPort_)} callback 276 | * @private 277 | */ 278 | u2f.getIosPort_ = function(callback) { 279 | setTimeout(function() { 280 | callback(new u2f.WrappedIosPort_()); 281 | }, 0); 282 | }; 283 | 284 | /** 285 | * A wrapper for chrome.runtime.Port that is compatible with MessagePort. 286 | * @param {Port} port 287 | * @constructor 288 | * @private 289 | */ 290 | u2f.WrappedChromeRuntimePort_ = function(port) { 291 | this.port_ = port; 292 | }; 293 | 294 | /** 295 | * Format and return a sign request compliant with the JS API version supported by the extension. 296 | * @param {Array} signRequests 297 | * @param {number} timeoutSeconds 298 | * @param {number} reqId 299 | * @return {Object} 300 | */ 301 | u2f.formatSignRequest_ = 302 | function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { 303 | if (js_api_version === undefined || js_api_version < 1.1) { 304 | // Adapt request to the 1.0 JS API 305 | var signRequests = []; 306 | for (var i = 0; i < registeredKeys.length; i++) { 307 | signRequests[i] = { 308 | version: registeredKeys[i].version, 309 | challenge: challenge, 310 | keyHandle: registeredKeys[i].keyHandle, 311 | appId: appId 312 | }; 313 | } 314 | return { 315 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 316 | signRequests: signRequests, 317 | timeoutSeconds: timeoutSeconds, 318 | requestId: reqId 319 | }; 320 | } 321 | // JS 1.1 API 322 | return { 323 | type: u2f.MessageTypes.U2F_SIGN_REQUEST, 324 | appId: appId, 325 | challenge: challenge, 326 | registeredKeys: registeredKeys, 327 | timeoutSeconds: timeoutSeconds, 328 | requestId: reqId 329 | }; 330 | }; 331 | 332 | /** 333 | * Format and return a register request compliant with the JS API version supported by the extension.. 334 | * @param {Array} signRequests 335 | * @param {Array} signRequests 336 | * @param {number} timeoutSeconds 337 | * @param {number} reqId 338 | * @return {Object} 339 | */ 340 | u2f.formatRegisterRequest_ = 341 | function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { 342 | if (js_api_version === undefined || js_api_version < 1.1) { 343 | // Adapt request to the 1.0 JS API 344 | for (var i = 0; i < registerRequests.length; i++) { 345 | registerRequests[i].appId = appId; 346 | } 347 | var signRequests = []; 348 | for (var i = 0; i < registeredKeys.length; i++) { 349 | signRequests[i] = { 350 | version: registeredKeys[i].version, 351 | challenge: registerRequests[0], 352 | keyHandle: registeredKeys[i].keyHandle, 353 | appId: appId 354 | }; 355 | } 356 | return { 357 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, 358 | signRequests: signRequests, 359 | registerRequests: registerRequests, 360 | timeoutSeconds: timeoutSeconds, 361 | requestId: reqId 362 | }; 363 | } 364 | // JS 1.1 API 365 | return { 366 | type: u2f.MessageTypes.U2F_REGISTER_REQUEST, 367 | appId: appId, 368 | registerRequests: registerRequests, 369 | registeredKeys: registeredKeys, 370 | timeoutSeconds: timeoutSeconds, 371 | requestId: reqId 372 | }; 373 | }; 374 | 375 | 376 | /** 377 | * Posts a message on the underlying channel. 378 | * @param {Object} message 379 | */ 380 | u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { 381 | this.port_.postMessage(message); 382 | }; 383 | 384 | 385 | /** 386 | * Emulates the HTML 5 addEventListener interface. Works only for the 387 | * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. 388 | * @param {string} eventName 389 | * @param {function({data: Object})} handler 390 | */ 391 | u2f.WrappedChromeRuntimePort_.prototype.addEventListener = 392 | function(eventName, handler) { 393 | var name = eventName.toLowerCase(); 394 | if (name == 'message' || name == 'onmessage') { 395 | this.port_.onMessage.addListener(function(message) { 396 | // Emulate a minimal MessageEvent object 397 | handler({'data': message}); 398 | }); 399 | } else { 400 | console.error('WrappedChromeRuntimePort only supports onMessage'); 401 | } 402 | }; 403 | 404 | /** 405 | * Wrap the Authenticator app with a MessagePort interface. 406 | * @constructor 407 | * @private 408 | */ 409 | u2f.WrappedAuthenticatorPort_ = function() { 410 | this.requestId_ = -1; 411 | this.requestObject_ = null; 412 | } 413 | 414 | /** 415 | * Launch the Authenticator intent. 416 | * @param {Object} message 417 | */ 418 | u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { 419 | var intentUrl = 420 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + 421 | ';S.request=' + encodeURIComponent(JSON.stringify(message)) + 422 | ';end'; 423 | document.location = intentUrl; 424 | }; 425 | 426 | /** 427 | * Tells what type of port this is. 428 | * @return {String} port type 429 | */ 430 | u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { 431 | return "WrappedAuthenticatorPort_"; 432 | }; 433 | 434 | 435 | /** 436 | * Emulates the HTML 5 addEventListener interface. 437 | * @param {string} eventName 438 | * @param {function({data: Object})} handler 439 | */ 440 | u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { 441 | var name = eventName.toLowerCase(); 442 | if (name == 'message') { 443 | var self = this; 444 | /* Register a callback to that executes when 445 | * chrome injects the response. */ 446 | window.addEventListener( 447 | 'message', self.onRequestUpdate_.bind(self, handler), false); 448 | } else { 449 | console.error('WrappedAuthenticatorPort only supports message'); 450 | } 451 | }; 452 | 453 | /** 454 | * Callback invoked when a response is received from the Authenticator. 455 | * @param function({data: Object}) callback 456 | * @param {Object} message message Object 457 | */ 458 | u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = 459 | function(callback, message) { 460 | var messageObject = JSON.parse(message.data); 461 | var intentUrl = messageObject['intentURL']; 462 | 463 | var errorCode = messageObject['errorCode']; 464 | var responseObject = null; 465 | if (messageObject.hasOwnProperty('data')) { 466 | responseObject = /** @type {Object} */ ( 467 | JSON.parse(messageObject['data'])); 468 | } 469 | 470 | callback({'data': responseObject}); 471 | }; 472 | 473 | /** 474 | * Base URL for intents to Authenticator. 475 | * @const 476 | * @private 477 | */ 478 | u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = 479 | 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; 480 | 481 | /** 482 | * Wrap the iOS client app with a MessagePort interface. 483 | * @constructor 484 | * @private 485 | */ 486 | u2f.WrappedIosPort_ = function() {}; 487 | 488 | /** 489 | * Launch the iOS client app request 490 | * @param {Object} message 491 | */ 492 | u2f.WrappedIosPort_.prototype.postMessage = function(message) { 493 | var str = JSON.stringify(message); 494 | var url = "u2f://auth?" + encodeURI(str); 495 | location.replace(url); 496 | }; 497 | 498 | /** 499 | * Tells what type of port this is. 500 | * @return {String} port type 501 | */ 502 | u2f.WrappedIosPort_.prototype.getPortType = function() { 503 | return "WrappedIosPort_"; 504 | }; 505 | 506 | /** 507 | * Emulates the HTML 5 addEventListener interface. 508 | * @param {string} eventName 509 | * @param {function({data: Object})} handler 510 | */ 511 | u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { 512 | var name = eventName.toLowerCase(); 513 | if (name !== 'message') { 514 | console.error('WrappedIosPort only supports message'); 515 | } 516 | }; 517 | 518 | /** 519 | * Sets up an embedded trampoline iframe, sourced from the extension. 520 | * @param {function(MessagePort)} callback 521 | * @private 522 | */ 523 | u2f.getIframePort_ = function(callback) { 524 | // Create the iframe 525 | var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; 526 | var iframe = document.createElement('iframe'); 527 | iframe.src = iframeOrigin + '/u2f-comms.html'; 528 | iframe.setAttribute('style', 'display:none'); 529 | document.body.appendChild(iframe); 530 | 531 | var channel = new MessageChannel(); 532 | var ready = function(message) { 533 | if (message.data == 'ready') { 534 | channel.port1.removeEventListener('message', ready); 535 | callback(channel.port1); 536 | } else { 537 | console.error('First event on iframe port was not "ready"'); 538 | } 539 | }; 540 | channel.port1.addEventListener('message', ready); 541 | channel.port1.start(); 542 | 543 | iframe.addEventListener('load', function() { 544 | // Deliver the port to the iframe and initialize 545 | iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); 546 | }); 547 | }; 548 | 549 | 550 | //High-level JS API 551 | 552 | /** 553 | * Default extension response timeout in seconds. 554 | * @const 555 | */ 556 | u2f.EXTENSION_TIMEOUT_SEC = 30; 557 | 558 | /** 559 | * A singleton instance for a MessagePort to the extension. 560 | * @type {MessagePort|u2f.WrappedChromeRuntimePort_} 561 | * @private 562 | */ 563 | u2f.port_ = null; 564 | 565 | /** 566 | * Callbacks waiting for a port 567 | * @type {Array} 568 | * @private 569 | */ 570 | u2f.waitingForPort_ = []; 571 | 572 | /** 573 | * A counter for requestIds. 574 | * @type {number} 575 | * @private 576 | */ 577 | u2f.reqCounter_ = 0; 578 | 579 | /** 580 | * A map from requestIds to client callbacks 581 | * @type {Object.} 583 | * @private 584 | */ 585 | u2f.callbackMap_ = {}; 586 | 587 | /** 588 | * Creates or retrieves the MessagePort singleton to use. 589 | * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback 590 | * @private 591 | */ 592 | u2f.getPortSingleton_ = function(callback) { 593 | if (u2f.port_) { 594 | callback(u2f.port_); 595 | } else { 596 | if (u2f.waitingForPort_.length == 0) { 597 | u2f.getMessagePort(function(port) { 598 | u2f.port_ = port; 599 | u2f.port_.addEventListener('message', 600 | /** @type {function(Event)} */ (u2f.responseHandler_)); 601 | 602 | // Careful, here be async callbacks. Maybe. 603 | while (u2f.waitingForPort_.length) 604 | u2f.waitingForPort_.shift()(u2f.port_); 605 | }); 606 | } 607 | u2f.waitingForPort_.push(callback); 608 | } 609 | }; 610 | 611 | /** 612 | * Handles response messages from the extension. 613 | * @param {MessageEvent.} message 614 | * @private 615 | */ 616 | u2f.responseHandler_ = function(message) { 617 | var response = message.data; 618 | var reqId = response['requestId']; 619 | if (!reqId || !u2f.callbackMap_[reqId]) { 620 | console.error('Unknown or missing requestId in response.'); 621 | return; 622 | } 623 | var cb = u2f.callbackMap_[reqId]; 624 | delete u2f.callbackMap_[reqId]; 625 | cb(response['responseData']); 626 | }; 627 | 628 | /** 629 | * Dispatches an array of sign requests to available U2F tokens. 630 | * If the JS API version supported by the extension is unknown, it first sends a 631 | * message to the extension to find out the supported API version and then it sends 632 | * the sign request. 633 | * @param {string=} appId 634 | * @param {string=} challenge 635 | * @param {Array} registeredKeys 636 | * @param {function((u2f.Error|u2f.SignResponse))} callback 637 | * @param {number=} opt_timeoutSeconds 638 | */ 639 | u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { 640 | if (js_api_version === undefined) { 641 | // Send a message to get the extension to JS API version, then send the actual sign request. 642 | u2f.getApiVersion( 643 | function (response) { 644 | js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; 645 | console.log("Extension JS API Version: ", js_api_version); 646 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); 647 | }); 648 | } else { 649 | // We know the JS API version. Send the actual sign request in the supported API version. 650 | u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); 651 | } 652 | }; 653 | 654 | /** 655 | * Dispatches an array of sign requests to available U2F tokens. 656 | * @param {string=} appId 657 | * @param {string=} challenge 658 | * @param {Array} registeredKeys 659 | * @param {function((u2f.Error|u2f.SignResponse))} callback 660 | * @param {number=} opt_timeoutSeconds 661 | */ 662 | u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { 663 | u2f.getPortSingleton_(function(port) { 664 | var reqId = ++u2f.reqCounter_; 665 | u2f.callbackMap_[reqId] = callback; 666 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? 667 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); 668 | var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); 669 | port.postMessage(req); 670 | }); 671 | }; 672 | 673 | /** 674 | * Dispatches register requests to available U2F tokens. An array of sign 675 | * requests identifies already registered tokens. 676 | * If the JS API version supported by the extension is unknown, it first sends a 677 | * message to the extension to find out the supported API version and then it sends 678 | * the register request. 679 | * @param {string=} appId 680 | * @param {Array} registerRequests 681 | * @param {Array} registeredKeys 682 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback 683 | * @param {number=} opt_timeoutSeconds 684 | */ 685 | u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { 686 | if (js_api_version === undefined) { 687 | // Send a message to get the extension to JS API version, then send the actual register request. 688 | u2f.getApiVersion( 689 | function (response) { 690 | js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; 691 | console.log("Extension JS API Version: ", js_api_version); 692 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, 693 | callback, opt_timeoutSeconds); 694 | }); 695 | } else { 696 | // We know the JS API version. Send the actual register request in the supported API version. 697 | u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, 698 | callback, opt_timeoutSeconds); 699 | } 700 | }; 701 | 702 | /** 703 | * Dispatches register requests to available U2F tokens. An array of sign 704 | * requests identifies already registered tokens. 705 | * @param {string=} appId 706 | * @param {Array} registerRequests 707 | * @param {Array} registeredKeys 708 | * @param {function((u2f.Error|u2f.RegisterResponse))} callback 709 | * @param {number=} opt_timeoutSeconds 710 | */ 711 | u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { 712 | u2f.getPortSingleton_(function(port) { 713 | var reqId = ++u2f.reqCounter_; 714 | u2f.callbackMap_[reqId] = callback; 715 | var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? 716 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); 717 | var req = u2f.formatRegisterRequest_( 718 | appId, registeredKeys, registerRequests, timeoutSeconds, reqId); 719 | port.postMessage(req); 720 | }); 721 | }; 722 | 723 | 724 | /** 725 | * Dispatches a message to the extension to find out the supported 726 | * JS API version. 727 | * If the user is on a mobile phone and is thus using Google Authenticator instead 728 | * of the Chrome extension, don't send the request and simply return 0. 729 | * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback 730 | * @param {number=} opt_timeoutSeconds 731 | */ 732 | u2f.getApiVersion = function(callback, opt_timeoutSeconds) { 733 | u2f.getPortSingleton_(function(port) { 734 | // If we are using Android Google Authenticator or iOS client app, 735 | // do not fire an intent to ask which JS API version to use. 736 | if (port.getPortType) { 737 | var apiVersion; 738 | switch (port.getPortType()) { 739 | case 'WrappedIosPort_': 740 | case 'WrappedAuthenticatorPort_': 741 | apiVersion = 1.1; 742 | break; 743 | 744 | default: 745 | apiVersion = 0; 746 | break; 747 | } 748 | callback({ 'js_api_version': apiVersion }); 749 | return; 750 | } 751 | var reqId = ++u2f.reqCounter_; 752 | u2f.callbackMap_[reqId] = callback; 753 | var req = { 754 | type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, 755 | timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? 756 | opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), 757 | requestId: reqId 758 | }; 759 | port.postMessage(req); 760 | }); 761 | }; 762 | })(); 763 | --------------------------------------------------------------------------------