forked from mirrors/gecko-dev
bug 515433, bug 515437: Content Security Policy (CSP) core
This commit is contained in:
parent
d3cc2ac124
commit
f2cab6a506
24 changed files with 3159 additions and 2 deletions
|
|
@ -326,6 +326,7 @@
|
||||||
@BINPATH@/components/nsWebHandlerApp.js
|
@BINPATH@/components/nsWebHandlerApp.js
|
||||||
@BINPATH@/components/nsBadCertHandler.js
|
@BINPATH@/components/nsBadCertHandler.js
|
||||||
@BINPATH@/components/nsFormAutoComplete.js
|
@BINPATH@/components/nsFormAutoComplete.js
|
||||||
|
@BINPATH@/components/contentSecurityPolicy.js
|
||||||
#ifdef XP_MACOSX
|
#ifdef XP_MACOSX
|
||||||
@BINPATH@/components/libalerts_s.dylib
|
@BINPATH@/components/libalerts_s.dylib
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,12 @@ struct JSPrincipals;
|
||||||
%}
|
%}
|
||||||
|
|
||||||
interface nsIURI;
|
interface nsIURI;
|
||||||
|
interface IContentSecurityPolicy;
|
||||||
|
|
||||||
[ptr] native JSContext(JSContext);
|
[ptr] native JSContext(JSContext);
|
||||||
[ptr] native JSPrincipals(JSPrincipals);
|
[ptr] native JSPrincipals(JSPrincipals);
|
||||||
|
|
||||||
[scriptable, uuid(b8268b9a-2403-44ed-81e3-614075c92034)]
|
[scriptable, uuid(799ab95c-0038-4e0f-b705-74c21f185bb5)]
|
||||||
interface nsIPrincipal : nsISerializable
|
interface nsIPrincipal : nsISerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -241,4 +242,9 @@ interface nsIPrincipal : nsISerializable
|
||||||
* one, this will return null. Getting this attribute never throws.
|
* one, this will return null. Getting this attribute never throws.
|
||||||
*/
|
*/
|
||||||
readonly attribute nsISupports certificate;
|
readonly attribute nsISupports certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Content Security Policy associated with this principal.
|
||||||
|
*/
|
||||||
|
[noscript] attribute IContentSecurityPolicy csp;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ protected:
|
||||||
|
|
||||||
DomainPolicy* mSecurityPolicy;
|
DomainPolicy* mSecurityPolicy;
|
||||||
|
|
||||||
|
nsCOMPtr<IContentSecurityPolicy> mCSP;
|
||||||
nsCOMPtr<nsIURI> mCodebase;
|
nsCOMPtr<nsIURI> mCodebase;
|
||||||
nsCOMPtr<nsIURI> mDomain;
|
nsCOMPtr<nsIURI> mDomain;
|
||||||
PRPackedBool mTrusted;
|
PRPackedBool mTrusted;
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,21 @@ nsNullPrincipal::GetURI(nsIURI** aURI)
|
||||||
return NS_EnsureSafeToReturn(mURI, aURI);
|
return NS_EnsureSafeToReturn(mURI, aURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsNullPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
|
||||||
|
{
|
||||||
|
// CSP on a null principal makes no sense
|
||||||
|
*aCsp = nsnull;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsNullPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
|
||||||
|
{
|
||||||
|
// CSP on a null principal makes no sense
|
||||||
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsNullPrincipal::GetDomain(nsIURI** aDomain)
|
nsNullPrincipal::GetDomain(nsIURI** aDomain)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
#include "nsIPrefService.h"
|
#include "nsIPrefService.h"
|
||||||
#include "nsIClassInfoImpl.h"
|
#include "nsIClassInfoImpl.h"
|
||||||
#include "nsDOMError.h"
|
#include "nsDOMError.h"
|
||||||
|
#include "IContentSecurityPolicy.h"
|
||||||
|
|
||||||
#include "nsPrincipal.h"
|
#include "nsPrincipal.h"
|
||||||
|
|
||||||
|
|
@ -774,6 +775,25 @@ nsPrincipal::GetCertificate(nsISupports** aCertificate)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
|
||||||
|
{
|
||||||
|
NS_IF_ADDREF(*aCsp = mCSP);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
|
||||||
|
{
|
||||||
|
// If CSP was already set, it should not be destroyed! Instead, it should
|
||||||
|
// get set anew when a new principal is created.
|
||||||
|
if (mCSP)
|
||||||
|
return NS_ERROR_ALREADY_INITIALIZED;
|
||||||
|
|
||||||
|
mCSP = aCsp;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsPrincipal::GetHashValue(PRUint32* aValue)
|
nsPrincipal::GetHashValue(PRUint32* aValue)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,20 @@ nsSystemPrincipal::GetHasCertificate(PRBool* aResult)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsSystemPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
|
||||||
|
{
|
||||||
|
*aCsp = nsnull;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
nsSystemPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
|
||||||
|
{
|
||||||
|
// CSP on a null principal makes no sense
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsSystemPrincipal::GetDomain(nsIURI** aDomain)
|
nsSystemPrincipal::GetDomain(nsIURI** aDomain)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
152
content/base/public/IContentSecurityPolicy.idl
Normal file
152
content/base/public/IContentSecurityPolicy.idl
Normal 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IContentSecurityPolicy
|
||||||
|
* Describes an XPCOM component used to model an enforce CSPs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[scriptable, uuid(AB36A2BF-CB32-4AA6-AB41-6B4E4444A221)]
|
||||||
|
interface IContentSecurityPolicy : 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);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
@ -113,6 +113,7 @@ XPIDLSRCS = \
|
||||||
nsIObjectLoadingContent.idl \
|
nsIObjectLoadingContent.idl \
|
||||||
nsIFrameLoader.idl \
|
nsIFrameLoader.idl \
|
||||||
nsIXMLHttpRequest.idl \
|
nsIXMLHttpRequest.idl \
|
||||||
|
IContentSecurityPolicy.idl \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
|
||||||
1295
content/base/src/CSPUtils.jsm
Normal file
1295
content/base/src/CSPUtils.jsm
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -79,6 +79,7 @@ CPPSRCS = \
|
||||||
nsContentUtils.cpp \
|
nsContentUtils.cpp \
|
||||||
nsCopySupport.cpp \
|
nsCopySupport.cpp \
|
||||||
nsCrossSiteListenerProxy.cpp \
|
nsCrossSiteListenerProxy.cpp \
|
||||||
|
nsCSPService.cpp \
|
||||||
nsDataDocumentContentPolicy.cpp \
|
nsDataDocumentContentPolicy.cpp \
|
||||||
nsDOMAttribute.cpp \
|
nsDOMAttribute.cpp \
|
||||||
nsDOMAttributeMap.cpp \
|
nsDOMAttributeMap.cpp \
|
||||||
|
|
@ -141,7 +142,14 @@ GQI_SRCS = contentbase.gqi
|
||||||
# static lib.
|
# static lib.
|
||||||
FORCE_STATIC_LIB = 1
|
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
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
|
|
|
||||||
445
content/base/src/contentSecurityPolicy.js
Normal file
445
content/base/src/contentSecurityPolicy.js
Normal file
|
|
@ -0,0 +1,445 @@
|
||||||
|
/* ***** 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.IContentSecurityPolicy]),
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// 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>" +
|
||||||
|
(typeof blockedUri === "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]);
|
||||||
214
content/base/src/nsCSPService.cpp
Normal file
214
content/base/src/nsCSPService.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* ***** 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 mozilla.org code.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is
|
||||||
|
* Mozilla Corporation.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Brandon Sterne <bsterne@mozilla.com>
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either of 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 "prlog.h"
|
||||||
|
#include "nsString.h"
|
||||||
|
#include "nsCOMPtr.h"
|
||||||
|
#include "nsIURI.h"
|
||||||
|
#include "nsIPrincipal.h"
|
||||||
|
#include "nsIObserver.h"
|
||||||
|
#include "nsIDocument.h"
|
||||||
|
#include "nsIContent.h"
|
||||||
|
#include "nsContentUtils.h"
|
||||||
|
#include "nsCSPService.h"
|
||||||
|
#include "IContentSecurityPolicy.h"
|
||||||
|
|
||||||
|
/* Keeps track of whether or not CSP is enabled */
|
||||||
|
static PRBool gCSPEnabled = PR_TRUE;
|
||||||
|
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
static PRLogModuleInfo* gCspPRLog;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CSPService::CSPService()
|
||||||
|
{
|
||||||
|
nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled);
|
||||||
|
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
if (!gCspPRLog)
|
||||||
|
gCspPRLog = PR_NewLogModule("CSP");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
CSPService::~CSPService()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMPL_ISUPPORTS1(CSPService, nsIContentPolicy)
|
||||||
|
|
||||||
|
/* nsIContentPolicy implementation */
|
||||||
|
NS_IMETHODIMP
|
||||||
|
CSPService::ShouldLoad(PRUint32 aContentType,
|
||||||
|
nsIURI *aContentLocation,
|
||||||
|
nsIURI *aRequestOrigin,
|
||||||
|
nsISupports *aRequestContext,
|
||||||
|
const nsACString &aMimeTypeGuess,
|
||||||
|
nsISupports *aExtra,
|
||||||
|
PRInt16 *aDecision)
|
||||||
|
{
|
||||||
|
if (!aContentLocation)
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
{
|
||||||
|
nsCAutoString location;
|
||||||
|
aContentLocation->GetSpec(location);
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("CSPService::ShouldLoad called for %s", location.get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// default decision, CSP can revise it if there's a policy to enforce
|
||||||
|
*aDecision = nsIContentPolicy::ACCEPT;
|
||||||
|
|
||||||
|
// No need to continue processing if CSP is disabled
|
||||||
|
if (!gCSPEnabled)
|
||||||
|
return NS_OK;
|
||||||
|
|
||||||
|
// find the nsDocument that initiated this request and see if it has a
|
||||||
|
// CSP policy object
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIDocument> doc;
|
||||||
|
nsCOMPtr<nsIPrincipal> principal;
|
||||||
|
nsCOMPtr<IContentSecurityPolicy> csp;
|
||||||
|
nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext));
|
||||||
|
if (node) {
|
||||||
|
doc = node->GetOwnerDoc();
|
||||||
|
}
|
||||||
|
if (!doc) {
|
||||||
|
doc = do_QueryInterface(aRequestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
principal = doc->NodePrincipal();
|
||||||
|
principal->GetCsp(getter_AddRefs(csp));
|
||||||
|
|
||||||
|
if (csp) {
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
nsAutoString policy;
|
||||||
|
csp->GetPolicy(policy);
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("Document has CSP: %s",
|
||||||
|
NS_ConvertUTF16toUTF8(policy).get()));
|
||||||
|
#endif
|
||||||
|
// obtain the enforcement decision
|
||||||
|
csp->ShouldLoad(aContentType,
|
||||||
|
aContentLocation,
|
||||||
|
aRequestOrigin,
|
||||||
|
aRequestContext,
|
||||||
|
aMimeTypeGuess,
|
||||||
|
aExtra,
|
||||||
|
aDecision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
else {
|
||||||
|
nsCAutoString uriSpec;
|
||||||
|
aContentLocation->GetSpec(uriSpec);
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("COULD NOT get nsIDocument for location: %s", uriSpec.get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
CSPService::ShouldProcess(PRUint32 aContentType,
|
||||||
|
nsIURI *aContentLocation,
|
||||||
|
nsIURI *aRequestOrigin,
|
||||||
|
nsISupports *aRequestContext,
|
||||||
|
const nsACString &aMimeTypeGuess,
|
||||||
|
nsISupports *aExtra,
|
||||||
|
PRInt16 *aDecision)
|
||||||
|
{
|
||||||
|
if (!aContentLocation)
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
|
||||||
|
// default decision is to accept the item
|
||||||
|
*aDecision = nsIContentPolicy::ACCEPT;
|
||||||
|
|
||||||
|
// No need to continue processing if CSP is disabled
|
||||||
|
if (!gCSPEnabled)
|
||||||
|
return NS_OK;
|
||||||
|
|
||||||
|
// find the nsDocument that initiated this request and see if it has a
|
||||||
|
// CSP policy object
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIDocument> doc;
|
||||||
|
nsCOMPtr<nsIPrincipal> principal;
|
||||||
|
nsCOMPtr<IContentSecurityPolicy> csp;
|
||||||
|
nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext));
|
||||||
|
if (node) {
|
||||||
|
doc = node->GetOwnerDoc();
|
||||||
|
}
|
||||||
|
if (!doc) {
|
||||||
|
doc = do_QueryInterface(aRequestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc) {
|
||||||
|
principal = doc->NodePrincipal();
|
||||||
|
principal->GetCsp(getter_AddRefs(csp));
|
||||||
|
|
||||||
|
if (csp) {
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
nsAutoString policy;
|
||||||
|
csp->GetPolicy(policy);
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("shouldProcess - document has policy: %s",
|
||||||
|
NS_ConvertUTF16toUTF8(policy).get()));
|
||||||
|
#endif
|
||||||
|
// obtain the enforcement decision
|
||||||
|
csp->ShouldProcess(aContentType,
|
||||||
|
aContentLocation,
|
||||||
|
aRequestOrigin,
|
||||||
|
aRequestContext,
|
||||||
|
aMimeTypeGuess,
|
||||||
|
aExtra,
|
||||||
|
aDecision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
else {
|
||||||
|
nsCAutoString uriSpec;
|
||||||
|
aContentLocation->GetSpec(uriSpec);
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("COULD NOT get nsIDocument for location: %s", uriSpec.get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
57
content/base/src/nsCSPService.h
Normal file
57
content/base/src/nsCSPService.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* ***** 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 mozilla.org code.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is
|
||||||
|
* Mozilla Corporation.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Brandon Sterne <bsterne@mozilla.com>
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either of 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 "nsXPCOM.h"
|
||||||
|
#include "nsIContentPolicy.h"
|
||||||
|
|
||||||
|
#define CSPSERVICE_CONTRACTID "@mozilla.org/cspservice;1"
|
||||||
|
#define CSPSERVICE_CID \
|
||||||
|
{ 0x8d2f40b2, 0x4875, 0x4c95, \
|
||||||
|
{ 0x97, 0xd9, 0x3f, 0x7d, 0xca, 0x2c, 0xb4, 0x60 } }
|
||||||
|
class CSPService : public nsIContentPolicy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NS_DECL_ISUPPORTS
|
||||||
|
NS_DECL_NSICONTENTPOLICY
|
||||||
|
|
||||||
|
CSPService();
|
||||||
|
virtual ~CSPService();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PRBool mEnabled;
|
||||||
|
};
|
||||||
|
|
@ -181,6 +181,9 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID);
|
||||||
#include "nsSVGUtils.h"
|
#include "nsSVGUtils.h"
|
||||||
#endif // MOZ_SMIL
|
#endif // MOZ_SMIL
|
||||||
|
|
||||||
|
// FOR CSP (autogenerated by xpidl)
|
||||||
|
#include "IContentSecurityPolicy.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef MOZ_LOGGING
|
#ifdef MOZ_LOGGING
|
||||||
// so we can get logging even in release builds
|
// so we can get logging even in release builds
|
||||||
|
|
@ -188,8 +191,12 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID);
|
||||||
#endif
|
#endif
|
||||||
#include "prlog.h"
|
#include "prlog.h"
|
||||||
|
|
||||||
|
/* Keeps track of whether or not CSP is enabled */
|
||||||
|
static PRBool gCSPEnabled = PR_TRUE;
|
||||||
|
|
||||||
#ifdef PR_LOGGING
|
#ifdef PR_LOGGING
|
||||||
static PRLogModuleInfo* gDocumentLeakPRLog;
|
static PRLogModuleInfo* gDocumentLeakPRLog;
|
||||||
|
static PRLogModuleInfo* gCspPRLog;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -1495,8 +1502,13 @@ nsDocument::nsDocument(const char* aContentType)
|
||||||
if (gDocumentLeakPRLog)
|
if (gDocumentLeakPRLog)
|
||||||
PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
|
PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
|
||||||
("DOCUMENT %p created", this));
|
("DOCUMENT %p created", this));
|
||||||
|
|
||||||
|
if (!gCspPRLog)
|
||||||
|
gCspPRLog = PR_NewLogModule("CSP");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled);
|
||||||
|
|
||||||
// Start out mLastStyleSheetSet as null, per spec
|
// Start out mLastStyleSheetSet as null, per spec
|
||||||
SetDOMStringToNull(mLastStyleSheetSet);
|
SetDOMStringToNull(mLastStyleSheetSet);
|
||||||
}
|
}
|
||||||
|
|
@ -2251,10 +2263,114 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
|
||||||
RetrieveRelevantHeaders(aChannel);
|
RetrieveRelevantHeaders(aChannel);
|
||||||
|
|
||||||
mChannel = aChannel;
|
mChannel = aChannel;
|
||||||
|
|
||||||
|
nsresult rv = InitCSP();
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsDocument::InitCSP()
|
||||||
|
{
|
||||||
|
if (gCSPEnabled) {
|
||||||
|
nsAutoString cspHeaderValue;
|
||||||
|
nsAutoString cspROHeaderValue;
|
||||||
|
|
||||||
|
this->GetHeaderData(nsGkAtoms::headerCSP, cspHeaderValue);
|
||||||
|
this->GetHeaderData(nsGkAtoms::headerCSPReportOnly, cspROHeaderValue);
|
||||||
|
|
||||||
|
PRBool system = PR_FALSE;
|
||||||
|
nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
|
||||||
|
|
||||||
|
if (NS_SUCCEEDED(ssm->IsSystemPrincipal(NodePrincipal(), &system)) && system) {
|
||||||
|
// only makes sense to register new CSP if this document is not priviliged
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty()) {
|
||||||
|
// no CSP header present, stop processing
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP header specified for document %p", this));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<IContentSecurityPolicy> mCSP;
|
||||||
|
mCSP = do_CreateInstance("@mozilla.org/contentsecuritypolicy;1", &rv);
|
||||||
|
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Failed to create CSP object: %x", rv));
|
||||||
|
#endif
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the request context for violation reports
|
||||||
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
||||||
|
mCSP->ScanRequestData(httpChannel);
|
||||||
|
|
||||||
|
// Start parsing the policy
|
||||||
|
nsCOMPtr<nsIURI> chanURI;
|
||||||
|
mChannel->GetURI(getter_AddRefs(chanURI));
|
||||||
|
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP Loaded"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ReportOnly mode is enabled *only* if there are no regular-strength CSP
|
||||||
|
// headers present. If there are, then we ignore the ReportOnly mode and
|
||||||
|
// toss a warning into the error console, proceeding with enforcing the
|
||||||
|
// regular-strength CSP.
|
||||||
|
if (cspHeaderValue.IsEmpty()) {
|
||||||
|
mCSP->SetReportOnlyMode(true);
|
||||||
|
mCSP->RefinePolicy(cspROHeaderValue, chanURI);
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
{
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("CSP (report only) refined, policy: \"%s\"",
|
||||||
|
NS_ConvertUTF16toUTF8(cspROHeaderValue).get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
//XXX(sstamm): maybe we should post a warning when both read only and regular
|
||||||
|
// CSP headers are present.
|
||||||
|
mCSP->RefinePolicy(cspHeaderValue, chanURI);
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
{
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("CSP refined, policy: \"%s\"",
|
||||||
|
NS_ConvertUTF16toUTF8(cspHeaderValue).get()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy into principal
|
||||||
|
nsIPrincipal* principal = GetPrincipal();
|
||||||
|
|
||||||
|
if (principal) {
|
||||||
|
principal->SetCsp(mCSP);
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("Inserted CSP into principal %p", principal));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("Couldn't copy CSP into absent principal %p", principal));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef PR_LOGGING
|
||||||
|
else { //CSP was not enabled!
|
||||||
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
||||||
|
("CSP is disabled, skipping CSP init for document %p", this));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsDocument::StopDocumentLoad()
|
nsDocument::StopDocumentLoad()
|
||||||
{
|
{
|
||||||
|
|
@ -6642,6 +6758,8 @@ nsDocument::RetrieveRelevantHeaders(nsIChannel *aChannel)
|
||||||
"content-disposition",
|
"content-disposition",
|
||||||
"refresh",
|
"refresh",
|
||||||
"x-dns-prefetch-control",
|
"x-dns-prefetch-control",
|
||||||
|
"x-content-security-policy",
|
||||||
|
"x-content-security-policy-read-only",
|
||||||
// add more http headers if you need
|
// add more http headers if you need
|
||||||
// XXXbz don't add content-location support without reading bug
|
// XXXbz don't add content-location support without reading bug
|
||||||
// 238654 and its dependencies/dups first.
|
// 238654 and its dependencies/dups first.
|
||||||
|
|
|
||||||
|
|
@ -1211,6 +1211,8 @@ private:
|
||||||
void PostUnblockOnloadEvent();
|
void PostUnblockOnloadEvent();
|
||||||
void DoUnblockOnload();
|
void DoUnblockOnload();
|
||||||
|
|
||||||
|
nsresult InitCSP();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See if aDocument is a child of this. If so, return the frame element in
|
* See if aDocument is a child of this. If so, return the frame element in
|
||||||
* this document that holds currentDoc (or an ancestor).
|
* this document that holds currentDoc (or an ancestor).
|
||||||
|
|
|
||||||
|
|
@ -1007,6 +1007,8 @@ GK_ATOM(withParam, "with-param")
|
||||||
GK_ATOM(wizard, "wizard")
|
GK_ATOM(wizard, "wizard")
|
||||||
GK_ATOM(wrap, "wrap")
|
GK_ATOM(wrap, "wrap")
|
||||||
GK_ATOM(headerDNSPrefetchControl,"x-dns-prefetch-control")
|
GK_ATOM(headerDNSPrefetchControl,"x-dns-prefetch-control")
|
||||||
|
GK_ATOM(headerCSP, "x-content-security-policy")
|
||||||
|
GK_ATOM(headerCSPReportOnly, "x-content-security-policy-report-only")
|
||||||
GK_ATOM(xml, "xml")
|
GK_ATOM(xml, "xml")
|
||||||
GK_ATOM(xmlns, "xmlns")
|
GK_ATOM(xmlns, "xmlns")
|
||||||
GK_ATOM(xmp, "xmp")
|
GK_ATOM(xmp, "xmp")
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,10 @@ _TEST_FILES = test_bug5141.html \
|
||||||
test_bug503481b.html \
|
test_bug503481b.html \
|
||||||
file_bug503481b_inner.html \
|
file_bug503481b_inner.html \
|
||||||
test_viewport_scroll.html \
|
test_viewport_scroll.html \
|
||||||
|
test_CSP.html \
|
||||||
|
file_CSP.sjs \
|
||||||
|
file_CSP_main.html \
|
||||||
|
file_CSP_main.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
# Disabled; see bug 492181
|
# Disabled; see bug 492181
|
||||||
|
|
|
||||||
44
content/base/test/file_CSP.sjs
Normal file
44
content/base/test/file_CSP.sjs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
// SJS file for CSP mochitests
|
||||||
|
|
||||||
|
function handleRequest(request, response)
|
||||||
|
{
|
||||||
|
var query = {};
|
||||||
|
request.queryString.split('&').forEach(function (val) {
|
||||||
|
var [name, value] = val.split('=');
|
||||||
|
query[name] = unescape(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
var isPreflight = request.method == "OPTIONS";
|
||||||
|
|
||||||
|
|
||||||
|
//avoid confusing cache behaviors
|
||||||
|
response.setHeader("Cache-Control", "no-cache", false);
|
||||||
|
|
||||||
|
if ("main" in query) {
|
||||||
|
var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||||
|
.createInstance(Components.interfaces.nsIXMLHttpRequest);
|
||||||
|
//serve the main page with a CSP header!
|
||||||
|
// -- anything served from 'self' (localhost:8888) will be allowed,
|
||||||
|
// -- anything served from other hosts (example.com:80) will be blocked.
|
||||||
|
// -- XHR tests are set up in the file_CSP_main.js file which is sourced.
|
||||||
|
response.setHeader("X-Content-Security-Policy",
|
||||||
|
"allow 'self'",
|
||||||
|
false);
|
||||||
|
xhr.open("GET", "http://localhost:8888/tests/content/base/test/file_CSP_main.html", false);
|
||||||
|
xhr.send(null);
|
||||||
|
if(xhr.status == 200) {
|
||||||
|
response.write(xhr.responseText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ("type" in query) {
|
||||||
|
response.setHeader("Content-Type", unescape(query['type']), false);
|
||||||
|
} else {
|
||||||
|
response.setHeader("Content-Type", "text/html", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("content" in query) {
|
||||||
|
response.setHeader("Content-Type", "text/html", false);
|
||||||
|
response.write(unescape(query['content']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
content/base/test/file_CSP_main.html
Normal file
55
content/base/test/file_CSP_main.html
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel='stylesheet' type='text/css'
|
||||||
|
href='http://example.org/tests/content/base/test/file_CSP.sjs?testid=style_bad&type=text/css' />
|
||||||
|
<link rel='stylesheet' type='text/css'
|
||||||
|
href='file_CSP.sjs?testid=style_good&type=text/css' />
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* CSS font embedding tests */
|
||||||
|
@font-face {
|
||||||
|
font-family: "arbitrary_good";
|
||||||
|
src: url('file_CSP.sjs?testid=font_good&type=application/octet-stream');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "arbitrary_bad";
|
||||||
|
src: url('http://example.org/tests/content/base/test/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_arbitrary_good { font-family: "arbitrary_good"; }
|
||||||
|
.div_arbitrary_bad { font-family: "arbitrary_bad"; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- these should be stopped by CSP. :) -->
|
||||||
|
<img src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=img_bad&type=img/png" />
|
||||||
|
<audio src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=media_bad&type=audio/vorbis"></audio>
|
||||||
|
<script src='http://example.org/tests/content/base/test/file_CSP.sjs?testid=script_bad&type=text/javascript'></script>
|
||||||
|
<iframe src='http://example.org/tests/content/base/test/file_CSP.sjs?testid=frame_bad&content=FAIL'></iframe>
|
||||||
|
<object width="10" height="10">
|
||||||
|
<param name="movie" value="http://example.org/tests/content/base/test/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash">
|
||||||
|
<embed src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash"></embed>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
<!-- these should load ok. :) -->
|
||||||
|
<img src="file_CSP.sjs?testid=img_good&type=img/png" />
|
||||||
|
<audio src="file_CSP.sjs?testid=media_good&type=audio/vorbis"></audio>
|
||||||
|
<script src='file_CSP.sjs?testid=script_good&type=text/javascript'></script>
|
||||||
|
<iframe src='file_CSP.sjs?testid=frame_good&content=PASS'></iframe>
|
||||||
|
|
||||||
|
<object width="10" height="10">
|
||||||
|
<param name="movie" value="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash">
|
||||||
|
<embed src="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash"></embed>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
<!-- XHR tests... they're taken care of in this script,
|
||||||
|
and since the URI doesn't have any 'testid' values,
|
||||||
|
it will just be ignored by the test framework. -->
|
||||||
|
<script src='file_CSP_main.js'></script>
|
||||||
|
|
||||||
|
<!-- Support elements for the @font-face test -->
|
||||||
|
<div class="div_arbitrary_good">arbitrary good</div>
|
||||||
|
<div class="div_arbitrary_bad">arbitrary_bad</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
content/base/test/file_CSP_main.js
Normal file
16
content/base/test/file_CSP_main.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// some javascript for the CSP XHR tests
|
||||||
|
//
|
||||||
|
|
||||||
|
try {
|
||||||
|
var xhr_good = new XMLHttpRequest();
|
||||||
|
var xhr_good_uri ="http://localhost:8888/tests/content/base/test/file_CSP.sjs?testid=xhr_good";
|
||||||
|
xhr_good.open("GET", xhr_good_uri, true);
|
||||||
|
xhr_good.send(null);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var xhr_bad = new XMLHttpRequest();
|
||||||
|
var xhr_bad_uri ="http://example.com/tests/content/base/test/file_CSP.sjs?testid=xhr_bad";
|
||||||
|
xhr_bad.open("GET", xhr_bad_uri, true);
|
||||||
|
xhr_bad.send(null);
|
||||||
|
} catch(e) {}
|
||||||
127
content/base/test/test_CSP.html
Normal file
127
content/base/test/test_CSP.html
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test for Content Security Policy Connections</title>
|
||||||
|
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||||
|
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content" style="display: none">
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
|
||||||
|
<script class="testbody" type="text/javascript">
|
||||||
|
|
||||||
|
var path = "/tests/content/base/test/";
|
||||||
|
|
||||||
|
// These are test results: -1 means it hasn't run,
|
||||||
|
// true/false is the pass/fail result.
|
||||||
|
window.tests = {
|
||||||
|
img_good: -1,
|
||||||
|
img_bad: -1,
|
||||||
|
style_good: -1,
|
||||||
|
style_bad: -1,
|
||||||
|
frame_good: -1,
|
||||||
|
frame_bad: -1,
|
||||||
|
script_good: -1,
|
||||||
|
script_bad: -1,
|
||||||
|
xhr_good: -1,
|
||||||
|
xhr_bad: -1,
|
||||||
|
media_good: -1,
|
||||||
|
media_bad: -1,
|
||||||
|
font_good: -1,
|
||||||
|
font_bad: -1,
|
||||||
|
object_good: -1,
|
||||||
|
object_bad: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// This is used to watch the blocked data bounce off CSP and allowed data
|
||||||
|
// get sent out to the wire.
|
||||||
|
function examiner() {
|
||||||
|
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||||
|
var obsvc = Components.classes['@mozilla.org/observer-service;1']
|
||||||
|
.getService(Components.interfaces.nsIObserverService);
|
||||||
|
obsvc.addObserver(this, "csp-on-violate-policy", false);
|
||||||
|
obsvc.addObserver(this, "http-on-modify-request", false);
|
||||||
|
}
|
||||||
|
examiner.prototype = {
|
||||||
|
observe: function(subject, topic, data) {
|
||||||
|
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||||
|
// subject should be an nsURI, and should be either allowed or blocked.
|
||||||
|
if(!subject.QueryInterface)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var testpat = new RegExp("testid=([a-z0-9_]+)");
|
||||||
|
|
||||||
|
//_good things better be allowed!
|
||||||
|
//_bad things better be stopped!
|
||||||
|
|
||||||
|
if (topic === "http-on-modify-request") {
|
||||||
|
//these things were allowed by CSP
|
||||||
|
var uri = subject.QueryInterface(Components.interfaces.nsIHttpChannel).URI;
|
||||||
|
if (!testpat.test(uri.asciiSpec)) return;
|
||||||
|
var testid = testpat.exec(uri.asciiSpec)[1];
|
||||||
|
window.testResult(testid,
|
||||||
|
/_good/.test(testid),
|
||||||
|
uri.asciiSpec + " allowed by csp");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(topic === "csp-on-violate-policy") {
|
||||||
|
//these were blocked... record that they were blocked
|
||||||
|
var uri = subject.QueryInterface(Components.interfaces.nsIURI);
|
||||||
|
if (!testpat.test(uri.asciiSpec)) return;
|
||||||
|
var testid = testpat.exec(uri.asciiSpec)[1];
|
||||||
|
window.testResult(testid,
|
||||||
|
/_bad/.test(testid),
|
||||||
|
uri.asciiSpec + " blocked by \"" + data + "\"");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// must eventually call this to remove the listener,
|
||||||
|
// or mochitests might get borked.
|
||||||
|
remove: function() {
|
||||||
|
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||||
|
var obsvc = Components.classes['@mozilla.org/observer-service;1']
|
||||||
|
.getService(Components.interfaces.nsIObserverService);
|
||||||
|
obsvc.removeObserver(this, "csp-on-violate-policy");
|
||||||
|
obsvc.removeObserver(this, "http-on-modify-request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.examiner = new examiner();
|
||||||
|
|
||||||
|
window.testResult = function(testname, result, msg) {
|
||||||
|
|
||||||
|
//test already complete.... forget it... remember the first result.
|
||||||
|
if (window.tests[testname] != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.tests[testname] = result;
|
||||||
|
is(result, true, testname + ' test: ' + msg);
|
||||||
|
|
||||||
|
// if any test is incomplete, keep waiting
|
||||||
|
for (var v in window.tests)
|
||||||
|
if(tests[v] == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// ... otherwise, finish
|
||||||
|
window.examiner.remove();
|
||||||
|
SimpleTest.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
||||||
|
// save this for last so that our listeners are registered.
|
||||||
|
// ... this loads the testbed of good and bad requests.
|
||||||
|
document.getElementById('cspframe').src = 'file_CSP.sjs?main=1';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
497
content/base/test/unit/test_csputils.js
Normal file
497
content/base/test/unit/test_csputils.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -268,6 +268,7 @@ static void Shutdown();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "nsGeolocation.h"
|
#include "nsGeolocation.h"
|
||||||
|
#include "nsCSPService.h"
|
||||||
|
|
||||||
// Transformiix
|
// Transformiix
|
||||||
/* {0C351177-0159-4500-86B0-A219DFDE4258} */
|
/* {0C351177-0159-4500-86B0-A219DFDE4258} */
|
||||||
|
|
@ -848,6 +849,60 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGeolocation, Init)
|
||||||
|
|
||||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
|
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
|
||||||
|
|
||||||
|
static NS_METHOD
|
||||||
|
CSPServiceRegistration(nsIComponentManager *aCompMgr,
|
||||||
|
nsIFile *aPath,
|
||||||
|
const char *registryLocation,
|
||||||
|
const char *componentType,
|
||||||
|
const nsModuleComponentInfo *info)
|
||||||
|
{
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv);
|
||||||
|
if (NS_FAILED(rv))
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
nsCOMPtr<nsICategoryManager> catman;
|
||||||
|
rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID,
|
||||||
|
NS_GET_IID(nsICategoryManager),
|
||||||
|
getter_AddRefs(catman));
|
||||||
|
if (NS_FAILED(rv))
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
nsXPIDLCString previous;
|
||||||
|
rv = catman->AddCategoryEntry("content-policy",
|
||||||
|
"CSPService",
|
||||||
|
CSPSERVICE_CONTRACTID,
|
||||||
|
PR_TRUE,
|
||||||
|
PR_TRUE,
|
||||||
|
getter_Copies(previous));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NS_METHOD
|
||||||
|
CSPServiceUnregistration(nsIComponentManager *aCompMgr,
|
||||||
|
nsIFile *aPath,
|
||||||
|
const char *registryLocation,
|
||||||
|
const nsModuleComponentInfo *info){
|
||||||
|
nsresult rv;
|
||||||
|
|
||||||
|
nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv);
|
||||||
|
if (NS_FAILED(rv)) return rv;
|
||||||
|
|
||||||
|
nsCOMPtr<nsICategoryManager> catman;
|
||||||
|
rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID,
|
||||||
|
NS_GET_IID(nsICategoryManager),
|
||||||
|
getter_AddRefs(catman));
|
||||||
|
if (NS_FAILED(rv)) return rv;
|
||||||
|
|
||||||
|
rv = catman->DeleteCategoryEntry("content-policy",
|
||||||
|
"CSPService",
|
||||||
|
PR_TRUE);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_GENERIC_FACTORY_CONSTRUCTOR(CSPService)
|
||||||
|
|
||||||
// The list of components we register
|
// The list of components we register
|
||||||
static const nsModuleComponentInfo gComponents[] = {
|
static const nsModuleComponentInfo gComponents[] = {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
|
@ -1453,6 +1508,12 @@ static const nsModuleComponentInfo gComponents[] = {
|
||||||
"@mozilla.org/focus-manager;1",
|
"@mozilla.org/focus-manager;1",
|
||||||
CreateFocusManager },
|
CreateFocusManager },
|
||||||
|
|
||||||
|
{ "Content Security Policy Service",
|
||||||
|
CSPSERVICE_CID,
|
||||||
|
CSPSERVICE_CONTRACTID,
|
||||||
|
CSPServiceConstructor,
|
||||||
|
CSPServiceRegistration,
|
||||||
|
CSPServiceUnregistration },
|
||||||
|
|
||||||
{ "Event Listener Service",
|
{ "Event Listener Service",
|
||||||
NS_EVENTLISTENERSERVICE_CID,
|
NS_EVENTLISTENERSERVICE_CID,
|
||||||
|
|
|
||||||
|
|
@ -950,6 +950,8 @@ pref("security.xpconnect.plugin.unrestricted", true);
|
||||||
// security-sensitive dialogs should delay button enabling. In milliseconds.
|
// security-sensitive dialogs should delay button enabling. In milliseconds.
|
||||||
pref("security.dialog_enable_delay", 2000);
|
pref("security.dialog_enable_delay", 2000);
|
||||||
|
|
||||||
|
pref("security.csp.enable", true);
|
||||||
|
|
||||||
// Modifier key prefs: default to Windows settings,
|
// Modifier key prefs: default to Windows settings,
|
||||||
// menu access key = alt, accelerator key = control.
|
// 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
|
// Use 17 for Ctrl, 18 for Alt, 224 for Meta, 0 for none. Mac settings in macprefs.js
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue