Bug 992096 - Implement Sub Resource Integrity [1/2]. r=baku,r=ckerschb

Code changes
This commit is contained in:
Francois Marier 2015-08-12 20:19:11 -07:00
parent bd8676f33f
commit 6349d3c212
34 changed files with 898 additions and 42 deletions

View File

@ -359,6 +359,7 @@ Florian Scholz <elchi3@elchi3.de>
<flying@dom.natm.ru>
France Telecom Research and Development
Franck
Francois Marier <francois@fmarier.org>
Frank Tang <ftang@netscape.com>
Frank Yan <fyan@mozilla.com>
Franky Braem

View File

@ -4807,6 +4807,7 @@ var Utils = {
case "CORS":
case "Iframe Sandbox":
case "Tracking Protection":
case "Sub-resource Integrity":
return CATEGORY_SECURITY;
default:

View File

@ -432,6 +432,7 @@ LOCAL_INCLUDES += [
'/layout/svg',
'/layout/xul',
'/netwerk/base',
'/security/manager/ssl',
'/widget',
'/xpcom/ds',
]

View File

@ -51,6 +51,16 @@
#include "nsParserConstants.h"
#include "nsSandboxFlags.h"
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
using namespace mozilla;
PRLogModuleInfo* gContentSinkLogModuleInfo;
@ -750,12 +760,23 @@ nsContentSink::ProcessStyleLink(nsIContent* aElement,
aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
"We only expect processing instructions here");
nsAutoString integrity;
if (aElement) {
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
}
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsContentSink::ProcessStyleLink, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
}
// If this is a fragment parser, we don't want to observe.
// We don't support CORS for processing instructions
bool isAlternate;
rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
CORS_NONE, mDocument->GetReferrerPolicy(),
mRunsToCompletion ? nullptr : this, &isAlternate);
integrity, mRunsToCompletion ? nullptr : this,
&isAlternate);
NS_ENSURE_SUCCESS(rv, rv);
if (!isAlternate && !mRunsToCompletion) {

View File

@ -9876,7 +9876,8 @@ NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
void
nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
const nsAString& aCrossOriginAttr,
const ReferrerPolicy aReferrerPolicy)
const ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
// The CSSLoader will retain this object after we return.
nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
@ -9886,7 +9887,7 @@ nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
NS_LossyConvertUTF16toASCII(charset),
obs,
Element::StringToCORSMode(aCrossOriginAttr),
aReferrerPolicy);
aReferrerPolicy, aIntegrity);
}
nsresult

View File

@ -1147,7 +1147,8 @@ public:
virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
const nsAString& aCrossOriginAttr,
ReferrerPolicy aReferrerPolicy) override;
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity) override;
virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
mozilla::CSSStyleSheet** sheet) override;

View File

@ -489,6 +489,7 @@ GK_ATOM(instanceOf, "instanceOf")
GK_ATOM(int32, "int32")
GK_ATOM(int64, "int64")
GK_ATOM(integer, "integer")
GK_ATOM(integrity, "integrity")
GK_ATOM(intersection, "intersection")
GK_ATOM(is, "is")
GK_ATOM(iscontainer, "iscontainer")

View File

