// hash-source = "'" hash-algorithm "-" hash-value "'" // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512" // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) // bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm) { // Any additions or subtractions from this struct should also modify the // respective entries in the kAlgorithmMap array in checkDigest(). static const struct { const char* prefix; ContentSecurityPolicyHashAlgorithm type; } kSupportedPrefixes[] = { // FIXME: Drop support for SHA-1. It's not in the spec. { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 }, { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 }, { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 }, { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 }, { "'sha-256-", ContentSecurityPolicyHashAlgorithmSha256 }, { "'sha-384-", ContentSecurityPolicyHashAlgorithmSha384 }, { "'sha-512-", ContentSecurityPolicyHashAlgorithmSha512 } }; String prefix; hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; size_t hashLength = end - begin; for (const auto& algorithm : kSupportedPrefixes) { if (hashLength > strlen(algorithm.prefix) && equalIgnoringCase(algorithm.prefix, begin, strlen(algorithm.prefix))) { prefix = algorithm.prefix; hashAlgorithm = algorithm.type; break; } } if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) return true; const UChar* position = begin + prefix.length(); const UChar* hashBegin = position; ASSERT(position < end); skipWhile<UChar, isBase64EncodedCharacter>(position, end); ASSERT(hashBegin <= position); // Base64 encodings may end with exactly one or two '=' characters if (position < end) skipExactly<UChar>(position, position + 1, '='); if (position < end) skipExactly<UChar>(position, position + 1, '='); if (position + 1 != end || *position != '\'' || position == hashBegin) return false; Vector<char> hashVector; // We accept base64url-encoded data here by normalizing it to base64. base64Decode(normalizeToBase64(String(hashBegin, position - hashBegin)), hashVector); if (hashVector.size() > kMaxDigestSize) return false; hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); return true; }
// hash-source = "'" hash-algorithm "-" hash-value "'" // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512" // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) // bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm) { // Any additions or subtractions from this struct should also modify the // respective entries in the kAlgorithmMap array in checkDigest(). static const struct { const char* prefix; ContentSecurityPolicyHashAlgorithm algorithm; } kSupportedPrefixes[] = { { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 }, { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 }, { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 }, { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 } }; String prefix; hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; // Instead of this sizeof() calculation to get the length of this array, // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to // guarantee a compile time calculation. Unfortunately, on some // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous // stucts, so, for now, it is necessary to resort to this sizeof // calculation. for (size_t i = 0; i < (sizeof(kSupportedPrefixes) / sizeof(kSupportedPrefixes[0])); i++) { if (equalIgnoringCase(kSupportedPrefixes[i].prefix, begin, strlen(kSupportedPrefixes[i].prefix))) { prefix = kSupportedPrefixes[i].prefix; hashAlgorithm = kSupportedPrefixes[i].algorithm; break; } } if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) return true; const UChar* position = begin + prefix.length(); const UChar* hashBegin = position; skipWhile<UChar, isBase64EncodedCharacter>(position, end); ASSERT(hashBegin <= position); // Base64 encodings may end with exactly one or two '=' characters skipExactly<UChar>(position, position + 1, '='); skipExactly<UChar>(position, position + 1, '='); if ((position + 1) != end || *position != '\'' || !(position - hashBegin)) return false; Vector<char> hashVector; base64Decode(hashBegin, position - hashBegin, hashVector); if (hashVector.size() > kMaxDigestSize) return false; hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); return true; }
void finishDigestor(WebCryptoDigestor* digestor, DigestValue& digestResult) { unsigned char* result = 0; unsigned resultSize = 0; if (!digestor->finish(result, resultSize)) return; ASSERT(result); digestResult.append(static_cast<uint8_t*>(result), resultSize); }
bool computeDigest(HashAlgorithm algorithm, const char* digestable, size_t length, DigestValue& digestResult) { WebCryptoAlgorithmId algorithmId = toWebCryptoAlgorithmId(algorithm); WebCrypto* crypto = Platform::current()->crypto(); unsigned char* result; unsigned resultSize; ASSERT(crypto); OwnPtr<WebCryptoDigestor> digestor = adoptPtr(crypto->createDigestor(algorithmId)); if (!digestor.get() || !digestor->consume(reinterpret_cast<const unsigned char*>(digestable), length) || !digestor->finish(result, resultSize)) return false; digestResult.append(static_cast<uint8_t*>(result), resultSize); return true; }
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; }