Browse Source

Update U2F API

Adds support for Firefox
spaghetti 7 years ago
parent
commit
f629c6efda
1 changed files with 403 additions and 377 deletions
  1. 403
    377
      static/functions/u2f.js

+ 403
- 377
static/functions/u2f.js View File

@@ -8,354 +8,301 @@
8 8
 
9 9
 'use strict';
10 10
 
11
-// Namespace for the U2F api.
12
-var u2f = u2f || {};
13
-
14
-// The U2F extension id
15
-u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
16
-
17
-// Message types for messsages to/from the extension
18
-u2f.MessageTypes = {
19
-    'U2F_REGISTER_REQUEST': 'u2f_register_request',
20
-    'U2F_SIGN_REQUEST': 'u2f_sign_request',
21
-    'U2F_REGISTER_RESPONSE': 'u2f_register_response',
22
-    'U2F_SIGN_RESPONSE': 'u2f_sign_response'
23
-};
11
+if (!u2f) {
12
+  // Namespace for the U2F api.
13
+  var u2f = u2f || {};
24 14
 
25
-// Response status codes
26
-u2f.ErrorCodes = {
27
-    'OK': 0,
28
-    'OTHER_ERROR': 1,
29
-    'BAD_REQUEST': 2,
30
-    'CONFIGURATION_UNSUPPORTED': 3,
31
-    'DEVICE_INELIGIBLE': 4,
32
-    'TIMEOUT': 5
33
-};
15
+  // FIDO U2F Javascript API Version
16
+  var js_api_version;
17
+
18
+  // The U2F extension id
19
+  u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
20
+
21
+  // Message types for messsages to/from the extension
22
+  u2f.MessageTypes = {
23
+      'U2F_REGISTER_REQUEST': 'u2f_register_request',
24
+      'U2F_REGISTER_RESPONSE': 'u2f_register_response',
25
+      'U2F_SIGN_REQUEST': 'u2f_sign_request',
26
+      'U2F_SIGN_RESPONSE': 'u2f_sign_response',
27
+      'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
28
+      'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
29
+  };
30
+
31
+  // A message for registration requests
32
+  u2f.U2fRequest;
34 33
 
35
-// A message type for registration requests
36
-u2f.Request;
34
+  // A message for registration responses
35
+  u2f.U2fResponse;
37 36
 
38
-// A message for registration responses
39
-u2f.Response;
37
+  // An error object for responses
38
+  u2f.Error;
40 39
 
41
-// An error object for responses
42
-u2f.Error;
40
+  // Data object for a single sign request.
41
+  u2f.Transport;
43 42
 
44
-// Data object for a single sign request.
45
-u2f.SignRequest;
43
+  // Data object for a single sign request.
44
+  u2f.Transports;
46 45
 
47
-// Data object for a sign response.
48
-u2f.SignResponse;
46
+  // Data object for a single sign request.
47
+  u2f.SignRequest;
49 48
 
50
-// Data object for a registration request.
51
-u2f.RegisterRequest;
49
+  // Data object for a sign response.
50
+  u2f.SignResponse;
52 51
 
53
-// Data object for a registration response.
54
-u2f.RegisterResponse;
52
+  // Data object for a registration request.
53
+  u2f.RegisterRequest;
55 54
 
55
+  // Data object for a registration response.
56
+  u2f.RegisterResponse;
56 57
 
57
-// Low level MessagePort API support
58
+  // Data object for a registered key.
59
+  u2f.RegisteredKey;
58 60
 
59
-// Sets up a MessagePort to the U2F extension using the available mechanisms.
60
-u2f.getMessagePort = function(callback) {
61
+  // Data object for a get API register response.
62
+  u2f.GetJsApiVersionResponse;
63
+
64
+
65
+  //Low level MessagePort API support
66
+
67
+  // Sets up a MessagePort to the U2F extension using the
68
+  u2f.getMessagePort = function(callback) {
61 69
     if (typeof chrome != 'undefined' && chrome.runtime) {
62
-        // The actual message here does not matter, but we need to get a reply
63
-        // for the callback to run. Thus, send an empty signature request
64
-        // in order to get a failure response.
65
-        var msg = {
66
-            type: u2f.MessageTypes.U2F_SIGN_REQUEST,
67
-            signRequests: []
68
-        };
69
-        chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
70
-            if (!chrome.runtime.lastError) {
71
-                // We are on a whitelisted origin and can talk directly
72
-                // with the extension.
73
-                u2f.getChromeRuntimePort_(callback);
74
-            } else {
75
-                // chrome.runtime was available, but we couldn't message
76
-                // the extension directly, use iframe
77
-                u2f.getIframePort_(callback);
78
-            }
79
-        });
70
+      // The actual message here does not matter, but we need to get a reply
71
+      // for the callback to run. Thus, send an empty signature request
72
+      // in order to get a failure response.
73
+      var msg = {
74
+          type: u2f.MessageTypes.U2F_SIGN_REQUEST,
75
+          signRequests: []
76
+      };
77
+      chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
78
+        if (!chrome.runtime.lastError) {
79
+          // We are on a whitelisted origin and can talk directly
80
+          // with the extension.
81
+          u2f.getChromeRuntimePort_(callback);
82
+        } else {
83
+          // chrome.runtime was available, but we couldn't message
84
+          // the extension directly, use iframe
85
+          u2f.getIframePort_(callback);
86
+        }
87
+      });
80 88
     } else if (u2f.isAndroidChrome_()) {
81
-        u2f.getAuthenticatorPort_(callback);
89
+      u2f.getAuthenticatorPort_(callback);
90
+    } else if (u2f.isIosChrome_()) {
91
+      u2f.getIosPort_(callback);
82 92
     } else {
83
-        // chrome.runtime was not available at all, which is normal
84
-        // when this origin doesn't have access to any extensions.
85
-        u2f.getIframePort_(callback);
93
+      // chrome.runtime was not available at all, which is normal
94
+      // when this origin doesn't have access to any extensions.
95
+      u2f.getIframePort_(callback);
86 96
     }
