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