011-u2f-api.js 25 KB

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