bug 515433: CSP Core patch by sstamm and bsterne, r=mrbkap, sr=jst

This commit is contained in:
Sid Stamm 2010-01-21 10:41:24 -08:00
parent e6a13e6c97
commit bfb875e04a
8 changed files with 2403 additions and 1 deletions

View File

@ -327,6 +327,7 @@
@BINPATH@/components/nsWebHandlerApp.js
@BINPATH@/components/nsBadCertHandler.js
@BINPATH@/components/nsFormAutoComplete.js
@BINPATH@/components/contentSecurityPolicy.js
#ifdef XP_MACOSX
@BINPATH@/components/libalerts_s.dylib
#endif

View File

@ -113,6 +113,7 @@ XPIDLSRCS = \
nsIObjectLoadingContent.idl \
nsIFrameLoader.idl \
nsIXMLHttpRequest.idl \
nsIContentSecurityPolicy.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,152 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Content Security Policy IDL definition.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
*
* Contributor(s):
* Sid Stamm <sid@mozilla.com>
* Brandon Sterne <bsterne@mozilla.com>
* Daniel Veditz <dveditz@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsIURI;
interface nsIHttpChannel;
interface nsIDocShell;
/**
* nsIContentSecurityPolicy
* Describes an XPCOM component used to model an enforce CSPs.
*/
[scriptable, uuid(AB36A2BF-CB32-4AA6-AB41-6B4E4444A221)]
interface nsIContentSecurityPolicy : nsISupports
{
/**
* Set to true when the CSP has been read in and parsed and is ready to
* enforce. This is a barrier for the nsDocument so it doesn't load any
* sub-content until either it knows that a CSP is ready or will not be used.
*/
attribute boolean isInitialized;
/**
* When set to true, content load-blocking and fail-closed are disabled: CSP
* will ONLY send reports, and not modify behavior.
*/
attribute boolean reportOnlyMode;
/**
* A read-only string version of the policy for debugging.
*/
readonly attribute AString policy;
/**
* Whether this policy allows in-page script.
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*/
readonly attribute boolean allowsInlineScript;
/**
* whether this policy allows eval and eval-like functions
* such as setTimeout("code string", time).
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*/
readonly attribute boolean allowsEval;
/**
* Manually triggers violation report sending given a URI and reason.
* The URI may be null, in which case "self" is sent.
* @param blockedURI
* the URI that violated the policy
* @param violatedDirective
* the directive that was violated.
* @return
* nothing.
*/
void sendReports(in AString blockedURI, in AString violatedDirective);
/**
* Called after the CSP object is created to fill in the appropriate request
* and request header information needed in case a report needs to be sent.
*/
void scanRequestData(in nsIHttpChannel aChannel);
/**
* Updates the policy currently stored in the CSP to be "refined" or
* tightened by the one specified in the string policyString.
*/
void refinePolicy(in AString policyString, in nsIURI selfURI);
/**
* Verifies ancestry as permitted by the policy.
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*
* @param docShell
* containing the protected resource
* @return
* true if the frame's ancestors are all permitted by policy
*/
boolean permitsAncestry(in nsIDocShell docShell);
/**
* Delegate method called by the service when sub-elements of the protected
* document are being loaded. Given a bit of information about the request,
* decides whether or not the policy is satisfied.
*
* Calls to this may trigger violation reports when queried, so
* this value should not be cached.
*/
short shouldLoad(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeTypeGuess,
in nsISupports aExtra);
/**
* Delegate method called by the service when sub-elements of the protected
* document are being processed. Given a bit of information about the request,
* decides whether or not the policy is satisfied.
*/
short shouldProcess(in unsigned long aContentType,
in nsIURI aContentLocation,
in nsIURI aRequestOrigin,
in nsISupports aContext,
in ACString aMimeType,
in nsISupports aExtra);
};

File diff suppressed because it is too large Load Diff

View File

