Chromium Code Reviews| 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 static bool isIntegrityCharacter(UChar c) | |
| 27 { | |
| 28 // Check if it's a base64 encoded value. | |
| 29 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; | |
|
Mike West
2014/09/16 06:45:04
Nit: It's probably worth sharing the base64 check
jww
2014/09/16 22:34:49
Acknowledged.
| |
| 30 } | |
| 31 | |
| 32 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) | |
| 33 { | |
| 34 if (digest1.size() != digest2.size()) | |
| 35 return false; | |
| 36 | |
| 37 for (size_t i = 0; i < digest1.size(); i++) { | |
| 38 if (digest1[i] != digest2[i]) | |
| 39 return false; | |
| 40 } | |
| 41 | |
| 42 return true; | |
| 43 } | |
| 44 | |
| 45 // TODO(jww) If CheckSubresourceIntegrity fails, Blink should create a console | |
| 46 // message to alert the developer of the failure. | |
| 47 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con st String& source, const KURL& resourceUrl) | |
| 48 { | |
| 49 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) | |
| 50 return true; | |
| 51 | |
| 52 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) | |
| 53 return true; | |
| 54 | |
| 55 // TODO(jww): If insecureOriginMsg is not empty after the check, Blink | |
| 56 // should send a console message. | |
| 57 // | |
| 58 // Instead of just checking SecurityOrigin::isSecure on resourceUrl, this | |
| 59 // checks canAccessFeatureRequiringSecureOrigin so that file:// protocols | |
| 60 // and localhost resources can be allowed. These may be useful for testing | |
| 61 // and are allowed for features requiring authenticated origins, so Chrome | |
| 62 // allows them here. | |
|
Mike West
2014/09/16 06:45:04
I don't like that we've ended up with two levels o
jww
2014/09/16 22:34:49
Okay, I will do that, although there *is* a subtle
| |
| 63 String insecureOriginMsg = ""; | |
| 64 RefPtr<SecurityOrigin> resourceSecurityOrigin = SecurityOrigin::create(resou rceUrl); | |
| 65 if (!element.document().securityOrigin()->canAccessFeatureRequiringSecureOri gin(insecureOriginMsg) || !resourceSecurityOrigin->canAccessFeatureRequiringSecu reOrigin(insecureOriginMsg)) { | |
| 66 UseCounter::count(element.document(), UseCounter::SRIElementWithIntegrit yAttributeAndInsecureResource); | |
|
Mike West
2014/09/16 06:45:04
This will also trigger if the document itself is i
jww
2014/09/16 22:34:49
Done.
| |
| 67 return false; | |
| 68 } | |
| 69 | |
| 70 String integrity; | |
| 71 HashAlgorithm algorithm; | |
| 72 if (!parseIntegrityAttribute(element.fastGetAttribute(HTMLNames::integrityAt tr), integrity, algorithm)) { | |
| 73 UseCounter::count(element.document(), UseCounter::SRIElementWithUnparsab leIntegrityAttribute); | |
| 74 return false; | |
| 75 } | |
| 76 | |
| 77 Vector<char> hashVector; | |
| 78 base64Decode(integrity, hashVector); | |
| 79 | |
| 80 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF ::EntitiesForUnencodables); | |
| 81 | |
| 82 DigestValue digest; | |
| 83 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma lizedSource.length(), digest); | |
| 84 | |
| 85 if (digestSuccess) { | |
| 86 DigestValue convertedHashVector; | |
| 87 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.data()) , hashVector.size()); | |
| 88 if (DigestsEqual(digest, convertedHashVector)) { | |
| 89 UseCounter::count(element.document(), UseCounter::SRIElementWithMatc hingIntegrityAttribute); | |
| 90 return true; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 UseCounter::count(element.document(), UseCounter::SRIElementWithNonMatchingI ntegrityAttribute); | |
| 95 return false; | |
| 96 } | |
| 97 | |
| 98 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri ng& integrity, HashAlgorithm& algorithm) | |
|
Mike West
2014/09/16 06:45:03
This parsing algorithm looks solid, but I'd really
jww
2014/09/16 22:34:49
Done.
| |
| 99 { | |
| 100 DEFINE_STATIC_LOCAL(const String, integrityPrefix, ("ni://")); | |
| 101 // Any additions or subtractions from this struct should also modify the | |
| 102 // respective entries in the kAlgorithmMap array in checkDigest(). | |
| 103 static const struct { | |
| 104 const char* prefix; | |
| 105 HashAlgorithm algorithm; | |
| 106 } kSupportedPrefixes[] = { | |
| 107 { "sha256", HashAlgorithmSha256 }, | |
| 108 { "sha384", HashAlgorithmSha384 }, | |
| 109 { "sha512", HashAlgorithmSha512 } | |
| 110 }; | |
| 111 Vector<UChar> characters; | |
| 112 attribute.appendTo(characters); | |
| 113 UChar* begin = characters.data(); | |
| 114 UChar* end = characters.end(); | |
| 115 | |
| 116 if (!equalIgnoringCase(integrityPrefix.characters8(), begin, integrityPrefix .length())) | |
|
Mike West
2014/09/16 06:45:03
I don't recall if 'fastGetAttribute' strips whites
jww
2014/09/16 22:34:49
Done.
| |
| 117 return false; | |
| 118 | |
| 119 const UChar* algorithmStart = begin + integrityPrefix.length(); | |
| 120 const UChar* algorithmEnd = algorithmStart; | |
| 121 | |
| 122 skipUntil<UChar>(algorithmEnd, end, ';'); | |
| 123 | |
| 124 // Instead of this sizeof() calculation to get the length of this array, | |
| 125 // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to | |
| 126 // guarantee a compile time calculation. Unfortunately, on some | |
| 127 // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous | |
| 128 // stucts, so, for now, it is necessary to resort to this sizeof | |
| 129 // calculation. | |
| 130 size_t i = 0; | |
| 131 size_t kSupportedPrefixesLength = sizeof(kSupportedPrefixes) / sizeof(kSuppo rtedPrefixes[0]); | |
| 132 for (; i < kSupportedPrefixesLength; i++) { | |
| 133 if (equalIgnoringCase(kSupportedPrefixes[i].prefix, algorithmStart, strl en(kSupportedPrefixes[i].prefix))) { | |
| 134 algorithm = kSupportedPrefixes[i].algorithm; | |
| 135 break; | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 if (i == kSupportedPrefixesLength) | |
| 140 return false; | |
| 141 | |
| 142 const UChar* integrityStart = algorithmEnd; | |
| 143 if (!skipExactly<UChar>(integrityStart, end, ';')) | |
| 144 return false; | |
| 145 | |
| 146 const UChar* integrityEnd = integrityStart; | |
| 147 skipWhile<UChar, isIntegrityCharacter>(integrityEnd, end); | |
| 148 if (integrityEnd != end) | |
| 149 return false; | |
| 150 | |
| 151 integrity = String(integrityStart, integrityEnd - integrityStart); | |
| 152 return true; | |
| 153 } | |
| 154 | |
| 155 } // namespace blink | |
| OLD | NEW |