Browse Source
- Bug 1174323 - Disable screenClientXYConst subtest of pointerlock test on OS X. rs=KWierso (2d0db6d1b) - Bug 992096 - Implement Sub Resource Integrity [1/2]. r=baku,r=ckerschb (c30671ac0) - Bug 992096 - Implement Sub Resource Integrity [2/2]. r=ckerschb (0afc64d88) - Bug 1091883 - Added test, this is fixed by a fix to bug 1113438. r=sstamm CLOSED TREE (fd9a64b43) - Bug 1196740 - Consider redirects when looking for SRI-eligibility. r=ckerschb (5c749cdc9) - Bug 1202015 - Better document the SRI strings for translators. r=ckerschb (a7860e0fb) - Bug 1202027 - Make SRI require CORS loads for cross-origin resources. r=ckerschb (ea451323d) - bit of Bug 1202902 - Mass replace toplevel 'let' with 'var' (a6e8a587d) - Bug 1208629 - Properly support data: and blob: URIs with an integrity atribute. r=ckerschb (6b2018fe4) - Bug 1140129 - Don't clear tab title when location changes (r=Mossop) (ca1945ba8) - Bug 1073462: Send synthetic property with Content:LocationChange message. r=felipe (1aa418acf) - bug 1165017 - annotate content process URL on location change. r=mconley (cdca4fa75) - Bug 1157561 - Add webRequest-like API to Firefox (r=Mossop) (546a57822) - Bug 1163861 - Include windowID in all WebRequest notifications (r=Mossop) (c140af560) - Bug 1171248 - Add MatchPattern support to WebRequest module (r=Mossop) (b09a05658)pull/8/head
99 changed files with 3558 additions and 67 deletions
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<body> |
||||
<h2>Frame I am.</h2> |
||||
<script> |
||||
var subframeOrigin = location.hash.substr(1); // e.g., "http://example.com" |
||||
var subframe = document.createElement("iframe"); |
||||
subframe.src = subframeOrigin + |
||||
"/tests/dom/base/test/file_bug1091883_subframe.html"; |
||||
document.body.appendChild(subframe); |
||||
</script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<body> |
||||
<h3>Subframe I am.</h3> |
||||
</body> |
||||
</html> |
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<h3>Target I am.</h3> |
||||
<script> |
||||
var testRun = location.hash.substr(1); |
||||
parent.parent.postMessage(document.referrer + " " + testRun, |
||||
"http://mochi.test:8888"); |
||||
</script> |
||||
</head> |
||||
<body> |
||||
</body> |
||||
</html> |
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<!-- |
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1091883 |
||||
--> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="referrer" content="origin-when-crossorigin"> |
||||
<title>Test for Bug 1091883</title> |
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> |
||||
</head> |
||||
<body> |
||||
<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1091883">Mozilla Bug 1091883</a></p> |
||||
<h2>Results</h2> |
||||
<pre id="results">Running...</pre> |
||||
|
||||
<script> |
||||
SimpleTest.waitForExplicitFinish(); |
||||
|
||||
var origins = [ |
||||
"http://mochi.test:8888", "http://example.com", "http://example.org"]; |
||||
var numOrigins = origins.length; |
||||
|
||||
// For each combination of (frame, subframe, target) origins, this test |
||||
// includes a "frame" that includes a "subframe"; and then this test |
||||
// navigates this "subframe" to the "target". Both the referrer and |
||||
// the triggering principal are this test, i.e., "http://mochi.test:8888". |
||||
// Since the referrer policy is origin-when-crossorigin, we expect to have |
||||
// a full referrer if and only if the target is also "http://mochi.test:8888"; |
||||
// in all other cases, the referrer needs to be the origin alone. |
||||
var numTests = numOrigins * numOrigins * numOrigins; |
||||
|
||||
// Helpers to look up the approriate origins for a given test number. |
||||
function getFrameOrigin(i) { |
||||
return origins[(i / (numOrigins * numOrigins)) | 0]; |
||||
} |
||||
function getSubframeOrigin(i) { |
||||
return origins[((i / numOrigins) | 0) % 3]; |
||||
} |
||||
function getTargetOrigin(i) { |
||||
return origins[i % 3]; |
||||
} |
||||
|
||||
// Create the frames, and tell them which subframes to load. |
||||
for (var i = 0; i < numTests; i++) { |
||||
var frame = document.createElement("iframe"); |
||||
frame.src = getFrameOrigin(i) + |
||||
"/tests/dom/base/test/file_bug1091883_frame.html#" + |
||||
getSubframeOrigin(i); |
||||
document.body.appendChild(frame); |
||||
} |
||||
|
||||
// Navigate all subframes to the target. |
||||
window.onload = function() { |
||||
for (var i = 0; i < numTests; i++) { |
||||
frames[i].frames[0].location = getTargetOrigin(i) + |
||||
"/tests/dom/base/test/file_bug1091883_target.html#" + i; |
||||
} |
||||
}; |
||||
|
||||
// Check referrer messages from the target. |
||||
var results = {}; |
||||
function makeResultsKey(i) { |
||||
return i + ": " + getFrameOrigin(i) + " | " + getSubframeOrigin(i) + " -> " + |
||||
getTargetOrigin(i); |
||||
} |
||||
window.addEventListener("message", function(event) { |
||||
var out = event.data.split(" "); |
||||
var referrer = out[0]; |
||||
var testRun = +out[1]; |
||||
results[makeResultsKey(testRun)] = referrer; |
||||
if (event.origin == "http://mochi.test:8888") { |
||||
is(referrer, |
||||
"http://mochi.test:8888/tests/dom/base/test/test_bug1091883.html", |
||||
"must be full referrer"); |
||||
} else { |
||||
is(referrer, "http://mochi.test:8888", "must be origin referrer"); |
||||
} |
||||
if (Object.keys(results).length == numTests) { |
||||
document.getElementById("results").textContent = |
||||
JSON.stringify(results, null, 4); |
||||
SimpleTest.finish(); |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
</body> |
||||
</html> |
@ -0,0 +1,349 @@
|
||||
/* -*- 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 "nsIChannel.h" |
||||
#include "nsICryptoHash.h" |
||||
#include "nsIDocument.h" |
||||
#include "nsIProtocolHandler.h" |
||||
#include "nsIScriptError.h" |
||||
#include "nsIScriptSecurityManager.h" |
||||
#include "nsIStreamLoader.h" |
||||
#include "nsIUnicharStreamLoader.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(nsIChannel* aChannel, const CORSMode aCORSMode, |
||||
const nsIDocument* aDocument) |
||||
{ |
||||
NS_ENSURE_ARG_POINTER(aDocument); |
||||
|
||||
if (!aChannel) { |
||||
SRILOG(("SRICheck::IsEligible, null channel")); |
||||
return NS_ERROR_SRI_NOT_ELIGIBLE; |
||||
} |
||||
|
||||
// Was the sub-resource loaded via CORS?
|
||||
if (aCORSMode != CORS_NONE) { |
||||
SRILOG(("SRICheck::IsEligible, CORS mode")); |
||||
return NS_OK; |
||||
} |
||||
|
||||
nsCOMPtr<nsIURI> finalURI; |
||||
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI)); |
||||
NS_ENSURE_SUCCESS(rv, rv); |
||||
nsCOMPtr<nsIURI> originalURI; |
||||
rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI)); |
||||
NS_ENSURE_SUCCESS(rv, rv); |
||||
nsAutoCString requestSpec; |
||||
rv = originalURI->GetSpec(requestSpec); |
||||
NS_ENSURE_SUCCESS(rv, rv); |
||||
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) { |
||||
nsAutoCString documentSpec, finalSpec; |
||||
aDocument->GetDocumentURI()->GetAsciiSpec(documentSpec); |
||||
if (finalURI) { |
||||
finalURI->GetSpec(finalSpec); |
||||
} |
||||
SRILOG(("SRICheck::IsEligible, documentURI=%s; requestURI=%s; finalURI=%s", |
||||
documentSpec.get(), requestSpec.get(), finalSpec.get())); |
||||
} |
||||
|
||||
// Is the sub-resource same-origin?
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
||||
if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(), |
||||
finalURI, false))) { |
||||
SRILOG(("SRICheck::IsEligible, same-origin")); |
||||
return NS_OK; |
||||
} |
||||
SRILOG(("SRICheck::IsEligible, NOT same origin")); |
||||
|
||||
NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec); |
||||
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, |
||||
"MalformedIntegrityHash", |
||||
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 |
||||
VerifyIntegrityInternal(const SRIMetadata& aMetadata, |
||||
nsIChannel* aChannel, |
||||
const CORSMode aCORSMode, |
||||
uint32_t aStringLen, |
||||
const uint8_t* aString, |
||||
const nsIDocument* aDocument) |
||||
{ |
||||
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(aChannel, aCORSMode, aDocument))) { |
||||
return NS_ERROR_SRI_NOT_ELIGIBLE; |
||||
} |
||||
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; |
||||
} |
||||
|
||||
/* static */ nsresult |
||||
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata, |
||||
nsIUnicharStreamLoader* aLoader, |
||||
const CORSMode aCORSMode, |
||||
const nsAString& aString, |
||||
const nsIDocument* aDocument) |
||||
{ |
||||
NS_ENSURE_ARG_POINTER(aLoader); |
||||
|
||||
NS_ConvertUTF16toUTF8 utf8Hash(aString); |
||||
nsCOMPtr<nsIChannel> channel; |
||||
aLoader->GetChannel(getter_AddRefs(channel)); |
||||
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) { |
||||
nsAutoCString requestURL; |
||||
nsCOMPtr<nsIURI> originalURI; |
||||
if (channel && |
||||
NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) && |
||||
originalURI) { |
||||
originalURI->GetAsciiSpec(requestURL); |
||||
} |
||||
SRILOG(("SRICheck::VerifyIntegrity (unichar stream), url=%s (length=%u)", |
||||
requestURL.get(), utf8Hash.Length())); |
||||
} |
||||
|
||||
return VerifyIntegrityInternal(aMetadata, channel, aCORSMode, |
||||
utf8Hash.Length(), (uint8_t*)utf8Hash.get(), |
||||
aDocument); |
||||
} |
||||
|
||||
/* static */ nsresult |
||||
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata, |
||||
nsIStreamLoader* aLoader, |
||||
const CORSMode aCORSMode, |
||||
uint32_t aStringLen, |
||||
const uint8_t* aString, |
||||
const nsIDocument* aDocument) |
||||
{ |
||||
NS_ENSURE_ARG_POINTER(aLoader); |
||||
|
||||
nsCOMPtr<nsIRequest> request; |
||||
aLoader->GetRequest(getter_AddRefs(request)); |
||||
NS_ENSURE_ARG_POINTER(request); |
||||
nsCOMPtr<nsIChannel> channel; |
||||
channel = do_QueryInterface(request); |
||||
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) { |
||||
nsAutoCString requestURL; |
||||
request->GetName(requestURL); |
||||
SRILOG(("SRICheck::VerifyIntegrity (stream), url=%s (length=%u)", |
||||
requestURL.get(), aStringLen)); |
||||
} |
||||
|
||||
return VerifyIntegrityInternal(aMetadata, channel, aCORSMode, |
||||
aStringLen, aString, aDocument); |
||||
} |
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
@ -0,0 +1,60 @@
|
||||
/* -*- 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 nsIStreamLoader; |
||||
class nsIUnicharStreamLoader; |
||||
|
||||
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, |
||||
nsIUnicharStreamLoader* aLoader, |
||||
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, |
||||
nsIStreamLoader* aLoader, |
||||
const CORSMode aCORSMode, |
||||
uint32_t aStringLen, |
||||
const uint8_t* aString, |
||||
const nsIDocument* aDocument); |
||||
}; |
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_SRICheck_h
|
@ -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
|
@ -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
|
@ -0,0 +1,135 @@
|
||||
<!DOCTYPE HTML> |
||||
<!-- Any copyright is dedicated to the Public Domain. |
||||
http://creativecommons.org/publicdomain/zero/1.0/ --> |
||||
<html> |
||||
<head> |
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> |
||||
</head> |
||||
<body> |
||||
<p id="display"></p> |
||||
<div id="content" style="display: none"> |
||||
</div> |
||||
<pre id="test"> |
||||
</pre> |
||||
|
||||
<script type="application/javascript"> |
||||
SimpleTest.waitForExplicitFinish(); |
||||
|
||||