OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "config.h" |
| 6 #include "core/frame/SubresourceIntegrity.h" |
| 7 |
| 8 #include "core/HTMLNames.h" |
| 9 #include "core/dom/Document.h" |
| 10 #include "core/dom/Element.h" |
| 11 #include "core/frame/UseCounter.h" |
| 12 #include "platform/Crypto.h" |
| 13 #include "platform/ParsingUtilities.h" |
| 14 #include "platform/RuntimeEnabledFeatures.h" |
| 15 #include "platform/weborigin/KURL.h" |
| 16 #include "platform/weborigin/SecurityOrigin.h" |
| 17 #include "public/platform/WebCrypto.h" |
| 18 #include "public/platform/WebCryptoAlgorithm.h" |
| 19 #include "wtf/ASCIICType.h" |
| 20 #include "wtf/text/Base64.h" |
| 21 #include "wtf/text/StringUTF8Adaptor.h" |
| 22 #include "wtf/text/WTFString.h" |
| 23 |
| 24 namespace blink { |
| 25 |
| 26 // FIXME: This should probably use common functions with ContentSecurityPolicy. |
| 27 static bool isIntegrityCharacter(UChar c) |
| 28 { |
| 29 // Check if it's a base64 encoded value. |
| 30 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; |
| 31 } |
| 32 |
| 33 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) |
| 34 { |
| 35 if (digest1.size() != digest2.size()) |
| 36 return false; |
| 37 |
| 38 for (size_t i = 0; i < digest1.size(); i++) { |
| 39 if (digest1[i] != digest2[i]) |
| 40 return false; |
| 41 } |
| 42 |
| 43 return true; |
| 44 } |
| 45 |
| 46 // FIXME: If CheckSubresourceIntegrity fails, Blink should create a console |
| 47 // message to alert the developer of the failure. |
| 48 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con
st String& source, const KURL& resourceUrl) |
| 49 { |
| 50 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) |
| 51 return true; |
| 52 |
| 53 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) |
| 54 return true; |
| 55 |
| 56 // FIXME: If insecureOriginMsg is not empty after the check, Blink |
| 57 // should send a console message. |
| 58 // |
| 59 // Instead of just checking SecurityOrigin::isSecure on resourceUrl, this |
| 60 // checks canAccessFeatureRequiringSecureOrigin so that file:// protocols |
| 61 // and localhost resources can be allowed. These may be useful for testing |
| 62 // and are allowed for features requiring authenticated origins, so Chrome |
| 63 // allows them here. |
| 64 String insecureOriginMsg = ""; |
| 65 RefPtr<SecurityOrigin> resourceSecurityOrigin = SecurityOrigin::create(resou
rceUrl); |
| 66 if (!element.document().securityOrigin()->canAccessFeatureRequiringSecureOri
gin(insecureOriginMsg)) { |
| 67 UseCounter::count(element.document(), UseCounter::SRIElementWithIntegrit
yAttributeAndInsecureOrigin); |
| 68 return false; |
| 69 } |
| 70 if (!resourceSecurityOrigin->canAccessFeatureRequiringSecureOrigin(insecureO
riginMsg)) { |
| 71 UseCounter::count(element.document(), UseCounter::SRIElementWithIntegrit
yAttributeAndInsecureResource); |
| 72 return false; |
| 73 } |
| 74 |
| 75 String integrity; |
| 76 HashAlgorithm algorithm; |
| 77 if (!parseIntegrityAttribute(element.fastGetAttribute(HTMLNames::integrityAt
tr), integrity, algorithm)) { |
| 78 UseCounter::count(element.document(), UseCounter::SRIElementWithUnparsab
leIntegrityAttribute); |
| 79 return false; |
| 80 } |
| 81 |
| 82 Vector<char> hashVector; |
| 83 base64Decode(integrity, hashVector); |
| 84 |
| 85 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF
::EntitiesForUnencodables); |
| 86 |
| 87 DigestValue digest; |
| 88 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma
lizedSource.length(), digest); |
| 89 |
| 90 if (digestSuccess) { |
| 91 DigestValue convertedHashVector; |
| 92 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data())
, hashVector.size()); |
| 93 if (DigestsEqual(digest, convertedHashVector)) { |
| 94 UseCounter::count(element.document(), UseCounter::SRIElementWithMatc
hingIntegrityAttribute); |
| 95 return true; |
| 96 } |
| 97 } |
| 98 |
| 99 UseCounter::count(element.document(), UseCounter::SRIElementWithNonMatchingI
ntegrityAttribute); |
| 100 return false; |
| 101 } |
| 102 |
| 103 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri
ng& integrity, HashAlgorithm& algorithm) |
| 104 { |
| 105 DEFINE_STATIC_LOCAL(const String, integrityPrefix, ("ni://")); |
| 106 // Any additions or subtractions from this struct should also modify the |
| 107 // respective entries in the kAlgorithmMap array in checkDigest(). |
| 108 static const struct { |
| 109 const char* prefix; |
| 110 HashAlgorithm algorithm; |
| 111 } kSupportedPrefixes[] = { |
| 112 { "sha256", HashAlgorithmSha256 }, |
| 113 { "sha384", HashAlgorithmSha384 }, |
| 114 { "sha512", HashAlgorithmSha512 } |
| 115 }; |
| 116 Vector<UChar> characters; |
| 117 attribute.stripWhiteSpace().appendTo(characters); |
| 118 UChar* begin = characters.data(); |
| 119 UChar* end = characters.end(); |
| 120 |
| 121 if (characters.size() < 1) |
| 122 return false; |
| 123 |
| 124 if (!equalIgnoringCase(integrityPrefix.characters8(), begin, integrityPrefix
.length())) |
| 125 return false; |
| 126 |
| 127 const UChar* algorithmStart = begin + integrityPrefix.length(); |
| 128 const UChar* algorithmEnd = algorithmStart; |
| 129 |
| 130 skipUntil<UChar>(algorithmEnd, end, ';'); |
| 131 |
| 132 // Instead of this sizeof() calculation to get the length of this array, |
| 133 // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to |
| 134 // guarantee a compile time calculation. Unfortunately, on some |
| 135 // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous |
| 136 // stucts, so, for now, it is necessary to resort to this sizeof |
| 137 // calculation. |
| 138 size_t i = 0; |
| 139 size_t kSupportedPrefixesLength = sizeof(kSupportedPrefixes) / sizeof(kSuppo
rtedPrefixes[0]); |
| 140 for (; i < kSupportedPrefixesLength; i++) { |
| 141 if (equalIgnoringCase(kSupportedPrefixes[i].prefix, algorithmStart, strl
en(kSupportedPrefixes[i].prefix))) { |
| 142 algorithm = kSupportedPrefixes[i].algorithm; |
| 143 break; |
| 144 } |
| 145 } |
| 146 |
| 147 if (i == kSupportedPrefixesLength) |
| 148 return false; |
| 149 |
| 150 const UChar* integrityStart = algorithmEnd; |
| 151 if (!skipExactly<UChar>(integrityStart, end, ';')) |
| 152 return false; |
| 153 |
| 154 const UChar* integrityEnd = integrityStart; |
| 155 skipWhile<UChar, isIntegrityCharacter>(integrityEnd, end); |
| 156 if (integrityEnd != end) |
| 157 return false; |
| 158 |
| 159 integrity = String(integrityStart, integrityEnd - integrityStart); |
| 160 return true; |
| 161 } |
| 162 |
| 163 } // namespace blink |
OLD | NEW |