87
-};
97
+  };
88 98
 
89
-// Detect chrome running on android based on the browser's useragent.
90
-u2f.isAndroidChrome_ = function() {
99
+  // Detect chrome running on android based on the browser's useragent.
100
+  u2f.isAndroidChrome_ = function() {
91 101
     var userAgent = navigator.userAgent;
92 102
     return userAgent.indexOf('Chrome') != -1 &&
93
-        userAgent.indexOf('Android') != -1;
94
-};
103
+    userAgent.indexOf('Android') != -1;
104
+  };
105
+
106
+  // Detect chrome running on iOS based on the browser's platform.
107
+  u2f.isIosChrome_ = function() {
108
+    return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
109
+  };
95 110
 
96
-// Connects directly to the extension via chrome.runtime.connect
97
-u2f.getChromeRuntimePort_ = function(callback) {
111
+  // Connects directly to the extension via chrome.runtime.connect.
112
+  u2f.getChromeRuntimePort_ = function(callback) {
98 113
     var port = chrome.runtime.connect(u2f.EXTENSION_ID,
99 114
         {'includeTlsChannelId': true});
100 115
     setTimeout(function() {
101
-        callback(new u2f.WrappedChromeRuntimePort_(port));
116
+      callback(new u2f.WrappedChromeRuntimePort_(port));
102 117
     }, 0);
103
-};
118
+  };
104 119
 
105
-// Return a 'port' abstraction to the Authenticator app.
106
-u2f.getAuthenticatorPort_ = function(callback) {
120
+  // Return a 'port' abstraction to the Authenticator app.
121
+  u2f.getAuthenticatorPort_ = function(callback) {
107 122
     setTimeout(function() {
108
-        callback(new u2f.WrappedAuthenticatorPort_());
123
+      callback(new u2f.WrappedAuthenticatorPort_());
109 124
     }, 0);
110
-};
125
+  };
111 126
 
112
-// A wrapper for chrome.runtime.Port that is compatible with MessagePort.
113
-u2f.WrappedChromeRuntimePort_ = function(port) {
114
-    this.port_ = port;
115
-};
127
+  // Return a 'port' abstraction to the iOS client app.
128
+  u2f.getIosPort_ = function(callback) {
129
+    setTimeout(function() {
130
+      callback(new u2f.WrappedIosPort_());
131
+    }, 0);
132
+  };
116 133
 