@ -152,8 +152,8 @@ typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0xbbce44c8, 0x22fe, 0x404f, \
{ 0x9e, 0x71, 0x23, 0x1d, 0xf4, 0xcc, 0x8e, 0x34 } }
{ 0x6d18ec0b, 0x1f68, 0x4ae6, \
{ 0x8b, 0x3d, 0x8d, 0x7d, 0x8b, 0x8e, 0x28, 0xd4 } }
// Enum for requesting a particular type of document when creating a doc
enum DocumentFlavor {
@ -2023,7 +2023,8 @@ public:
*/
virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy) = 0;
ReferrerPolicyEnum aReferrerPolicy,
const nsAString& aIntegrity) = 0;
/**
* Called by the chrome registry to load style sheets. Can be put

View File

@ -53,9 +53,21 @@
#include "mozilla/Attributes.h"
#include "mozilla/unused.h"
#include "mozilla/dom/SRICheck.h"
#include "nsIScriptError.h"
static PRLogModuleInfo* gCspPRLog;
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
using namespace mozilla;
using namespace mozilla::dom;
@ -606,7 +618,22 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
if (!request) {
// no usable preload
request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
SRIMetadata sriMetadata;
{
nsAutoString integrity;
scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
integrity);
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsScriptLoader::ProcessScriptElement, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
}
}
request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
sriMetadata);
request->mURI = scriptURI;
request->mIsInline = false;
request->mLoading = true;
@ -720,7 +747,8 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
}
// Inline scripts ignore ther CORS mode and are always CORS_NONE
request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
request = new nsScriptLoadRequest(aElement, version, CORS_NONE,
SRIMetadata()); // SRI doesn't apply
request->mJSVersion = version;
request->mLoading = false;
request->mIsInline = true;
@ -1408,8 +1436,15 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
NS_ASSERTION(request, "null request in stream complete handler");
NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
aString);
nsresult rv = NS_ERROR_SRI_CORRUPT;
if (request->mIntegrity.IsEmpty() ||
NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity,
request->mURI,
request->mCORSMode, aStringLen,
aString, mDocument))) {
rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
}
if (NS_FAILED(rv)) {
/*
* Handle script not loading error because source was a tracking URL.
@ -1603,6 +1638,7 @@ void
nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
const nsAString &aType,
const nsAString &aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
const mozilla::net::ReferrerPolicy aReferrerPolicy)
{
@ -1611,9 +1647,18 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
return;
}
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsScriptLoader::PreloadURI, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
}
nsRefPtr<nsScriptLoadRequest> request =
new nsScriptLoadRequest(nullptr, 0,
Element::StringToCORSMode(aCrossOrigin));
Element::StringToCORSMode(aCrossOrigin),
sriMetadata);
request->mURI = aURI;
request->mIsInline = false;
request->mLoading = true;

View File

@ -19,6 +19,7 @@
#include "nsIDocument.h"
#include "nsIStreamLoader.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/LinkedList.h"
#include "mozilla/net/ReferrerPolicy.h"
@ -56,7 +57,8 @@ class nsScriptLoadRequest final : public nsISupports,
public:
nsScriptLoadRequest(nsIScriptElement* aElement,
uint32_t aVersion,
mozilla::CORSMode aCORSMode)
mozilla::CORSMode aCORSMode,
const mozilla::dom::SRIMetadata &aIntegrity)
: mElement(aElement),
mLoading(true),
mIsInline(true),
@ -71,6 +73,7 @@ public:
mJSVersion(aVersion),
mLineNo(1),
mCORSMode(aCORSMode),
mIntegrity(aIntegrity),
mReferrerPolicy(mozilla::net::RP_Default)
{
}
@ -122,6 +125,7 @@ public:
nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
int32_t mLineNo;
const mozilla::CORSMode mCORSMode;
const mozilla::dom::SRIMetadata mIntegrity;
mozilla::net::ReferrerPolicy mReferrerPolicy;
};
@ -367,11 +371,13 @@ public:
* @param aType The type parameter for the script.
* @param aCrossOrigin The crossorigin attribute for the script.
* Void if not present.
* @param aIntegrity The expect hash url, if avail, of the request
* @param aScriptFromHead Whether or not the script was a child of head
*/
virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
const nsAString &aType,
const nsAString &aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead,
const mozilla::net::ReferrerPolicy aReferrerPolicy);

View File

@ -421,13 +421,21 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
scopeElement, aObserver, &doneLoading, &isAlternate);
}
else {
nsAutoString integrity;
thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
if (!integrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
NS_ConvertUTF16toUTF8(integrity).get()));
}
// XXXbz clone the URI here to work around content policies modifying URIs.
nsCOMPtr<nsIURI> clonedURI;
uri->Clone(getter_AddRefs(clonedURI));
NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
rv = doc->CSSLoader()->
LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
GetCORSMode(), doc->GetReferrerPolicy(),
GetCORSMode(), doc->GetReferrerPolicy(), integrity,
aObserver, &isAlternate);
if (NS_FAILED(rv)) {
// Don't propagate LoadStyleLink() errors further than this, since some

View File

@ -214,6 +214,11 @@ HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
aResult.ParseAtomArray(aValue);
return true;
}
if (aAttribute == nsGkAtoms::integrity) {
aResult.ParseStringOrAtom(aValue);
return true;
}
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,

View File

@ -143,6 +143,14 @@ public:
{
SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
}
void GetIntegrity(nsAString& aIntegrity) const
{
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
}
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
{
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
}
already_AddRefed<nsIDocument> GetImport();
already_AddRefed<ImportLoader> GetImportLoader()

View File

@ -75,10 +75,16 @@ HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
const nsAString& aValue,
nsAttrValue& aResult)
{
if (aNamespaceID == kNameSpaceID_None &&
aAttribute == nsGkAtoms::crossorigin) {
ParseCORSValue(aValue, aResult);
return true;
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::crossorigin) {
ParseCORSValue(aValue, aResult);
return true;
}
if (aAttribute == nsGkAtoms::integrity) {
aResult.ParseStringOrAtom(aValue);
return true;
}
}
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,

View File

@ -79,6 +79,14 @@ public:
{
SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
}
void GetIntegrity(nsAString& aIntegrity)
{
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
}
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
{
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
}
bool Async();
void SetAsync(bool aValue, ErrorResult& rv);

View File

