Bug 1059202 - CSP and CA verfication for Trusted Hosted Apps r=fabrice,sicking,rlb

This commit is contained in:
Zoran Jovanovic 2014-09-19 14:58:34 -07:00
Родитель 1d9c2eb85b
Коммит 1c124be1f8
5 изменённых файлов: 211 добавлений и 2 удалений

Просмотреть файл

@ -246,6 +246,15 @@ pref("security.alternate_certificate_error_page", "certerror");
pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616712.
// 2 = strict certificate pinning checks.
// This default preference is more strict than Firefox because B2G
// currently does not have a way to install local root certificates.
// Strict checking is effectively equivalent to non-strict checking as
// long as that is true. If an ability to add local certificates is
// added, there may be a need to change this pref.
pref("security.cert_pinning.enforcement_level", 2);
// Override some named colors to avoid inverse OS themes
pref("ui.-moz-dialog", "#efebe7");
pref("ui.-moz-dialogtext", "#101010");

Просмотреть файл

@ -0,0 +1,148 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global Components, Services, dump */
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
this.EXPORTED_SYMBOLS = ["TrustedHostedAppsUtils"];
Cu.import("resource://gre/modules/Services.jsm");
#ifdef MOZ_WIDGET_ANDROID
// On Android, define the "debug" function as a binding of the "d" function
// from the AndroidLog module so it gets the "debug" priority and a log tag.
// We always report debug messages on Android because it's unnecessary
// to restrict reporting, per bug 1003469.
let debug = Cu
.import("resource://gre/modules/AndroidLog.jsm", {})
.AndroidLog.d.bind(null, "TrustedHostedAppsUtils");
#else
// Elsewhere, report debug messages only if dom.mozApps.debug is set to true.
// The pref is only checked once, on startup, so restart after changing it.
let debug = Services.prefs.getBoolPref("dom.mozApps.debug") ?
aMsg => dump("-*- TrustedHostedAppsUtils.jsm : " + aMsg + "\n") :
() => {};
#endif
/**
* Verification functions for Trusted Hosted Apps.
* (Manifest signature verification is in Webapps.jsm as part of
* regular signature verification.)
*/
this.TrustedHostedAppsUtils = {
/**
* Check if the given host is pinned in the CA pinning database.
*/
isHostPinned: function (aUrl) {
let uri;
try {
uri = Services.io.newURI(aUrl, null, null);
} catch(e) {
debug("Host parsing failed: " + e);
return false;
}
// TODO: use nsSiteSecurityService.isSecureURI()
if (!uri.host || "https" != uri.scheme) {
return false;
}
// Check certificate pinning
let siteSecurityService;
try {
siteSecurityService = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
} catch (e) {
debug("nsISiteSecurityService error: " + e);
// unrecoverable error, don't bug the user
throw "CERTDB_ERROR";
}
if (siteSecurityService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP, uri.host, 0)) {
debug("\tvalid certificate pinning for host: " + uri.host + "\n");
return true;
}
debug("\tHost NOT pinned: " + uri.host + "\n");
return false;
},
/**
* Take a CSP policy string as input and ensure that it contains at
* least the directives that are required ('script-src' and
* 'style-src'). If the CSP policy string is 'undefined' or does
* not contain some of the required csp directives the function will
* return empty list with status set to false. Otherwise a parsed
* list of the unique sources listed from the required csp
* directives is returned.
*/
getCSPWhiteList: function(aCsp) {
let isValid = false;
let whiteList = [];
let requiredDirectives = [ "script-src", "style-src" ];
if (aCsp) {
let validDirectives = [];
let directives = aCsp.split(";");
// TODO: Use nsIContentSecurityPolicy
directives
.map(aDirective => aDirective.trim().split(" "))
.filter(aList => aList.length > 1)
// we only restrict on requiredDirectives
.filter(aList => (requiredDirectives.indexOf(aList[0]) != -1))
.forEach(aList => {
// aList[0] contains the directive name.
// aList[1..n] contains sources.
let directiveName = aList.shift()
let sources = aList;
if ((-1 == validDirectives.indexOf(directiveName))) {
validDirectives.push(directiveName);
}
whiteList.push(...sources.filter(
// 'self' is checked separately during manifest check
aSource => (aSource !="'self'" && whiteList.indexOf(aSource) == -1)
));
});
// Check if all required directives are present.
isValid = requiredDirectives.length === validDirectives.length;
if (!isValid) {
debug("White list doesn't contain all required directives!");
whiteList = [];
}
}
debug("White list contains " + whiteList.length + " hosts");
return { list: whiteList, valid: isValid };
},
/**
* Verify that the given csp is valid:
* 1. contains required directives "script-src" and "style-src"
* 2. required directives contain only "https" URLs
* 3. domains of the restricted sources exist in the CA pinning database
*/
verifyCSPWhiteList: function(aCsp) {
let domainWhitelist = this.getCSPWhiteList(aCsp);
if (!domainWhitelist.valid) {
debug("TRUSTED_APPLICATION_WHITELIST_PARSING_FAILED");
return false;
}
if (!domainWhitelist.list.every(aUrl => this.isHostPinned(aUrl))) {
debug("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return false;
}
return true;
}
};