117
-// Format a return a sign request.
118
-u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ =
119
-    function(signRequests, timeoutSeconds, reqId) {
120
-        return {
121
-            type: u2f.MessageTypes.U2F_SIGN_REQUEST,
122
-            signRequests: signRequests,
123
-            timeoutSeconds: timeoutSeconds,
124
-            requestId: reqId
134
+  // A wrapper for chrome.runtime.Port that is compatible with MessagePort.
135
+  u2f.WrappedChromeRuntimePort_ = function(port) {
136
+    this.port_ = port;
137
+  };
138
+
139
+  // Format and return a sign request compliant with the JS API version supported by the extension.
140
+  u2f.formatSignRequest_ =
141
+    function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
142
+    if (js_api_version === undefined || js_api_version < 1.1) {
143
+      // Adapt request to the 1.0 JS API
144
+      var signRequests = [];
145
+      for (var i = 0; i < registeredKeys.length; i++) {
146
+        signRequests[i] = {
147
+            version: registeredKeys[i].version,
148
+            challenge: challenge,
149
+            keyHandle: registeredKeys[i].keyHandle,
150
+            appId: appId
125 151
         };
152
+      }
153
+      return {
154
+        type: u2f.MessageTypes.U2F_SIGN_REQUEST,
155
+        signRequests: signRequests,
156
+        timeoutSeconds: timeoutSeconds,
157
+        requestId: reqId
158
+      };
159
+    }
160
+    // JS 1.1 API
161
+    return {
162
+      type: u2f.MessageTypes.U2F_SIGN_REQUEST,
163
+      appId: appId,
164
+      challenge: challenge,
165
+      registeredKeys: registeredKeys,
166
+      timeoutSeconds: timeoutSeconds,
167
+      requestId: reqId
126 168
     };
127
-
128
-// Format a return a register request.
129
-u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ =
130
-    function(signRequests, registerRequests, timeoutSeconds, reqId) {
131
-        return {
132
-            type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
133
-            signRequests: signRequests,
134
-            registerRequests: registerRequests,
135
-            timeoutSeconds: timeoutSeconds,
136
-            requestId: reqId
169
+  };
170
+
171
+  // Format and return a register request compliant with the JS API version supported by the extension.
172
+  u2f.formatRegisterRequest_ =
173
+    function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
174
+    if (js_api_version === undefined || js_api_version < 1.1) {
175
+      // Adapt request to the 1.0 JS API
176
+      for (var i = 0; i < registerRequests.length; i++) {
177
+        registerRequests[i].appId = appId;
178
+      }
179
+      var signRequests = [];
180
+      for (var i = 0; i < registeredKeys.length; i++) {
181
+        signRequests[i] = {
182
+            version: registeredKeys[i].version,
183
+            challenge: registerRequests[0],
184
+            keyHandle: registeredKeys[i].keyHandle,
185
+            appId: appId
137 186
         };
187
+      }
188
+      return {
189
+        type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
190
+        signRequests: signRequests,
191
+        registerRequests: registerRequests,
192
+        timeoutSeconds: timeoutSeconds,
193
+        requestId: reqId
194
+      };
195
+    }
196
+    // JS 1.1 API
197
+    return {
198
+      type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
199
+      appId: appId,
200
+      registerRequests: registerRequests,
201
+      registeredKeys: registeredKeys,
202
+      timeoutSeconds: timeoutSeconds,
203
+      requestId: reqId
138 204
     };
205
+  };
139 206
 
140
-// Posts a message on the underlying channel.
141
-u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
142
-    this.port_.postMessage(message);
143
-};
144 207
 
145
-// Emulates the HTML 5 addEventListener interface. Works only for the
146
-// onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
147
-u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
148
-    function(eventName, handler) {
149
-        var name = eventName.toLowerCase();
150
-        if (name == 'message' || name == 'onmessage') {
151
-            this.port_.onMessage.addListener(function(message) {
152
-                // Emulate a minimal MessageEvent object
153
-                handler({'data': message});
154
-            });
155
-        } else {
156
-            console.error('WrappedChromeRuntimePort only supports onMessage');
157
-        }
158
-    };
208
+  // Posts a message on the underlying channel.
209
+  u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
210
+    this.port_.postMessage(message);
211
+  };
212
+
213
+
214
+  // Emulates the HTML 5 addEventListener interface. Works only for the onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
215
+  u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
216
+      function(eventName, handler) {
217
+    var name = eventName.toLowerCase();
218
+    if (name == 'message' || name == 'onmessage') {
219
+      this.port_.onMessage.addListener(function(message) {
220
+        // Emulate a minimal MessageEvent object
221
+        handler({'data': message});
222
+      });
223
+    } else {
224
+      console.error('WrappedChromeRuntimePort only supports onMessage');
225
+    }
226
+  };
159 227
 
