rack-u2f 0.1.0

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