no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / security / SRICheck.cpp
blob9eadcd04fcd6d9785f2a6fbdb2fba0079dfbbf54
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SRICheck.h"
9 #include "mozilla/Base64.h"
10 #include "mozilla/LoadTainting.h"
11 #include "mozilla/Logging.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/dom/SRILogHelper.h"
14 #include "mozilla/dom/SRIMetadata.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsContentUtils.h"
17 #include "nsIChannel.h"
18 #include "nsIConsoleReportCollector.h"
19 #include "nsIScriptError.h"
20 #include "nsIURI.h"
21 #include "nsNetUtil.h"
22 #include "nsWhitespaceTokenizer.h"
24 #define SRIVERBOSE(args) \
25 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Verbose, args)
26 #define SRILOG(args) \
27 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args)
28 #define SRIERROR(args) \
29 MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args)
31 namespace mozilla::dom {
33 /**
34 * Returns whether or not the sub-resource about to be loaded is eligible
35 * for integrity checks. If it's not, the checks will be skipped and the
36 * sub-resource will be loaded.
38 static nsresult IsEligible(nsIChannel* aChannel,
39 mozilla::LoadTainting aTainting,
40 const nsACString& aSourceFileURI,
41 nsIConsoleReportCollector* aReporter) {
42 NS_ENSURE_ARG_POINTER(aReporter);
44 if (!aChannel) {
45 SRILOG(("SRICheck::IsEligible, null channel"));
46 return NS_ERROR_SRI_NOT_ELIGIBLE;
49 // Was the sub-resource loaded via CORS?
50 if (aTainting == LoadTainting::CORS) {
51 SRILOG(("SRICheck::IsEligible, CORS mode"));
52 return NS_OK;
55 nsCOMPtr<nsIURI> finalURI;
56 nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
57 NS_ENSURE_SUCCESS(rv, rv);
58 nsCOMPtr<nsIURI> originalURI;
59 rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
60 NS_ENSURE_SUCCESS(rv, rv);
61 nsAutoCString requestSpec;
62 rv = originalURI->GetSpec(requestSpec);
63 NS_ENSURE_SUCCESS(rv, rv);
65 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
66 SRILOG(("SRICheck::IsEligible, requestURI=%s; finalURI=%s",
67 requestSpec.get(),
68 finalURI ? finalURI->GetSpecOrDefault().get() : ""));
71 // Is the sub-resource same-origin?
72 if (aTainting == LoadTainting::Basic) {
73 SRILOG(("SRICheck::IsEligible, same-origin"));
74 return NS_OK;
76 SRILOG(("SRICheck::IsEligible, NOT same-origin"));
78 NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
79 nsTArray<nsString> params;
80 params.AppendElement(requestSpecUTF16);
81 aReporter->AddConsoleReport(
82 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
83 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
84 "IneligibleResource"_ns, const_cast<const nsTArray<nsString>&>(params));
85 return NS_ERROR_SRI_NOT_ELIGIBLE;
88 /* static */
89 nsresult SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
90 const nsACString& aSourceFileURI,
91 nsIConsoleReportCollector* aReporter,
92 SRIMetadata* outMetadata) {
93 NS_ENSURE_ARG_POINTER(outMetadata);
94 NS_ENSURE_ARG_POINTER(aReporter);
95 MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
97 NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
98 SRILOG(("SRICheck::IntegrityMetadata, metadataList=%s", metadataList.get()));
100 // the integrity attribute is a list of whitespace-separated hashes
101 // and options so we need to look at them one by one and pick the
102 // strongest (valid) one
103 nsCWhitespaceTokenizer tokenizer(metadataList);
104 nsAutoCString token;
105 while (tokenizer.hasMoreTokens()) {
106 token = tokenizer.nextToken();
108 SRIMetadata metadata(token);
109 if (metadata.IsMalformed()) {
110 NS_ConvertUTF8toUTF16 tokenUTF16(token);
111 nsTArray<nsString> params;
112 params.AppendElement(tokenUTF16);
113 aReporter->AddConsoleReport(
114 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
115 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
116 "MalformedIntegrityHash"_ns,
117 const_cast<const nsTArray<nsString>&>(params));
118 } else if (!metadata.IsAlgorithmSupported()) {
119 nsAutoCString alg;
120 metadata.GetAlgorithm(&alg);
121 NS_ConvertUTF8toUTF16 algUTF16(alg);
122 nsTArray<nsString> params;
123 params.AppendElement(algUTF16);
124 aReporter->AddConsoleReport(
125 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
126 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
127 "UnsupportedHashAlg"_ns,
128 const_cast<const nsTArray<nsString>&>(params));
131 nsAutoCString alg1, alg2;
132 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
133 outMetadata->GetAlgorithm(&alg1);
134 metadata.GetAlgorithm(&alg2);
136 if (*outMetadata == metadata) {
137 SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
138 alg1.get(), alg2.get()));
139 *outMetadata += metadata; // add new hash to strongest metadata
140 } else if (*outMetadata < metadata) {
141 SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
142 alg1.get(), alg2.get()));
143 *outMetadata = metadata; // replace strongest metadata with current
147 outMetadata->mIntegrityString = aMetadataList;
149 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
150 if (outMetadata->IsValid()) {
151 nsAutoCString alg;
152 outMetadata->GetAlgorithm(&alg);
153 SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
154 } else if (outMetadata->IsEmpty()) {
155 SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
156 } else {
157 SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
160 return NS_OK;
163 //////////////////////////////////////////////////////////////
165 //////////////////////////////////////////////////////////////
166 SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
167 const nsACString& aSourceFileURI,
168 nsIConsoleReportCollector* aReporter)
169 : mCryptoHash(nullptr),
170 mBytesHashed(0),
171 mHashLength(0),
172 mHashType('\0'),
173 mInvalidMetadata(false),
174 mComplete(false) {
175 MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
176 MOZ_ASSERT(aReporter);
178 if (!aMetadata.IsValid()) {
179 nsTArray<nsString> params;
180 aReporter->AddConsoleReport(
181 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
182 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
183 "NoValidMetadata"_ns, const_cast<const nsTArray<nsString>&>(params));
184 mInvalidMetadata = true;
185 return; // ignore invalid metadata for forward-compatibility
188 aMetadata.GetHashType(&mHashType, &mHashLength);
191 nsresult SRICheckDataVerifier::EnsureCryptoHash() {
192 MOZ_ASSERT(!mInvalidMetadata);
194 if (mCryptoHash) {
195 return NS_OK;
198 nsCOMPtr<nsICryptoHash> cryptoHash;
199 nsresult rv = NS_NewCryptoHash(mHashType, getter_AddRefs(cryptoHash));
200 NS_ENSURE_SUCCESS(rv, rv);
202 mCryptoHash = cryptoHash;
203 return NS_OK;
206 nsresult SRICheckDataVerifier::Update(uint32_t aStringLen,
207 const uint8_t* aString) {
208 NS_ENSURE_ARG_POINTER(aString);
209 if (mInvalidMetadata) {
210 return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
213 nsresult rv;
214 rv = EnsureCryptoHash();
215 NS_ENSURE_SUCCESS(rv, rv);
217 mBytesHashed += aStringLen;
219 return mCryptoHash->Update(aString, aStringLen);
222 nsresult SRICheckDataVerifier::Finish() {
223 if (mInvalidMetadata || mComplete) {
224 return NS_OK; // already finished or invalid metadata
227 nsresult rv;
228 rv = EnsureCryptoHash(); // we need computed hash even for 0-length data
229 NS_ENSURE_SUCCESS(rv, rv);
231 rv = mCryptoHash->Finish(false, mComputedHash);
232 mCryptoHash = nullptr;
233 mComplete = true;
234 return rv;
237 nsresult SRICheckDataVerifier::VerifyHash(
238 const SRIMetadata& aMetadata, uint32_t aHashIndex,
239 const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter) {
240 NS_ENSURE_ARG_POINTER(aReporter);
242 nsAutoCString base64Hash;
243 aMetadata.GetHash(aHashIndex, &base64Hash);
244 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u]=%s", aHashIndex,
245 base64Hash.get()));
247 nsAutoCString binaryHash;
249 // We're decoding the supplied hash twice. Trying base64 first.
250 nsresult rv = Base64Decode(base64Hash, binaryHash);
252 if (NS_FAILED(rv)) {
253 SRILOG(
254 ("SRICheckDataVerifier::VerifyHash, base64 decoding failed. Trying "
255 "base64url next."));
256 FallibleTArray<uint8_t> decoded;
257 rv = Base64URLDecode(base64Hash, Base64URLDecodePaddingPolicy::Ignore,
258 decoded);
259 if (NS_FAILED(rv)) {
260 SRILOG(
261 ("SRICheckDataVerifier::VerifyHash, base64url decoding failed too. "
262 "Bailing out."));
263 // if neither succeeded, we can bail out and warn
264 nsTArray<nsString> params;
265 aReporter->AddConsoleReport(
266 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
267 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
268 "InvalidIntegrityBase64"_ns,
269 const_cast<const nsTArray<nsString>&>(params));
270 return NS_ERROR_SRI_CORRUPT;
272 binaryHash.Assign(reinterpret_cast<const char*>(decoded.Elements()),
273 decoded.Length());
274 SRILOG(
275 ("SRICheckDataVerifier::VerifyHash, decoded supplied base64url hash "
276 "successfully."));
277 } else {
278 SRILOG(
279 ("SRICheckDataVerifier::VerifyHash, decoded supplied base64 hash "
280 "successfully."));
283 uint32_t hashLength;
284 int8_t hashType;
285 aMetadata.GetHashType(&hashType, &hashLength);
286 if (binaryHash.Length() != hashLength) {
287 SRILOG(
288 ("SRICheckDataVerifier::VerifyHash, supplied base64(url) hash was "
289 "incorrect length after decoding."));
290 nsTArray<nsString> params;
291 aReporter->AddConsoleReport(
292 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
293 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
294 "InvalidIntegrityLength"_ns,
295 const_cast<const nsTArray<nsString>&>(params));
296 return NS_ERROR_SRI_CORRUPT;
299 // the decoded supplied hash should match our computed binary hash.
300 if (!binaryHash.Equals(mComputedHash)) {
301 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] did not match",
302 aHashIndex));
303 return NS_ERROR_SRI_CORRUPT;
306 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] verified successfully",
307 aHashIndex));
308 return NS_OK;
311 nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
312 nsIChannel* aChannel,
313 const nsACString& aSourceFileURI,
314 nsIConsoleReportCollector* aReporter) {
315 NS_ENSURE_ARG_POINTER(aReporter);
317 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
318 nsAutoCString requestURL;
319 nsCOMPtr<nsIRequest> request = aChannel;
320 request->GetName(requestURL);
321 SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%zu)",
322 requestURL.get(), mBytesHashed));
325 nsresult rv = Finish();
326 NS_ENSURE_SUCCESS(rv, rv);
328 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
329 LoadTainting tainting = loadInfo->GetTainting();
331 if (NS_FAILED(IsEligible(aChannel, tainting, aSourceFileURI, aReporter))) {
332 return NS_ERROR_SRI_NOT_ELIGIBLE;
335 if (mInvalidMetadata) {
336 return NS_OK; // ignore invalid metadata for forward-compatibility
339 for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
340 if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aSourceFileURI, aReporter))) {
341 return NS_OK; // stop at the first valid hash
345 nsAutoCString alg;
346 aMetadata.GetAlgorithm(&alg);
347 NS_ConvertUTF8toUTF16 algUTF16(alg);
348 nsAutoCString encodedHash;
349 rv = Base64Encode(mComputedHash, encodedHash);
350 NS_ENSURE_SUCCESS(rv, rv);
351 NS_ConvertUTF8toUTF16 encodedHashUTF16(encodedHash);
353 nsTArray<nsString> params;
354 params.AppendElement(algUTF16);
355 params.AppendElement(encodedHashUTF16);
356 aReporter->AddConsoleReport(
357 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
358 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
359 "IntegrityMismatch2"_ns, const_cast<const nsTArray<nsString>&>(params));
361 return NS_ERROR_SRI_CORRUPT;
364 uint32_t SRICheckDataVerifier::DataSummaryLength() {
365 MOZ_ASSERT(!mInvalidMetadata);
366 return sizeof(mHashType) + sizeof(mHashLength) + mHashLength;
369 uint32_t SRICheckDataVerifier::EmptyDataSummaryLength() {
370 return sizeof(int8_t) + sizeof(uint32_t);
373 nsresult SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen,
374 const uint8_t* aData,
375 uint32_t* length) {
376 *length = 0;
377 NS_ENSURE_ARG_POINTER(aData);
379 // we expect to always encode an SRI, even if it is empty or incomplete
380 if (aDataLen < EmptyDataSummaryLength()) {
381 SRILOG(
382 ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too "
383 "small",
384 aDataLen));
385 return NS_ERROR_SRI_IMPORT;
388 // decode the content of the buffer
389 size_t offset = sizeof(mHashType);
390 decltype(mHashLength) len = 0;
391 memcpy(&len, &aData[offset], sizeof(mHashLength));
392 offset += sizeof(mHashLength);
394 SRIVERBOSE(
395 ("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, "
396 "...}",
397 aData[0], aData[1], aData[2], aData[3], aData[4]));
399 if (offset + len > aDataLen) {
400 SRILOG(
401 ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow "
402 "the buffer size",
403 aDataLen));
404 SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]",
405 uint32_t(offset), uint32_t(len)));
406 return NS_ERROR_SRI_IMPORT;
408 *length = uint32_t(offset + len);
409 return NS_OK;
412 nsresult SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen,
413 const uint8_t* aData) {
414 MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
415 MOZ_ASSERT(!mCryptoHash); // EnsureCryptoHash should not have been called
416 NS_ENSURE_ARG_POINTER(aData);
417 if (mInvalidMetadata) {
418 return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
421 // we expect to always encode an SRI, even if it is empty or incomplete
422 if (aDataLen < DataSummaryLength()) {
423 SRILOG(
424 ("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too "
425 "small",
426 aDataLen));
427 return NS_ERROR_SRI_IMPORT;
430 SRIVERBOSE(
431 ("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, "
432 "...}",
433 aData[0], aData[1], aData[2], aData[3], aData[4]));
435 // decode the content of the buffer
436 size_t offset = 0;
437 decltype(mHashType) hashType;
438 memcpy(&hashType, &aData[offset], sizeof(mHashType));
439 if (hashType != mHashType) {
440 SRILOG(
441 ("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not "
442 "match[%d]",
443 hashType, mHashType));
444 return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
446 offset += sizeof(mHashType);
448 decltype(mHashLength) hashLength;
449 memcpy(&hashLength, &aData[offset], sizeof(mHashLength));
450 if (hashLength != mHashLength) {
451 SRILOG(
452 ("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not "
453 "match[%d]",
454 hashLength, mHashLength));
455 return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
457 offset += sizeof(mHashLength);
459 // copy the hash to mComputedHash, as-if we had finished streaming the bytes
460 mComputedHash.Assign(reinterpret_cast<const char*>(&aData[offset]),
461 mHashLength);
462 mCryptoHash = nullptr;
463 mComplete = true;
464 return NS_OK;
467 nsresult SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen,
468 uint8_t* aData) {
469 MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
470 MOZ_ASSERT(mComplete); // finished streaming
471 NS_ENSURE_ARG_POINTER(aData);
472 NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG);
474 // serialize the hash in the buffer
475 size_t offset = 0;
476 memcpy(&aData[offset], &mHashType, sizeof(mHashType));
477 offset += sizeof(mHashType);
478 memcpy(&aData[offset], &mHashLength, sizeof(mHashLength));
479 offset += sizeof(mHashLength);
481 SRIVERBOSE(
482 ("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, "
483 "...}",
484 aData[0], aData[1], aData[2], aData[3], aData[4]));
486 // copy the hash to mComputedHash, as-if we had finished streaming the bytes
487 nsCharTraits<char>::copy(reinterpret_cast<char*>(&aData[offset]),
488 mComputedHash.get(), mHashLength);
489 return NS_OK;
492 nsresult SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen,
493 uint8_t* aData) {
494 NS_ENSURE_ARG_POINTER(aData);
495 NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG);
497 // serialize an unknown hash in the buffer, to be able to skip it later
498 size_t offset = 0;
499 memset(&aData[offset], 0, sizeof(mHashType));
500 offset += sizeof(mHashType);
501 memset(&aData[offset], 0, sizeof(mHashLength));
503 SRIVERBOSE(
504 ("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, "
505 "%x, ...}",
506 aData[0], aData[1], aData[2], aData[3], aData[4]));
508 return NS_OK;
511 } // namespace mozilla::dom