160
-// Wrap the Authenticator app with a MessagePort interface.
161
-u2f.WrappedAuthenticatorPort_ = function() {
228
+  // Wrap the Authenticator app with a MessagePort interface.
229
+  u2f.WrappedAuthenticatorPort_ = function() {
162 230
     this.requestId_ = -1;
163 231
     this.requestObject_ = null;
164
-}
165
-
166
-// Launch the Authenticator intent.
167
-u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
168
-    var intentLocation = /** @type {string} */ (message);
169
-    document.location = intentLocation;
170
-};
171
-
172
-// Emulates the HTML5 addEventListener interface.
173
-u2f.WrappedAuthenticatorPort_.prototype.addEventListener =
174
-    function(eventName, handler) {
175
-        var name = eventName.toLowerCase();
176
-        if (name == 'message') {
177
-            var self = this;
178
-            /* Register a callback to that executes when
179
-             * chrome injects the response. */
180
-            window.addEventListener(
181
-                'message', self.onRequestUpdate_.bind(self, handler), false);
182
-        } else {
183
-            console.error('WrappedAuthenticatorPort only supports message');
184
-        }
185
-    };
186
-
187
-// Callback invoked  when a response is received from the Authenticator.
188
-u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
189
-    function(callback, message) {
190
-        var messageObject = JSON.parse(message.data);
191
-        var intentUrl = messageObject['intentURL'];
192
-
193
-        var errorCode = messageObject['errorCode'];
194
-        var responseObject = null;
195
-        if (messageObject.hasOwnProperty('data')) {
196
-            responseObject = /** @type {Object} */ (
197
-                JSON.parse(messageObject['data']));
198
-            responseObject['requestId'] = this.requestId_;
199
-        }
200
-
201
-        /* Sign responses from the authenticator do not conform to U2F,
202
-         * convert to U2F here. */
203
-        responseObject = this.doResponseFixups_(responseObject);
204
-        callback({'data': responseObject});
205
-    };
232
+  }
206 233
 
207
-// Fixup the response provided by the Authenticator to conform with the U2F spec.
208
-u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ =
209
-    function(responseObject) {
210
-        if (responseObject.hasOwnProperty('responseData')) {
211
-            return responseObject;
212
-        } else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) {
213
-            // Only sign responses require fixups.  If this is not a response
214
-            // to a sign request, then an internal error has occurred.
215
-            return {
216
-                'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE,
217
-                'responseData': {
218
-                    'errorCode': u2f.ErrorCodes.OTHER_ERROR,
219
-                    'errorMessage': 'Internal error: invalid response from Authenticator'
220
-                }
221
-            };
222
-        }
234
+  // Launch the Authenticator intent.
235
+  u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
236
+    var intentUrl =
237
+      u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
238
+      ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
239
+      ';end';
240
+    document.location = intentUrl;
241
+  };
242
+
243
+  // Tells what type of port this is.
244
+  u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
245
+    return "WrappedAuthenticatorPort_";
246
+  };
247
+
248
+
249
+  // Emulates the HTML 5 addEventListener interface.
250
+  u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
251
+    var name = eventName.toLowerCase();
252
+    if (name == 'message') {
253
+      var self = this;
254
+      // Register a callback to that executes when chrome injects the response.
255
+      window.addEventListener('message', self.onRequestUpdate_.bind(self, handler), false);
256
+    } else {
257
+      console.error('WrappedAuthenticatorPort only supports message');
258
+    }
259
+  };
260
+
261
+  // Callback invoked  when a response is received from the Authenticator.
262
+  u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
263
+      function(callback, message) {
264
+    var messageObject = JSON.parse(message.data);
265
+    var intentUrl = messageObject['intentURL'];
266
+
267
+    var errorCode = messageObject['errorCode'];
268
+    var responseObject = null;
269
+    if (messageObject.hasOwnProperty('data')) {
270
+      responseObject = /** @type {Object} */ (
271
+          JSON.parse(messageObject['data']));
272
+    }
223 273
 
224
-        /* Non-conformant sign response, do fixups. */
225
-        var encodedChallengeObject = responseObject['challenge'];
226
-        if (typeof encodedChallengeObject !== 'undefined') {
227
-            var challengeObject = JSON.parse(atob(encodedChallengeObject));
228
-            var serverChallenge = challengeObject['challenge'];
229
-            var challengesList = this.requestObject_['signData'];
230
-            var requestChallengeObject = null;
231
-            for (var i = 0; i < challengesList.length; i++) {
232
-                var challengeObject = challengesList[i];
233
-                if (challengeObject['keyHandle'] == responseObject['keyHandle']) {
234
-                    requestChallengeObject = challengeObject;
235
-                    break;
236
-                }
237
-            }
238
-        }
239
-        var responseData = {
240
-            'errorCode': responseObject['resultCode'],
241
-            'keyHandle': responseObject['keyHandle'],
242
-            'signatureData': responseObject['signature'],
243
-            'clientData': encodedChallengeObject
244
-        };
245
-        return {
246
-            'type': u2f.MessageTypes.U2F_SIGN_RESPONSE,
247
-            'responseData': responseData,
248
-            'requestId': responseObject['requestId']
249
-        }
250
-    };
274
+    callback({'data': responseObject});
275
+  };
251 276
 
