u2f-api.js 25 KB

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