author | Nicolas B. Pierron <nicolas.b.pierron@mozilla.com> |
Thu, 20 Oct 2016 09:44:33 +0000 | |
changeset 318826 | 3ae084908fc41b787076e13a5253cc61d912bbc4 |
parent 318825 | 4a8c5061f3b753cb34028b59c9ccc654027ba021 |
child 318827 | e30472b2861ab0f353e59964018bbee6dcc63235 |
push id | 30854 |
push user | ryanvm@gmail.com |
push date | Fri, 21 Oct 2016 21:08:02 +0000 |
treeherder | mozilla-central@806054dd12bd [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | francois |
bugs | 1288104 |
milestone | 52.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/dom/security/SRICheck.cpp +++ b/dom/security/SRICheck.cpp @@ -18,16 +18,18 @@ #include "nsIProtocolHandler.h" #include "nsIScriptError.h" #include "nsIIncrementalStreamLoader.h" #include "nsIUnicharStreamLoader.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsWhitespaceTokenizer.h" +#define SRIVERBOSE(args) \ + MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Verbose, args) #define SRILOG(args) \ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args) #define SRIERROR(args) \ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args) namespace mozilla { namespace dom { @@ -237,18 +239,17 @@ SRICheckDataVerifier::SRICheckDataVerifi nsContentUtils::eSECURITY_PROPERTIES, aSourceFileURI, 0, 0, NS_LITERAL_CSTRING("NoValidMetadata"), const_cast<const nsTArray<nsString>&>(params)); mInvalidMetadata = true; return; // ignore invalid metadata for forward-compatibility } - uint32_t hashLength; - aMetadata.GetHashType(&mHashType, &hashLength); + aMetadata.GetHashType(&mHashType, &mHashLength); } nsresult SRICheckDataVerifier::EnsureCryptoHash() { MOZ_ASSERT(!mInvalidMetadata); if (mCryptoHash) { @@ -403,10 +404,140 @@ SRICheckDataVerifier::Verify(const SRIMe 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; } +uint32_t +SRICheckDataVerifier::DataSummaryLength() +{ + MOZ_ASSERT(!mInvalidMetadata); + return sizeof(mHashType) + sizeof(mHashLength) + mHashLength; +} + +uint32_t +SRICheckDataVerifier::EmptyDataSummaryLength() +{ + return sizeof(int8_t) + sizeof(uint32_t); +} + +nsresult +SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length) +{ + *length = 0; + NS_ENSURE_ARG_POINTER(aData); + + // we expect to always encode an SRI, even if it is empty or incomplete + if (aDataLen < EmptyDataSummaryLength()) { + SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too small", aDataLen)); + return NS_ERROR_SRI_IMPORT; + } + + // decode the content of the buffer + size_t offset = sizeof(mHashType); + size_t len = *reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]); + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + if (offset + len > aDataLen) { + SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow the buffer size", aDataLen)); + SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]", + uint32_t(offset), uint32_t(len))); + return NS_ERROR_SRI_IMPORT; + } + *length = uint32_t(offset + len); + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen, const uint8_t* aData) +{ + MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid + MOZ_ASSERT(!mCryptoHash); // EnsureCryptoHash should not have been called + NS_ENSURE_ARG_POINTER(aData); + if (mInvalidMetadata) { + return NS_OK; // ignoring any data updates, see mInvalidMetadata usage + } + + // we expect to always encode an SRI, even if it is empty or incomplete + if (aDataLen < DataSummaryLength()) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too small", aDataLen)); + return NS_ERROR_SRI_IMPORT; + } + + SRIVERBOSE(("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + // decode the content of the buffer + size_t offset = 0; + if (*reinterpret_cast<const decltype(mHashType)*>(&aData[offset]) != mHashType) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not match[%d]", + *reinterpret_cast<const decltype(mHashType)*>(&aData[offset]), + mHashType)); + return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE; + } + offset += sizeof(mHashType); + + if (*reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]) != mHashLength) { + SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not match[%d]", + *reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]), + mHashLength)); + return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE; + } + offset += sizeof(mHashLength); + + // copy the hash to mComputedHash, as-if we had finished streaming the bytes + mComputedHash.Assign(reinterpret_cast<const char*>(&aData[offset]), mHashLength); + mCryptoHash = nullptr; + mComplete = true; + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen, uint8_t* aData) +{ + MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid + MOZ_ASSERT(mComplete); // finished streaming + NS_ENSURE_ARG_POINTER(aData); + NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG); + + // serialize the hash in the buffer + size_t offset = 0; + *reinterpret_cast<decltype(mHashType)*>(&aData[offset]) = mHashType; + offset += sizeof(mHashType); + *reinterpret_cast<decltype(mHashLength)*>(&aData[offset]) = mHashLength; + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + // copy the hash to mComputedHash, as-if we had finished streaming the bytes + nsCharTraits<char>::copy(reinterpret_cast<char*>(&aData[offset]), + mComputedHash.get(), mHashLength); + return NS_OK; +} + +nsresult +SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData) +{ + NS_ENSURE_ARG_POINTER(aData); + NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG); + + // serialize an unknown hash in the buffer, to be able to skip it later + size_t offset = 0; + *reinterpret_cast<decltype(mHashType)*>(&aData[offset]) = 0; + offset += sizeof(mHashType); + *reinterpret_cast<decltype(mHashLength)*>(&aData[offset]) = 0; + offset += sizeof(mHashLength); + + SRIVERBOSE(("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, %x, ...}", + aData[0], aData[1], aData[2], aData[3], aData[4])); + + return NS_OK; +} + } // namespace dom } // namespace mozilla
--- a/dom/security/SRICheck.h +++ b/dom/security/SRICheck.h @@ -40,32 +40,73 @@ public: */ static nsresult VerifyIntegrity(const SRIMetadata& aMetadata, nsIUnicharStreamLoader* aLoader, const nsAString& aString, const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter); }; +// The SRICheckDataVerifier can be used in 2 different mode: +// +// 1. The streaming mode involves reading bytes from an input, and to use +// the |Update| function to stream new bytes, and to use the |Verify| +// function to check the hash of the content with the hash provided by +// the metadata. +// +// Optionally, one can serialize the verified hash with |ExportDataSummary|, +// in a buffer in order to rely on the second mode the next time. +// +// 2. The pre-computed mode, involves reading a hash with |ImportDataSummary|, +// which got exported by the SRICheckDataVerifier and potentially cached, and +// then use the |Verify| function to check against the hash provided by the +// metadata. class SRICheckDataVerifier final { public: SRICheckDataVerifier(const SRIMetadata& aMetadata, const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter); + // Append the following bytes to the content used to compute the hash. Once + // all bytes are streamed, use the Verify function to check the integrity. nsresult Update(uint32_t aStringLen, const uint8_t* aString); + + // Verify that the computed hash corresponds to the metadata. nsresult Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel, const nsACString& aSourceFileURI, nsIConsoleReportCollector* aReporter); + bool IsComplete() const { + return mComplete; + } + + // Report the length of the computed hash and its type, such that we can + // reserve the space for encoding it in a vector. + uint32_t DataSummaryLength(); + static uint32_t EmptyDataSummaryLength(); + + // Write the computed hash and its type in a pre-allocated buffer. + nsresult ExportDataSummary(uint32_t aDataLen, uint8_t* aData); + static nsresult ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData); + + // Report the length of the computed hash and its type, such that we can + // skip these data while reading a buffer. + static nsresult DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length); + + // Extract the computed hash and its type, such that we can |Verify| if it + // matches the metadata. The buffer should be at least the same size or + // larger than the value returned by |DataSummaryLength|. + nsresult ImportDataSummary(uint32_t aDataLen, const uint8_t* aData); + private: nsCOMPtr<nsICryptoHash> mCryptoHash; nsAutoCString mComputedHash; size_t mBytesHashed; + uint32_t mHashLength; int8_t mHashType; bool mInvalidMetadata; bool mComplete; nsresult EnsureCryptoHash(); nsresult Finish(); nsresult VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex, const nsACString& aSourceFileURI,
--- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -691,16 +691,18 @@ /* 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)), + ERROR(NS_ERROR_SRI_UNEXPECTED_HASH_TYPE, FAILURE(203)), + ERROR(NS_ERROR_SRI_IMPORT, FAILURE(204)), /* 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)),