/* -*- 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/Logging.h" #include "nsICryptoHash.h" static mozilla::LogModule* GetSriMetadataLog() { static mozilla::LazyLogModule gSriMetadataPRLog("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::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(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=%zu)", 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; } bool SRIMetadata::CanTrustBeDelegatedTo(const SRIMetadata& aOther) const { if (IsEmpty()) { // No integrity requirements enforced, just let go. return true; } if (aOther.IsEmpty()) { // This metadata requires a check and the other has none, can't delegate. return false; } if (mAlgorithmType != aOther.mAlgorithmType) { // They must use the same hash algorithm. return false; } // They must be completely identical, except for the order of hashes. // We don't know which hash is the one passing eventually the check, so only // option is to require this metadata to contain the same set of hashes as the // one we want to delegate the trust to. if (mHashes.Length() != aOther.mHashes.Length()) { return false; } for (const auto& hash : mHashes) { if (!aOther.mHashes.Contains(hash)) { return false; } } return true; } } // namespace mozilla::dom