252
-// Base URL for intents to Authenticator.
253
-u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
277
+  // Base URL for intents to Authenticator.
278
+  u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
254 279
     'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
255 280
 
256
-// Format a return a sign request.
257
-u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ =
258
-    function(signRequests, timeoutSeconds, reqId) {
259
-        if (!signRequests || signRequests.length == 0) {
260
-            return null;
261
-        }
262
-        /* TODO(fixme): stash away requestId, as the authenticator app does
263
-         * not return it for sign responses. */
264
-        this.requestId_ = reqId;
265
-        /* TODO(fixme): stash away the signRequests, to deal with the legacy
266
-         * response format returned by the Authenticator app. */
267
-        this.requestObject_ = {
268
-            'type': u2f.MessageTypes.U2F_SIGN_REQUEST,
269
-            'signData': signRequests,
270
-            'requestId': reqId,
271
-            'timeout': timeoutSeconds
272
-        };
273
-
274
-        var appId = signRequests[0]['appId'];
275
-        var intentUrl =
276
-            u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
277
-            ';S.appId=' + encodeURIComponent(appId) +
278
-            ';S.eventId=' + reqId +
279
-            ';S.challenges=' +
280
-            encodeURIComponent(
281
-                JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end';
282
-        return intentUrl;
283
-    };
284
-
285
-// Get the browser data objects from the challenge list
286
-u2f.WrappedAuthenticatorPort_
287
-    .prototype.getBrowserDataList_ = function(challenges) {
288
-    return challenges
289
-        .map(function(challenge) {
290
-            var browserData = {
291
-                'typ': 'navigator.id.getAssertion',
292
-                'challenge': challenge['challenge']
293
-            };
294
-            var challengeObject = {
295
-                'challenge' : browserData,
296
-                'keyHandle' : challenge['keyHandle']
297
-            };
298
-            return challengeObject;
299
-        });
300
-};
301
-
302
-// Format a return a register request.
303
-u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ =
304
-    function(signRequests, enrollChallenges, timeoutSeconds, reqId) {
305
-        if (!enrollChallenges || enrollChallenges.length == 0) {
306
-            return null;
307
-        }
308
-        // Assume the appId is the same for all enroll challenges.
309
-        var appId = enrollChallenges[0]['appId'];
310
-        var registerRequests = [];
311
-        for (var i = 0; i < enrollChallenges.length; i++) {
312
-            var registerRequest = {
313
-                'challenge': enrollChallenges[i]['challenge'],
314
-                'version': enrollChallenges[i]['version']
315
-            };
316
-            if (enrollChallenges[i]['appId'] != appId) {
317
-                // Only include the appId when it differs from the first appId.
318
-                registerRequest['appId'] = enrollChallenges[i]['appId'];
319
-            }
320
-            registerRequests.push(registerRequest);
321
-        }
322
-        var registeredKeys = [];
323
-        if (signRequests) {
324
-            for (i = 0; i < signRequests.length; i++) {
325
-                var key = {
326
-                    'keyHandle': signRequests[i]['keyHandle'],
327
-                    'version': signRequests[i]['version']
328
-                };
329
-                // Only include the appId when it differs from the appId that's
330
-                // being registered now.
331
-                if (signRequests[i]['appId'] != appId) {
332
-                    key['appId'] = signRequests[i]['appId'];
333
-                }
334
-                registeredKeys.push(key);
335
-            }
336
-        }
337
-        var request = {
338
-            'type': u2f.MessageTypes.U2F_REGISTER_REQUEST,
339
-            'appId': appId,
340
-            'registerRequests': registerRequests,
341
-            'registeredKeys': registeredKeys,
342
-            'requestId': reqId,
343
-            'timeoutSeconds': timeoutSeconds
344
-        };
345
-        var intentUrl =
346
-            u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
347
-            ';S.request=' + encodeURIComponent(JSON.stringify(request)) +
348
-            ';end';
349
-        /* TODO(fixme): stash away requestId, this is is not necessary for
350
-         * register requests, but here to keep parity with sign.
351
-         */
352
-        this.requestId_ = reqId;
353
-        return intentUrl;
354
-    };
355
-
281
+  // Wrap the iOS client app with a MessagePort interface.
282
+  u2f.WrappedIosPort_ = function() {};
283
+
284
+  // Launch the iOS client app request
285
+  u2f.WrappedIosPort_.prototype.postMessage = function(message) {
286
+    var str = JSON.stringify(message);
287
+    var url = "u2f://auth?" + encodeURI(str);
288
+    location.replace(url);
289
+  };
290
+
291
+  // Tells what type of port this is.
292
+  u2f.WrappedIosPort_.prototype.getPortType = function() {
293
+    return "WrappedIosPort_";
294
+  };
295
+
296
+  // Emulates the HTML 5 addEventListener interface.
297
+  u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
298
+    var name = eventName.toLowerCase();
299
+    if (name !== 'message') {
300
+      console.error('WrappedIosPort only supports message');
301
+    }
302
+  };
356 303
 
