| 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" |
| 11 #include "core/frame/ConsoleTypes.h" |
| 11 #include "core/frame/UseCounter.h" | 12 #include "core/frame/UseCounter.h" |
| 13 #include "core/inspector/ConsoleMessage.h" |
| 12 #include "platform/Crypto.h" | 14 #include "platform/Crypto.h" |
| 13 #include "platform/ParsingUtilities.h" | 15 #include "platform/ParsingUtilities.h" |
| 14 #include "platform/RuntimeEnabledFeatures.h" | 16 #include "platform/RuntimeEnabledFeatures.h" |
| 15 #include "platform/weborigin/KURL.h" | 17 #include "platform/weborigin/KURL.h" |
| 16 #include "platform/weborigin/SecurityOrigin.h" | 18 #include "platform/weborigin/SecurityOrigin.h" |
| 17 #include "public/platform/WebCrypto.h" | 19 #include "public/platform/WebCrypto.h" |
| 18 #include "public/platform/WebCryptoAlgorithm.h" | 20 #include "public/platform/WebCryptoAlgorithm.h" |
| 19 #include "wtf/ASCIICType.h" | 21 #include "wtf/ASCIICType.h" |
| 20 #include "wtf/text/Base64.h" | 22 #include "wtf/text/Base64.h" |
| 21 #include "wtf/text/StringUTF8Adaptor.h" | 23 #include "wtf/text/StringUTF8Adaptor.h" |
| 22 #include "wtf/text/WTFString.h" | 24 #include "wtf/text/WTFString.h" |
| 23 | 25 |
| 24 namespace blink { | 26 namespace blink { |
| 25 | 27 |
| 26 // FIXME: This should probably use common functions with ContentSecurityPolicy. | 28 // FIXME: This should probably use common functions with ContentSecurityPolicy. |
| 27 static bool isIntegrityCharacter(UChar c) | 29 static bool isIntegrityCharacter(UChar c) |
| 28 { | 30 { |
| 29 // Check if it's a base64 encoded value. | 31 // Check if it's a base64 encoded value. |
| 30 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; | 32 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; |
| 31 } | 33 } |
| 32 | 34 |
| 35 static void logErrorToConsole(const String& message, Document& document) |
| 36 { |
| 37 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err
orMessageLevel, message)); |
| 38 } |
| 39 |
| 33 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) | 40 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) |
| 34 { | 41 { |
| 35 if (digest1.size() != digest2.size()) | 42 if (digest1.size() != digest2.size()) |
| 36 return false; | 43 return false; |
| 37 | 44 |
| 38 for (size_t i = 0; i < digest1.size(); i++) { | 45 for (size_t i = 0; i < digest1.size(); i++) { |
| 39 if (digest1[i] != digest2[i]) | 46 if (digest1[i] != digest2[i]) |
| 40 return false; | 47 return false; |
| 41 } | 48 } |
| 42 | 49 |
| 43 return true; | 50 return true; |
| 44 } | 51 } |
| 45 | 52 |
| 46 // FIXME: If CheckSubresourceIntegrity fails, Blink should create a console | 53 static String algorithmToString(HashAlgorithm algorithm) |
| 47 // message to alert the developer of the failure. | 54 { |
| 55 static const struct { |
| 56 HashAlgorithm algorithm; |
| 57 const char* name; |
| 58 } kAlgorithmToString[] = { |
| 59 { HashAlgorithmSha256, "SHA-256" }, |
| 60 { HashAlgorithmSha384, "SHA-384" }, |
| 61 { HashAlgorithmSha512, "SHA-512" } |
| 62 }; |
| 63 |
| 64 // See comment in parseIntegrityAttribute about why sizeof() is used |
| 65 // instead of WTF_ARRAY_LENGTH. |
| 66 size_t i = 0; |
| 67 size_t kSupportedAlgorithmsLength = sizeof(kAlgorithmToString) / sizeof(kAlg
orithmToString[0]); |
| 68 for (; i < kSupportedAlgorithmsLength; i++) { |
| 69 if (kAlgorithmToString[i].algorithm == algorithm) |
| 70 return kAlgorithmToString[i].name; |
| 71 } |
| 72 |
| 73 ASSERT_NOT_REACHED(); |
| 74 return String(); |
| 75 } |
| 76 |
| 77 static String digestToString(const DigestValue& digest) |
| 78 { |
| 79 return base64Encode(reinterpret_cast<const char*>(digest.data()), digest.siz
e(), Base64DoNotInsertLFs); |
| 80 } |
| 81 |
| 48 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con
st String& source, const KURL& resourceUrl) | 82 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con
st String& source, const KURL& resourceUrl) |
| 49 { | 83 { |
| 50 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) | 84 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) |
| 51 return true; | 85 return true; |
| 52 | 86 |
| 53 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) | 87 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) |
| 54 return true; | 88 return true; |
| 55 | 89 |
| 56 // FIXME: If insecureOriginMsg is not empty after the check, Blink | 90 Document& document = element.document(); |
| 57 // should send a console message. | 91 |
| 58 // | |
| 59 // Instead of just checking SecurityOrigin::isSecure on resourceUrl, this | 92 // Instead of just checking SecurityOrigin::isSecure on resourceUrl, this |
| 60 // checks canAccessFeatureRequiringSecureOrigin so that file:// protocols | 93 // checks canAccessFeatureRequiringSecureOrigin so that file:// protocols |
| 61 // and localhost resources can be allowed. These may be useful for testing | 94 // and localhost resources can be allowed. These may be useful for testing |
| 62 // and are allowed for features requiring authenticated origins, so Chrome | 95 // and are allowed for features requiring authenticated origins, so Chrome |
| 63 // allows them here. | 96 // allows them here. |
| 64 String insecureOriginMsg = ""; | 97 String insecureOriginMsg = ""; |
| 65 RefPtr<SecurityOrigin> resourceSecurityOrigin = SecurityOrigin::create(resou
rceUrl); | 98 RefPtr<SecurityOrigin> resourceSecurityOrigin = SecurityOrigin::create(resou
rceUrl); |
| 66 if (!element.document().securityOrigin()->canAccessFeatureRequiringSecureOri
gin(insecureOriginMsg)) { | 99 if (!document.securityOrigin()->canAccessFeatureRequiringSecureOrigin(insecu
reOriginMsg)) { |
| 67 UseCounter::count(element.document(), UseCounter::SRIElementWithIntegrit
yAttributeAndInsecureOrigin); | 100 UseCounter::count(document, UseCounter::SRIElementWithIntegrityAttribute
AndInsecureOrigin); |
| 101 // FIXME: This console message should probably utilize |
| 102 // inesecureOriginMsg to give a more helpful message to the user. |
| 103 logErrorToConsole("The 'integrity' attribute may only be used in documen
ts in secure origins.", document); |
| 68 return false; | 104 return false; |
| 69 } | 105 } |
| 70 if (!resourceSecurityOrigin->canAccessFeatureRequiringSecureOrigin(insecureO
riginMsg)) { | 106 if (!resourceSecurityOrigin->canAccessFeatureRequiringSecureOrigin(insecureO
riginMsg)) { |
| 71 UseCounter::count(element.document(), UseCounter::SRIElementWithIntegrit
yAttributeAndInsecureResource); | 107 UseCounter::count(document, UseCounter::SRIElementWithIntegrityAttribute
AndInsecureResource); |
| 108 logErrorToConsole("The 'integrity' attribute may only be used with resou
rces on secure origins.", document); |
| 72 return false; | 109 return false; |
| 73 } | 110 } |
| 74 | 111 |
| 75 String integrity; | 112 String integrity; |
| 76 HashAlgorithm algorithm; | 113 HashAlgorithm algorithm; |
| 77 if (!parseIntegrityAttribute(element.fastGetAttribute(HTMLNames::integrityAt
tr), integrity, algorithm)) { | 114 String attribute = element.fastGetAttribute(HTMLNames::integrityAttr); |
| 78 UseCounter::count(element.document(), UseCounter::SRIElementWithUnparsab
leIntegrityAttribute); | 115 if (!parseIntegrityAttribute(attribute, integrity, algorithm)) { |
| 116 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrit
yAttribute); |
| 117 logErrorToConsole("The 'integrity' attribute's value '" + attribute + "
' is not valid integrity metadata.", document); |
| 79 return false; | 118 return false; |
| 80 } | 119 } |
| 81 | 120 |
| 82 Vector<char> hashVector; | 121 Vector<char> hashVector; |
| 83 base64Decode(integrity, hashVector); | 122 base64Decode(integrity, hashVector); |
| 84 | 123 |
| 85 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF
::EntitiesForUnencodables); | 124 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF
::EntitiesForUnencodables); |
| 86 | 125 |
| 87 DigestValue digest; | 126 DigestValue digest; |
| 88 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma
lizedSource.length(), digest); | 127 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma
lizedSource.length(), digest); |
| 89 | 128 |
| 90 if (digestSuccess) { | 129 if (digestSuccess) { |
| 91 DigestValue convertedHashVector; | 130 DigestValue convertedHashVector; |
| 92 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data())
, hashVector.size()); | 131 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data())
, hashVector.size()); |
| 93 if (DigestsEqual(digest, convertedHashVector)) { | 132 if (DigestsEqual(digest, convertedHashVector)) { |
| 94 UseCounter::count(element.document(), UseCounter::SRIElementWithMatc
hingIntegrityAttribute); | 133 UseCounter::count(document, UseCounter::SRIElementWithMatchingIntegr
ityAttribute); |
| 95 return true; | 134 return true; |
| 135 } else { |
| 136 // This message exposes the digest of the resource to the console. |
| 137 // Because this is only to the console, that's okay for now, but we |
| 138 // need to be very careful not to expose this in exceptions or |
| 139 // JavaScript, otherwise it risks exposing information about the |
| 140 // resource cross-origin. |
| 141 logErrorToConsole("The computed " + algorithmToString(algorithm) + "
integrity '" + digestToString(digest) + "' does not match the 'integrity' attri
bute '" + integrity + "' for resource '" + resourceUrl.elidedString() + "'.", do
cument); |
| 96 } | 142 } |
| 143 } else { |
| 144 logErrorToConsole("There was an error computing an 'integrity' value for
resource '" + resourceUrl.elidedString() + "'.", document); |
| 97 } | 145 } |
| 98 | 146 |
| 99 UseCounter::count(element.document(), UseCounter::SRIElementWithNonMatchingI
ntegrityAttribute); | 147 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt
tribute); |
| 100 return false; | 148 return false; |
| 101 } | 149 } |
| 102 | 150 |
| 103 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri
ng& integrity, HashAlgorithm& algorithm) | 151 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri
ng& integrity, HashAlgorithm& algorithm) |
| 104 { | 152 { |
| 105 DEFINE_STATIC_LOCAL(const String, integrityPrefix, ("ni://")); | 153 DEFINE_STATIC_LOCAL(const String, integrityPrefix, ("ni://")); |
| 106 // Any additions or subtractions from this struct should also modify the | 154 // Any additions or subtractions from this struct should also modify the |
| 107 // respective entries in the kAlgorithmMap array in checkDigest(). | 155 // respective entries in the kAlgorithmMap array in checkDigest() as well |
| 156 // as the array in algorithmToString(). |
| 108 static const struct { | 157 static const struct { |
| 109 const char* prefix; | 158 const char* prefix; |
| 110 HashAlgorithm algorithm; | 159 HashAlgorithm algorithm; |
| 111 } kSupportedPrefixes[] = { | 160 } kSupportedPrefixes[] = { |
| 112 { "sha256", HashAlgorithmSha256 }, | 161 { "sha256", HashAlgorithmSha256 }, |
| 113 { "sha384", HashAlgorithmSha384 }, | 162 { "sha384", HashAlgorithmSha384 }, |
| 114 { "sha512", HashAlgorithmSha512 } | 163 { "sha512", HashAlgorithmSha512 } |
| 115 }; | 164 }; |
| 116 Vector<UChar> characters; | 165 Vector<UChar> characters; |
| 117 attribute.stripWhiteSpace().appendTo(characters); | 166 attribute.stripWhiteSpace().appendTo(characters); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 154 const UChar* integrityEnd = integrityStart; | 203 const UChar* integrityEnd = integrityStart; |
| 155 skipWhile<UChar, isIntegrityCharacter>(integrityEnd, end); | 204 skipWhile<UChar, isIntegrityCharacter>(integrityEnd, end); |
| 156 if (integrityEnd != end) | 205 if (integrityEnd != end) |
| 157 return false; | 206 return false; |
| 158 | 207 |
| 159 integrity = String(integrityStart, integrityEnd - integrityStart); | 208 integrity = String(integrityStart, integrityEnd - integrityStart); |
| 160 return true; | 209 return true; |
| 161 } | 210 } |
| 162 | 211 |
| 163 } // namespace blink | 212 } // namespace blink |
| OLD | NEW |