@ -53,6 +53,21 @@ LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a
# LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
# Sub-Resource Integrity
# LOCALIZATION NOTE: Do not translate "script" or "integrity"
MalformedIntegrityURI=The script element has a malformed URI in its integrity attribute: "%1$S". The correct format is "<hash algorithm>-<hash value>".
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
# LOCALIZATION NOTE: Do not translate "integrity"
InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
# LOCALIZATION NOTE: Do not translate "integrity"
IntegrityMismatch=None of the "%1$S" hashes in the integrity attribute match the content of the subresource.
IneligibleResource="%1$S" is not eligible for integrity checks since it's neither CORS-enabled nor same-origin.
# LOCALIZATION NOTE: Do not translate "integrity"
UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: "%1$S"
# LOCALIZATION NOTE: Do not translate "integrity"
NoValidMetadata=The integrity attribute does not contain any valid metadata.
# LOCALIZATION NOTE: Do not translate "SSL 3.0".
WeakProtocolVersionWarning=This site uses the protocol SSL 3.0 for encryption, which is deprecated and insecure.
# LOCALIZATION NOTE: Do not translate "RC4".

295
dom/security/SRICheck.cpp Normal file
View File

@ -0,0 +1,295 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SRICheck.h"
#include "mozilla/Base64.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
#include "nsICryptoHash.h"
#include "nsIDocument.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsWhitespaceTokenizer.h"
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
namespace mozilla {
namespace dom {
/**
* Returns whether or not the sub-resource about to be loaded is eligible
* for integrity checks. If it's not, the checks will be skipped and the
* sub-resource will be loaded.
*/
static nsresult
IsEligible(nsIURI* aRequestURI, const CORSMode aCORSMode,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aRequestURI);
NS_ENSURE_ARG_POINTER(aDocument);
nsAutoCString requestSpec;
nsresult rv = aRequestURI->GetSpec(requestSpec);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
// Was the sub-resource loaded via CORS?
if (aCORSMode != CORS_NONE) {
SRILOG(("SRICheck::IsEligible, CORS mode"));
return NS_OK;
}
// Is the sub-resource same-origin?
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(),
aRequestURI, false))) {
SRILOG(("SRICheck::IsEligible, same-origin"));
return NS_OK;
}
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
nsAutoCString documentURI;
aDocument->GetDocumentURI()->GetAsciiSpec(documentURI);
// documentURI will be empty if GetAsciiSpec failed
SRILOG(("SRICheck::IsEligible, NOT same origin: documentURI=%s; requestURI=%s",
documentURI.get(), requestSpec.get()));
}
const char16_t* params[] = { requestSpecUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"IneligibleResource",
params, ArrayLength(params));
return NS_ERROR_SRI_NOT_ELIGIBLE;
}
/**
* Compute the hash of a sub-resource and compare it with the expected
* value.
*/
static nsresult
VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
uint32_t aStringLen, const uint8_t* aString,
const nsIDocument* aDocument)
{
NS_ENSURE_ARG_POINTER(aString);
NS_ENSURE_ARG_POINTER(aDocument);
nsAutoCString base64Hash;
aMetadata.GetHash(aHashIndex, &base64Hash);
SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
nsAutoCString binaryHash;
if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"InvalidIntegrityBase64");
return NS_ERROR_SRI_CORRUPT;
}
uint32_t hashLength;
int8_t hashType;
aMetadata.GetHashType(&hashType, &hashLength);
if (binaryHash.Length() != hashLength) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"InvalidIntegrityLength");
return NS_ERROR_SRI_CORRUPT;
}
nsresult rv;
nsCOMPtr<nsICryptoHash> cryptoHash =
do_CreateInstance("@mozilla.org/security/hash;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Init(hashType);
NS_ENSURE_SUCCESS(rv, rv);
rv = cryptoHash->Update(aString, aStringLen);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString computedHash;
rv = cryptoHash->Finish(false, computedHash);
NS_ENSURE_SUCCESS(rv, rv);
if (!binaryHash.Equals(computedHash)) {
SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
return NS_ERROR_SRI_CORRUPT;
}
SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
return NS_OK;
}
/* static */ nsresult
SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
const nsIDocument* aDocument,
SRIMetadata* outMetadata)
{
NS_ENSURE_ARG_POINTER(outMetadata);
NS_ENSURE_ARG_POINTER(aDocument);
MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
if (!Preferences::GetBool("security.sri.enable", false)) {
SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
return NS_ERROR_SRI_DISABLED;
}
// put a reasonable bound on the length of the metadata
NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
}
MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
// the integrity attribute is a list of whitespace-separated hashes
// and options so we need to look at them one by one and pick the
// strongest (valid) one
nsCWhitespaceTokenizer tokenizer(metadataList);
nsAutoCString token;
for (uint32_t i=0; tokenizer.hasMoreTokens() &&
i < SRICheck::MAX_METADATA_TOKENS; ++i) {
token = tokenizer.nextToken();
SRIMetadata metadata(token);
if (metadata.IsMalformed()) {
NS_ConvertUTF8toUTF16 tokenUTF16(token);
const char16_t* params[] = { tokenUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"MalformedIntegrityURI",
params, ArrayLength(params));
} else if (!metadata.IsAlgorithmSupported()) {
nsAutoCString alg;
metadata.GetAlgorithm(&alg);
NS_ConvertUTF8toUTF16 algUTF16(alg);
const char16_t* params[] = { algUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"UnsupportedHashAlg",
params, ArrayLength(params));
}
nsAutoCString alg1, alg2;
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
outMetadata->GetAlgorithm(&alg1);
metadata.GetAlgorithm(&alg2);
}
if (*outMetadata == metadata) {
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
alg1.get(), alg2.get()));
*outMetadata += metadata; // add new hash to strongest metadata
} else if (*outMetadata < metadata) {
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
alg1.get(), alg2.get()));
*outMetadata = metadata; // replace strongest metadata with current
}
}
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
if (outMetadata->IsValid()) {
nsAutoCString alg;
outMetadata->GetAlgorithm(&alg);
SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
} else if (outMetadata->IsEmpty()) {
SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
} else {
SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
}
}
return NS_OK;
}
/* static */ nsresult
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
nsIURI* aRequestURI,
const CORSMode aCORSMode,
const nsAString& aString,
const nsIDocument* aDocument)
{
NS_ConvertUTF16toUTF8 utf8Hash(aString);
return VerifyIntegrity(aMetadata, aRequestURI, aCORSMode, utf8Hash.Length(),
(uint8_t*)utf8Hash.get(), aDocument);
}
/* static */ nsresult
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
nsIURI* aRequestURI,
const CORSMode aCORSMode,
uint32_t aStringLen,
const uint8_t* aString,
const nsIDocument* aDocument)
{
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
nsAutoCString requestURL;
aRequestURI->GetAsciiSpec(requestURL);
// requestURL will be empty if GetAsciiSpec fails
SRILOG(("SRICheck::VerifyIntegrity, url=%s (length=%u)",
requestURL.get(), aStringLen));
}
MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
// IntegrityMetadata() checks this and returns "no metadata" if
// it's disabled so we should never make it this far
MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
if (NS_FAILED(IsEligible(aRequestURI, aCORSMode, aDocument))) {
return NS_OK; // ignore non-CORS resources for forward-compatibility
}
if (!aMetadata.IsValid()) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"NoValidMetadata");
return NS_OK; // ignore invalid metadata for forward-compatibility
}
for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
aString, aDocument))) {
return NS_OK; // stop at the first valid hash
}
}
nsAutoCString alg;
aMetadata.GetAlgorithm(&alg);
NS_ConvertUTF8toUTF16 algUTF16(alg);
const char16_t* params[] = { algUTF16.get() };
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("Sub-resource Integrity"),
aDocument,
nsContentUtils::eSECURITY_PROPERTIES,
"IntegrityMismatch",
params, ArrayLength(params));
return NS_ERROR_SRI_CORRUPT;
}
} // namespace dom
} // namespace mozilla

