/** * 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; }
/* 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; }
nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel, const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter) { NS_ENSURE_ARG_POINTER(aReporter); if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) { nsAutoCString requestURL; nsCOMPtr<nsIRequest> request = aChannel; request->GetName(requestURL); SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%zu)", requestURL.get(), mBytesHashed)); } nsresult rv = Finish(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); LoadTainting tainting = loadInfo->GetTainting(); if (NS_FAILED(IsEligible(aChannel, tainting, aSourceFileURI, aReporter))) { return NS_ERROR_SRI_NOT_ELIGIBLE; } if (mInvalidMetadata) { return NS_OK; // ignore invalid metadata for forward-compatibility } for (uint32_t i = 0; i < aMetadata.HashCount(); i++) { if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aSourceFileURI, aReporter))) { return NS_OK; // stop at the first valid hash } } nsAutoCString alg; aMetadata.GetAlgorithm(&alg); NS_ConvertUTF8toUTF16 algUTF16(alg); nsTArray<nsString> params; params.AppendElement(algUTF16); aReporter->AddConsoleReport(nsIScriptError::errorFlag, NS_LITERAL_CSTRING("Sub-resource Integrity"), nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0, NS_LITERAL_CSTRING("IntegrityMismatch"), const_cast<const nsTArray<nsString>&>(params)); return NS_ERROR_SRI_CORRUPT; }
nsresult SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel, const CORSMode aCORSMode, const nsIDocument* aDocument) { NS_ENSURE_ARG_POINTER(aDocument); if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) { nsAutoCString requestURL; nsCOMPtr<nsIRequest> request; request = do_QueryInterface(aChannel); request->GetName(requestURL); SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%lu)", requestURL.get(), mBytesHashed)); } nsresult rv = Finish(); NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(IsEligible(aChannel, aCORSMode, aDocument))) { return NS_ERROR_SRI_NOT_ELIGIBLE; } if (mInvalidMetadata) { return NS_OK; // ignore invalid metadata for forward-compatibility } for (uint32_t i = 0; i < aMetadata.HashCount(); i++) { if (NS_SUCCEEDED(VerifyHash(aMetadata, i, 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::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; }