Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(411)

Side by Side Diff: Source/core/frame/SubresourceIntegrity.cpp

Issue 1126343003: Ignore unknown options to subresource integrity (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Removed option parsing + nits from mkwst Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "config.h" 5 #include "config.h"
6 #include "core/frame/SubresourceIntegrity.h" 6 #include "core/frame/SubresourceIntegrity.h"
7 7
8 #include "core/HTMLNames.h" 8 #include "core/HTMLNames.h"
9 #include "core/dom/Document.h" 9 #include "core/dom/Document.h"
10 #include "core/dom/Element.h" 10 #include "core/dom/Element.h"
(...skipping 17 matching lines...) Expand all
28 namespace blink { 28 namespace blink {
29 29
30 // FIXME: This should probably use common functions with ContentSecurityPolicy. 30 // FIXME: This should probably use common functions with ContentSecurityPolicy.
31 static bool isIntegrityCharacter(UChar c) 31 static bool isIntegrityCharacter(UChar c)
32 { 32 {
33 // Check if it's a base64 encoded value. We're pretty loose here, as there's 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. 34 // not much risk in it, and it'll make it simpler for developers.
35 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || c == '/ ' || c == '='; 35 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || c == '/ ' || c == '=';
36 } 36 }
37 37
38 static bool isTypeCharacter(UChar c) 38 static bool isValueCharacter(UChar c)
39 { 39 {
40 return isASCIIAlphanumeric(c) || c == '+' || c == '.' || c == '-'; 40 // VCHAR per https://tools.ietf.org/html/rfc5234#appendix-B.1
41 return c >= 0x21 && c <= 0x7e;
41 } 42 }
42 43
43 static void logErrorToConsole(const String& message, Document& document) 44 static void logErrorToConsole(const String& message, Document& document)
44 { 45 {
45 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err orMessageLevel, message)); 46 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err orMessageLevel, message));
46 } 47 }
47 48
48 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) 49 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2)
49 { 50 {
50 if (digest1.size() != digest2.size()) 51 if (digest1.size() != digest2.size())
51 return false; 52 return false;
52 53
53 for (size_t i = 0; i < digest1.size(); i++) { 54 for (size_t i = 0; i < digest1.size(); i++) {
54 if (digest1[i] != digest2[i]) 55 if (digest1[i] != digest2[i])
55 return false; 56 return false;
56 } 57 }
57 58
58 return true; 59 return true;
59 } 60 }
60 61
61 static String digestToString(const DigestValue& digest) 62 static String digestToString(const DigestValue& digest)
62 { 63 {
63 // We always output base64url encoded data, even though we use base64 intern ally. 64 // We always output base64url encoded data, even though we use base64 intern ally.
64 return base64URLEncode(reinterpret_cast<const char*>(digest.data()), digest. size(), Base64DoNotInsertLFs); 65 return base64URLEncode(reinterpret_cast<const char*>(digest.data()), digest. size(), Base64DoNotInsertLFs);
65 } 66 }
66 67
67 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con st String& source, const KURL& resourceUrl, const String& resourceType, const Re source& resource) 68 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con st String& source, const KURL& resourceUrl, const Resource& resource)
68 { 69 {
69 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) 70 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled())
70 return true; 71 return true;
71 72
72 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) 73 if (!element.fastHasAttribute(HTMLNames::integrityAttr))
73 return true; 74 return true;
74 75
75 Document& document = element.document(); 76 Document& document = element.document();
76 77
77 if (!resource.isEligibleForIntegrityCheck(document.securityOrigin())) { 78 if (!resource.isEligibleForIntegrityCheck(document.securityOrigin())) {
(...skipping 17 matching lines...) Expand all
95 digest.clear(); 96 digest.clear();
96 bool digestSuccess = computeDigest(metadata.algorithm, normalizedSource. data(), normalizedSource.length(), digest); 97 bool digestSuccess = computeDigest(metadata.algorithm, normalizedSource. data(), normalizedSource.length(), digest);
97 98
98 if (digestSuccess) { 99 if (digestSuccess) {
99 Vector<char> hashVector; 100 Vector<char> hashVector;
100 base64Decode(metadata.digest, hashVector); 101 base64Decode(metadata.digest, hashVector);
101 DigestValue convertedHashVector; 102 DigestValue convertedHashVector;
102 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.dat a()), hashVector.size()); 103 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.dat a()), hashVector.size());
103 104
104 if (DigestsEqual(digest, convertedHashVector)) { 105 if (DigestsEqual(digest, convertedHashVector)) {
105 String& type = metadata.type; 106 UseCounter::count(document, UseCounter::SRIElementWithMatchingIn tegrityAttribute);
106 if (!type.isEmpty() && !equalIgnoringCase(type, resourceType)) 107 return true;
107 UseCounter::count(document, UseCounter::SRIElementWithNonMat chingIntegrityType);
108 else
109 return true;
110 } 108 }
111 } 109 }
112 } 110 }
113 111
114 if (computeDigest(HashAlgorithmSha256, normalizedSource.data(), normalizedSo urce.length(), digest)) { 112 if (computeDigest(HashAlgorithmSha256, normalizedSource.data(), normalizedSo urce.length(), digest)) {
115 // This message exposes the digest of the resource to the console. 113 // This message exposes the digest of the resource to the console.
116 // Because this is only to the console, that's okay for now, but we 114 // Because this is only to the console, that's okay for now, but we
117 // need to be very careful not to expose this in exceptions or 115 // need to be very careful not to expose this in exceptions or
118 // JavaScript, otherwise it risks exposing information about the 116 // JavaScript, otherwise it risks exposing information about the
119 // resource cross-origin. 117 // resource cross-origin.
120 logErrorToConsole("Failed to find a valid digest with matching content-t ype in the 'integrity' attribute for resource '" + resourceUrl.elidedString() + "' with computed SHA-256 integrity '" + digestToString(digest) + "'. The resourc e has been blocked.", document); 118 logErrorToConsole("Failed to find a valid digest in the 'integrity' attr ibute for resource '" + resourceUrl.elidedString() + "' with computed SHA-256 in tegrity '" + digestToString(digest) + "'. The resource has been blocked.", docum ent);
121 } else { 119 } else {
122 logErrorToConsole("There was an error computing an integrity value for r esource '" + resourceUrl.elidedString() + "'. The resource has been blocked.", d ocument); 120 logErrorToConsole("There was an error computing an integrity value for r esource '" + resourceUrl.elidedString() + "'. The resource has been blocked.", d ocument);
123 } 121 }
124 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt tribute); 122 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt tribute);
125 return false; 123 return false;
126 } 124 }
127 125
128 // Before: 126 // Before:
129 // 127 //
130 // [algorithm]-[hash] 128 // [algorithm]-[hash]
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
196 if (position == begin || (position != end && *position != '?')) { 194 if (position == begin || (position != end && *position != '?')) {
197 digest = emptyString(); 195 digest = emptyString();
198 return false; 196 return false;
199 } 197 }
200 198
201 // We accept base64url encoding, but normalize to "normal" base64 internally : 199 // We accept base64url encoding, but normalize to "normal" base64 internally :
202 digest = normalizeToBase64(String(begin, position - begin)); 200 digest = normalizeToBase64(String(begin, position - begin));
203 return true; 201 return true;
204 } 202 }
205 203
206
207 // Before:
208 //
209 // [algorithm]-[hash] OR [algorithm]-[hash]?[options]
210 // ^ ^ ^
211 // position/end position end
212 //
213 // After (if successful: if the method returns false, we make no promises and th e caller should exit early):
214 //
215 // [algorithm]-[hash] OR [algorithm]-[hash]?[options]
216 // ^ ^
217 // position/end position/end
218 bool SubresourceIntegrity::parseMimeType(const UChar*& position, const UChar* en d, String& type)
219 {
220 type = emptyString();
221
222 if (position == end)
223 return true;
224
225 if (!skipToken<UChar>(position, end, "?ct="))
226 return false;
227
228 const UChar* begin = position;
229 skipWhile<UChar, isASCIIAlpha>(position, end);
230 if (position == end)
231 return false;
232
233 if (!skipExactly<UChar>(position, end, '/'))
234 return false;
235
236 if (position == end)
237 return false;
238
239 skipWhile<UChar, isTypeCharacter>(position, end);
240 if (position != end)
241 return false;
242
243 type = String(begin, position - begin);
244 return true;
245 }
246
247 SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityA ttribute(const WTF::String& attribute, WTF::Vector<IntegrityMetadata>& metadataL ist, Document& document) 204 SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityA ttribute(const WTF::String& attribute, WTF::Vector<IntegrityMetadata>& metadataL ist, Document& document)
248 { 205 {
249 Vector<UChar> characters; 206 Vector<UChar> characters;
250 attribute.stripWhiteSpace().appendTo(characters); 207 attribute.stripWhiteSpace().appendTo(characters);
251 const UChar* position = characters.data(); 208 const UChar* position = characters.data();
252 const UChar* end = characters.end(); 209 const UChar* end = characters.end();
253 const UChar* currentIntegrityEnd; 210 const UChar* currentIntegrityEnd;
254 211
255 metadataList.clear(); 212 metadataList.clear();
256 bool error = false; 213 bool error = false;
257 214
258 // The integrity attribute takes the form: 215 // The integrity attribute takes the form:
259 // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP 216 // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP
260 // To parse this, break on whitespace, parsing each algorithm/digest/mime 217 // To parse this, break on whitespace, parsing each algorithm/digest/option
261 // type in order. 218 // in order.
262 while (position < end) { 219 while (position < end) {
263 WTF::String digest; 220 WTF::String digest;
264 HashAlgorithm algorithm; 221 HashAlgorithm algorithm;
265 WTF::String type;
266 222
267 skipWhile<UChar, isASCIISpace>(position, end); 223 skipWhile<UChar, isASCIISpace>(position, end);
268 currentIntegrityEnd = position; 224 currentIntegrityEnd = position;
269 skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end); 225 skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end);
270 226
271 // Algorithm parsing errors are non-fatal (the subresource should 227 // Algorithm parsing errors are non-fatal (the subresource should
272 // still be loaded) because strong hash algorithms should be used 228 // still be loaded) because strong hash algorithms should be used
273 // without fear of breaking older user agents that don't support 229 // without fear of breaking older user agents that don't support
274 // them. 230 // them.
275 AlgorithmParseResult parseResult = parseAlgorithm(position, currentInteg rityEnd, algorithm); 231 AlgorithmParseResult parseResult = parseAlgorithm(position, currentInteg rityEnd, algorithm);
(...skipping 17 matching lines...) Expand all
293 ASSERT(parseResult == AlgorithmValid); 249 ASSERT(parseResult == AlgorithmValid);
294 250
295 if (!parseDigest(position, currentIntegrityEnd, digest)) { 251 if (!parseDigest(position, currentIntegrityEnd, digest)) {
296 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The digest must be a valid, base64-encoded value.", document); 252 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The digest must be a valid, base64-encoded value.", document);
297 error = true; 253 error = true;
298 skipUntil<UChar, isASCIISpace>(position, end); 254 skipUntil<UChar, isASCIISpace>(position, end);
299 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute); 255 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute);
300 continue; 256 continue;
301 } 257 }
302 258
303 if (!parseMimeType(position, currentIntegrityEnd, type)) { 259 // The spec defines a space in the syntax for options, separated by a
304 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The content type could not be parsed.", document); 260 // '?' character followed by unbounded VCHARs, but no actual options
305 error = true; 261 // have been defined yet. Thus, for forward compatibility, ignore any
306 skipUntil<UChar, isASCIISpace>(position, end); 262 // options specified.
307 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute); 263 if (skipExactly<UChar>(position, end, '?')) {
308 continue; 264 const UChar* begin = position;
265 skipWhile<UChar, isValueCharacter>(position, end);
266 if (begin != position)
267 logErrorToConsole("Ignoring unrecogized 'integrity' attribute op tion '" + String(begin, position - begin) + "'.", document);
309 } 268 }
310 269
311 IntegrityMetadata integrityMetadata = { 270 IntegrityMetadata integrityMetadata = {
312 digest, 271 digest,
313 algorithm, 272 algorithm
314 type
315 }; 273 };
316 metadataList.append(integrityMetadata); 274 metadataList.append(integrityMetadata);
317 } 275 }
318 276
319 if (metadataList.size() == 0 && error) 277 if (metadataList.size() == 0 && error)
320 return IntegrityParseNoValidResult; 278 return IntegrityParseNoValidResult;
321 279
322 return IntegrityParseValidResult; 280 return IntegrityParseValidResult;
323 } 281 }
324 282
325 } // namespace blink 283 } // namespace blink
OLDNEW
« no previous file with comments | « Source/core/frame/SubresourceIntegrity.h ('k') | Source/core/frame/SubresourceIntegrityTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698