@ -141,7 +141,14 @@ GQI_SRCS = contentbase.gqi
# static lib.
FORCE_STATIC_LIB = 1
EXTRA_COMPONENTS = $(srcdir)/nsBadCertHandler.js
EXTRA_COMPONENTS = \
$(srcdir)/nsBadCertHandler.js \
contentSecurityPolicy.js \
$(NULL)
EXTRA_JS_MODULES = \
CSPUtils.jsm \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,447 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the ContentSecurityPolicy module.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
*
* Contributor(s):
* Sid Stamm <sid@mozilla.com>
* Brandon Sterne <bsterne@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Content Security Policy
*
* Overview
* This is a stub component that will be fleshed out to do all the fancy stuff
* that ContentSecurityPolicy has to do.
*/
/* :::::::: Constants and Helpers ::::::::::::::: */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/CSPUtils.jsm");
/* ::::: Policy Parsing & Data structures :::::: */
function ContentSecurityPolicy() {
CSPdebug("CSP CREATED");
this._isInitialized = false;
this._reportOnlyMode = false;
this._policy = CSPRep.fromString("allow *");
// default options "wide open" since this policy will be intersected soon
this._policy._allowInlineScripts = true;
this._policy._allowEval = true;
this._requestHeaders = [];
this._request = "";
CSPdebug("CSP POLICY INITED TO 'allow *'");
this._observerService = Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService);
}
/*
* Set up mappings from nsIContentPolicy content types to CSP directives.
*/
{
let cp = Ci.nsIContentPolicy;
let csp = ContentSecurityPolicy;
let cspr_sd = CSPRep.SRC_DIRECTIVES;
csp._MAPPINGS=[];
/* default, catch-all case */
csp._MAPPINGS[cp.TYPE_OTHER] = cspr_sd.ALLOW;
/* self */
csp._MAPPINGS[cp.TYPE_DOCUMENT] = null;
/* shouldn't see this one */
csp._MAPPINGS[cp.TYPE_REFRESH] = null;
/* categorized content types */
csp._MAPPINGS[cp.TYPE_SCRIPT] = cspr_sd.SCRIPT_SRC;
csp._MAPPINGS[cp.TYPE_IMAGE] = cspr_sd.IMG_SRC;
csp._MAPPINGS[cp.TYPE_STYLESHEET] = cspr_sd.STYLE_SRC;
csp._MAPPINGS[cp.TYPE_OBJECT] = cspr_sd.OBJECT_SRC;
csp._MAPPINGS[cp.TYPE_SUBDOCUMENT] = cspr_sd.FRAME_SRC;
csp._MAPPINGS[cp.TYPE_MEDIA] = cspr_sd.MEDIA_SRC;
csp._MAPPINGS[cp.TYPE_FONT] = cspr_sd.FONT_SRC;
csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST] = cspr_sd.XHR_SRC;
/* These must go through the catch-all */
csp._MAPPINGS[cp.TYPE_XBL] = cspr_sd.ALLOW;
csp._MAPPINGS[cp.TYPE_PING] = cspr_sd.ALLOW;
csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd.ALLOW;
csp._MAPPINGS[cp.TYPE_DTD] = cspr_sd.ALLOW;
}
ContentSecurityPolicy.prototype = {
classDescription: "Content Security Policy Component",
contractID: "@mozilla.org/contentsecuritypolicy;1",
classID: Components.ID("{AB36A2BF-CB32-4AA6-AB41-6B4E4444A221}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy]),
// get this contractID registered for certain categories via XPCOMUtils
_xpcom_categories: [ ],
get isInitialized() {
return this._isInitialized;
},
set isInitialized (foo) {
this._isInitialized = foo;
},
get policy () {
return this._policy.toString();
},
get allowsInlineScript() {
// trigger automatic report to go out when inline scripts are disabled.
if (!this._policy.allowsInlineScripts) {
var violation = 'violated base restriction: Inline Scripts will not execute';
// gotta wrap the violation string, since it's sent out to observers as
// an nsISupports.
let wrapper = Cc["@mozilla.org/supports-cstring;1"]
.createInstance(Ci.nsISupportsCString);
wrapper.data = violation;
this._observerService.notifyObservers(
wrapper,
CSP_VIOLATION_TOPIC,
'inline script base restriction');
this.sendReports('self', violation);
}
return this._reportOnlyMode || this._policy.allowsInlineScripts;
},
get allowsEval() {
// trigger automatic report to go out when eval and friends are disabled.
if (!this._policy.allowsEvalInScripts) {
var violation = 'violated base restriction: Code will not be created from strings';
// gotta wrap the violation string, since it's sent out to observers as
// an nsISupports.
let wrapper = Cc["@mozilla.org/supports-cstring;1"]
.createInstance(Ci.nsISupportsCString);
wrapper.data = violation;
this._observerService.notifyObservers(
wrapper,
CSP_VIOLATION_TOPIC,
'eval script base restriction');
this.sendReports('self', violation);
}
return this._reportOnlyMode || this._policy.allowsEvalInScripts;
},
set reportOnlyMode(val) {
this._reportOnlyMode = val;
},
get reportOnlyMode () {
return this._reportOnlyMode;
},
/*
// Having a setter is a bad idea... opens up the policy to "loosening"
// Instead, use "refinePolicy."
set policy (aStr) {
this._policy = CSPRep.fromString(aStr);
},
*/
/**
* Given an nsIHttpChannel, fill out the appropriate data.
*/
scanRequestData:
function(aChannel) {
if (!aChannel)
return;
// grab the request line
var internalChannel = aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
var reqMaj = {};
var reqMin = {};
var reqVersion = internalChannel.getRequestVersion(reqMaj, reqMin);
this._request = aChannel.requestMethod + " "
+ aChannel.URI.asciiSpec
+ " HTTP/" + reqMaj.value + "." + reqMin.value;
// grab the request headers
var self = this;
aChannel.visitRequestHeaders({
visitHeader: function(aHeader, aValue) {
self._requestHeaders.push(aHeader + ": " + aValue);
}});
},
/* ........ Methods .............. */
/**
* Given a new policy, intersects the currently enforced policy with the new
* one and stores the result. The effect is a "tightening" or refinement of
* an old policy. This is called any time a new policy is encountered and
* the effective policy has to be refined.
*/
refinePolicy:
function csp_refinePolicy(aPolicy, selfURI) {
CSPdebug("REFINE POLICY: " + aPolicy);
CSPdebug(" SELF: " + selfURI.asciiSpec);
// stay uninitialized until policy merging is done
this._isInitialized = false;
// If there is a policy-uri, fetch the policy, then re-call this function.
// (1) parse and create a CSPRep object
var newpolicy = CSPRep.fromString(aPolicy,
selfURI.scheme + "://" + selfURI.hostPort);
// (2) Intersect the currently installed CSPRep object with the new one
var intersect = this._policy.intersectWith(newpolicy);
// (3) Save the result
this._policy = intersect;
this._isInitialized = true;
},
/**
* Generates and sends a violation report to the specified report URIs.
*/
sendReports:
function(blockedUri, violatedDirective) {
var uriString = this._policy.getReportURIs();
var uris = uriString.split(/\s+/);
if (uris.length > 0) {
// Generate report to send composed of:
// <csp-report>
// <request>GET /index.html HTTP/1.1</request>
// <request-headers>Host: example.com
// User-Agent: ...
// ...
// </request-headers>
// <blocked-uri>...</blocked-uri>
// <violated-directive>...</violated-directive>
// </csp-report>
//
var strHeaders = "";
for (let i in this._requestHeaders) {
strHeaders += this._requestHeaders[i] + "\n";
}
var report = "<csp-report>\n" +
" <request>" + this._request + "</request>\n" +
" <request-headers><![CDATA[\n" +
strHeaders +
" ]]></request-headers>\n" +
" <blocked-uri>" +
(blockedUri instanceof Ci.nsIURI ? blockedUri.asciiSpec : blockedUri) +
"</blocked-uri>\n" +
" <violated-directive>" + violatedDirective + "</violated-directive>\n" +
"</csp-report>\n";
CSPdebug("Constructed violation report:\n" + report);
// For each URI in the report list, send out a report.
for (let i in uris) {
if (uris[i] === "")
continue;
var failure = function(aEvt) {
if (req.readyState == 4 && req.status != 200) {
CSPError("Failed to send report to " + reportURI);
}
};
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
try {
req.open("POST", uris[i], true);
req.setRequestHeader('Content-Type', 'application/xml');
req.upload.addEventListener("error", failure, false);
req.upload.addEventListener("abort", failure, false);
//req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// make request anonymous
// This prevents sending cookies with the request,
// in case the policy URI is injected, it can't be
// abused for CSRF.
req.channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
req.send(report);
CSPdebug("Sent violation report to " + uris[i]);
} catch(e) {
// it's possible that the URI was invalid, just log a
// warning and skip over that.
CSPWarning("Tried to send report to invalid URI: \"" + uris[i] + "\"");
}
}
}
},
/**
* Exposed Method to analyze docShell for approved frame ancestry.
* Also sends violation reports if necessary.
* @param docShell
* the docShell for this policy's resource.
* @return
* true if the frame ancestry is allowed by this policy.
*/
permitsAncestry:
function(docShell) {
if (!docShell) { return false; }
CSPdebug(" in permitsAncestry(), docShell = " + docShell);
// walk up this docShell tree until we hit chrome
var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShellTreeItem);
// collect ancestors and make sure they're allowed.
var ancestors = [];
while (dst.parent) {
dst = dst.parent;
let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
if (it.currentURI) {
if (it.currentURI.scheme === "chrome") {
break;
}
let ancestor = it.currentURI;
CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
ancestors.push(ancestor);
}
}
// scan the discovered ancestors
let cspContext = CSPRep.SRC_DIRECTIVES.FRAME_ANCESTORS;
for (let i in ancestors) {
let ancestor = ancestors[i].prePath;
if (!this._policy.permits(ancestor, cspContext)) {
// report the frame-ancestor violation
let directive = this._policy._directives[cspContext];
let violatedPolicy = (directive._isImplicit
? 'allow' : 'frame-ancestors ')
+ directive.toString();
// send an nsIURI object to the observers (more interesting than a string)
this._observerService.notifyObservers(
ancestors[i],
CSP_VIOLATION_TOPIC,
violatedPolicy);
this.sendReports(ancestors[i].asciiSpec, violatedPolicy);
// need to lie if we are testing in report-only mode
return this._reportOnlyMode;
}
}
return true;
},
/**
* Delegate method called by the service when sub-elements of the protected
* document are being loaded. Given a bit of information about the request,
* decides whether or not the policy is satisfied.
*/
shouldLoad:
function csp_shouldLoad(aContentType,
aContentLocation,
aRequestOrigin,
aContext,
aMimeTypeGuess,
aExtra) {
// don't filter chrome stuff
if (aContentLocation.scheme === 'chrome') {
return Ci.nsIContentPolicy.ACCEPT;
}
// interpret the context, and then pass off to the decision structure
CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
CSPdebug("shouldLoad content type = " + aContentType);
var cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
// CSPdebug("shouldLoad CSP directive =" + cspContext);
// if the mapping is null, there's no policy, let it through.
if (!cspContext) {
return Ci.nsIContentPolicy.ACCEPT;
}
// otherwise, honor the translation
// var source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
var res = this._policy.permits(aContentLocation, cspContext)
? Ci.nsIContentPolicy.ACCEPT
: Ci.nsIContentPolicy.REJECT_SERVER;
// frame-ancestors is taken care of early on (as this document is loaded)
// If the result is *NOT* ACCEPT, then send report
if (res != Ci.nsIContentPolicy.ACCEPT) {
CSPdebug("blocking request for " + aContentLocation.asciiSpec);
try {
let directive = this._policy._directives[cspContext];
let violatedPolicy = (directive._isImplicit
? 'allow' : cspContext)
+ ' ' + directive.toString();
this._observerService.notifyObservers(
aContentLocation,
CSP_VIOLATION_TOPIC,
violatedPolicy);
this.sendReports(aContentLocation, violatedPolicy);
} catch(e) {
CSPdebug('---------------- ERROR: ' + e);
}
}
return (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
},
shouldProcess:
function csp_shouldProcess(aContentType,
aContentLocation,
aRequestOrigin,
aContext,
aMimeType,
aExtra) {
// frame-ancestors check is done outside the ContentPolicy
var res = Ci.nsIContentPolicy.ACCEPT;
CSPdebug("shouldProcess aContext=" + aContext);
return res;
},
};
function NSGetModule(aComMgr, aFileSpec)
XPCOMUtils.generateModule([ContentSecurityPolicy]);

View File

@ -0,0 +1,497 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Content Security Policy Data Structures testing code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
*
* Contributor(s):
* Sid Stamm <sid@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
//load('CSPUtils.jsm');
Components.utils.import('resource://gre/modules/CSPUtils.jsm');
// load the HTTP server
do_load_httpd_js();
var httpServer = new nsHttpServer();
const POLICY_FROM_URI = "allow 'self'; img-src *";
const POLICY_PORT = 9000;
const POLICY_URI = "http://localhost:" + POLICY_PORT + "/policy";
// helper to assert that an object or array must have a given key
function do_check_has_key(foo, key, stack) {
if (!stack)
stack = Components.stack.caller;
var keys = [];
for(let k in keys) { keys.push(k); }
var text = key + " in [" + keys.join(",") + "]";
for(var x in foo) {
if(x == key) {
//succeed
++_passedChecks;
dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
stack.lineNumber + "] " + text + "\n");
return;
}
}
do_throw(text, stack);
}
// helper to use .equals on stuff
function do_check_equivalent(foo, bar, stack) {
if (!stack)
stack = Components.stack.caller;
var text = foo + ".equals(" + bar + ")";
if(foo.equals && foo.equals(bar)) {
++_passedChecks;
dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
stack.lineNumber + "] " + text + "\n");
return;
}
do_throw(text, stack);
}
var tests = [];
function test(fcn) {
tests.push(fcn);
}
test(
function test_CSPHost_fromstring() {
var h;
h = CSPHost.fromString("*");
do_check_neq(null, h); // "* lone wildcard should work"
h = CSPHost.fromString("foo.bar");
do_check_neq(null, h); // "standard tuple failed"
h = CSPHost.fromString("*.bar");
do_check_neq(null, h); // "wildcard failed"
h = CSPHost.fromString("foo.*.bar");
do_check_eq(null, h); // "wildcard in wrong place worked"
h = CSPHost.fromString("com");
do_check_eq(null, h); // "lone symbol should fail"
h = CSPHost.fromString("f00b4r.com");
do_check_neq(null, h); // "Numbers in hosts should work"
h = CSPHost.fromString("foo-bar.com");
do_check_neq(null, h); // "dashes in hosts should work"
h = CSPHost.fromString("foo!bar.com");
do_check_eq(null, h); // "special chars in hosts should fail"
});
test(
function test_CSPHost_clone() {
h = CSPHost.fromString("*.a.b.c");
h2 = h.clone();
for(var i in h._segments) {
// "cloned segments should match"
do_check_eq(h._segments[i], h2._segments[i]);
}
});
test(
function test_CSPHost_permits() {
var h = CSPHost.fromString("*.b.c");
var h2 = CSPHost.fromString("a.b.c");
do_check_true( h.permits(h2)); //"CSPHost *.b.c should allow CSPHost a.b.c"
do_check_true( h.permits("a.b.c")); //"CSPHost *.b.c should allow string a.b.c"
do_check_false(h.permits("b.c")); //"CSPHost *.b.c should not allow string b.c"
do_check_false(h.permits("a.a.c")); //"CSPHost *.b.c should not allow string a.a.c"
do_check_false(h2.permits(h)); //"CSPHost a.b.c should not allow CSPHost *.b.c"
do_check_false(h2.permits("b.c")); //"CSPHost a.b.c should not allow string b.c"
do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c"
});
test(
function test_CSPHost_intersectWith() {
var h = CSPHost.fromString("*.b.c");
//"*.a.b.c ^ *.b.c should be *.a.b.c"
do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString());
//"*.b.c ^ *.d.e should not work (null)"
do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e")));
});
///////////////////// Test the Source object //////////////////////
test(
function test_CSPSource_fromString() {
// can't do these tests because "self" is not defined.
//"basic source should not be null.");
do_check_neq(null, CSPSource.fromString("a.com"));
//"ldh characters should all work for host.");
do_check_neq(null, CSPSource.fromString("a2-c.com"));
//"wildcard should work in first token for host.");
do_check_neq(null, CSPSource.fromString("*.a.com"));
//print(" --- Ignore the following two errors if they print ---");
//"wildcard should not work in non-first token for host.");
do_check_eq(null, CSPSource.fromString("x.*.a.com"));
//"funny characters (#) should not work for host.");
do_check_eq(null, CSPSource.fromString("a#2-c.com"));
//print(" --- Stop ignoring errors that print ---\n");
//"failed to parse host with port.");
do_check_neq(null, CSPSource.create("a.com:23"));
//"failed to parse host with scheme.");
do_check_neq(null, CSPSource.create("https://a.com"));
//"failed to parse host with scheme and port.");
do_check_neq(null, CSPSource.create("https://a.com:200"));
});
test(
function test_CSPSource_fromString_withSelf() {
var src;
src = CSPSource.create("a.com", "https://foobar.com:443");
//"src should inherit port *
do_check_true(src.permits("https://a.com:443"));
//"src should inherit and require https scheme
do_check_false(src.permits("http://a.com"));
//"src should inherit scheme 'https'"
do_check_true(src.permits("https://a.com"));
src = CSPSource.create("http://a.com", "https://foobar.com:443");
//"src should inherit and require http scheme"
do_check_false(src.permits("https://a.com"));
//"src should inherit scheme 'http'"
do_check_true(src.permits("http://a.com"));
//"src should inherit port and scheme from parent"
//"src should inherit default port for 'http'"
do_check_true(src.permits("http://a.com:80"));
src = CSPSource.create("'self'", "https://foobar.com:443");
//"src should inherit port *
do_check_true(src.permits("https://foobar.com:443"));
//"src should inherit and require https scheme
do_check_false(src.permits("http://foobar.com"));
//"src should inherit scheme 'https'"
do_check_true(src.permits("https://foobar.com"));
//"src should reject other hosts"
do_check_false(src.permits("https://a.com"));
});
///////////////////// Test the source list //////////////////////
test(
function test_CSPSourceList_fromString() {
var sd = CSPSourceList.fromString("'none'");
//"'none' -- should parse"
do_check_neq(null,sd);
// "'none' should be a zero-length list"
do_check_eq(0, sd._sources.length);
do_check_true(sd.isNone());
sd = CSPSourceList.fromString("*");
//"'*' should be a zero-length list"
do_check_eq(0, sd._sources.length);
//print(" --- Ignore the following three errors if they print ---");
//"funny char in host"
do_check_true(CSPSourceList.fromString("f!oo.bar").isNone());
//"funny char in scheme"
do_check_true(CSPSourceList.fromString("ht!ps://f-oo.bar").isNone());
//"funny char in port"
do_check_true(CSPSourceList.fromString("https://f-oo.bar:3f").isNone());
//print(" --- Stop ignoring errors that print ---\n");
});
test(
function test_CSPSourceList_fromString_twohost() {
var str = "foo.bar:21 https://ras.bar";
var parsed = "foo.bar:21 https://ras.bar";
var sd = CSPSourceList.fromString(str, "http://self.com:80");
//"two-host list should parse"
do_check_neq(null,sd);
//"two-host list should parse to two hosts"
do_check_eq(2, sd._sources.length);
//"two-host list should contain original data"
do_check_eq(parsed, sd.toString());
});
test(
function test_CSPSourceList_permits() {
var nullSourceList = CSPSourceList.fromString("'none'");
var simpleSourceList = CSPSourceList.fromString("a.com", "http://self.com");
var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88",
"http://self.com:88");
var allSourceList = CSPSourceList.fromString("*");
//'none' should permit none."
do_check_false( nullSourceList.permits("http://a.com"));
//a.com should permit a.com"
do_check_true( simpleSourceList.permits("http://a.com"));
//wrong host"
do_check_false( simpleSourceList.permits("http://b.com"));
//double list permits http://bar.com:88"
do_check_true( doubleSourceList.permits("http://bar.com:88"));
//double list permits https://bar.com:88"
do_check_false( doubleSourceList.permits("https://bar.com:88"));
//double list does not permit http://bar.com:443"
do_check_false( doubleSourceList.permits("http://bar.com:443"));
//"double list permits https://foo.com:88" (should not inherit port)
do_check_false( doubleSourceList.permits("https://foo.com:88"));
//"double list does not permit foo.com on http"
do_check_false( doubleSourceList.permits("http://foo.com"));
//"* does not permit specific host"
do_check_true( allSourceList.permits("http://x.com:23"));
//"* does not permit a long host with no port"
do_check_true( allSourceList.permits("http://a.b.c.d.e.f.g.h.i.j.k.l.x.com"));
});
test(
function test_CSPSourceList_intersect() {
// for this test, 'self' values are irrelevant
// policy a /\ policy b intersects policies, not context (where 'self'
// values come into play)
var nullSourceList = CSPSourceList.fromString("'none'");
var simpleSourceList = CSPSourceList.fromString("a.com");
var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88");
var singleFooSourceList = CSPSourceList.fromString("https://foo.com");
var allSourceList = CSPSourceList.fromString("*");
//"Intersection of one source with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone());
//"Intersection of two sources with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone());
//"Intersection of '*' with 'none' source list should be none.");
do_check_true(nullSourceList.intersectWith(allSourceList).isNone());
//"Intersection of one source with '*' source list should be one source.");
do_check_equivalent(allSourceList.intersectWith(simpleSourceList),
simpleSourceList);
//"Intersection of two sources with '*' source list should be two sources.");
do_check_equivalent(allSourceList.intersectWith(doubleSourceList),
doubleSourceList);
//"Non-overlapping source lists should intersect to 'none'");
do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone());
//"subset and superset should intersect to subset.");
do_check_equivalent(singleFooSourceList,
doubleSourceList.intersectWith(singleFooSourceList));
//TODO: write more tests?
});
///////////////////// Test the Whole CSP rep object //////////////////////
test(
function test_CSPRep_fromString() {
// check default init
//ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.")
var cspr;
var cspr_allowval;
// check default policy "allow *"
cspr = CSPRep.fromString("allow *", "http://self.com:80");
//"ALLOW directive is missing when specified in fromString"
do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW);
// ... and check that the other directives were auto-filled with the
// ALLOW one.
var SD = CSPRep.SRC_DIRECTIVES;
cspr_allowval = cspr._directives[SD.ALLOW];
for(var d in CSPRep.SRC_DIRECTIVES) {
//"Missing key " + d
do_check_has_key(cspr._directives, SD[d]);
//"Implicit directive " + d + " has non-allow value."
do_check_eq(cspr._directives[SD[d]].toString(), cspr_allowval.toString());
}
});
test(
function test_CSPRep_fromString_oneDir() {
var cspr;
var SD = CSPRep.SRC_DIRECTIVES;
var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.IMG_SRC,
SD.FRAME_ANCESTORS, SD.FRAME_SRC];
// check one-directive policies
cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com",
"http://self.com");
for(var x in DEFAULTS) {
//DEFAULTS[x] + " does not use default rule."
do_check_false(cspr.permits("http://bar.com:22", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule."
do_check_true(cspr.permits("http://bar.com:80", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule."
do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule."
do_check_false(cspr.permits("https://foo.com", DEFAULTS[x]));
}
//"script-src false positive in policy.
do_check_false(cspr.permits("http://bar.com:22", SD.SCRIPT_SRC));
//"script-src false negative in policy.
do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC));
});
test(
function test_CSPRep_fromString_twodir() {
var cspr;
var SD = CSPRep.SRC_DIRECTIVES;
var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.FRAME_ANCESTORS, SD.FRAME_SRC];
// check two-directive policies
var polstr = "allow allow.com; "
+ "script-src https://foo.com; "
+ "img-src bar.com:*";
cspr = CSPRep.fromString(polstr, "http://self.com");
for(var x in DEFAULTS) {
do_check_true(cspr.permits("http://allow.com", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule.
do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule.
do_check_false(cspr.permits("http://bar.com:400", DEFAULTS[x]));
//DEFAULTS[x] + " does not use default rule.
}
//"img-src does not use default rule.
do_check_false(cspr.permits("http://allow.com:22", SD.IMG_SRC));
//"img-src does not use default rule.
do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC));
//"img-src does not use default rule.
do_check_true(cspr.permits("http://bar.com:88", SD.IMG_SRC));
//"script-src does not use default rule.
do_check_false(cspr.permits("http://allow.com:22", SD.SCRIPT_SRC));
//"script-src does not use default rule.
do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC));
//"script-src does not use default rule.
do_check_false(cspr.permits("http://bar.com:400", SD.SCRIPT_SRC));
});
test(function test_CSPRep_fromString_withself() {
var cspr;
var SD = CSPRep.SRC_DIRECTIVES;
var self = "https://self.com:34";
// check one-directive policies
cspr = CSPRep.fromString("allow 'self'; script-src 'self' https://*:*",
self);
//"img-src does not enforce default rule, 'self'.
do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC));
//"img-src does not allow self
CSPdebug(cspr);
do_check_true(cspr.permits(self, SD.IMG_SRC));
//"script-src is too relaxed
do_check_false(cspr.permits("http://evil.com", SD.SCRIPT_SRC));
//"script-src should allow self
do_check_true(cspr.permits(self, SD.SCRIPT_SRC));
//"script-src is too strict on host/port
do_check_true(cspr.permits("https://evil.com:100", SD.SCRIPT_SRC));
});
///////////////////// TEST POLICY_URI //////////////////////
test(function test_CSPRep_fromPolicyURI() {
var cspr;
var SD = CSPRep.SRC_DIRECTIVES;
var self = "http://localhost:" + POLICY_PORT;
cspr = CSPRep.fromString("policy-uri " + POLICY_URI, self);
cspr_static = CSPRep.fromString(POLICY_FROM_URI, self);
//"policy-uri failed to load"
do_check_neq(null,cspr);
// other directives inherit self
for(var i in SD) {
//SD[i] + " parsed wrong from policy uri"
do_check_equivalent(cspr._directives[SD[i]],
cspr_static._directives[SD[i]]);
}
});
/*
test(function test_CSPRep_fromPolicyURI_failswhenmixed() {
var cspr;
var self = "http://localhost:" + POLICY_PORT;
var closed_policy = CSPRep.fromString("allow 'none'");
var my_uri_policy = "policy-uri " + POLICY_URI;
//print(" --- Ignore the following two errors if they print ---");
cspr = CSPRep.fromString("allow *; " + my_uri_policy, self);
//"Parsing should fail when 'policy-uri' is mixed with allow directive"
do_check_equivalent(cspr, closed_policy);
cspr = CSPRep.fromString("img-src 'self'; " + my_uri_policy, self);
//"Parsing should fail when 'policy-uri' is mixed with other directives"
do_check_equivalent(cspr, closed_policy);
//print(" --- Stop ignoring errors that print ---\n");
});
*/
// TODO: test reporting
// TODO: test refinements (?)
// TODO: test 'eval' and 'inline' keywords
function run_test() {
function policyresponder(request,response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/csp", false);
response.bodyOutputStream.write(POLICY_FROM_URI, POLICY_FROM_URI.length);
}
//server.registerDirectory("/", nsILocalFileForBasePath);
httpServer.registerPathHandler("/policy", policyresponder);
httpServer.start(POLICY_PORT);
for(let i in tests) {
tests[i]();
}
//teardown
httpServer.stop(function() { });
do_test_finished();
}

View File

@ -948,6 +948,8 @@ pref("security.xpconnect.plugin.unrestricted", true);
// security-sensitive dialogs should delay button enabling. In milliseconds.
pref("security.dialog_enable_delay", 2000);
pref("security.csp.enable", true);
// Modifier key prefs: default to Windows settings,
// menu access key = alt, accelerator key = control.
// Use 17 for Ctrl, 18 for Alt, 224 for Meta, 0 for none. Mac settings in macprefs.js