Просмотреть файл

@ -64,6 +64,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader",
"resource://gre/modules/ScriptPreloader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TrustedHostedAppsUtils",
"resource://gre/modules/TrustedHostedAppsUtils.jsm");
#ifdef MOZ_WIDGET_GONK
XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
Cu.import("resource://gre/modules/systemlibs.js");
@ -1411,6 +1414,24 @@ this.DOMApplicationRegistry = {
return;
}
// Check if launching trusted hosted app
if (this.kTrustedHosted == app.kind) {
debug("Launching Trusted Hosted App!");
// sanity check on manifest host's CA
// (proper CA check with pinning is done by regular networking code)
if (!TrustedHostedAppsUtils.isHostPinned(aManifestURL)) {
debug("Trusted App Host certificate Not OK");
aOnFailure("TRUSTED_APPLICATION_HOST_CERTIFICATE_INVALID");
return;
}
debug("Trusted App Host pins exist");
if (!TrustedHostedAppsUtils.verifyCSPWhiteList(app.csp)) {
aOnFailure("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
}
// We have to clone the app object as nsIDOMApplication objects are
// stringified as an empty object. (see bug 830376)
let appClone = AppsUtils.cloneAppObject(app);
@ -2295,6 +2316,23 @@ this.DOMApplicationRegistry = {
// in which case we don't need to load it.
if (app.manifest) {
if (checkManifest()) {
if (this.kTrustedHosted == this.appKind(app, app.manifest)) {
// sanity check on manifest host's CA
// (proper CA check with pinning is done by regular networking code)
if (!TrustedHostedAppsUtils.isHostPinned(app.manifestURL)) {
sendError("TRUSTED_APPLICATION_HOST_CERTIFICATE_INVALID");
return;
}
// Signature of the manifest should be verified here.
// Bug 1059216.
if (!TrustedHostedAppsUtils.verifyCSPWhiteList(app.manifest.csp)) {
sendError("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
}
installApp();
}
return;
@ -2319,6 +2357,19 @@ this.DOMApplicationRegistry = {
app.manifest = xhr.response;
if (checkManifest()) {
app.etag = xhr.getResponseHeader("Etag");
if (this.kTrustedHosted == this.appKind(app, app.manifest)) {
// checking trusted host for pinning is not needed here, since
// network code will have already done that
// Signature of the manifest should be verified here.
// Bug 1059216.
if (!TrustedHostedAppsUtils.verifyCSPWhiteList(app.manifest.csp)) {
sendError("TRUSTED_APPLICATION_WHITELIST_VALIDATION_FAILED");
return;
}
}
installApp();
}
} else {

Просмотреть файл

@ -44,6 +44,7 @@ EXTRA_PP_JS_MODULES += [
'AppsUtils.jsm',
'OperatorApps.jsm',
'ScriptPreloader.jsm',
'TrustedHostedAppsUtils.jsm',
'Webapps.jsm',
]

Просмотреть файл

@ -1729,7 +1729,7 @@ pref("security.mixed_content.block_active_content", false);
pref("security.mixed_content.block_display_content", false);
// Disable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 0);
pref("security.cert_pinning.enforcement_level", 2);
// Modifier key prefs: default to Windows settings,
// menu access key = alt, accelerator key = control.
@ -4093,7 +4093,7 @@ pref("dom.mozApps.signed_apps_installable_from", "https://marketplace.firefox.co
// Only checked on startup, so restart after changing this pref.
// Ignored on Android, where we always report debug messages because it's
// unnecessary to restrict reporting, per bug 1003469.
pref("dom.mozApps.debug", false);
pref("dom.mozApps.debug", true);
// Minimum delay in milliseconds between network activity notifications (0 means
// no notifications). The delay is the same for both download and upload, though