62
dom/security/SRICheck.h Normal file
View File

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SRICheck_h
#define mozilla_dom_SRICheck_h
#include "mozilla/CORSMode.h"
#include "nsCOMPtr.h"
#include "SRIMetadata.h"
class nsIDocument;
class nsIHttpChannel;
class nsIScriptSecurityManager;
class nsIStreamLoader;
class nsIURI;
namespace mozilla {
namespace dom {
class SRICheck final
{
public:
static const uint32_t MAX_METADATA_LENGTH = 24*1024;
static const uint32_t MAX_METADATA_TOKENS = 512;
/**
* Parse the multiple hashes specified in the integrity attribute and
* return the strongest supported hash.
*/
static nsresult IntegrityMetadata(const nsAString& aMetadataList,
const nsIDocument* aDocument,
SRIMetadata* outMetadata);
/**
* Process the integrity attribute of the element. A result of false
* must prevent the resource from loading.
*/
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
nsIURI* aRequestURI,
const CORSMode aCORSMode,
const nsAString& aString,
const nsIDocument* aDocument);
/**
* Process the integrity attribute of the element. A result of false
* must prevent the resource from loading.
*/
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
nsIURI* aRequestURI,
const CORSMode aCORSMode,
uint32_t aStringLen,
const uint8_t* aString,
const nsIDocument* aDocument);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SRICheck_h

View File

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SRIMetadata.h"
#include "hasht.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/Logging.h"
#include "nsICryptoHash.h"
static PRLogModuleInfo*
GetSriMetadataLog()
{
static PRLogModuleInfo *gSriMetadataPRLog;
if (!gSriMetadataPRLog) {
gSriMetadataPRLog = PR_NewLogModule("SRIMetadata");
}
return gSriMetadataPRLog;
}
#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
namespace mozilla {
namespace dom {
SRIMetadata::SRIMetadata(const nsACString& aToken)
: mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
{
MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
PromiseFlatCString(aToken).get()));
int32_t hyphen = aToken.FindChar('-');
if (hyphen == -1) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
return; // invalid metadata
}
// split the token into its components
mAlgorithm = Substring(aToken, 0, hyphen);
uint32_t hashStart = hyphen + 1;
if (hashStart >= aToken.Length()) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
return; // invalid metadata
}
int32_t question = aToken.FindChar('?');
if (question == -1) {
mHashes.AppendElement(Substring(aToken, hashStart,
aToken.Length() - hashStart));
} else {
MOZ_ASSERT(question > 0);
if (static_cast<uint32_t>(question) <= hashStart) {
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
return; // invalid metadata
}
mHashes.AppendElement(Substring(aToken, hashStart,
question - hashStart));
}
if (mAlgorithm.EqualsLiteral("sha256")) {
mAlgorithmType = nsICryptoHash::SHA256;
} else if (mAlgorithm.EqualsLiteral("sha384")) {
mAlgorithmType = nsICryptoHash::SHA384;
} else if (mAlgorithm.EqualsLiteral("sha512")) {
mAlgorithmType = nsICryptoHash::SHA512;
}
SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
mHashes[0].get(), mAlgorithm.get()));
}
bool
SRIMetadata::operator<(const SRIMetadata& aOther) const
{
static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
"We rely on the order indicating relative alg strength");
static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
"We rely on the order indicating relative alg strength");
MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
mAlgorithmType == nsICryptoHash::SHA256 ||
mAlgorithmType == nsICryptoHash::SHA384 ||
mAlgorithmType == nsICryptoHash::SHA512);
MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
aOther.mAlgorithmType == nsICryptoHash::SHA512);
if (mEmpty) {
SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
return true; // anything beats the empty metadata (incl. invalid ones)
}
SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
mAlgorithmType, aOther.mAlgorithmType));
return (mAlgorithmType < aOther.mAlgorithmType);
}
bool
SRIMetadata::operator>(const SRIMetadata& aOther) const
{
MOZ_ASSERT(false);
return false;
}
SRIMetadata&
SRIMetadata::operator+=(const SRIMetadata& aOther)
{
MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
MOZ_ASSERT(aOther.IsValid() && IsValid());
MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
// We only pull in the first element of the other metadata
MOZ_ASSERT(aOther.mHashes.Length() == 1);
if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
mAlgorithm.get(), mHashes.Length()));
mHashes.AppendElement(aOther.mHashes[0]);
}
MOZ_ASSERT(mHashes.Length() > 1);
MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
return *this;
}
bool
SRIMetadata::operator==(const SRIMetadata& aOther) const
{
if (IsEmpty() || !IsValid()) {
return false;
}
return mAlgorithmType == aOther.mAlgorithmType;
}
void
SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
{
MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
if (NS_WARN_IF(aIndex >= mHashes.Length())) {
*outHash = nullptr;
return;
}
*outHash = mHashes[aIndex];
}
void
SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
{
// these constants are defined in security/nss/lib/util/hasht.h and
// netwerk/base/public/nsICryptoHash.idl
switch (mAlgorithmType) {
case nsICryptoHash::SHA256:
*outLength = SHA256_LENGTH;
break;
case nsICryptoHash::SHA384:
*outLength = SHA384_LENGTH;
break;
case nsICryptoHash::SHA512:
*outLength = SHA512_LENGTH;
break;
default:
*outLength = 0;
}
*outType = mAlgorithmType;
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,74 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_SRIMetadata_h
#define mozilla_dom_SRIMetadata_h
#include "nsTArray.h"
#include "nsString.h"
namespace mozilla {
namespace dom {
class SRIMetadata final
{
public:
static const uint32_t MAX_ALTERNATE_HASHES = 256;
static const int8_t UNKNOWN_ALGORITHM = -1;
/**
* Create an empty metadata object.
*/
SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
/**
* Split a string token into the components of an SRI metadata
* attribute.
*/
explicit SRIMetadata(const nsACString& aToken);
/**
* Returns true when this object's hash algorithm is weaker than the
* other object's hash algorithm.
*/
bool operator<(const SRIMetadata& aOther) const;
/**
* Not implemented. Should not be used.
*/
bool operator>(const SRIMetadata& aOther) const;
/**
* Add another metadata's hash to this one.
*/
SRIMetadata& operator+=(const SRIMetadata& aOther);
/**
* Returns true when the two metadata use the same hash algorithm.
*/
bool operator==(const SRIMetadata& aOther) const;
bool IsEmpty() const { return mEmpty; }
bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
uint32_t HashCount() const { return mHashes.Length(); }
void GetHash(uint32_t aIndex, nsCString* outHash) const;
void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
void GetHashType(int8_t* outType, uint32_t* outLength) const;
private:
nsTArray<nsCString> mHashes;
nsCString mAlgorithm;
int8_t mAlgorithmType;
bool mEmpty;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SRIMetadata_h

View File

@ -12,6 +12,8 @@ EXPORTS.mozilla.dom += [
'nsCSPService.h',
'nsCSPUtils.h',
'nsMixedContentBlocker.h',
'SRICheck.h',
'SRIMetadata.h',
]
EXPORTS += [
@ -27,6 +29,8 @@ UNIFIED_SOURCES += [
'nsCSPService.cpp',
'nsCSPUtils.cpp',
'nsMixedContentBlocker.cpp',
'SRICheck.cpp',
'SRIMetadata.cpp',
]
FAIL_ON_WARNINGS = True

View File

@ -48,3 +48,8 @@ partial interface HTMLLinkElement {
readonly attribute Document? import;
};
// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
partial interface HTMLLinkElement {
[SetterThrows]
attribute DOMString integrity;
};

View File

@ -33,3 +33,8 @@ partial interface HTMLScriptElement {
attribute DOMString htmlFor;
};
// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmlscriptelement-1
partial interface HTMLScriptElement {
[SetterThrows]
attribute DOMString integrity;
};

View File

@ -815,10 +815,12 @@ namespace mozilla {
CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const SRIMetadata& aIntegrity)
: mSheets()
, mCORSMode(aCORSMode)
, mReferrerPolicy (aReferrerPolicy)
, mIntegrity(aIntegrity)
, mComplete(false)
#ifdef DEBUG
, mPrincipalSet(false)
@ -940,6 +942,7 @@ CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
mPrincipal(aCopy.mPrincipal),
mCORSMode(aCopy.mCORSMode),
mReferrerPolicy(aCopy.mReferrerPolicy),
mIntegrity(aCopy.mIntegrity),
mComplete(aCopy.mComplete)
#ifdef DEBUG
, mPrincipalSet(aCopy.mPrincipalSet)
@ -1080,7 +1083,26 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy)
mScopeElement(nullptr),
mRuleProcessors(nullptr)
{
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy);
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
SRIMetadata());
}
CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const SRIMetadata& aIntegrity)
: mTitle(),
mParent(nullptr),
mOwnerRule(nullptr),
mDocument(nullptr),
mOwningNode(nullptr),
mDisabled(false),
mDirty(false),
mInRuleProcessorCache(false),
mScopeElement(nullptr),
mRuleProcessors(nullptr)
{
mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
aIntegrity);
}
CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy,

