Bug 1795723 - Unified extensions UI should support High Contrast Mode. r=ayeddi,deskt...
[gecko.git] / dom / security / SRICheck.cpp
blob42e6a8c8960b1a5c9bed9427d7c468af974a85d2
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 // put a reasonable bound on the length of the metadata
98 NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
99 if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
100 metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
102 SRILOG(("SRICheck::IntegrityMetadata, metadataList=%s", metadataList.get()));
104 // the integrity attribute is a list of whitespace-separated hashes
105 // and options so we need to look at them one by one and pick the
106 // strongest (valid) one
107 nsCWhitespaceTokenizer tokenizer(metadataList);
108 nsAutoCString token;
109 for (uint32_t i = 0;
110 tokenizer.hasMoreTokens() && i < SRICheck::MAX_METADATA_TOKENS; ++i) {
111 token = tokenizer.nextToken();
113 SRIMetadata metadata(token);
114 if (metadata.IsMalformed()) {
115 NS_ConvertUTF8toUTF16 tokenUTF16(token);
116 nsTArray<nsString> params;
117 params.AppendElement(tokenUTF16);
118 aReporter->AddConsoleReport(
119 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
120 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
121 "MalformedIntegrityHash"_ns,
122 const_cast<const nsTArray<nsString>&>(params));
123 } else if (!metadata.IsAlgorithmSupported()) {
124 nsAutoCString alg;
125 metadata.GetAlgorithm(&alg);
126 NS_ConvertUTF8toUTF16 algUTF16(alg);
127 nsTArray<nsString> params;
128 params.AppendElement(algUTF16);
129 aReporter->AddConsoleReport(
130 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
131 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
132 "UnsupportedHashAlg"_ns,
133 const_cast<const nsTArray<nsString>&>(params));
136 nsAutoCString alg1, alg2;
137 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
138 outMetadata->GetAlgorithm(&alg1);
139 metadata.GetAlgorithm(&alg2);
141 if (*outMetadata == metadata) {
142 SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
143 alg1.get(), alg2.get()));
144 *outMetadata += metadata; // add new hash to strongest metadata
145 } else if (*outMetadata < metadata) {
146 SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
147 alg1.get(), alg2.get()));
148 *outMetadata = metadata; // replace strongest metadata with current
152 outMetadata->mIntegrityString = aMetadataList;
154 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
155 if (outMetadata->IsValid()) {
156 nsAutoCString alg;
157 outMetadata->GetAlgorithm(&alg);
158 SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
159 } else if (outMetadata->IsEmpty()) {
160 SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
161 } else {
162 SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
165 return NS_OK;
168 //////////////////////////////////////////////////////////////
170 //////////////////////////////////////////////////////////////
171 SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
172 const nsACString& aSourceFileURI,
173 nsIConsoleReportCollector* aReporter)
174 : mCryptoHash(nullptr),
175 mBytesHashed(0),
176 mHashLength(0),
177 mHashType('\0'),
178 mInvalidMetadata(false),
179 mComplete(false) {
180 MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
181 MOZ_ASSERT(aReporter);
183 if (!aMetadata.IsValid()) {
184 nsTArray<nsString> params;
185 aReporter->AddConsoleReport(
186 nsIScriptError::warningFlag, "Sub-resource Integrity"_ns,
187 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
188 "NoValidMetadata"_ns, const_cast<const nsTArray<nsString>&>(params));
189 mInvalidMetadata = true;
190 return; // ignore invalid metadata for forward-compatibility
193 aMetadata.GetHashType(&mHashType, &mHashLength);
196 nsresult SRICheckDataVerifier::EnsureCryptoHash() {
197 MOZ_ASSERT(!mInvalidMetadata);
199 if (mCryptoHash) {
200 return NS_OK;
203 nsCOMPtr<nsICryptoHash> cryptoHash;
204 nsresult rv = NS_NewCryptoHash(mHashType, getter_AddRefs(cryptoHash));
205 NS_ENSURE_SUCCESS(rv, rv);
207 mCryptoHash = cryptoHash;
208 return NS_OK;
211 nsresult SRICheckDataVerifier::Update(uint32_t aStringLen,
212 const uint8_t* aString) {
213 NS_ENSURE_ARG_POINTER(aString);
214 if (mInvalidMetadata) {
215 return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
218 nsresult rv;
219 rv = EnsureCryptoHash();
220 NS_ENSURE_SUCCESS(rv, rv);
222 mBytesHashed += aStringLen;
224 return mCryptoHash->Update(aString, aStringLen);
227 nsresult SRICheckDataVerifier::Finish() {
228 if (mInvalidMetadata || mComplete) {
229 return NS_OK; // already finished or invalid metadata
232 nsresult rv;
233 rv = EnsureCryptoHash(); // we need computed hash even for 0-length data
234 NS_ENSURE_SUCCESS(rv, rv);
236 rv = mCryptoHash->Finish(false, mComputedHash);
237 mCryptoHash = nullptr;
238 mComplete = true;
239 return rv;
242 nsresult SRICheckDataVerifier::VerifyHash(
243 const SRIMetadata& aMetadata, uint32_t aHashIndex,
244 const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter) {
245 NS_ENSURE_ARG_POINTER(aReporter);
247 nsAutoCString base64Hash;
248 aMetadata.GetHash(aHashIndex, &base64Hash);
249 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u]=%s", aHashIndex,
250 base64Hash.get()));
252 nsAutoCString binaryHash;
253 if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
254 nsTArray<nsString> params;
255 aReporter->AddConsoleReport(
256 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
257 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
258 "InvalidIntegrityBase64"_ns,
259 const_cast<const nsTArray<nsString>&>(params));
260 return NS_ERROR_SRI_CORRUPT;
263 uint32_t hashLength;
264 int8_t hashType;
265 aMetadata.GetHashType(&hashType, &hashLength);
266 if (binaryHash.Length() != hashLength) {
267 nsTArray<nsString> params;
268 aReporter->AddConsoleReport(
269 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
270 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
271 "InvalidIntegrityLength"_ns,
272 const_cast<const nsTArray<nsString>&>(params));
273 return NS_ERROR_SRI_CORRUPT;
276 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
277 nsAutoCString encodedHash;
278 nsresult rv = Base64Encode(mComputedHash, encodedHash);
279 if (NS_SUCCEEDED(rv)) {
280 SRILOG(("SRICheckDataVerifier::VerifyHash, mComputedHash=%s",
281 encodedHash.get()));
285 if (!binaryHash.Equals(mComputedHash)) {
286 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] did not match",
287 aHashIndex));
288 return NS_ERROR_SRI_CORRUPT;
291 SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] verified successfully",
292 aHashIndex));
293 return NS_OK;
296 nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
297 nsIChannel* aChannel,
298 const nsACString& aSourceFileURI,
299 nsIConsoleReportCollector* aReporter) {
300 NS_ENSURE_ARG_POINTER(aReporter);
302 if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
303 nsAutoCString requestURL;
304 nsCOMPtr<nsIRequest> request = aChannel;
305 request->GetName(requestURL);
306 SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%zu)",
307 requestURL.get(), mBytesHashed));
310 nsresult rv = Finish();
311 NS_ENSURE_SUCCESS(rv, rv);
313 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
314 LoadTainting tainting = loadInfo->GetTainting();
316 if (NS_FAILED(IsEligible(aChannel, tainting, aSourceFileURI, aReporter))) {
317 return NS_ERROR_SRI_NOT_ELIGIBLE;
320 if (mInvalidMetadata) {
321 return NS_OK; // ignore invalid metadata for forward-compatibility
324 for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
325 if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aSourceFileURI, aReporter))) {
326 return NS_OK; // stop at the first valid hash
330 nsAutoCString alg;
331 aMetadata.GetAlgorithm(&alg);
332 NS_ConvertUTF8toUTF16 algUTF16(alg);
333 nsTArray<nsString> params;
334 params.AppendElement(algUTF16);
335 aReporter->AddConsoleReport(
336 nsIScriptError::errorFlag, "Sub-resource Integrity"_ns,
337 nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0,
338 "IntegrityMismatch"_ns, const_cast<const nsTArray<nsString>&>(params));
339 return NS_ERROR_SRI_CORRUPT;
342 uint32_t SRICheckDataVerifier::DataSummaryLength() {
343 MOZ_ASSERT(!mInvalidMetadata);
344 return sizeof(mHashType) + sizeof(mHashLength) + mHashLength;
347 uint32_t SRICheckDataVerifier::EmptyDataSummaryLength() {
348 return sizeof(int8_t) + sizeof(uint32_t);
351 nsresult SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen,
352 const uint8_t* aData,
353 uint32_t* length) {
354 *length = 0;
355 NS_ENSURE_ARG_POINTER(aData);
357 // we expect to always encode an SRI, even if it is empty or incomplete
358 if (aDataLen < EmptyDataSummaryLength()) {
359 SRILOG(
360 ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too "
361 "small",
362 aDataLen));
363 return NS_ERROR_SRI_IMPORT;
366 // decode the content of the buffer
367 size_t offset = sizeof(mHashType);
368 decltype(mHashLength) len = 0;
369 memcpy(&len, &aData[offset], sizeof(mHashLength));
370 offset += sizeof(mHashLength);
372 SRIVERBOSE(
373 ("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, "
374 "...}",
375 aData[0], aData[1], aData[2], aData[3], aData[4]));
377 if (offset + len > aDataLen) {
378 SRILOG(
379 ("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow "
380 "the buffer size",
381 aDataLen));
382 SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]",
383 uint32_t(offset), uint32_t(len)));
384 return NS_ERROR_SRI_IMPORT;
386 *length = uint32_t(offset + len);
387 return NS_OK;
390 nsresult SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen,
391 const uint8_t* aData) {
392 MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
393 MOZ_ASSERT(!mCryptoHash); // EnsureCryptoHash should not have been called
394 NS_ENSURE_ARG_POINTER(aData);
395 if (mInvalidMetadata) {
396 return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
399 // we expect to always encode an SRI, even if it is empty or incomplete
400 if (aDataLen < DataSummaryLength()) {
401 SRILOG(
402 ("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too "
403 "small",
404 aDataLen));
405 return NS_ERROR_SRI_IMPORT;
408 SRIVERBOSE(
409 ("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, "
410 "...}",
411 aData[0], aData[1], aData[2], aData[3], aData[4]));
413 // decode the content of the buffer
414 size_t offset = 0;
415 decltype(mHashType) hashType;
416 memcpy(&hashType, &aData[offset], sizeof(mHashType));
417 if (hashType != mHashType) {
418 SRILOG(
419 ("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not "
420 "match[%d]",
421 hashType, mHashType));
422 return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
424 offset += sizeof(mHashType);
426 decltype(mHashLength) hashLength;
427 memcpy(&hashLength, &aData[offset], sizeof(mHashLength));
428 if (hashLength != mHashLength) {
429 SRILOG(
430 ("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not "
431 "match[%d]",
432 hashLength, mHashLength));
433 return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
435 offset += sizeof(mHashLength);
437 // copy the hash to mComputedHash, as-if we had finished streaming the bytes
438 mComputedHash.Assign(reinterpret_cast<const char*>(&aData[offset]),
439 mHashLength);
440 mCryptoHash = nullptr;
441 mComplete = true;
442 return NS_OK;
445 nsresult SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen,
446 uint8_t* aData) {
447 MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
448 MOZ_ASSERT(mComplete); // finished streaming
449 NS_ENSURE_ARG_POINTER(aData);
450 NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG);
452 // serialize the hash in the buffer
453 size_t offset = 0;
454 memcpy(&aData[offset], &mHashType, sizeof(mHashType));
455 offset += sizeof(mHashType);
456 memcpy(&aData[offset], &mHashLength, sizeof(mHashLength));
457 offset += sizeof(mHashLength);
459 SRIVERBOSE(
460 ("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, "
461 "...}",
462 aData[0], aData[1], aData[2], aData[3], aData[4]));
464 // copy the hash to mComputedHash, as-if we had finished streaming the bytes
465 nsCharTraits<char>::copy(reinterpret_cast<char*>(&aData[offset]),
466 mComputedHash.get(), mHashLength);
467 return NS_OK;
470 nsresult SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen,
471 uint8_t* aData) {
472 NS_ENSURE_ARG_POINTER(aData);
473 NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG);
475 // serialize an unknown hash in the buffer, to be able to skip it later
476 size_t offset = 0;
477 memset(&aData[offset], 0, sizeof(mHashType));
478 offset += sizeof(mHashType);
479 memset(&aData[offset], 0, sizeof(mHashLength));
480 offset += sizeof(mHashLength);
482 SRIVERBOSE(
483 ("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, "
484 "%x, ...}",
485 aData[0], aData[1], aData[2], aData[3], aData[4]));
487 return NS_OK;
490 } // namespace mozilla::dom