author | Francois Marier <francois@mozilla.com> |
Wed, 12 Aug 2015 20:19:11 -0700 | |
changeset 290131 | bab5913ea6cbb558457670e1785b0296cc13acc0 |
parent 290130 | bd4a7f61176e9b5407ba2c8f6efa37f55decb23f |
child 290132 | 948b2e9d1fa282b3acd35a67e6b020b3e68510c7 |
push id | 5245 |
push user | raliiev@mozilla.com |
push date | Thu, 29 Oct 2015 11:30:51 +0000 |
treeherder | mozilla-beta@dac831dc1bd0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | baku, ckerschb |
bugs | 992096 |
milestone | 43.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/AUTHORS +++ b/AUTHORS @@ -354,16 +354,17 @@ Fernando Jimenez <ferjmoreno@gmail.com> Flock Inc. Florian Boesch <pyalot@gmail.com> Florian Hänel <heeen@gmx.de> Florian Queze <florian@queze.net> 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 <franky@pacificconnections.com> Franz Sirl <Franz.Sirl-kernel@lauterbach.com> Frederic Plourde <frederic.plourde@polymtl.ca> Frederic Wang <fred.wang@free.fr> Fredrik Holmqvist <thesuckiestemail@yahoo.se>
--- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -4802,16 +4802,17 @@ var Utils = { case "Invalid HSTS Headers": case "Invalid HPKP Headers": case "SHA-1 Signature": case "Insecure Password Field": case "SSL": case "CORS": case "Iframe Sandbox": case "Tracking Protection": + case "Sub-resource Integrity": return CATEGORY_SECURITY; default: return CATEGORY_JS; } }, /**
--- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -427,16 +427,17 @@ LOCAL_INCLUDES += [ '/js/xpconnect/src', '/js/xpconnect/wrappers', '/layout/base', '/layout/generic', '/layout/style', '/layout/svg', '/layout/xul', '/netwerk/base', + '/security/manager/ssl', '/widget', '/xpcom/ds', ] if CONFIG['MOZ_B2G_BT_API_V1']: LOCAL_INCLUDES += [ '../bluetooth/bluetooth1', ]
--- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -46,16 +46,26 @@ #include "nsIWebNavigation.h" #include "nsGenericHTMLElement.h" #include "nsHTMLDNSPrefetch.h" #include "nsIObserverService.h" #include "mozilla/Preferences.h" #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; NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink) @@ -745,22 +755,33 @@ nsContentSink::ProcessStyleLink(nsIConte // The URI is bad, move along, don't propagate the error (for now) return NS_OK; } NS_ASSERTION(!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) { ++mPendingSheetCount; mScriptLoader->AddExecuteBlocker(); } return NS_OK;
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -9871,27 +9871,28 @@ public: }; NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver) } // namespace 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(); // Charset names are always ASCII. CSSLoader()->LoadSheet(uri, NodePrincipal(), NS_LossyConvertUTF16toASCII(charset), obs, Element::StringToCORSMode(aCrossOriginAttr), - aReferrerPolicy); + aReferrerPolicy, aIntegrity); } nsresult nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet, CSSStyleSheet** sheet) { return CSSLoader()->LoadSheetSync(uri, isAgentSheet, isAgentSheet, sheet); }
--- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -1142,17 +1142,18 @@ public: ReferrerPolicy aReferrerPolicy) override; virtual void ForgetImagePreload(nsIURI* aURI) override; virtual void MaybePreconnect(nsIURI* uri, mozilla::CORSMode aCORSMode) override; 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; virtual nsISupports* GetCurrentContentSink() override; virtual mozilla::EventStates GetDocumentState() override;
--- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -484,16 +484,17 @@ GK_ATOM(input, "input") GK_ATOM(inputmode, "inputmode") GK_ATOM(ins, "ins") GK_ATOM(insertafter, "insertafter") GK_ATOM(insertbefore, "insertbefore") 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") GK_ATOM(isempty, "isempty") GK_ATOM(ismap, "ismap") GK_ATOM(itemid, "itemid") GK_ATOM(itemprop, "itemprop") GK_ATOM(itemref, "itemref")
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -147,18 +147,18 @@ template<typename> class Sequence; template<typename, typename> class CallbackObjectHolder; typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder; } // namespace dom } // 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 { DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true DocumentFlavorSVG, // SVGDocument DocumentFlavorPlain, // Just a Document }; @@ -2018,17 +2018,18 @@ public: /** * Called by nsParser to preload style sheets. Can also be merged into the * parser if and when the parser is merged with libgklayout. aCrossOriginAttr * should be a void string if the attr is not present. */ 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 * back there if and when when that module is merged with libgklayout. * * This always does a synchronous load. If aIsAgentSheet is true, * it also uses the system principal and enables unsafe rules. * DO NOT USE FOR UNTRUSTED CONTENT.
--- a/dom/base/nsScriptLoader.cpp +++ b/dom/base/nsScriptLoader.cpp @@ -48,19 +48,31 @@ #include "nsSandboxFlags.h" #include "nsContentTypeParser.h" #include "nsINetworkPredictor.h" #include "ImportManager.h" #include "mozilla/dom/EncodingUtils.h" #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; // The nsScriptLoadRequest is passed as the context to necko, and thus // it needs to be threadsafe. Necko won't do anything with this // context, but it will AddRef and Release it on other threads. NS_IMPL_ISUPPORTS0(nsScriptLoadRequest) @@ -601,17 +613,32 @@ nsScriptLoader::ProcessScriptElement(nsI } else { // Drop the preload request = nullptr; } } 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; request->mReferrerPolicy = ourRefPolicy; // set aScriptFromHead to false so we don't treat non preloaded scripts as // blockers for full page load. See bug 792438. rv = StartLoad(request, type, false); @@ -715,17 +742,18 @@ nsScriptLoader::ProcessScriptElement(nsI } // Does CSP allow this inline script to run? if (!CSPAllowsInlineScript(aElement, mDocument)) { return false; } // 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; request->mURI = mDocument->GetDocumentURI(); request->mLineNo = aElement->GetScriptLineNumber(); if (aElement->GetParserCreated() == FROM_PARSER_XSLT && (!ReadyToExecuteScripts() || !mXSLTRequests.isEmpty())) { @@ -1403,18 +1431,25 @@ nsScriptLoader::OnStreamComplete(nsIStre nsresult aStatus, uint32_t aStringLen, const uint8_t* aString) { nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext); 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. * We make a note of this script node by including it in a dedicated * array of blocked tracking nodes under its parent document. */ if (rv == NS_ERROR_TRACKING_URI) { nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement); @@ -1598,27 +1633,37 @@ nsScriptLoader::ParsingComplete(bool aTe // onload and all. ProcessPendingRequests(); } void nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset, const nsAString &aType, const nsAString &aCrossOrigin, + const nsAString& aIntegrity, bool aScriptFromHead, const mozilla::net::ReferrerPolicy aReferrerPolicy) { // Check to see if scripts has been turned off. if (!mEnabled || !mDocument->IsScriptEnabled()) { 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; request->mReferrerPolicy = aReferrerPolicy; nsresult rv = StartLoad(request, aType, aScriptFromHead); if (NS_FAILED(rv)) { return;
--- a/dom/base/nsScriptLoader.h +++ b/dom/base/nsScriptLoader.h @@ -14,16 +14,17 @@ #include "nsCOMPtr.h" #include "nsIScriptElement.h" #include "nsCOMArray.h" #include "nsTArray.h" #include "nsAutoPtr.h" #include "nsIDocument.h" #include "nsIStreamLoader.h" #include "mozilla/CORSMode.h" +#include "mozilla/dom/SRIMetadata.h" #include "mozilla/LinkedList.h" #include "mozilla/net/ReferrerPolicy.h" class nsScriptLoadRequestList; class nsIURI; namespace JS { class SourceBufferHolder; @@ -51,31 +52,33 @@ class nsScriptLoadRequest final : public // Allow LinkedListElement<nsScriptLoadRequest> to cast us to itself as needed. friend class mozilla::LinkedListElement<nsScriptLoadRequest>; friend class nsScriptLoadRequestList; public: nsScriptLoadRequest(nsIScriptElement* aElement, uint32_t aVersion, - mozilla::CORSMode aCORSMode) + mozilla::CORSMode aCORSMode, + const mozilla::dom::SRIMetadata &aIntegrity) : mElement(aElement), mLoading(true), mIsInline(true), mHasSourceMapURL(false), mIsDefer(false), mIsAsync(false), mIsNonAsyncScriptInserted(false), mIsXSLT(false), mIsCanceled(false), mScriptTextBuf(nullptr), mScriptTextLength(0), mJSVersion(aVersion), mLineNo(1), mCORSMode(aCORSMode), + mIntegrity(aIntegrity), mReferrerPolicy(mozilla::net::RP_Default) { } NS_DECL_THREADSAFE_ISUPPORTS void FireScriptAvailable(nsresult aResult) { @@ -117,16 +120,17 @@ public: char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't size_t mScriptTextLength; // use nsString so we can give ownership to jsapi. uint32_t mJSVersion; nsCOMPtr<nsIURI> mURI; nsCOMPtr<nsIPrincipal> mOriginPrincipal; 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; }; class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest> { typedef mozilla::LinkedList<nsScriptLoadRequest> super; public: @@ -362,21 +366,23 @@ public: /** * Adds aURI to the preload list and starts loading it. * * @param aURI The URI of the external script. * @param aCharset The charset parameter for the script. * @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); /** * Process a request that was deferred so that the script could be compiled * off thread. */ nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest,
--- a/dom/base/nsStyleLinkElement.cpp +++ b/dom/base/nsStyleLinkElement.cpp @@ -416,23 +416,31 @@ nsStyleLinkElement::DoUpdateStyleSheet(n return rv; // Parse the style sheet. rv = doc->CSSLoader()-> LoadInlineStyle(thisContent, text, mLineNumber, title, media, 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 // consumers (e.g. nsXMLContentSink) will completely abort on innocuous // things like a stylesheet load being blocked by the security system. doneLoading = true; isAlternate = false; rv = NS_OK;
--- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -209,16 +209,21 @@ HTMLLinkElement::ParseAttribute(int32_t ParseCORSValue(aValue, aResult); return true; } if (aAttribute == nsGkAtoms::sizes) { aResult.ParseAtomArray(aValue); return true; } + + if (aAttribute == nsGkAtoms::integrity) { + aResult.ParseStringOrAtom(aValue); + return true; + } } return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, aResult); } void HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
--- a/dom/html/HTMLLinkElement.h +++ b/dom/html/HTMLLinkElement.h @@ -138,16 +138,24 @@ public: { SetHTMLAttr(nsGkAtoms::rev, aRev, aRv); } // XPCOM GetTarget is fine. void SetTarget(const nsAString& aTarget, ErrorResult& aRv) { 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() { return nsRefPtr<ImportLoader>(mImportLoader).forget(); } protected:
--- a/dom/html/HTMLScriptElement.cpp +++ b/dom/html/HTMLScriptElement.cpp @@ -70,20 +70,26 @@ HTMLScriptElement::BindToTree(nsIDocumen } bool HTMLScriptElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute, 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, aResult); } nsresult HTMLScriptElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
--- a/dom/html/HTMLScriptElement.h +++ b/dom/html/HTMLScriptElement.h @@ -74,16 +74,24 @@ public: // always parse to an enum value, so we don't need an invalid // default, and we _want_ the missing default to be null. GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult); } void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError) { 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); protected: virtual ~HTMLScriptElement(); virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override; // nsScriptElement
--- a/dom/locales/en-US/chrome/security/security.properties +++ b/dom/locales/en-US/chrome/security/security.properties @@ -48,12 +48,27 @@ InsecurePasswordsPresentOnPage=Password InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen. InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen. # LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource LoadingMixedActiveContent2=Loading mixed (insecure) active content "%1$S" on a secure page LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a secure page # 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". WeakCipherSuiteWarning=This site uses the cipher RC4 for encryption, which is deprecated and insecure.
new file mode 100644 --- /dev/null +++ b/dom/security/SRICheck.cpp @@ -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
new file mode 100644 --- /dev/null +++ b/dom/security/SRICheck.h @@ -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
new file mode 100644 --- /dev/null +++ b/dom/security/SRIMetadata.cpp @@ -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
new file mode 100644 --- /dev/null +++ b/dom/security/SRIMetadata.h @@ -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
--- a/dom/security/moz.build +++ b/dom/security/moz.build @@ -7,31 +7,35 @@ TEST_DIRS += ['test'] EXPORTS.mozilla.dom += [ 'nsContentSecurityManager.h', 'nsCSPContext.h', 'nsCSPService.h', 'nsCSPUtils.h', 'nsMixedContentBlocker.h', + 'SRICheck.h', + 'SRIMetadata.h', ] EXPORTS += [ 'nsContentSecurityManager.h', 'nsCORSListenerProxy.h' ] UNIFIED_SOURCES += [ 'nsContentSecurityManager.cpp', 'nsCORSListenerProxy.cpp', 'nsCSPContext.cpp', 'nsCSPParser.cpp', 'nsCSPService.cpp', 'nsCSPUtils.cpp', 'nsMixedContentBlocker.cpp', + 'SRICheck.cpp', + 'SRIMetadata.cpp', ] FAIL_ON_WARNINGS = True FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/caps', '/netwerk/base',
--- a/dom/webidl/HTMLLinkElement.webidl +++ b/dom/webidl/HTMLLinkElement.webidl @@ -43,8 +43,13 @@ partial interface HTMLLinkElement { }; // http://w3c.github.io/webcomponents/spec/imports/#interface-import partial interface HTMLLinkElement { [Func="nsDocument::IsWebComponentsEnabled"] readonly attribute Document? import; }; +// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1 +partial interface HTMLLinkElement { + [SetterThrows] + attribute DOMString integrity; +};
--- a/dom/webidl/HTMLScriptElement.webidl +++ b/dom/webidl/HTMLScriptElement.webidl @@ -28,8 +28,13 @@ interface HTMLScriptElement : HTMLElemen // http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis partial interface HTMLScriptElement { [SetterThrows] attribute DOMString event; [SetterThrows] attribute DOMString htmlFor; }; +// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmlscriptelement-1 +partial interface HTMLScriptElement { + [SetterThrows] + attribute DOMString integrity; +};
--- a/layout/style/CSSStyleSheet.cpp +++ b/layout/style/CSSStyleSheet.cpp @@ -810,20 +810,22 @@ namespace mozilla { // ------------------------------- // CSS Style Sheet Inner Data Container // 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) #endif { MOZ_COUNT_CTOR(CSSStyleSheetInner); mSheets.AppendElement(aPrimarySheet); @@ -935,16 +937,17 @@ CSSStyleSheetInner::CSSStyleSheetInner(C CSSStyleSheet* aPrimarySheet) : mSheets(), mSheetURI(aCopy.mSheetURI), mOriginalSheetURI(aCopy.mOriginalSheetURI), mBaseURI(aCopy.mBaseURI), mPrincipal(aCopy.mPrincipal), mCORSMode(aCopy.mCORSMode), mReferrerPolicy(aCopy.mReferrerPolicy), + mIntegrity(aCopy.mIntegrity), mComplete(aCopy.mComplete) #ifdef DEBUG , mPrincipalSet(aCopy.mPrincipalSet) #endif { MOZ_COUNT_CTOR(CSSStyleSheetInner); AddSheet(aPrimarySheet); aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules); @@ -1075,17 +1078,36 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aC mDocument(nullptr), mOwningNode(nullptr), mDisabled(false), mDirty(false), mInRuleProcessorCache(false), 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, CSSStyleSheet* aParentToUse, css::ImportRule* aOwnerRuleToUse, nsIDocument* aDocumentToUse, nsINode* aOwningNodeToUse) : mTitle(aCopy.mTitle),
--- a/layout/style/CSSStyleSheet.h +++ b/layout/style/CSSStyleSheet.h @@ -21,16 +21,17 @@ #include "nsIDOMCSSStyleSheet.h" #include "nsICSSLoaderObserver.h" #include "nsTArrayForwardDeclare.h" #include "nsString.h" #include "mozilla/CORSMode.h" #include "nsCycleCollectionParticipant.h" #include "nsWrapperCache.h" #include "mozilla/net/ReferrerPolicy.h" +#include "mozilla/dom/SRIMetadata.h" class CSSRuleListImpl; class nsCSSRuleProcessor; class nsIPrincipal; class nsIURI; class nsMediaList; class nsMediaQueryResultCacheKey; class nsPresContext; @@ -58,17 +59,18 @@ class CSSStyleSheetInner public: friend class mozilla::CSSStyleSheet; friend class ::nsCSSRuleProcessor; typedef net::ReferrerPolicy ReferrerPolicy; private: CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet, CORSMode aCORSMode, - ReferrerPolicy aReferrerPolicy); + ReferrerPolicy aReferrerPolicy, + const dom::SRIMetadata& aIntegrity); CSSStyleSheetInner(CSSStyleSheetInner& aCopy, CSSStyleSheet* aPrimarySheet); ~CSSStyleSheetInner(); CSSStyleSheetInner* CloneFor(CSSStyleSheet* aPrimarySheet); void AddSheet(CSSStyleSheet* aSheet); void RemoveSheet(CSSStyleSheet* aSheet); @@ -91,16 +93,17 @@ private: // currently this is the case) that any time page JS can get ts hands on a // child sheet that means we've already ensured unique inners throughout its // parent chain and things are good. nsRefPtr<CSSStyleSheet> mFirstChild; CORSMode mCORSMode; // 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 bool mPrincipalSet; #endif }; @@ -118,16 +121,18 @@ private: class CSSStyleSheet final : public nsIStyleSheet, public nsIDOMCSSStyleSheet, public nsICSSLoaderObserver, public nsWrapperCache { 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, nsIStyleSheet) NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_STYLE_SHEET_IMPL_CID) // nsIStyleSheet interface @@ -254,16 +259,19 @@ public: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; // Get this style sheet's CORS mode CORSMode GetCORSMode() const { return mInner->mCORSMode; } // 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) { mScopeElement = aScopeElement; } // WebIDL StyleSheet API // Our nsIStyleSheet::GetType is a const method, so it ends up
--- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -57,16 +57,17 @@ #include "nsXULPrototypeCache.h" #endif #include "nsIMediaList.h" #include "nsIDOMStyleSheet.h" #include "nsError.h" #include "nsIContentSecurityPolicy.h" +#include "mozilla/dom/SRICheck.h" #include "mozilla/dom/EncodingUtils.h" using mozilla::dom::EncodingUtils; using namespace mozilla::dom; /** * OVERALL ARCHITECTURE @@ -260,16 +261,26 @@ static PRLogModuleInfo * GetLoaderLog() { static PRLogModuleInfo *sLog; if (!sLog) sLog = PR_NewLogModule("nsCSSLoader"); 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) #define LOG(args) LOG_DEBUG(args) #define LOG_ERROR_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Error) #define LOG_WARN_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Warning) #define LOG_DEBUG_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Debug) @@ -923,16 +934,28 @@ SheetLoadData::OnStreamComplete(nsIUnich if (errorFlag == nsIScriptError::errorFlag) { LOG_WARN((" Ignoring sheet with improper MIME type %s", contentType.get())); mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE); return NS_OK; } } + 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); bool completed; result = mLoader->ParseSheet(aBuffer, this, completed); NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete"); return result; @@ -1052,16 +1075,17 @@ Loader::CheckLoadAllowed(nsIPrincipal* a * CreateSheet(). */ nsresult Loader::CreateSheet(nsIURI* aURI, nsIContent* aLinkingContent, nsIPrincipal* aLoaderPrincipal, CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, + const nsAString& aIntegrity, bool aSyncLoad, bool aHasAlternateRel, const nsAString& aTitle, StyleSheetState& aSheetState, bool *aIsAlternate, CSSStyleSheet** aSheet) { LOG(("css::Loader::CreateSheet")); @@ -1199,17 +1223,27 @@ Loader::CreateSheet(nsIURI* aURI, sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI(); originalURI = nullptr; } else { baseURI = aURI; sheetURI = 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); } NS_ASSERTION(*aSheet, "We should have a sheet by now!"); NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!"); LOG((" State: %s", gStateStrings[aSheetState])); @@ -1410,16 +1444,18 @@ Loader::LoadSheet(SheetLoadData* aLoadDa (nsContentUtils::URIIsLocalFile(aLoadData->mURI) && NS_SUCCEEDED(aLoadData->mLoaderPrincipal-> CheckMayLoad(aLoadData->mURI, false, false)))); } else { triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); } + SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity(); + if (aLoadData->mSyncLoad) { LOG((" Synchronous load")); NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?"); NS_ASSERTION(aSheetState == eSheetNeedsParser, "Sync loads can't reuse existing async loads"); // Create a nsIUnicharStreamLoader instance to which we will feed // the data from the sync load. Do this before creating the @@ -1594,17 +1630,17 @@ Loader::LoadSheet(SheetLoadData* aLoadDa nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel)); if (cos) { cos->AddClassFlags(nsIClassOfService::Leader); } } 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); nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI(); if (referrerURI) httpChannel->SetReferrerWithPolicy(referrerURI, aLoadData->mSheet->GetReferrerPolicy()); @@ -1927,18 +1963,20 @@ Loader::LoadInlineStyle(nsIContent* aEle NS_ASSERTION(owningElement, "Element is not a style linking element!"); // Since we're not planning to load a URI, no need to hand a principal to the // load data or to CreateSheet(). Also, OK to use CORS_NONE for the CORS // mode and mDocument's ReferrerPolicy. 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"); LOG((" Sheet is alternate: %d", *aIsAlternate)); PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate); @@ -1974,16 +2012,17 @@ Loader::LoadInlineStyle(nsIContent* aEle nsresult Loader::LoadStyleLink(nsIContent* aElement, nsIURI* aURL, const nsAString& aTitle, const nsAString& aMedia, bool aHasAlternateRel, CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, + const nsAString& aIntegrity, nsICSSLoaderObserver* aObserver, bool* aIsAlternate) { LOG(("css::Loader::LoadStyleLink")); NS_PRECONDITION(aURL, "Must have URL to load"); NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?"); LOG_URI(" Link uri: '%s'", aURL); @@ -2008,17 +2047,17 @@ Loader::LoadStyleLink(nsIContent* aEleme nsresult rv = CheckLoadAllowed(principal, aURL, context); if (NS_FAILED(rv)) return rv; LOG((" Passed load check")); 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); LOG((" Sheet is alternate: %d", *aIsAlternate)); PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate); @@ -2172,16 +2211,17 @@ Loader::LoadChildSheet(CSSStyleSheet* aP // loop) do so. nsRefPtr<CSSStyleSheet> sheet; bool isAlternate; StyleSheetState state; const nsSubstring& empty = EmptyString(); // 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); PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate); rv = InsertChildSheet(sheet, aParentSheet, aParentRule); NS_ENSURE_SUCCESS(rv, rv); @@ -2238,35 +2278,37 @@ Loader::LoadSheet(nsIURI* aURL, } nsresult Loader::LoadSheet(nsIURI* aURL, nsIPrincipal* aOriginPrincipal, 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 Loader::InternalLoadNonDocumentSheet(nsIURI* aURL, bool aAllowUnsafeRules, bool aUseSystemPrincipal, nsIPrincipal* aOriginPrincipal, const nsCString& aCharset, 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"); NS_PRECONDITION(!aUseSystemPrincipal || !aObserver, "Shouldn't load system-principal sheets async"); NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?"); LOG_URI(" Non-document sheet uri: '%s'", aURL); @@ -2287,17 +2329,17 @@ Loader::InternalLoadNonDocumentSheet(nsI StyleSheetState state; bool isAlternate; nsRefPtr<CSSStyleSheet> sheet; bool syncLoad = (aObserver == nullptr); 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); PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate); if (state == eSheetComplete) { LOG((" Sheet already complete")); if (aObserver || !mObservers.IsEmpty()) {
--- a/layout/style/Loader.h +++ b/layout/style/Loader.h @@ -218,16 +218,17 @@ public: */ nsresult LoadStyleLink(nsIContent* aElement, nsIURI* aURL, const nsAString& aTitle, const nsAString& aMedia, bool aHasAlternateRel, CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, + const nsAString& aIntegrity, nsICSSLoaderObserver* aObserver, bool* aIsAlternate); /** * Load a child (@import-ed) style sheet. In addition to loading the sheet, * this method will insert it into the child sheet list of aParentSheet. If * there is no sheet currently being parsed and the child sheet is not * complete when this method returns, then when the child sheet becomes @@ -315,17 +316,18 @@ public: * Same as above, to be used when the caller doesn't care about the * not-yet-loaded sheet. */ nsresult LoadSheet(nsIURI* aURL, nsIPrincipal* aOriginPrincipal, 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 * notified with NS_BINDING_ABORTED as the status, possibly synchronously. */ nsresult Stop(void); /** @@ -412,16 +414,17 @@ private: // must be non-null then. The loader principal must never be null // if aURI is not null. // *aIsAlternate is set based on aTitle and aHasAlternateRel. nsresult CreateSheet(nsIURI* aURI, nsIContent* aLinkingContent, nsIPrincipal* aLoaderPrincipal, CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy, + const nsAString& aIntegrity, bool aSyncLoad, bool aHasAlternateRel, const nsAString& aTitle, StyleSheetState& aSheetState, bool *aIsAlternate, CSSStyleSheet** aSheet); // Pass in either a media string or the nsMediaList from the @@ -445,17 +448,18 @@ private: nsresult InternalLoadNonDocumentSheet(nsIURI* aURL, bool aAllowUnsafeRules, bool aUseSystemPrincipal, nsIPrincipal* aOriginPrincipal, const nsCString& aCharset, 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 // canceled at some point (in which case it will be sent with // NS_BINDING_ABORTED). aWasAlternate indicates the state when the load was // initiated, not the state at some later time. aURI should be the URI the // sheet was loaded from (may be null for inline sheets). aElement is the // owning element for this sheet.
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -1953,16 +1953,19 @@ pref("security.csp.experimentalEnabled", // Default Content Security Policy to apply to privileged apps. pref("security.apps.privileged.CSP.default", "default-src * data: blob:; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'"); // Mixed content blocking 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. // This is to prevent accidental pinning from MITM devices and is used // for tests. pref("security.cert_pinning.process_headers_from_non_builtin_roots", false); // Modifier key prefs: default to Windows settings,
--- a/parser/html/nsHtml5SpeculativeLoad.cpp +++ b/parser/html/nsHtml5SpeculativeLoad.cpp @@ -40,24 +40,24 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5T aExecutor->PreloadEndPicture(); break; case eSpeculativeLoadPictureSource: aExecutor->PreloadPictureSource(mSrcset, mSizes, mTypeOrCharsetSource, mMedia); 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); break; case eSpeculativeLoadSetDocumentCharset: { nsAutoCString narrowName; CopyUTF16toUTF8(mCharset, narrowName); NS_ASSERTION(mTypeOrCharsetSource.Length() == 1,
--- a/parser/html/nsHtml5SpeculativeLoad.h +++ b/parser/html/nsHtml5SpeculativeLoad.h @@ -100,37 +100,41 @@ class nsHtml5SpeculativeLoad { mTypeOrCharsetSource.Assign(aType); mMedia.Assign(aMedia); } inline void InitScript(const nsAString& aUrl, const nsAString& aCharset, const nsAString& aType, const nsAString& aCrossOrigin, + const nsAString& aIntegrity, bool aParserInHead) { NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized, "Trying to reinitialize a speculative load!"); mOpCode = aParserInHead ? eSpeculativeLoadScriptFromHead : eSpeculativeLoadScript; mUrl.Assign(aUrl); 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!"); mOpCode = eSpeculativeLoadStyle; mUrl.Assign(aUrl); mCharset.Assign(aCharset); mCrossOrigin.Assign(aCrossOrigin); + mIntegrity.Assign(aIntegrity); } /** * "Speculative" manifest loads aren't truly speculative--if a manifest * gets loaded, we are committed to it. There can never be a <script> * before the manifest, so the situation of having to undo a manifest due * to document.write() never arises. The reason why a parser * thread-discovered manifest gets loaded via the speculative load queue @@ -214,11 +218,17 @@ class nsHtml5SpeculativeLoad { * attribute. If the attribute is not set, this will be a void string. */ nsString mSizes; /** * If mOpCode is eSpeculativeLoadPictureSource, this is the value of "media" * 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
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -160,41 +160,47 @@ nsHtml5TreeBuilder::createElement(int32_ treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber()); nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC); if (url) { nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET); 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) || aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER); } } else if (nsHtml5Atoms::link == aName) { nsString* rel = aAttributes->getValue(nsHtml5AttributeName::ATTR_REL); // Not splitting on space here is bogus but the old parser didn't even // do a case-insensitive check. if (rel) { if (rel->LowerCaseEqualsASCII("stylesheet")) { nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF); if (url) { 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); if (url) { nsString* crossOrigin = aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN); mSpeculativeLoadQueue.AppendElement()-> InitPreconnect(*url, (crossOrigin) ? *crossOrigin : NullString()); @@ -251,35 +257,41 @@ nsHtml5TreeBuilder::createElement(int32_ NS_ASSERTION(treeOp, "Tree op allocation failed."); treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber()); nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF); if (url) { 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) { nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(); NS_ASSERTION(treeOp, "Tree op allocation failed."); treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber()); nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF); 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; } } else if (aNamespace != kNameSpaceID_MathML) { // No speculative loader--just line numbers and defer/async check if (nsHtml5Atoms::style == aName) { nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
--- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -906,38 +906,40 @@ nsHtml5TreeOpExecutor::ShouldPreloadURI( return true; } void nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL, const nsAString& aCharset, const nsAString& aType, const nsAString& aCrossOrigin, + const nsAString& aIntegrity, bool aScriptFromHead) { nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); if (!uri) { 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 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL, const nsAString& aCrossOrigin, const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aImageReferrerPolicy)
--- a/parser/html/nsHtml5TreeOpExecutor.h +++ b/parser/html/nsHtml5TreeOpExecutor.h @@ -243,20 +243,22 @@ class nsHtml5TreeOpExecutor final : publ #endif nsIURI* GetViewSourceBaseURI(); void PreloadScript(const nsAString& aURL, 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, const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aImageReferrerPolicy); void PreloadOpenPicture();
--- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -652,16 +652,21 @@ /* ======================================================================= */ /* 21: NS_ERROR_MODULE_SECURITY */ /* ======================================================================= */ #define MODULE NS_ERROR_MODULE_SECURITY /* Error code for CSP */ 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)), ERROR(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO, FAILURE(1025)), ERROR(NS_ERROR_CMS_VERIFY_BAD_DIGEST, FAILURE(1026)), ERROR(NS_ERROR_CMS_VERIFY_NOCERT, FAILURE(1028)), ERROR(NS_ERROR_CMS_VERIFY_UNTRUSTED, FAILURE(1029)), ERROR(NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED, FAILURE(1031)),