357
-// Sets up an embedded trampoline iframe, sourced from the extension.
358
-u2f.getIframePort_ = function(callback) {
304
+  // Sets up an embedded trampoline iframe, sourced from the extension.
305
+  u2f.getIframePort_ = function(callback) {
359 306
     // Create the iframe
360 307
     var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
361 308
     var iframe = document.createElement('iframe');
@@ -365,108 +312,193 @@ u2f.getIframePort_ = function(callback) {
365 312
 
366 313
     var channel = new MessageChannel();
367 314
     var ready = function(message) {
368
-        if (message.data == 'ready') {
369
-            channel.port1.removeEventListener('message', ready);
370
-            callback(channel.port1);
371
-        } else {
372
-            console.error('First event on iframe port was not "ready"');
373
-        }
315
+      if (message.data == 'ready') {
316
+        channel.port1.removeEventListener('message', ready);
317
+        callback(channel.port1);
318
+      } else {
319
+        console.error('First event on iframe port was not "ready"');
320
+      }
374 321
     };
375 322
     channel.port1.addEventListener('message', ready);
376 323
     channel.port1.start();
377 324
 
378 325
     iframe.addEventListener('load', function() {
379
-        // Deliver the port to the iframe and initialize
380
-        iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
326
+      // Deliver the port to the iframe and initialize
327
+      iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
381 328
     });
382
-};
329
+  };
383 330
 
384 331
 
385
-// High-level JS API
332
+  //High-level JS API
386 333
 
387
-// Default extension response timeout in seconds.
388
-u2f.EXTENSION_TIMEOUT_SEC = 30;
334
+  // Default extension response timeout in seconds.
335
+  u2f.EXTENSION_TIMEOUT_SEC = 30;
389 336
 
390
-// A singleton instance for a MessagePort to the extension.
391
-u2f.port_ = null;
337
+  // A singleton instance for a MessagePort to the extension.
338
+  u2f.port_ = null;
392 339
 
393
-// Callbacks waiting for a port
394
-u2f.waitingForPort_ = [];
340
+  // Callbacks waiting for a port
341
+  u2f.waitingForPort_ = [];
395 342
 
396
-// A counter for requestIds.
397
-u2f.reqCounter_ = 0;
343
+  // A counter for requestIds.
344
+  u2f.reqCounter_ = 0;
398 345
 
399
-// A map from requestIds to client callbacks
400
-u2f.callbackMap_ = {};
346
+  // A map from requestIds to client callbacks
347
+  u2f.callbackMap_ = {};
401 348
 
402
-// Creates or retrieves the MessagePort singleton to use.
403
-u2f.getPortSingleton_ = function(callback) {
349
+  // Creates or retrieves the MessagePort singleton to use.
350
+  u2f.getPortSingleton_ = function(callback) {
404 351
     if (u2f.port_) {
405
-        callback(u2f.port_);
352
+      callback(u2f.port_);
406 353
     } else {
407
-        if (u2f.waitingForPort_.length == 0) {
408
-            u2f.getMessagePort(function(port) {
409
-                u2f.port_ = port;
410
-                u2f.port_.addEventListener('message',
411
-                    /** @type {function(Event)} */ (u2f.responseHandler_));
412
-
413
-                // Careful, here be async callbacks. Maybe.
414
-                while (u2f.waitingForPort_.length)
415
-                    u2f.waitingForPort_.shift()(u2f.port_);
416
-            });
417
-        }
418
-        u2f.waitingForPort_.push(callback);
354
+      if (u2f.waitingForPort_.length == 0) {
355
+        u2f.getMessagePort(function(port) {
356
+          u2f.port_ = port;
357
+          u2f.port_.addEventListener('message', (u2f.responseHandler_));
358
+          // Careful, here be async callbacks. Maybe.
359
+          while (u2f.waitingForPort_.length)
360
+            u2f.waitingForPort_.shift()(u2f.port_);
361
+        });
362
+      }
363
+      u2f.waitingForPort_.push(callback);
419 364
     }
420
-};
365
+  };
421 366
 
422
-// Handles response messages from the extension.
423
-u2f.responseHandler_ = function(message) {
367
+  // Handles response messages from the extension.
368
+  u2f.responseHandler_ = function(message) {
424 369
     var response = message.data;
425 370
     var reqId = response['requestId'];
426 371
     if (!reqId || !u2f.callbackMap_[reqId]) {
427
-        console.error('Unknown or missing requestId in response.');
428
-        return;
372
+      console.error('Unknown or missing requestId in response.');
373
+      return;
429 374
     }
430 375
     var cb = u2f.callbackMap_[reqId];
431 376
     delete u2f.callbackMap_[reqId];
432 377
     cb(response['responseData']);
433
-};
378
+  };
379
+
380
+  // Dispatches an array of sign requests to available U2F tokens.
381
+  // If the JS API version supported by the extension is unknown, it first sends a
382
+  // message to the extension to find out the supported API version and then it sends
383
+  // the sign request.
384
+  u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
385
+    if (js_api_version === undefined) {
386
+      // Send a message to get the extension to JS API version, then send the actual sign request.
387
+      u2f.getApiVersion(
388
+          function (response) {
389
+            js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
390
+            console.log("Extension JS API Version: ", js_api_version);
391
+            u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
392
+          });
393
+    } else {
394
+      // We know the JS API version. Send the actual sign request in the supported API version.
395
+      u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
396
+    }
397
+  };
434 398
 
