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));
 }
示例#3
0
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;
}