Oilpan: fix build after r202625.
[chromium-blink-merge.git] / third_party / WebKit / Source / core / frame / SubresourceIntegrity.cpp
blobc91fc0fda9123bd28e89e914e2a57cf877cc5d4f
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "config.h"
6 #include "core/frame/SubresourceIntegrity.h"
8 #include "core/HTMLNames.h"
9 #include "core/dom/Document.h"
10 #include "core/dom/Element.h"
11 #include "core/fetch/Resource.h"
12 #include "core/frame/ConsoleTypes.h"
13 #include "core/frame/UseCounter.h"
14 #include "core/inspector/ConsoleMessage.h"
15 #include "platform/Crypto.h"
16 #include "platform/ParsingUtilities.h"
17 #include "platform/weborigin/KURL.h"
18 #include "platform/weborigin/SecurityOrigin.h"
19 #include "public/platform/WebCrypto.h"
20 #include "public/platform/WebCryptoAlgorithm.h"
21 #include "wtf/ASCIICType.h"
22 #include "wtf/Vector.h"
23 #include "wtf/dtoa/utils.h"
24 #include "wtf/text/Base64.h"
25 #include "wtf/text/StringUTF8Adaptor.h"
26 #include "wtf/text/WTFString.h"
28 namespace blink {
30 // FIXME: This should probably use common functions with ContentSecurityPolicy.
31 static bool isIntegrityCharacter(UChar c)
33 // Check if it's a base64 encoded value. We're pretty loose here, as there's
34 // not much risk in it, and it'll make it simpler for developers.
35 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || c == '/' || c == '=';
38 static bool isValueCharacter(UChar c)
40 // VCHAR per https://tools.ietf.org/html/rfc5234#appendix-B.1
41 return c >= 0x21 && c <= 0x7e;
44 static void logErrorToConsole(const String& message, Document& document)
46 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message));
49 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2)
51 if (digest1.size() != digest2.size())
52 return false;
54 for (size_t i = 0; i < digest1.size(); i++) {
55 if (digest1[i] != digest2[i])
56 return false;
59 return true;
62 static String digestToString(const DigestValue& digest)
64 return base64Encode(reinterpret_cast<const char*>(digest.data()), digest.size(), Base64DoNotInsertLFs);
68 HashAlgorithm SubresourceIntegrity::getPrioritizedHashFunction(HashAlgorithm algorithm1, HashAlgorithm algorithm2)
70 const HashAlgorithm weakerThanSha384[] = { HashAlgorithmSha256 };
71 const HashAlgorithm weakerThanSha512[] = { HashAlgorithmSha256, HashAlgorithmSha384 };
73 ASSERT(algorithm1 != HashAlgorithmSha1);
74 ASSERT(algorithm2 != HashAlgorithmSha1);
76 if (algorithm1 == algorithm2)
77 return algorithm1;
79 const HashAlgorithm* weakerAlgorithms = 0;
80 size_t length = 0;
81 switch (algorithm1) {
82 case HashAlgorithmSha256:
83 break;
84 case HashAlgorithmSha384:
85 weakerAlgorithms = weakerThanSha384;
86 length = ARRAY_SIZE(weakerThanSha384);
87 break;
88 case HashAlgorithmSha512:
89 weakerAlgorithms = weakerThanSha512;
90 length = ARRAY_SIZE(weakerThanSha512);
91 break;
92 default:
93 ASSERT_NOT_REACHED();
96 for (size_t i = 0; i < length; i++) {
97 if (weakerAlgorithms[i] == algorithm2)
98 return algorithm1;
101 return algorithm2;
104 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, const String& source, const KURL& resourceUrl, const Resource& resource)
106 Document& document = element.document();
107 String attribute = element.fastGetAttribute(HTMLNames::integrityAttr);
108 if (attribute.isEmpty())
109 return true;
111 if (!resource.isEligibleForIntegrityCheck(document.securityOrigin())) {
112 UseCounter::count(document, UseCounter::SRIElementIntegrityAttributeButIneligible);
113 logErrorToConsole("Subresource Integrity: The resource '" + resourceUrl.elidedString() + "' has an integrity attribute, but the resource requires the request to be CORS enabled to check the integrity, and it is not. The resource has been blocked because the integrity cannot be enforced.", document);
114 return false;
117 String errorMessage;
118 bool result = CheckSubresourceIntegrity(attribute, source, resourceUrl, document, errorMessage);
119 if (!result)
120 logErrorToConsole(errorMessage, document);
121 return result;
124 bool SubresourceIntegrity::CheckSubresourceIntegrity(const String& integrityMetadata, const WTF::String& source, const KURL& resourceUrl, Document& document, String& errorMessage)
126 WTF::Vector<IntegrityMetadata> metadataList;
127 IntegrityParseResult integrityParseResult = parseIntegrityAttribute(integrityMetadata, metadataList, document);
128 // On failed parsing, there's no need to log an error here, as
129 // parseIntegrityAttribute() will output an appropriate console message.
130 if (integrityParseResult != IntegrityParseValidResult)
131 return true;
133 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF::EntitiesForUnencodables);
135 if (!metadataList.size())
136 return true;
138 HashAlgorithm strongestAlgorithm = HashAlgorithmSha256;
139 for (const IntegrityMetadata& metadata : metadataList)
140 strongestAlgorithm = getPrioritizedHashFunction(metadata.algorithm, strongestAlgorithm);
142 DigestValue digest;
143 for (const IntegrityMetadata& metadata : metadataList) {
144 if (metadata.algorithm != strongestAlgorithm)
145 continue;
147 digest.clear();
148 bool digestSuccess = computeDigest(metadata.algorithm, normalizedSource.data(), normalizedSource.length(), digest);
150 if (digestSuccess) {
151 Vector<char> hashVector;
152 base64Decode(metadata.digest, hashVector);
153 DigestValue convertedHashVector;
154 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size());
156 if (DigestsEqual(digest, convertedHashVector)) {
157 UseCounter::count(document, UseCounter::SRIElementWithMatchingIntegrityAttribute);
158 return true;
163 digest.clear();
164 if (computeDigest(HashAlgorithmSha256, normalizedSource.data(), normalizedSource.length(), digest)) {
165 // This message exposes the digest of the resource to the console.
166 // Because this is only to the console, that's okay for now, but we
167 // need to be very careful not to expose this in exceptions or
168 // JavaScript, otherwise it risks exposing information about the
169 // resource cross-origin.
170 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.";
171 } else {
172 errorMessage = "There was an error computing an integrity value for resource '" + resourceUrl.elidedString() + "'. The resource has been blocked.";
174 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAttribute);
175 return false;
178 // Before:
180 // [algorithm]-[hash]
181 // ^ ^
182 // position end
184 // After (if successful: if the method does not return AlgorithmValid, we make
185 // no promises and the caller should exit early):
187 // [algorithm]-[hash]
188 // ^ ^
189 // position end
190 SubresourceIntegrity::AlgorithmParseResult SubresourceIntegrity::parseAlgorithm(const UChar*& position, const UChar* end, HashAlgorithm& algorithm)
192 // Any additions or subtractions from this struct should also modify the
193 // respective entries in the kAlgorithmMap array in checkDigest() as well
194 // as the array in algorithmToString().
195 static const struct {
196 const char* prefix;
197 HashAlgorithm algorithm;
198 } kSupportedPrefixes[] = {
199 { "sha256", HashAlgorithmSha256 },
200 { "sha-256", HashAlgorithmSha256 },
201 { "sha384", HashAlgorithmSha384 },
202 { "sha-384", HashAlgorithmSha384 },
203 { "sha512", HashAlgorithmSha512 },
204 { "sha-512", HashAlgorithmSha512 }
207 const UChar* begin = position;
209 for (auto& prefix : kSupportedPrefixes) {
210 if (skipToken<UChar>(position, end, prefix.prefix)) {
211 if (!skipExactly<UChar>(position, end, '-')) {
212 position = begin;
213 continue;
215 algorithm = prefix.algorithm;
216 return AlgorithmValid;
220 skipUntil<UChar>(position, end, '-');
221 if (position < end && *position == '-') {
222 position = begin;
223 return AlgorithmUnknown;
226 position = begin;
227 return AlgorithmUnparsable;
230 // Before:
232 // [algorithm]-[hash] OR [algorithm]-[hash]?[options]
233 // ^ ^ ^ ^
234 // position end position end
236 // After (if successful: if the method returns false, we make no promises and the caller should exit early):
238 // [algorithm]-[hash] OR [algorithm]-[hash]?[options]
239 // ^ ^ ^
240 // position/end position end
241 bool SubresourceIntegrity::parseDigest(const UChar*& position, const UChar* end, String& digest)
243 const UChar* begin = position;
244 skipWhile<UChar, isIntegrityCharacter>(position, end);
246 if (position == begin || (position != end && *position != '?')) {
247 digest = emptyString();
248 return false;
251 // We accept base64url encoding, but normalize to "normal" base64 internally:
252 digest = normalizeToBase64(String(begin, position - begin));
253 return true;
256 SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityAttribute(const WTF::String& attribute, WTF::Vector<IntegrityMetadata>& metadataList, Document& document)
258 Vector<UChar> characters;
259 attribute.stripWhiteSpace().appendTo(characters);
260 const UChar* position = characters.data();
261 const UChar* end = characters.end();
262 const UChar* currentIntegrityEnd;
264 metadataList.clear();
265 bool error = false;
267 // The integrity attribute takes the form:
268 // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP
269 // To parse this, break on whitespace, parsing each algorithm/digest/option
270 // in order.
271 while (position < end) {
272 WTF::String digest;
273 HashAlgorithm algorithm;
275 skipWhile<UChar, isASCIISpace>(position, end);
276 currentIntegrityEnd = position;
277 skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end);
279 // Algorithm parsing errors are non-fatal (the subresource should
280 // still be loaded) because strong hash algorithms should be used
281 // without fear of breaking older user agents that don't support
282 // them.
283 AlgorithmParseResult parseResult = parseAlgorithm(position, currentIntegrityEnd, algorithm);
284 if (parseResult == AlgorithmUnknown) {
285 // Unknown hash algorithms are treated as if they're not present,
286 // and thus are not marked as an error, they're just skipped.
287 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The specified hash algorithm must be one of 'sha256', 'sha384', or 'sha512'.", document);
288 skipUntil<UChar, isASCIISpace>(position, end);
289 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrityAttribute);
290 continue;
293 if (parseResult == AlgorithmUnparsable) {
294 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The hash algorithm must be one of 'sha256', 'sha384', or 'sha512', followed by a '-' character.", document);
295 error = true;
296 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrityAttribute);
297 skipUntil<UChar, isASCIISpace>(position, end);
298 continue;
301 ASSERT(parseResult == AlgorithmValid);
303 if (!parseDigest(position, currentIntegrityEnd, digest)) {
304 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + "'). The digest must be a valid, base64-encoded value.", document);
305 error = true;
306 skipUntil<UChar, isASCIISpace>(position, end);
307 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrityAttribute);
308 continue;
311 // The spec defines a space in the syntax for options, separated by a
312 // '?' character followed by unbounded VCHARs, but no actual options
313 // have been defined yet. Thus, for forward compatibility, ignore any
314 // options specified.
315 if (skipExactly<UChar>(position, end, '?')) {
316 const UChar* begin = position;
317 skipWhile<UChar, isValueCharacter>(position, end);
318 if (begin != position)
319 logErrorToConsole("Ignoring unrecogized 'integrity' attribute option '" + String(begin, position - begin) + "'.", document);
322 IntegrityMetadata integrityMetadata = {
323 digest,
324 algorithm
326 metadataList.append(integrityMetadata);
329 if (metadataList.size() == 0 && error)
330 return IntegrityParseNoValidResult;
332 return IntegrityParseValidResult;
335 } // namespace blink