OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 |
OLD | NEW |