| 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;
|
| +}
|
|
|