435
-// Dispatches an array of sign requests to available U2F tokens.
436
-u2f.sign = function(signRequests, callback, opt_timeoutSeconds) {
399
+  // Dispatches an array of sign requests to available U2F tokens.
400
+  u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
437 401
     u2f.getPortSingleton_(function(port) {
438
-        var reqId = ++u2f.reqCounter_;
439
-        u2f.callbackMap_[reqId] = callback;
440
-        var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
441
-            opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
442
-        var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId);
443
-        port.postMessage(req);
402
+      var reqId = ++u2f.reqCounter_;
403
+      u2f.callbackMap_[reqId] = callback;
404
+      var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
405
+          opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
406
+      var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
407
+      port.postMessage(req);
444 408
     });
445
-};
409
+  };
410
+
411
+  // Dispatches register requests to available U2F tokens. An array of sign
412
+  // requests identifies already registered tokens.
413
+  // If the JS API version supported by the extension is unknown, it first sends a
414
+  // message to the extension to find out the supported API version and then it sends
415
+  // the register request.
416
+  u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
417
+    if (js_api_version === undefined) {
418
+      // Send a message to get the extension to JS API version, then send the actual register request.
419
+      u2f.getApiVersion(
420
+          function (response) {
421
+            js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
422
+            console.log("Extension JS API Version: ", js_api_version);
423
+            u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
424
+                callback, opt_timeoutSeconds);
425
+          });
426
+    } else {
427
+      // We know the JS API version. Send the actual register request in the supported API version.
428
+      u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
429
+          callback, opt_timeoutSeconds);
430
+    }
431
+  };
446 432
 
