Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(288)

Side by Side Diff: chrome/browser/resources/cryptotoken/webrequest.js

Issue 249913002: FIDO U2F component extension (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge with HEAD Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview Does common handling for requests coming from web pages and
7 * routes them to the provided handler.
8 */
9
10 /**
11 * Gets the scheme + origin from a web url.
12 * @param {string} url
13 * @return {?string}
14 */
15 function getOriginFromUrl(url) {
16 var re = new RegExp('^(https?://)[^/]*/?');
17 var originarray = re.exec(url);
18 if (originarray == null) return originarray;
19 var origin = originarray[0];
20 while (origin.charAt(origin.length - 1) == '/') {
21 origin = origin.substring(0, origin.length - 1);
22 }
23 if (origin == 'http:' || origin == 'https:')
24 return null;
25 return origin;
26 }
27
28 /**
29 * Parses the text as JSON and returns it as an array of strings.
30 * @param {string} text
31 * @return {Array.<string>}
32 */
33 function getOriginsFromJson(text) {
34 try {
35 var urls = JSON.parse(text);
36 var origins = [];
37 for (var i = 0, url; url = urls[i]; i++) {
38 var origin = getOriginFromUrl(url);
39 if (origin)
40 origins.push(origin);
41 }
42 return origins;
43 } catch (e) {
44 console.log(UTIL_fmt('could not parse ' + text));
45 return [];
46 }
47 }
48
49 /**
50 * Fetches the app id, and calls a callback with list of allowed origins for it.
51 * @param {string} appId the app id to fetch.
52 * @param {Function} cb called with a list of allowed origins for the app id.
53 */
54 function fetchAppId(appId, cb) {
55 var origin = getOriginFromUrl(appId);
56 if (!origin) {
57 cb(404, appId);
58 return;
59 }
60 var xhr = new XMLHttpRequest();
61 var origins = [];
62 xhr.open('GET', appId, true);
63 xhr.onloadend = function() {
64 if (xhr.status != 200) {
65 cb(xhr.status, appId);
66 return;
67 }
68 cb(xhr.status, appId, getOriginsFromJson(xhr.responseText));
69 };
70 xhr.send();
71 }
72
73 /**
74 * Retrieves a set of distinct app ids from the SignData.
75 * @param {SignData=} signData
76 * @return {Array.<string>} array of distinct app ids.
77 */
78 function getDistinctAppIds(signData) {
79 var appIds = [];
80 if (!signData) {
81 return appIds;
82 }
83 for (var i = 0, request; request = signData[i]; i++) {
84 var appId = request['appId'];
85 if (appId && appIds.indexOf(appId) == -1) {
86 appIds.push(appId);
87 }
88 }
89 return appIds;
90 }
91
92 /**
93 * Reorganizes the requests from the SignData to an array of
94 * (appId, [Request]) tuples.
95 * @param {SignData} signData
96 * @return {Array.<[string, Array.<Request>]>} array of
97 * (appId, [Request]) tuples.
98 */
99 function requestsByAppId(signData) {
100 var requests = {};
101 var appIdOrder = {};
102 var orderToAppId = {};
103 var lastOrder = 0;
104 for (var i = 0, request; request = signData[i]; i++) {
105 var appId = request['appId'];
106 if (appId) {
107 if (!appIdOrder.hasOwnProperty(appId)) {
108 appIdOrder[appId] = lastOrder;
109 orderToAppId[lastOrder] = appId;
110 lastOrder++;
111 }
112 if (requests[appId]) {
113 requests[appId].push(request);
114 } else {
115 requests[appId] = [request];
116 }
117 }
118 }
119 var orderedRequests = [];
120 for (var order = 0; order < lastOrder; order++) {
121 appId = orderToAppId[order];
122 orderedRequests.push([appId, requests[appId]]);
123 }
124 return orderedRequests;
125 }
126
127 /**
128 * Fetches the allowed origins for an appId.
129 * @param {string} appId
130 * @param {boolean} allowHttp Whether http is a valid scheme for an appId.
131 * (This should be false except on test domains.)
132 * @param {function(number, !Array.<string>)} cb Called back with an HTTP
133 * response code and a list of allowed origins for appId.
134 */
135 function fetchAllowedOriginsForAppId(appId, allowHttp, cb) {
136 var allowedOrigins = [];
137 if (!appId) {
138 cb(200, allowedOrigins);
139 return;
140 }
141 if (appId.indexOf('http://') == 0 && !allowHttp) {
142 console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
143 cb(200, allowedOrigins);
144 return;
145 }
146 // TODO(juanlang): hack for old enrolled gnubbies, don't treat
147 // accounts.google.com/login.corp.google.com specially when cryptauth server
148 // stops reporting them as appId.
149 if (appId == 'https://accounts.google.com') {
150 allowedOrigins = ['https://login.corp.google.com'];
151 cb(200, allowedOrigins);
152 return;
153 }
154 if (appId == 'https://login.corp.google.com') {
155 allowedOrigins = ['https://accounts.google.com'];
156 cb(200, allowedOrigins);
157 return;
158 }
159 // Termination of this function relies in fetchAppId completing.
160 // (Not completing would be a bug in XMLHttpRequest.)
161 // TODO(juanlang): provide a termination guarantee, e.g. with a timer?
162 fetchAppId(appId, function(rc, fetchedAppId, origins) {
163 if (rc != 200) {
164 console.log(UTIL_fmt('fetching ' + fetchedAppId + ' failed: ' + rc));
165 allowedOrigins = [];
166 } else {
167 allowedOrigins = origins;
168 }
169 cb(rc, allowedOrigins);
170 });
171 }
172
173 /**
174 * Checks whether an appId is valid for a given origin.
175 * @param {!string} appId
176 * @param {!string} origin
177 * @param {!Array.<string>} allowedOrigins the list of allowed origins for each
178 * appId.
179 * @return {boolean} whether the appId is allowed for the origin.
180 */
181 function isValidAppIdForOrigin(appId, origin, allowedOrigins) {
182 if (!appId)
183 return false;
184 if (appId == origin) {
185 // trivially allowed
186 return true;
187 }
188 if (!allowedOrigins)
189 return false;
190 return allowedOrigins.indexOf(origin) >= 0;
191 }
192
193 /**
194 * Returns whether the signData object appears to be valid.
195 * @param {Array.<Object>} signData the signData object.
196 * @return {boolean} whether the object appears valid.
197 */
198 function isValidSignData(signData) {
199 for (var i = 0; i < signData.length; i++) {
200 var incomingChallenge = signData[i];
201 if (!incomingChallenge.hasOwnProperty('challenge'))
202 return false;
203 if (!incomingChallenge.hasOwnProperty('appId')) {
204 return false;
205 }
206 if (!incomingChallenge.hasOwnProperty('keyHandle'))
207 return false;
208 if (incomingChallenge['version']) {
209 if (incomingChallenge['version'] != 'U2F_V1' &&
210 incomingChallenge['version'] != 'U2F_V2') {
211 return false;
212 }
213 }
214 }
215 return true;
216 }
217
218 /** Posts the log message to the log url.
219 * @param {string} logMsg the log message to post.
220 * @param {string=} opt_logMsgUrl the url to post log messages to.
221 */
222 function logMessage(logMsg, opt_logMsgUrl) {
223 console.log(UTIL_fmt('logMessage("' + logMsg + '")'));
224
225 if (!opt_logMsgUrl) {
226 return;
227 }
228 // Image fetching is not allowed per packaged app CSP.
229 // But video and audio is.
230 var audio = new Audio();
231 audio.src = opt_logMsgUrl + logMsg;
232 }
233
234 /**
235 * Logs the result of fetching an appId.
236 * @param {!string} appId
237 * @param {number} millis elapsed time while fetching the appId.
238 * @param {Array.<string>} allowedOrigins the allowed origins retrieved.
239 * @param {string=} opt_logMsgUrl
240 */
241 function logFetchAppIdResult(appId, millis, allowedOrigins, opt_logMsgUrl) {
242 var logMsg = 'log=fetchappid&appid=' + appId + '&millis=' + millis +
243 '&numorigins=' + allowedOrigins.length;
244 logMessage(logMsg, opt_logMsgUrl);
245 }
246
247 /**
248 * Logs a mismatch between an origin and an appId.
249 * @param {string} origin
250 * @param {!string} appId
251 * @param {string=} opt_logMsgUrl
252 */
253 function logInvalidOriginForAppId(origin, appId, opt_logMsgUrl) {
254 var logMsg = 'log=originrejected&origin=' + origin + '&appid=' + appId;
255 logMessage(logMsg, opt_logMsgUrl);
256 }
257
258 /**
259 * Formats response parameters as an object.
260 * @param {string} type type of the post message.
261 * @param {number} code status code of the operation.
262 * @param {Object=} responseData the response data of the operation.
263 * @return {Object} formatted response.
264 */
265 function formatWebPageResponse(type, code, responseData) {
266 var responseJsonObject = {};
267 responseJsonObject['type'] = type;
268 responseJsonObject['code'] = code;
269 if (responseData)
270 responseJsonObject['responseData'] = responseData;
271 return responseJsonObject;
272 }
273
274 /**
275 * @param {!string} string
276 * @return {Array.<number>} SHA256 hash value of string.
277 */
278 function sha256HashOfString(string) {
279 var s = new SHA256();
280 s.update(UTIL_StringToBytes(string));
281 return s.digest();
282 }
283
284 /**
285 * Normalizes the TLS channel ID value:
286 * 1. Converts semantically empty values (undefined, null, 0) to the empty
287 * string.
288 * 2. Converts valid JSON strings to a JS object.
289 * 3. Otherwise, returns the input value unmodified.
290 * @param {Object|string|undefined} opt_tlsChannelId
291 * @return {Object|string} The normalized TLS channel ID value.
292 */
293 function tlsChannelIdValue(opt_tlsChannelId) {
294 if (!opt_tlsChannelId) {
295 // Case 1: Always set some value for TLS channel ID, even if it's the empty
296 // string: this browser definitely supports them.
297 return '';
298 }
299 if (typeof opt_tlsChannelId === 'string') {
300 try {
301 var obj = JSON.parse(opt_tlsChannelId);
302 if (!obj) {
303 // Case 1: The string value 'null' parses as the Javascript object null,
304 // so return an empty string: the browser definitely supports TLS
305 // channel id.
306 return '';
307 }
308 // Case 2: return the value as a JS object.
309 return /** @type {Object} */ (obj);
310 } catch (e) {
311 console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId);
312 // Case 3: return the value unmodified.
313 }
314 }
315 return opt_tlsChannelId;
316 }
317
318 /**
319 * Creates a browser data object with the given values.
320 * @param {!string} type A string representing the "type" of this browser data
321 * object.
322 * @param {!string} serverChallenge The server's challenge, as a base64-
323 * encoded string.
324 * @param {!string} origin The server's origin, as seen by the browser.
325 * @param {Object|string|undefined} opt_tlsChannelId
326 * @return {string} A string representation of the browser data object.
327 */
328 function makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) {
329 var browserData = {
330 'typ' : type,
331 'challenge' : serverChallenge,
332 'origin' : origin
333 };
334 browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId);
335 return JSON.stringify(browserData);
336 }
337
338 /**
339 * Creates a browser data object for an enroll request with the given values.
340 * @param {!string} serverChallenge The server's challenge, as a base64-
341 * encoded string.
342 * @param {!string} origin The server's origin, as seen by the browser.
343 * @param {Object|string|undefined} opt_tlsChannelId
344 * @return {string} A string representation of the browser data object.
345 */
346 function makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) {
347 return makeBrowserData(
348 'navigator.id.finishEnrollment', serverChallenge, origin,
349 opt_tlsChannelId);
350 }
351
352 /**
353 * Creates a browser data object for a sign request with the given values.
354 * @param {!string} serverChallenge The server's challenge, as a base64-
355 * encoded string.
356 * @param {!string} origin The server's origin, as seen by the browser.
357 * @param {Object|string|undefined} opt_tlsChannelId
358 * @return {string} A string representation of the browser data object.
359 */
360 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
361 return makeBrowserData(
362 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId);
363 }
364
365 /**
366 * @param {string} browserData
367 * @param {string} appId
368 * @param {string} encodedKeyHandle
369 * @param {string=} version
370 * @return {SignHelperChallenge}
371 */
372 function makeChallenge(browserData, appId, encodedKeyHandle, version) {
373 var appIdHash = B64_encode(sha256HashOfString(appId));
374 var browserDataHash = B64_encode(sha256HashOfString(browserData));
375 var keyHandle = encodedKeyHandle;
376
377 var challenge = {
378 'challengeHash': browserDataHash,
379 'appIdHash': appIdHash,
380 'keyHandle': keyHandle
381 };
382 // Version is implicitly U2F_V1 if not specified.
383 challenge['version'] = (version || 'U2F_V1');
384 return challenge;
385 }
OLDNEW
« no previous file with comments | « chrome/browser/resources/cryptotoken/util.js ('k') | chrome/common/extensions/api/_permission_features.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698