void expectEmptyParseResult(const char* integrityAttribute) { IntegrityMetadataSet metadataSet; EXPECT_EQ(SubresourceIntegrity::IntegrityParseValidResult, SubresourceIntegrity::parseIntegrityAttribute(integrityAttribute, metadataSet)); EXPECT_EQ(0u, metadataSet.size()); }
void expectParseMultipleHashes(const char* integrityAttribute, const IntegrityMetadata expectedMetadataArray[], size_t expectedMetadataArraySize) { IntegrityMetadataSet expectedMetadataSet; for (size_t i = 0; i < expectedMetadataArraySize; i++) { expectedMetadataSet.add(expectedMetadataArray[i].toPair()); } IntegrityMetadataSet metadataSet; EXPECT_EQ(SubresourceIntegrity::IntegrityParseValidResult, SubresourceIntegrity::parseIntegrityAttribute(integrityAttribute, metadataSet)); EXPECT_TRUE(IntegrityMetadata::setsEqual(expectedMetadataSet, metadataSet)); }
bool IntegrityMetadata::setsEqual(const IntegrityMetadataSet& set1, const IntegrityMetadataSet& set2) { if (set1.size() != set2.size()) return false; for (const IntegrityMetadataPair& metadata : set1) { if (!set2.contains(metadata)) return false; } return true; }
void expectParse(const char* integrityAttribute, const char* expectedDigest, HashAlgorithm expectedAlgorithm) { IntegrityMetadataSet metadataSet; EXPECT_EQ(SubresourceIntegrity::IntegrityParseValidResult, SubresourceIntegrity::parseIntegrityAttribute(integrityAttribute, metadataSet)); EXPECT_EQ(1u, metadataSet.size()); if (metadataSet.size() > 0) { IntegrityMetadata metadata = *metadataSet.begin(); EXPECT_EQ(expectedDigest, metadata.digest()); EXPECT_EQ(expectedAlgorithm, metadata.algorithm()); } }
bool SubresourceIntegrity::CheckSubresourceIntegrity(const IntegrityMetadataSet& metadataSet, const char* content, size_t size, const KURL& resourceUrl, Document& document, String& errorMessage) { if (!metadataSet.size()) return true; HashAlgorithm strongestAlgorithm = HashAlgorithmSha256; for (const IntegrityMetadata& metadata : metadataSet) strongestAlgorithm = getPrioritizedHashFunction(metadata.algorithm(), strongestAlgorithm); DigestValue digest; for (const IntegrityMetadata& metadata : metadataSet) { if (metadata.algorithm() != strongestAlgorithm) continue; digest.clear(); bool digestSuccess = computeDigest(metadata.algorithm(), content, size, digest); if (digestSuccess) { Vector<char> hashVector; base64Decode(metadata.digest(), hashVector); DigestValue convertedHashVector; convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); if (DigestsEqual(digest, convertedHashVector)) { UseCounter::count(document, UseCounter::SRIElementWithMatchingIntegrityAttribute); return true; } } } digest.clear(); if (computeDigest(HashAlgorithmSha256, content, size, digest)) { // This message exposes the digest of the resource to the console. // Because this is only to the console, that's okay for now, but we // need to be very careful not to expose this in exceptions or // JavaScript, otherwise it risks exposing information about the // resource cross-origin. errorMessage = "Failed to find a valid digest in the 'integrity' attribute for resource '" + resourceUrl.elidedString() + "' with computed SHA-256 integrity '" + digestToString(digest) + "'. The resource has been blocked."; } else { errorMessage = "There was an error computing an integrity value for resource '" + resourceUrl.elidedString() + "'. The resource has been blocked."; } UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAttribute); return false; }
SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityAttribute(const WTF::String& attribute, IntegrityMetadataSet& metadataSet, Document* document) { Vector<UChar> characters; attribute.stripWhiteSpace().appendTo(characters); const UChar* position = characters.data(); const UChar* end = characters.end(); const UChar* currentIntegrityEnd; metadataSet.clear(); bool error = false; // The integrity attribute takes the form: // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP // To parse this, break on whitespace, parsing each algorithm/digest/option // in order. while (position < end) { WTF::String digest; HashAlgorithm algorithm; skipWhile<UChar, isASCIISpace>(position, end); currentIntegrityEnd = position; skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end); // Algorithm parsing errors are non-fatal (the subresource should // still be loaded) because strong hash algorithms should be used // without fear of breaking older user agents that don't support // them. AlgorithmParseResult parseResult = parseAlgorithm(position, currentIntegrityEnd, algorithm); if (parseResult == AlgorithmUnknown) { // Unknown hash algorithms are treated as if they're not present, // and thus are not marked as an error, they're just skipped. skipUntil<UChar, isASCIISpace>(position, end); if (document) { logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The specified hash algorithm must be one of 'sha256', 'sha384', or 'sha512'.", *document); UseCounter::count(*document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); } continue; } if (parseResult == AlgorithmUnparsable) { error = true; skipUntil<UChar, isASCIISpace>(position, end); if (document) { logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The hash algorithm must be one of 'sha256', 'sha384', or 'sha512', followed by a '-' character.", *document); UseCounter::count(*document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); } continue; } ASSERT(parseResult == AlgorithmValid); if (!parseDigest(position, currentIntegrityEnd, digest)) { error = true; skipUntil<UChar, isASCIISpace>(position, end); if (document) { logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The digest must be a valid, base64-encoded value.", *document); UseCounter::count(*document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); } continue; } // The spec defines a space in the syntax for options, separated by a // '?' character followed by unbounded VCHARs, but no actual options // have been defined yet. Thus, for forward compatibility, ignore any // options specified. if (skipExactly<UChar>(position, end, '?')) { const UChar* begin = position; skipWhile<UChar, isValueCharacter>(position, end); if (begin != position && document) logErrorToConsole("Ignoring unrecogized 'integrity' attribute option '" + String(begin, position - begin) + "'.", *document); } IntegrityMetadata integrityMetadata(digest, algorithm); metadataSet.add(integrityMetadata.toPair()); } if (metadataSet.size() == 0 && error) return IntegrityParseNoValidResult; return IntegrityParseValidResult; }