u2f-api-polyfill.js 22 KB

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