447
-// Dispatches register requests to available U2F tokens. An array of sign
448
-// requests identifies already registered tokens.
449
-u2f.register = function(registerRequests, signRequests,
450
-                        callback, opt_timeoutSeconds) {
433
+  // Dispatches register requests to available U2F tokens. An array of sign
434
+  // requests identifies already registered tokens.
435
+  u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
451 436
     u2f.getPortSingleton_(function(port) {
452
-        var reqId = ++u2f.reqCounter_;
453
-        u2f.callbackMap_[reqId] = callback;
454
-        var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
455
-            opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
456
-        var req = port.formatRegisterRequest_(
457
-            signRequests, registerRequests, timeoutSeconds, reqId);
458
-        port.postMessage(req);
437
+      var reqId = ++u2f.reqCounter_;
438
+      u2f.callbackMap_[reqId] = callback;
439
+      var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
440
+          opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
441
+      var req = u2f.formatRegisterRequest_(
442
+          appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
443
+      port.postMessage(req);
459 444
     });
445
+  };
446
+
447
+
448
+  // Dispatches a message to the extension to find out the supported JS API version.
449
+  // If the user is on a mobile phone and is thus using Google Authenticator instead
450
+  // of the Chrome extension, don't send the request and simply return 0.
451
+  u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
452
+   u2f.getPortSingleton_(function(port) {
453
+     // If we are using Android Google Authenticator or iOS client app,
454
+     // do not fire an intent to ask which JS API version to use.
455
+     if (port.getPortType) {
456
+       var apiVersion;
457
+       switch (port.getPortType()) {
458
+         case 'WrappedIosPort_':
459
+         case 'WrappedAuthenticatorPort_':
460
+           apiVersion = 1.1;
461
+           break;
462
+
463
+         default:
464
+           apiVersion = 0;
465
+           break;
466
+       }
467
+       callback({ 'js_api_version': apiVersion });
468
+       return;
469
+     }
470
+      var reqId = ++u2f.reqCounter_;
471
+      u2f.callbackMap_[reqId] = callback;
472
+      var req = {
473
+        type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
474
+        timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
475
+            opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
476
+        requestId: reqId
477
+      };
478
+      port.postMessage(req);
479
+    });
480
+  };
481
+}
482
+
483
+// Response status codes
484
+u2f.ErrorCodes = {
485
+    'OK': 0,
486
+    'OTHER_ERROR': 1,
487
+    'BAD_REQUEST': 2,
488
+    'CONFIGURATION_UNSUPPORTED': 3,
489
+    'DEVICE_INELIGIBLE': 4,
490
+    'TIMEOUT': 5
460 491
 };
461 492
 
493
+
462 494
 // Everything below is Gazelle-specific and not part of the u2f API
463 495
 
464
-function initU2F() {
496
+$(function() {
465 497
   if (document.querySelector('#u2f_register_form') && document.querySelector('[name="u2f-request"]')) {
466 498
     var req = JSON.parse($('[name="u2f-request"]').raw().value);
467 499
     var sigs = JSON.parse($('[name="u2f-sigs"]').raw().value);
468 500
     if (req) {
469
-      u2f.register([req], sigs, function(data) {
501
+      u2f.register(req.appID, [req], sigs, function(data) {
470 502
         if (data.errorCode) {
471 503
           console.log('U2F Register Error: ' + Object.keys(u2f.ErrorCodes).find(k => u2f.ErrorCodes[k] == data.errorCode));
472 504
           return;
@@ -479,9 +511,9 @@ function initU2F() {
479 511
   }
480 512
 
481 513
   if (document.querySelector('#u2f_sign_form') && document.querySelector('[name="u2f-request"]')) {
482
-    var req = JSON.parse($('[name="u2f-request"]').raw().value);
514
+    var req = JSON.parse($('[name="u2f-request"]').raw().value)[0];
483 515
     if (req) {
484
-      u2f.sign(req, function(data) {
516
+      u2f.sign(req.appId, req.challenge, [{version:req.version, keyHandle:req.keyHandle}], function(data) {
485 517
         if (data.errorCode) {
486 518
           console.log('U2F Sign Error: ' + Object.keys(u2f.ErrorCodes).find(k => u2f.ErrorCodes[k] == data.errorCode));
487 519
           return;
@@ -491,10 +523,4 @@ function initU2F() {
491 523
       }, 3600)
492 524
     }
493 525
   }
494
-}
495
-
496
-if (document.readyState != 'loading') {
497
-  initU2F();
498
-} else {
499
-  document.addEventListener("DOMContentLoaded", initU2F);
500
-}
526
+})

Loading…
Cancel
Save