Chromium Code Reviews| 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 "core/frame/SubresourceIntegrity.h" | 5 #include "core/frame/SubresourceIntegrity.h" |
| 6 | 6 |
| 7 #include "core/HTMLNames.h" | 7 #include "core/HTMLNames.h" |
| 8 #include "core/dom/Document.h" | 8 #include "core/dom/Document.h" |
|
jochen (gone - plz use gerrit)
2017/01/05 16:04:14
do we still need this include now?
yhirano
2017/01/06 09:28:59
We are upcasting from Document to ExecutionContext
| |
| 9 #include "core/dom/Element.h" | 9 #include "core/dom/Element.h" |
| 10 #include "core/dom/ExecutionContext.h" | |
| 10 #include "core/fetch/Resource.h" | 11 #include "core/fetch/Resource.h" |
| 11 #include "core/frame/UseCounter.h" | 12 #include "core/frame/UseCounter.h" |
| 12 #include "core/inspector/ConsoleMessage.h" | 13 #include "core/inspector/ConsoleMessage.h" |
| 13 #include "platform/Crypto.h" | 14 #include "platform/Crypto.h" |
| 14 #include "platform/weborigin/KURL.h" | 15 #include "platform/weborigin/KURL.h" |
| 15 #include "platform/weborigin/SecurityOrigin.h" | 16 #include "platform/weborigin/SecurityOrigin.h" |
| 16 #include "public/platform/WebCrypto.h" | 17 #include "public/platform/WebCrypto.h" |
| 17 #include "public/platform/WebCryptoAlgorithm.h" | 18 #include "public/platform/WebCryptoAlgorithm.h" |
| 18 #include "wtf/ASCIICType.h" | 19 #include "wtf/ASCIICType.h" |
| 19 #include "wtf/Vector.h" | 20 #include "wtf/Vector.h" |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 31 // not much risk in it, and it'll make it simpler for developers. | 32 // not much risk in it, and it'll make it simpler for developers. |
| 32 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || | 33 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || |
| 33 c == '/' || c == '='; | 34 c == '/' || c == '='; |
| 34 } | 35 } |
| 35 | 36 |
| 36 static bool isValueCharacter(UChar c) { | 37 static bool isValueCharacter(UChar c) { |
| 37 // VCHAR per https://tools.ietf.org/html/rfc5234#appendix-B.1 | 38 // VCHAR per https://tools.ietf.org/html/rfc5234#appendix-B.1 |
| 38 return c >= 0x21 && c <= 0x7e; | 39 return c >= 0x21 && c <= 0x7e; |
| 39 } | 40 } |
| 40 | 41 |
| 41 static void logErrorToConsole(const String& message, Document& document) { | 42 static void logErrorToConsole(const String& message, |
| 42 document.addConsoleMessage(ConsoleMessage::create( | 43 ExecutionContext& executionContext) { |
| 44 executionContext.addConsoleMessage(ConsoleMessage::create( | |
| 43 SecurityMessageSource, ErrorMessageLevel, message)); | 45 SecurityMessageSource, ErrorMessageLevel, message)); |
| 44 } | 46 } |
| 45 | 47 |
| 46 static bool DigestsEqual(const DigestValue& digest1, | 48 static bool DigestsEqual(const DigestValue& digest1, |
| 47 const DigestValue& digest2) { | 49 const DigestValue& digest2) { |
| 48 if (digest1.size() != digest2.size()) | 50 if (digest1.size() != digest2.size()) |
| 49 return false; | 51 return false; |
| 50 | 52 |
| 51 for (size_t i = 0; i < digest1.size(); i++) { | 53 for (size_t i = 0; i < digest1.size(); i++) { |
| 52 if (digest1[i] != digest2[i]) | 54 if (digest1[i] != digest2[i]) |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 149 if (!result) | 151 if (!result) |
| 150 logErrorToConsole(errorMessage, document); | 152 logErrorToConsole(errorMessage, document); |
| 151 return result; | 153 return result; |
| 152 } | 154 } |
| 153 | 155 |
| 154 bool SubresourceIntegrity::CheckSubresourceIntegrity( | 156 bool SubresourceIntegrity::CheckSubresourceIntegrity( |
| 155 const String& integrityMetadata, | 157 const String& integrityMetadata, |
| 156 const char* content, | 158 const char* content, |
| 157 size_t size, | 159 size_t size, |
| 158 const KURL& resourceUrl, | 160 const KURL& resourceUrl, |
| 159 Document& document, | 161 ExecutionContext& executionContext, |
| 160 String& errorMessage) { | 162 String& errorMessage) { |
| 161 IntegrityMetadataSet metadataSet; | 163 IntegrityMetadataSet metadataSet; |
| 162 IntegrityParseResult integrityParseResult = | 164 IntegrityParseResult integrityParseResult = parseIntegrityAttribute( |
| 163 parseIntegrityAttribute(integrityMetadata, metadataSet, &document); | 165 integrityMetadata, metadataSet, &executionContext); |
| 164 // On failed parsing, there's no need to log an error here, as | 166 // On failed parsing, there's no need to log an error here, as |
| 165 // parseIntegrityAttribute() will output an appropriate console message. | 167 // parseIntegrityAttribute() will output an appropriate console message. |
| 166 if (integrityParseResult != IntegrityParseValidResult) | 168 if (integrityParseResult != IntegrityParseValidResult) |
| 167 return true; | 169 return true; |
| 168 | 170 |
| 169 return CheckSubresourceIntegrity(metadataSet, content, size, resourceUrl, | 171 return CheckSubresourceIntegrity(metadataSet, content, size, resourceUrl, |
| 170 document, errorMessage); | 172 executionContext, errorMessage); |
| 171 } | 173 } |
| 172 | 174 |
| 173 bool SubresourceIntegrity::CheckSubresourceIntegrity( | 175 bool SubresourceIntegrity::CheckSubresourceIntegrity( |
| 174 const IntegrityMetadataSet& metadataSet, | 176 const IntegrityMetadataSet& metadataSet, |
| 175 const char* content, | 177 const char* content, |
| 176 size_t size, | 178 size_t size, |
| 177 const KURL& resourceUrl, | 179 const KURL& resourceUrl, |
| 178 Document& document, | 180 ExecutionContext& executionContext, |
| 179 String& errorMessage) { | 181 String& errorMessage) { |
| 180 if (!metadataSet.size()) | 182 if (!metadataSet.size()) |
| 181 return true; | 183 return true; |
| 182 | 184 |
| 183 HashAlgorithm strongestAlgorithm = HashAlgorithmSha256; | 185 HashAlgorithm strongestAlgorithm = HashAlgorithmSha256; |
| 184 for (const IntegrityMetadata& metadata : metadataSet) | 186 for (const IntegrityMetadata& metadata : metadataSet) |
| 185 strongestAlgorithm = | 187 strongestAlgorithm = |
| 186 getPrioritizedHashFunction(metadata.algorithm(), strongestAlgorithm); | 188 getPrioritizedHashFunction(metadata.algorithm(), strongestAlgorithm); |
| 187 | 189 |
| 188 DigestValue digest; | 190 DigestValue digest; |
| 189 for (const IntegrityMetadata& metadata : metadataSet) { | 191 for (const IntegrityMetadata& metadata : metadataSet) { |
| 190 if (metadata.algorithm() != strongestAlgorithm) | 192 if (metadata.algorithm() != strongestAlgorithm) |
| 191 continue; | 193 continue; |
| 192 | 194 |
| 193 digest.clear(); | 195 digest.clear(); |
| 194 bool digestSuccess = | 196 bool digestSuccess = |
| 195 computeDigest(metadata.algorithm(), content, size, digest); | 197 computeDigest(metadata.algorithm(), content, size, digest); |
| 196 | 198 |
| 197 if (digestSuccess) { | 199 if (digestSuccess) { |
| 198 Vector<char> hashVector; | 200 Vector<char> hashVector; |
| 199 base64Decode(metadata.digest(), hashVector); | 201 base64Decode(metadata.digest(), hashVector); |
| 200 DigestValue convertedHashVector; | 202 DigestValue convertedHashVector; |
| 201 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data()), | 203 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data()), |
| 202 hashVector.size()); | 204 hashVector.size()); |
| 203 | 205 |
| 204 if (DigestsEqual(digest, convertedHashVector)) { | 206 if (DigestsEqual(digest, convertedHashVector)) { |
| 205 UseCounter::count(document, | 207 UseCounter::count(&executionContext, |
| 206 UseCounter::SRIElementWithMatchingIntegrityAttribute); | 208 UseCounter::SRIElementWithMatchingIntegrityAttribute); |
| 207 return true; | 209 return true; |
| 208 } | 210 } |
| 209 } | 211 } |
| 210 } | 212 } |
| 211 | 213 |
| 212 digest.clear(); | 214 digest.clear(); |
| 213 if (computeDigest(HashAlgorithmSha256, content, size, digest)) { | 215 if (computeDigest(HashAlgorithmSha256, content, size, digest)) { |
| 214 // This message exposes the digest of the resource to the console. | 216 // This message exposes the digest of the resource to the console. |
| 215 // Because this is only to the console, that's okay for now, but we | 217 // Because this is only to the console, that's okay for now, but we |
| 216 // need to be very careful not to expose this in exceptions or | 218 // need to be very careful not to expose this in exceptions or |
| 217 // JavaScript, otherwise it risks exposing information about the | 219 // JavaScript, otherwise it risks exposing information about the |
| 218 // resource cross-origin. | 220 // resource cross-origin. |
| 219 errorMessage = | 221 errorMessage = |
| 220 "Failed to find a valid digest in the 'integrity' attribute for " | 222 "Failed to find a valid digest in the 'integrity' attribute for " |
| 221 "resource '" + | 223 "resource '" + |
| 222 resourceUrl.elidedString() + "' with computed SHA-256 integrity '" + | 224 resourceUrl.elidedString() + "' with computed SHA-256 integrity '" + |
| 223 digestToString(digest) + "'. The resource has been blocked."; | 225 digestToString(digest) + "'. The resource has been blocked."; |
| 224 } else { | 226 } else { |
| 225 errorMessage = | 227 errorMessage = |
| 226 "There was an error computing an integrity value for resource '" + | 228 "There was an error computing an integrity value for resource '" + |
| 227 resourceUrl.elidedString() + "'. The resource has been blocked."; | 229 resourceUrl.elidedString() + "'. The resource has been blocked."; |
| 228 } | 230 } |
| 229 UseCounter::count(document, | 231 UseCounter::count(&executionContext, |
| 230 UseCounter::SRIElementWithNonMatchingIntegrityAttribute); | 232 UseCounter::SRIElementWithNonMatchingIntegrityAttribute); |
| 231 return false; | 233 return false; |
| 232 } | 234 } |
| 233 | 235 |
| 234 // Before: | 236 // Before: |
| 235 // | 237 // |
| 236 // [algorithm]-[hash] | 238 // [algorithm]-[hash] |
| 237 // ^ ^ | 239 // ^ ^ |
| 238 // position end | 240 // position end |
| 239 // | 241 // |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 310 } | 312 } |
| 311 | 313 |
| 312 SubresourceIntegrity::IntegrityParseResult | 314 SubresourceIntegrity::IntegrityParseResult |
| 313 SubresourceIntegrity::parseIntegrityAttribute( | 315 SubresourceIntegrity::parseIntegrityAttribute( |
| 314 const WTF::String& attribute, | 316 const WTF::String& attribute, |
| 315 IntegrityMetadataSet& metadataSet) { | 317 IntegrityMetadataSet& metadataSet) { |
| 316 return parseIntegrityAttribute(attribute, metadataSet, nullptr); | 318 return parseIntegrityAttribute(attribute, metadataSet, nullptr); |
| 317 } | 319 } |
| 318 | 320 |
| 319 SubresourceIntegrity::IntegrityParseResult | 321 SubresourceIntegrity::IntegrityParseResult |
| 320 SubresourceIntegrity::parseIntegrityAttribute(const WTF::String& attribute, | 322 SubresourceIntegrity::parseIntegrityAttribute( |
| 321 IntegrityMetadataSet& metadataSet, | 323 const WTF::String& attribute, |
| 322 Document* document) { | 324 IntegrityMetadataSet& metadataSet, |
| 325 ExecutionContext* executionContext) { | |
| 323 Vector<UChar> characters; | 326 Vector<UChar> characters; |
| 324 attribute.stripWhiteSpace().appendTo(characters); | 327 attribute.stripWhiteSpace().appendTo(characters); |
| 325 const UChar* position = characters.data(); | 328 const UChar* position = characters.data(); |
| 326 const UChar* end = characters.end(); | 329 const UChar* end = characters.end(); |
| 327 const UChar* currentIntegrityEnd; | 330 const UChar* currentIntegrityEnd; |
| 328 | 331 |
| 329 metadataSet.clear(); | 332 metadataSet.clear(); |
| 330 bool error = false; | 333 bool error = false; |
| 331 | 334 |
| 332 // The integrity attribute takes the form: | 335 // The integrity attribute takes the form: |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 344 // Algorithm parsing errors are non-fatal (the subresource should | 347 // Algorithm parsing errors are non-fatal (the subresource should |
| 345 // still be loaded) because strong hash algorithms should be used | 348 // still be loaded) because strong hash algorithms should be used |
| 346 // without fear of breaking older user agents that don't support | 349 // without fear of breaking older user agents that don't support |
| 347 // them. | 350 // them. |
| 348 AlgorithmParseResult parseResult = | 351 AlgorithmParseResult parseResult = |
| 349 parseAlgorithm(position, currentIntegrityEnd, algorithm); | 352 parseAlgorithm(position, currentIntegrityEnd, algorithm); |
| 350 if (parseResult == AlgorithmUnknown) { | 353 if (parseResult == AlgorithmUnknown) { |
| 351 // Unknown hash algorithms are treated as if they're not present, | 354 // Unknown hash algorithms are treated as if they're not present, |
| 352 // and thus are not marked as an error, they're just skipped. | 355 // and thus are not marked as an error, they're just skipped. |
| 353 skipUntil<UChar, isASCIISpace>(position, end); | 356 skipUntil<UChar, isASCIISpace>(position, end); |
| 354 if (document) { | 357 if (executionContext) { |
| 355 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + | 358 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + |
| 356 "'). The specified hash algorithm must be one of " | 359 "'). The specified hash algorithm must be one of " |
| 357 "'sha256', 'sha384', or 'sha512'.", | 360 "'sha256', 'sha384', or 'sha512'.", |
| 358 *document); | 361 *executionContext); |
| 359 UseCounter::count( | 362 UseCounter::count( |
| 360 *document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); | 363 executionContext, |
| 364 UseCounter::SRIElementWithUnparsableIntegrityAttribute); | |
| 361 } | 365 } |
| 362 continue; | 366 continue; |
| 363 } | 367 } |
| 364 | 368 |
| 365 if (parseResult == AlgorithmUnparsable) { | 369 if (parseResult == AlgorithmUnparsable) { |
| 366 error = true; | 370 error = true; |
| 367 skipUntil<UChar, isASCIISpace>(position, end); | 371 skipUntil<UChar, isASCIISpace>(position, end); |
| 368 if (document) { | 372 if (executionContext) { |
| 369 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + | 373 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute + |
| 370 "'). The hash algorithm must be one of 'sha256', " | 374 "'). The hash algorithm must be one of 'sha256', " |
| 371 "'sha384', or 'sha512', followed by a '-' " | 375 "'sha384', or 'sha512', followed by a '-' " |
| 372 "character.", | 376 "character.", |
| 373 *document); | 377 *executionContext); |
| 374 UseCounter::count( | 378 UseCounter::count( |
| 375 *document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); | 379 executionContext, |
| 380 UseCounter::SRIElementWithUnparsableIntegrityAttribute); | |
| 376 } | 381 } |
| 377 continue; | 382 continue; |
| 378 } | 383 } |
| 379 | 384 |
| 380 ASSERT(parseResult == AlgorithmValid); | 385 ASSERT(parseResult == AlgorithmValid); |
| 381 | 386 |
| 382 if (!parseDigest(position, currentIntegrityEnd, digest)) { | 387 if (!parseDigest(position, currentIntegrityEnd, digest)) { |
| 383 error = true; | 388 error = true; |
| 384 skipUntil<UChar, isASCIISpace>(position, end); | 389 skipUntil<UChar, isASCIISpace>(position, end); |
| 385 if (document) { | 390 if (executionContext) { |
| 386 logErrorToConsole( | 391 logErrorToConsole( |
| 387 "Error parsing 'integrity' attribute ('" + attribute + | 392 "Error parsing 'integrity' attribute ('" + attribute + |
| 388 "'). The digest must be a valid, base64-encoded value.", | 393 "'). The digest must be a valid, base64-encoded value.", |
| 389 *document); | 394 *executionContext); |
| 390 UseCounter::count( | 395 UseCounter::count( |
| 391 *document, UseCounter::SRIElementWithUnparsableIntegrityAttribute); | 396 executionContext, |
| 397 UseCounter::SRIElementWithUnparsableIntegrityAttribute); | |
| 392 } | 398 } |
| 393 continue; | 399 continue; |
| 394 } | 400 } |
| 395 | 401 |
| 396 // The spec defines a space in the syntax for options, separated by a | 402 // The spec defines a space in the syntax for options, separated by a |
| 397 // '?' character followed by unbounded VCHARs, but no actual options | 403 // '?' character followed by unbounded VCHARs, but no actual options |
| 398 // have been defined yet. Thus, for forward compatibility, ignore any | 404 // have been defined yet. Thus, for forward compatibility, ignore any |
| 399 // options specified. | 405 // options specified. |
| 400 if (skipExactly<UChar>(position, end, '?')) { | 406 if (skipExactly<UChar>(position, end, '?')) { |
| 401 const UChar* begin = position; | 407 const UChar* begin = position; |
| 402 skipWhile<UChar, isValueCharacter>(position, end); | 408 skipWhile<UChar, isValueCharacter>(position, end); |
| 403 if (begin != position && document) | 409 if (begin != position && executionContext) { |
| 404 logErrorToConsole( | 410 logErrorToConsole( |
| 405 "Ignoring unrecogized 'integrity' attribute option '" + | 411 "Ignoring unrecogized 'integrity' attribute option '" + |
| 406 String(begin, position - begin) + "'.", | 412 String(begin, position - begin) + "'.", |
| 407 *document); | 413 *executionContext); |
| 414 } | |
| 408 } | 415 } |
| 409 | 416 |
| 410 IntegrityMetadata integrityMetadata(digest, algorithm); | 417 IntegrityMetadata integrityMetadata(digest, algorithm); |
| 411 metadataSet.add(integrityMetadata.toPair()); | 418 metadataSet.add(integrityMetadata.toPair()); |
| 412 } | 419 } |
| 413 | 420 |
| 414 if (metadataSet.size() == 0 && error) | 421 if (metadataSet.size() == 0 && error) |
| 415 return IntegrityParseNoValidResult; | 422 return IntegrityParseNoValidResult; |
| 416 | 423 |
| 417 return IntegrityParseValidResult; | 424 return IntegrityParseValidResult; |
| 418 } | 425 } |
| 419 | 426 |
| 420 } // namespace blink | 427 } // namespace blink |
| OLD | NEW |