View File

@ -26,6 +26,7 @@
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "mozilla/dom/SRIMetadata.h"
class CSSRuleListImpl;
class nsCSSRuleProcessor;
@ -63,7 +64,8 @@ public:
private:
CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy);
ReferrerPolicy aReferrerPolicy,
const dom::SRIMetadata& aIntegrity);
CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
CSSStyleSheet* aPrimarySheet);
~CSSStyleSheetInner();
@ -96,6 +98,7 @@ private:
// The Referrer Policy of a stylesheet is used for its child sheets, so it is
// stored here.
ReferrerPolicy mReferrerPolicy;
dom::SRIMetadata mIntegrity;
bool mComplete;
#ifdef DEBUG
@ -123,6 +126,8 @@ class CSSStyleSheet final : public nsIStyleSheet,
public:
typedef net::ReferrerPolicy ReferrerPolicy;
CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy);
CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy,
const dom::SRIMetadata& aIntegrity);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CSSStyleSheet,
@ -259,6 +264,9 @@ public:
// Get this style sheet's Referrer Policy
ReferrerPolicy GetReferrerPolicy() const { return mInner->mReferrerPolicy; }
// Get this style sheet's integrity metadata
dom::SRIMetadata GetIntegrity() const { return mInner->mIntegrity; }
dom::Element* GetScopeElement() const { return mScopeElement; }
void SetScopeElement(dom::Element* aScopeElement)
{

View File

@ -62,6 +62,7 @@
#include "nsError.h"
#include "nsIContentSecurityPolicy.h"
#include "mozilla/dom/SRICheck.h"
#include "mozilla/dom/EncodingUtils.h"
using mozilla::dom::EncodingUtils;
@ -265,6 +266,16 @@ GetLoaderLog()
return sLog;
}
static PRLogModuleInfo*
GetSriLog()
{
static PRLogModuleInfo *gSriPRLog;
if (!gSriPRLog) {
gSriPRLog = PR_NewLogModule("SRI");
}
return gSriPRLog;
}
#define LOG_ERROR(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Error, args)
#define LOG_WARN(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Warning, args)
#define LOG_DEBUG(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Debug, args)
@ -928,6 +939,18 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
}
}
SRIMetadata sriMetadata = mSheet->GetIntegrity();
if (!sriMetadata.IsEmpty() &&
NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, channelURI,
mSheet->GetCORSMode(), aBuffer,
mLoader->mDocument))) {
LOG((" Load was blocked by SRI"));
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("css::Loader::OnStreamComplete, bad metadata"));
mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
return NS_OK;
}
// Enough to set the URIs on mSheet, since any sibling datas we have share
// the same mInner as mSheet and will thus get the same URI.
mSheet->SetURIs(channelURI, originalURI, channelURI);
@ -1057,6 +1080,7 @@ Loader::CreateSheet(nsIURI* aURI,
nsIPrincipal* aLoaderPrincipal,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
bool aSyncLoad,
bool aHasAlternateRel,
const nsAString& aTitle,
@ -1204,7 +1228,17 @@ Loader::CreateSheet(nsIURI* aURI,
originalURI = aURI;
}
nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode, aReferrerPolicy);
SRIMetadata sriMetadata;
if (!aIntegrity.IsEmpty()) {
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
("css::Loader::CreateSheet, integrity=%s",
NS_ConvertUTF16toUTF8(aIntegrity).get()));
SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
}
nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode,
aReferrerPolicy,
sriMetadata);
sheet->SetURIs(sheetURI, originalURI, baseURI);
sheet.forget(aSheet);
}
@ -1415,6 +1449,8 @@ Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
}
SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity();
if (aLoadData->mSyncLoad) {
LOG((" Synchronous load"));
NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
@ -1599,7 +1635,7 @@ Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
if (httpChannel) {
// send a minimal Accept header for text/css
// Send a minimal Accept header for text/css
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
false);
@ -1932,8 +1968,10 @@ Loader::LoadInlineStyle(nsIContent* aElement,
StyleSheetState state;
nsRefPtr<CSSStyleSheet> sheet;
nsresult rv = CreateSheet(nullptr, aElement, nullptr, CORS_NONE,
mDocument->GetReferrerPolicy(), false, false,
aTitle, state, aIsAlternate, getter_AddRefs(sheet));
mDocument->GetReferrerPolicy(),
EmptyString(), // no inline integrity checks
false, false, aTitle, state, aIsAlternate,
getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(state == eSheetNeedsParser,
"Inline sheets should not be cached");
@ -1979,6 +2017,7 @@ Loader::LoadStyleLink(nsIContent* aElement,
bool aHasAlternateRel,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
nsICSSLoaderObserver* aObserver,
bool* aIsAlternate)
{
@ -2013,7 +2052,7 @@ Loader::LoadStyleLink(nsIContent* aElement,
StyleSheetState state;
nsRefPtr<CSSStyleSheet> sheet;
rv = CreateSheet(aURL, aElement, principal, aCORSMode,
aReferrerPolicy, false,
aReferrerPolicy, aIntegrity, false,
aHasAlternateRel, aTitle, state, aIsAlternate,
getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
@ -2177,6 +2216,7 @@ Loader::LoadChildSheet(CSSStyleSheet* aParentSheet,
// For now, use CORS_NONE for child sheets
rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
aParentSheet->GetReferrerPolicy(),
EmptyString(), // integrity is only checked on main sheet
parentData ? parentData->mSyncLoad : false,
false, empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);
@ -2243,13 +2283,14 @@ Loader::LoadSheet(nsIURI* aURL,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
return InternalLoadNonDocumentSheet(aURL, false, false,
aOriginPrincipal, aCharset,
nullptr, aObserver, aCORSMode,
aReferrerPolicy);
aReferrerPolicy, aIntegrity);
}
nsresult
@ -2261,7 +2302,8 @@ Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
CSSStyleSheet** aSheet,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy)
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity)
{
NS_PRECONDITION(aURL, "Must have a URI to load");
NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
@ -2292,7 +2334,7 @@ Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
const nsSubstring& empty = EmptyString();
rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aCORSMode,
aReferrerPolicy, syncLoad, false,
aReferrerPolicy, aIntegrity, syncLoad, false,
empty, state, &isAlternate, getter_AddRefs(sheet));
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -223,6 +223,7 @@ public:
bool aHasAlternateRel,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
nsICSSLoaderObserver* aObserver,
bool* aIsAlternate);
@ -320,7 +321,8 @@ public:
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode = CORS_NONE,
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
const nsAString& aIntegrity = EmptyString());
/**
* Stop loading all sheets. All nsICSSLoaderObservers involved will be
@ -417,6 +419,7 @@ private:
nsIPrincipal* aLoaderPrincipal,
CORSMode aCORSMode,
ReferrerPolicy aReferrerPolicy,
const nsAString& aIntegrity,
bool aSyncLoad,
bool aHasAlternateRel,
const nsAString& aTitle,
@ -450,7 +453,8 @@ private:
CSSStyleSheet** aSheet,
nsICSSLoaderObserver* aObserver,
CORSMode aCORSMode = CORS_NONE,
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
const nsAString& aIntegrity = EmptyString());
// Post a load event for aObserver to be notified about aSheet. The
// notification will be sent with status NS_OK unless the load event is

View File

@ -1958,6 +1958,9 @@ pref("security.apps.privileged.CSP.default", "default-src * data: blob:; script-
pref("security.mixed_content.block_active_content", false);
pref("security.mixed_content.block_display_content", false);
// Sub-resource integrity
pref("security.sri.enable", false);
// Disable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 0);
// Do not process hpkp headers rooted by not built in roots by default.

View File

@ -45,14 +45,14 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
break;
case eSpeculativeLoadScript:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
mCrossOrigin, false);
mCrossOrigin, mIntegrity, false);
break;
case eSpeculativeLoadScriptFromHead:
aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
mCrossOrigin, true);
mCrossOrigin, mIntegrity, true);
break;
case eSpeculativeLoadStyle:
aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin);
aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);
break;
case eSpeculativeLoadManifest:
aExecutor->ProcessOfflineManifest(mUrl);

View File

@ -105,6 +105,7 @@ class nsHtml5SpeculativeLoad {
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aParserInHead)
{
NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
@ -115,10 +116,12 @@ class nsHtml5SpeculativeLoad {
mCharset.Assign(aCharset);
mTypeOrCharsetSource.Assign(aType);
mCrossOrigin.Assign(aCrossOrigin);
mIntegrity.Assign(aIntegrity);
}
inline void InitStyle(const nsAString& aUrl, const nsAString& aCharset,
const nsAString& aCrossOrigin)
const nsAString& aCrossOrigin,
const nsAString& aIntegrity)
{
NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
@ -126,6 +129,7 @@ class nsHtml5SpeculativeLoad {
mUrl.Assign(aUrl);
mCharset.Assign(aCharset);
mCrossOrigin.Assign(aCrossOrigin);
mIntegrity.Assign(aIntegrity);
}
/**
@ -219,6 +223,12 @@ class nsHtml5SpeculativeLoad {
* attribute. If the attribute is not set, this will be a void string.
*/
nsString mMedia;
/**
* If mOpCode is eSpeculativeLoadScript[FromHead], this is the value of the
* "integrity" attribute. If the attribute is not set, this will be a void
* string.
*/
nsString mIntegrity;
};
#endif // nsHtml5SpeculativeLoad_h

