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

Unified 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, 8 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/cryptotoken/webrequest.js
diff --git a/chrome/browser/resources/cryptotoken/webrequest.js b/chrome/browser/resources/cryptotoken/webrequest.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dbbeaaeb49d230e2fee60a544e9f24b2c1f97a1
--- /dev/null
+++ b/chrome/browser/resources/cryptotoken/webrequest.js
@@ -0,0 +1,385 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Does common handling for requests coming from web pages and
+ * routes them to the provided handler.
+ */
+
+/**
+ * Gets the scheme + origin from a web url.
+ * @param {string} url
+ * @return {?string}
+ */
+function getOriginFromUrl(url) {
+ var re = new RegExp('^(https?://)[^/]*/?');
+ var originarray = re.exec(url);
+ if (originarray == null) return originarray;
+ var origin = originarray[0];
+ while (origin.charAt(origin.length - 1) == '/') {
+ origin = origin.substring(0, origin.length - 1);
+ }
+ if (origin == 'http:' || origin == 'https:')
+ return null;
+ return origin;
+}
+
+/**
+ * Parses the text as JSON and returns it as an array of strings.
+ * @param {string} text
+ * @return {Array.<string>}
+ */
+function getOriginsFromJson(text) {
+ try {
+ var urls = JSON.parse(text);
+ var origins = [];
+ for (var i = 0, url; url = urls[i]; i++) {
+ var origin = getOriginFromUrl(url);
+ if (origin)
+ origins.push(origin);
+ }
+ return origins;
+ } catch (e) {
+ console.log(UTIL_fmt('could not parse ' + text));
+ return [];
+ }
+}
+
+/**
+ * Fetches the app id, and calls a callback with list of allowed origins for it.
+ * @param {string} appId the app id to fetch.
+ * @param {Function} cb called with a list of allowed origins for the app id.
+ */
+function fetchAppId(appId, cb) {
+ var origin = getOriginFromUrl(appId);
+ if (!origin) {
+ cb(404, appId);
+ return;
+ }
+ var xhr = new XMLHttpRequest();
+ var origins = [];
+ xhr.open('GET', appId, true);
+ xhr.onloadend = function() {
+ if (xhr.status != 200) {
+ cb(xhr.status, appId);
+ return;
+ }
+ cb(xhr.status, appId, getOriginsFromJson(xhr.responseText));
+ };
+ xhr.send();
+}
+
+/**
+ * Retrieves a set of distinct app ids from the SignData.
+ * @param {SignData=} signData
+ * @return {Array.<string>} array of distinct app ids.
+ */
+function getDistinctAppIds(signData) {
+ var appIds = [];
+ if (!signData) {
+ return appIds;
+ }
+ for (var i = 0, request; request = signData[i]; i++) {
+ var appId = request['appId'];
+ if (appId && appIds.indexOf(appId) == -1) {
+ appIds.push(appId);
+ }
+ }
+ return appIds;
+}
+
+/**
+ * Reorganizes the requests from the SignData to an array of
+ * (appId, [Request]) tuples.
+ * @param {SignData} signData
+ * @return {Array.<[string, Array.<Request>]>} array of
+ * (appId, [Request]) tuples.
+ */
+function requestsByAppId(signData) {
+ var requests = {};
+ var appIdOrder = {};
+ var orderToAppId = {};
+ var lastOrder = 0;
+ for (var i = 0, request; request = signData[i]; i++) {
+ var appId = request['appId'];
+ if (appId) {
+ if (!appIdOrder.hasOwnProperty(appId)) {
+ appIdOrder[appId] = lastOrder;
+ orderToAppId[lastOrder] = appId;
+ lastOrder++;
+ }
+ if (requests[appId]) {
+ requests[appId].push(request);
+ } else {
+ requests[appId] = [request];
+ }
+ }
+ }
+ var orderedRequests = [];
+ for (var order = 0; order < lastOrder; order++) {
+ appId = orderToAppId[order];
+ orderedRequests.push([appId, requests[appId]]);
+ }
+ return orderedRequests;
+}
+
+/**
+ * Fetches the allowed origins for an appId.
+ * @param {string} appId
+ * @param {boolean} allowHttp Whether http is a valid scheme for an appId.
+ * (This should be false except on test domains.)
+ * @param {function(number, !Array.<string>)} cb Called back with an HTTP
+ * response code and a list of allowed origins for appId.
+ */
+function fetchAllowedOriginsForAppId(appId, allowHttp, cb) {
+ var allowedOrigins = [];
+ if (!appId) {
+ cb(200, allowedOrigins);
+ return;
+ }
+ if (appId.indexOf('http://') == 0 && !allowHttp) {
+ console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
+ cb(200, allowedOrigins);
+ return;
+ }
+ // TODO(juanlang): hack for old enrolled gnubbies, don't treat
+ // accounts.google.com/login.corp.google.com specially when cryptauth server
+ // stops reporting them as appId.
+ if (appId == 'https://accounts.google.com') {
+ allowedOrigins = ['https://login.corp.google.com'];
+ cb(200, allowedOrigins);
+ return;
+ }
+ if (appId == 'https://login.corp.google.com') {
+ allowedOrigins = ['https://accounts.google.com'];
+ cb(200, allowedOrigins);
+ return;
+ }
+ // Termination of this function relies in fetchAppId completing.
+ // (Not completing would be a bug in XMLHttpRequest.)
+ // TODO(juanlang): provide a termination guarantee, e.g. with a timer?
+ fetchAppId(appId, function(rc, fetchedAppId, origins) {
+ if (rc != 200) {
+ console.log(UTIL_fmt('fetching ' + fetchedAppId + ' failed: ' + rc));
+ allowedOrigins = [];
+ } else {
+ allowedOrigins = origins;
+ }
+ cb(rc, allowedOrigins);
+ });
+}
+
+/**
+ * Checks whether an appId is valid for a given origin.
+ * @param {!string} appId
+ * @param {!string} origin
+ * @param {!Array.<string>} allowedOrigins the list of allowed origins for each
+ * appId.
+ * @return {boolean} whether the appId is allowed for the origin.
+ */
+function isValidAppIdForOrigin(appId, origin, allowedOrigins) {
+ if (!appId)
+ return false;
+ if (appId == origin) {
+ // trivially allowed
+ return true;
+ }
+ if (!allowedOrigins)
+ return false;
+ return allowedOrigins.indexOf(origin) >= 0;
+}
+
+/**
+ * Returns whether the signData object appears to be valid.
+ * @param {Array.<Object>} signData the signData object.
+ * @return {boolean} whether the object appears valid.
+ */
+function isValidSignData(signData) {
+ for (var i = 0; i < signData.length; i++) {
+ var incomingChallenge = signData[i];
+ if (!incomingChallenge.hasOwnProperty('challenge'))
+ return false;
+ if (!incomingChallenge.hasOwnProperty('appId')) {
+ return false;
+ }
+ if (!incomingChallenge.hasOwnProperty('keyHandle'))
+ return false;
+ if (incomingChallenge['version']) {
+ if (incomingChallenge['version'] != 'U2F_V1' &&
+ incomingChallenge['version'] != 'U2F_V2') {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/** Posts the log message to the log url.
+ * @param {string} logMsg the log message to post.
+ * @param {string=} opt_logMsgUrl the url to post log messages to.
+ */
+function logMessage(logMsg, opt_logMsgUrl) {
+ console.log(UTIL_fmt('logMessage("' + logMsg + '")'));
+
+ if (!opt_logMsgUrl) {
+ return;
+ }
+ // Image fetching is not allowed per packaged app CSP.
+ // But video and audio is.
+ var audio = new Audio();
+ audio.src = opt_logMsgUrl + logMsg;
+}
+
+/**
+ * Logs the result of fetching an appId.
+ * @param {!string} appId
+ * @param {number} millis elapsed time while fetching the appId.
+ * @param {Array.<string>} allowedOrigins the allowed origins retrieved.
+ * @param {string=} opt_logMsgUrl
+ */
+function logFetchAppIdResult(appId, millis, allowedOrigins, opt_logMsgUrl) {
+ var logMsg = 'log=fetchappid&appid=' + appId + '&millis=' + millis +
+ '&numorigins=' + allowedOrigins.length;
+ logMessage(logMsg, opt_logMsgUrl);
+}
+
+/**
+ * Logs a mismatch between an origin and an appId.
+ * @param {string} origin
+ * @param {!string} appId
+ * @param {string=} opt_logMsgUrl
+ */
+function logInvalidOriginForAppId(origin, appId, opt_logMsgUrl) {
+ var logMsg = 'log=originrejected&origin=' + origin + '&appid=' + appId;
+ logMessage(logMsg, opt_logMsgUrl);
+}
+
+/**
+ * Formats response parameters as an object.
+ * @param {string} type type of the post message.
+ * @param {number} code status code of the operation.
+ * @param {Object=} responseData the response data of the operation.
+ * @return {Object} formatted response.
+ */
+function formatWebPageResponse(type, code, responseData) {
+ var responseJsonObject = {};
+ responseJsonObject['type'] = type;
+ responseJsonObject['code'] = code;
+ if (responseData)
+ responseJsonObject['responseData'] = responseData;
+ return responseJsonObject;
+}
+
+/**
+ * @param {!string} string
+ * @return {Array.<number>} SHA256 hash value of string.
+ */
+function sha256HashOfString(string) {
+ var s = new SHA256();
+ s.update(UTIL_StringToBytes(string));
+ return s.digest();
+}
+
+/**
+ * Normalizes the TLS channel ID value:
+ * 1. Converts semantically empty values (undefined, null, 0) to the empty
+ * string.
+ * 2. Converts valid JSON strings to a JS object.
+ * 3. Otherwise, returns the input value unmodified.
+ * @param {Object|string|undefined} opt_tlsChannelId
+ * @return {Object|string} The normalized TLS channel ID value.
+ */
+function tlsChannelIdValue(opt_tlsChannelId) {
+ if (!opt_tlsChannelId) {
+ // Case 1: Always set some value for TLS channel ID, even if it's the empty
+ // string: this browser definitely supports them.
+ return '';
+ }
+ if (typeof opt_tlsChannelId === 'string') {
+ try {
+ var obj = JSON.parse(opt_tlsChannelId);
+ if (!obj) {
+ // Case 1: The string value 'null' parses as the Javascript object null,
+ // so return an empty string: the browser definitely supports TLS
+ // channel id.
+ return '';
+ }
+ // Case 2: return the value as a JS object.
+ return /** @type {Object} */ (obj);
+ } catch (e) {
+ console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId);
+ // Case 3: return the value unmodified.
+ }
+ }
+ return opt_tlsChannelId;
+}
+
+/**
+ * Creates a browser data object with the given values.
+ * @param {!string} type A string representing the "type" of this browser data
+ * object.
+ * @param {!string} serverChallenge The server's challenge, as a base64-
+ * encoded string.
+ * @param {!string} origin The server's origin, as seen by the browser.
+ * @param {Object|string|undefined} opt_tlsChannelId
+ * @return {string} A string representation of the browser data object.
+ */
+function makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) {
+ var browserData = {
+ 'typ' : type,
+ 'challenge' : serverChallenge,
+ 'origin' : origin
+ };
+ browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId);
+ return JSON.stringify(browserData);
+}
+
+/**
+ * Creates a browser data object for an enroll request with the given values.
+ * @param {!string} serverChallenge The server's challenge, as a base64-
+ * encoded string.
+ * @param {!string} origin The server's origin, as seen by the browser.
+ * @param {Object|string|undefined} opt_tlsChannelId
+ * @return {string} A string representation of the browser data object.
+ */
+function makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) {
+ return makeBrowserData(
+ 'navigator.id.finishEnrollment', serverChallenge, origin,
+ opt_tlsChannelId);
+}
+
+/**
+ * Creates a browser data object for a sign request with the given values.
+ * @param {!string} serverChallenge The server's challenge, as a base64-
+ * encoded string.
+ * @param {!string} origin The server's origin, as seen by the browser.
+ * @param {Object|string|undefined} opt_tlsChannelId
+ * @return {string} A string representation of the browser data object.
+ */
+function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
+ return makeBrowserData(
+ 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId);
+}
+
+/**
+ * @param {string} browserData
+ * @param {string} appId
+ * @param {string} encodedKeyHandle
+ * @param {string=} version
+ * @return {SignHelperChallenge}
+ */
+function makeChallenge(browserData, appId, encodedKeyHandle, version) {
+ var appIdHash = B64_encode(sha256HashOfString(appId));
+ var browserDataHash = B64_encode(sha256HashOfString(browserData));
+ var keyHandle = encodedKeyHandle;
+
+ var challenge = {
+ 'challengeHash': browserDataHash,
+ 'appIdHash': appIdHash,
+ 'keyHandle': keyHandle
+ };
+ // Version is implicitly U2F_V1 if not specified.
+ challenge['version'] = (version || 'U2F_V1');
+ return challenge;
+}
« 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