webrequest.js revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved. 25c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Use of this source code is governed by a BSD-style license that can be 35c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// found in the LICENSE file. 45c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 55c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 65c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @fileoverview Does common handling for requests coming from web pages and 75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * routes them to the provided handler. 85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Gets the scheme + origin from a web url. 125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} url 135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {?string} 145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction getOriginFromUrl(url) { 165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var re = new RegExp('^(https?://)[^/]*/?'); 175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var originarray = re.exec(url); 185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (originarray == null) return originarray; 195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var origin = originarray[0]; 205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu while (origin.charAt(origin.length - 1) == '/') { 215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu origin = origin.substring(0, origin.length - 1); 225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (origin == 'http:' || origin == 'https:') 245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return null; 255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return origin; 265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Parses the text as JSON and returns it as an array of strings. 305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} text 315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Array.<string>} 325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction getOriginsFromJson(text) { 345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu try { 355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var urls = JSON.parse(text); 365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var origins = []; 375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu for (var i = 0, url; url = urls[i]; i++) { 385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var origin = getOriginFromUrl(url); 395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (origin) 405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu origins.push(origin); 415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return origins; 435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } catch (e) { 445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu console.log(UTIL_fmt('could not parse ' + text)); 455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return []; 465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Fetches the app id, and calls a callback with list of allowed origins for it. 515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} appId the app id to fetch. 525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Function} cb called with a list of allowed origins for the app id. 535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction fetchAppId(appId, cb) { 555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var origin = getOriginFromUrl(appId); 565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!origin) { 575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(404, appId); 585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var xhr = new XMLHttpRequest(); 615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var origins = []; 625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xhr.open('GET', appId, true); 635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xhr.onloadend = function() { 645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (xhr.status != 200) { 655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(xhr.status, appId); 665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(xhr.status, appId, getOriginsFromJson(xhr.responseText)); 695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu }; 705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu xhr.send(); 715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Retrieves a set of distinct app ids from the SignData. 755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignData=} signData 765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Array.<string>} array of distinct app ids. 775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction getDistinctAppIds(signData) { 795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var appIds = []; 805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!signData) { 815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return appIds; 825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu for (var i = 0, request; request = signData[i]; i++) { 845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var appId = request['appId']; 855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId && appIds.indexOf(appId) == -1) { 865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu appIds.push(appId); 875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return appIds; 905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Reorganizes the requests from the SignData to an array of 945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * (appId, [Request]) tuples. 955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {SignData} signData 965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Array.<[string, Array.<Request>]>} array of 975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * (appId, [Request]) tuples. 985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction requestsByAppId(signData) { 1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var requests = {}; 1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var appIdOrder = {}; 1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var orderToAppId = {}; 1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var lastOrder = 0; 1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu for (var i = 0, request; request = signData[i]; i++) { 1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var appId = request['appId']; 1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId) { 1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!appIdOrder.hasOwnProperty(appId)) { 1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu appIdOrder[appId] = lastOrder; 1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu orderToAppId[lastOrder] = appId; 1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu lastOrder++; 1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (requests[appId]) { 1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu requests[appId].push(request); 1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } else { 1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu requests[appId] = [request]; 1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var orderedRequests = []; 1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu for (var order = 0; order < lastOrder; order++) { 1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu appId = orderToAppId[order]; 1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu orderedRequests.push([appId, requests[appId]]); 1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return orderedRequests; 1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Fetches the allowed origins for an appId. 1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} appId 1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} allowHttp Whether http is a valid scheme for an appId. 1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * (This should be false except on test domains.) 1325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(number, !Array.<string>)} cb Called back with an HTTP 1335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * response code and a list of allowed origins for appId. 1345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction fetchAllowedOriginsForAppId(appId, allowHttp, cb) { 1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var allowedOrigins = []; 1375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!appId) { 1385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(200, allowedOrigins); 1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 1405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId.indexOf('http://') == 0 && !allowHttp) { 1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested')); 1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(200, allowedOrigins); 1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // TODO(juanlang): hack for old enrolled gnubbies, don't treat 1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // accounts.google.com/login.corp.google.com specially when cryptauth server 1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // stops reporting them as appId. 1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId == 'https://accounts.google.com') { 1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu allowedOrigins = ['https://login.corp.google.com']; 1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(200, allowedOrigins); 1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId == 'https://login.corp.google.com') { 1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu allowedOrigins = ['https://accounts.google.com']; 1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(200, allowedOrigins); 1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Termination of this function relies in fetchAppId completing. 1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // (Not completing would be a bug in XMLHttpRequest.) 1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // TODO(juanlang): provide a termination guarantee, e.g. with a timer? 1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu fetchAppId(appId, function(rc, fetchedAppId, origins) { 1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (rc != 200) { 1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu console.log(UTIL_fmt('fetching ' + fetchedAppId + ' failed: ' + rc)); 1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu allowedOrigins = []; 1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } else { 1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu allowedOrigins = origins; 1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu cb(rc, allowedOrigins); 1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu }); 1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Checks whether an appId is valid for a given origin. 1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} appId 1765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} origin 1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!Array.<string>} allowedOrigins the list of allowed origins for each 1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * appId. 1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} whether the appId is allowed for the origin. 1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction isValidAppIdForOrigin(appId, origin, allowedOrigins) { 1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!appId) 1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (appId == origin) { 1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // trivially allowed 1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return true; 1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!allowedOrigins) 1895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 1905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return allowedOrigins.indexOf(origin) >= 0; 1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Returns whether the signData object appears to be valid. 1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Array.<Object>} signData the signData object. 1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} whether the object appears valid. 1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction isValidSignData(signData) { 1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu for (var i = 0; i < signData.length; i++) { 2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var incomingChallenge = signData[i]; 2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!incomingChallenge.hasOwnProperty('challenge')) 2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!incomingChallenge.hasOwnProperty('appId')) { 2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 2055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!incomingChallenge.hasOwnProperty('keyHandle')) 2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (incomingChallenge['version']) { 2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (incomingChallenge['version'] != 'U2F_V1' && 2105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu incomingChallenge['version'] != 'U2F_V2') { 2115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return false; 2125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return true; 2165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** Posts the log message to the log url. 2195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} logMsg the log message to post. 2205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl the url to post log messages to. 2215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction logMessage(logMsg, opt_logMsgUrl) { 2235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu console.log(UTIL_fmt('logMessage("' + logMsg + '")')); 2245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!opt_logMsgUrl) { 2265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return; 2275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Image fetching is not allowed per packaged app CSP. 2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // But video and audio is. 2305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var audio = new Audio(); 2315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu audio.src = opt_logMsgUrl + logMsg; 2325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 2355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Logs the result of fetching an appId. 2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} appId 2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} millis elapsed time while fetching the appId. 2385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Array.<string>} allowedOrigins the allowed origins retrieved. 2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl 2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction logFetchAppIdResult(appId, millis, allowedOrigins, opt_logMsgUrl) { 2425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var logMsg = 'log=fetchappid&appid=' + appId + '&millis=' + millis + 2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu '&numorigins=' + allowedOrigins.length; 2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu logMessage(logMsg, opt_logMsgUrl); 2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Logs a mismatch between an origin and an appId. 2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} origin 2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} appId 2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl 2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction logInvalidOriginForAppId(origin, appId, opt_logMsgUrl) { 2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var logMsg = 'log=originrejected&origin=' + origin + '&appid=' + appId; 2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu logMessage(logMsg, opt_logMsgUrl); 2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Formats response parameters as an object. 2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} type type of the post message. 2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} code status code of the operation. 2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object=} responseData the response data of the operation. 2635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Object} formatted response. 2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction formatWebPageResponse(type, code, responseData) { 2665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var responseJsonObject = {}; 2675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu responseJsonObject['type'] = type; 2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu responseJsonObject['code'] = code; 2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (responseData) 2705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu responseJsonObject['responseData'] = responseData; 2715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return responseJsonObject; 2725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 2755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} string 2765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Array.<number>} SHA256 hash value of string. 2775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction sha256HashOfString(string) { 2795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var s = new SHA256(); 2805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu s.update(UTIL_StringToBytes(string)); 2815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return s.digest(); 2825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 2835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 2845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 2855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Normalizes the TLS channel ID value: 2865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * 1. Converts semantically empty values (undefined, null, 0) to the empty 2875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * string. 2885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * 2. Converts valid JSON strings to a JS object. 2895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * 3. Otherwise, returns the input value unmodified. 2905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object|string|undefined} opt_tlsChannelId 2915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {Object|string} The normalized TLS channel ID value. 2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction tlsChannelIdValue(opt_tlsChannelId) { 2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!opt_tlsChannelId) { 2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Case 1: Always set some value for TLS channel ID, even if it's the empty 2965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // string: this browser definitely supports them. 2975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ''; 2985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 2995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (typeof opt_tlsChannelId === 'string') { 3005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu try { 3015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var obj = JSON.parse(opt_tlsChannelId); 3025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (!obj) { 3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Case 1: The string value 'null' parses as the Javascript object null, 3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // so return an empty string: the browser definitely supports TLS 3055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // channel id. 3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return ''; 3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 3085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Case 2: return the value as a JS object. 3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return /** @type {Object} */ (obj); 3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } catch (e) { 3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId); 3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Case 3: return the value unmodified. 3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu } 3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return opt_tlsChannelId; 3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 3185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 3195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a browser data object with the given values. 3205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} type A string representing the "type" of this browser data 3215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * object. 3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} serverChallenge The server's challenge, as a base64- 3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * encoded string. 3245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} origin The server's origin, as seen by the browser. 3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object|string|undefined} opt_tlsChannelId 3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {string} A string representation of the browser data object. 3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 3285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) { 3295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var browserData = { 3305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'typ' : type, 3315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'challenge' : serverChallenge, 3325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'origin' : origin 3335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu }; 3345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId); 3355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return JSON.stringify(browserData); 3365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 3375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 3385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a browser data object for an enroll request with the given values. 3405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} serverChallenge The server's challenge, as a base64- 3415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * encoded string. 3425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} origin The server's origin, as seen by the browser. 3435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object|string|undefined} opt_tlsChannelId 3445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {string} A string representation of the browser data object. 3455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 3465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) { 3475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return makeBrowserData( 3485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'navigator.id.finishEnrollment', serverChallenge, origin, 3495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu opt_tlsChannelId); 3505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 3515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 3525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 3535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a browser data object for a sign request with the given values. 3545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} serverChallenge The server's challenge, as a base64- 3555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * encoded string. 3565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!string} origin The server's origin, as seen by the browser. 3575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Object|string|undefined} opt_tlsChannelId 3585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {string} A string representation of the browser data object. 3595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 3605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) { 3615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return makeBrowserData( 3625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId); 3635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 3645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 3655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** 3665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} browserData 3675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} appId 3685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string} encodedKeyHandle 3695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} version 3705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {SignHelperChallenge} 3715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */ 3725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction makeChallenge(browserData, appId, encodedKeyHandle, version) { 3735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var appIdHash = B64_encode(sha256HashOfString(appId)); 3745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var browserDataHash = B64_encode(sha256HashOfString(browserData)); 3755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var keyHandle = encodedKeyHandle; 3765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 3775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu var challenge = { 3785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'challengeHash': browserDataHash, 3795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'appIdHash': appIdHash, 3805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu 'keyHandle': keyHandle 3815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu }; 3825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu // Version is implicitly U2F_V1 if not specified. 3835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu challenge['version'] = (version || 'U2F_V1'); 3845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu return challenge; 3855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu} 386