View File

@ -165,11 +165,14 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitScript(*url,
(charset) ? *charset : EmptyString(),
(type) ? *type : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString(),
mode == NS_HTML5TREE_BUILDER_IN_HEAD);
mCurrentHtmlScriptIsAsyncOrDefer =
aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
@ -186,10 +189,13 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitStyle(*url,
(charset) ? *charset : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString());
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString());
}
} else if (rel->LowerCaseEqualsASCII("preconnect")) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
@ -256,11 +262,14 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitScript(*url,
EmptyString(),
(type) ? *type : EmptyString(),
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString(),
mode == NS_HTML5TREE_BUILDER_IN_HEAD);
}
} else if (nsHtml5Atoms::style == aName) {
@ -272,9 +281,12 @@ nsHtml5TreeBuilder::createElement(int32_t aNamespace, nsIAtom* aName,
if (url) {
nsString* crossOrigin =
aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
nsString* integrity =
aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
mSpeculativeLoadQueue.AppendElement()->
InitStyle(*url, EmptyString(),
(crossOrigin) ? *crossOrigin : NullString());
(crossOrigin) ? *crossOrigin : NullString(),
(integrity) ? *integrity : NullString());
}
}
break;

View File

@ -911,6 +911,7 @@ nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
@ -918,21 +919,22 @@ nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
return;
}
mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
aScriptFromHead,
aIntegrity, aScriptFromHead,
mSpeculationReferrerPolicy);
}
void
nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aCrossOrigin)
const nsAString& aCrossOrigin,
const nsAString& aIntegrity)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
mSpeculationReferrerPolicy);
mSpeculationReferrerPolicy, aIntegrity);
}
void

View File

@ -248,10 +248,12 @@ class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
const nsAString& aCharset,
const nsAString& aType,
const nsAString& aCrossOrigin,
const nsAString& aIntegrity,
bool aScriptFromHead);
void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
const nsAString& aCrossOrigin);
const nsAString& aCrossOrigin,
const nsAString& aIntegrity);
void PreloadImage(const nsAString& aURL,
const nsAString& aCrossOrigin,

View File

@ -657,6 +657,11 @@
ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION, FAILURE(98)),
ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION, FAILURE(99)),
/* Error code for Sub-Resource Integrity */
ERROR(NS_ERROR_SRI_CORRUPT, FAILURE(200)),
ERROR(NS_ERROR_SRI_DISABLED, FAILURE(201)),
ERROR(NS_ERROR_SRI_NOT_ELIGIBLE, FAILURE(202)),
/* CMS specific nsresult error codes. Note: the numbers used here correspond
* to the values in nsICMSMessageErrors.idl. */
ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED, FAILURE(1024)),