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 10 matching lines...) Expand all Loading... |
21 #include "wtf/ASCIICType.h" | 21 #include "wtf/ASCIICType.h" |
22 #include "wtf/text/Base64.h" | 22 #include "wtf/text/Base64.h" |
23 #include "wtf/text/StringUTF8Adaptor.h" | 23 #include "wtf/text/StringUTF8Adaptor.h" |
24 #include "wtf/text/WTFString.h" | 24 #include "wtf/text/WTFString.h" |
25 | 25 |
26 namespace blink { | 26 namespace blink { |
27 | 27 |
28 // FIXME: This should probably use common functions with ContentSecurityPolicy. | 28 // FIXME: This should probably use common functions with ContentSecurityPolicy. |
29 static bool isIntegrityCharacter(UChar c) | 29 static bool isIntegrityCharacter(UChar c) |
30 { | 30 { |
| 31 // FIXME: This should be checking base64url encoding, not base64 encoding. |
| 32 |
31 // Check if it's a base64 encoded value. | 33 // Check if it's a base64 encoded value. |
32 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; | 34 return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '='; |
33 } | 35 } |
34 | 36 |
35 static void logErrorToConsole(const String& message, Document& document) | 37 static void logErrorToConsole(const String& message, Document& document) |
36 { | 38 { |
37 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err
orMessageLevel, message)); | 39 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err
orMessageLevel, message)); |
38 } | 40 } |
39 | 41 |
40 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) | 42 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 } | 107 } |
106 if (!resourceSecurityOrigin->canAccessFeatureRequiringSecureOrigin(insecureO
riginMsg)) { | 108 if (!resourceSecurityOrigin->canAccessFeatureRequiringSecureOrigin(insecureO
riginMsg)) { |
107 UseCounter::count(document, UseCounter::SRIElementWithIntegrityAttribute
AndInsecureResource); | 109 UseCounter::count(document, UseCounter::SRIElementWithIntegrityAttribute
AndInsecureResource); |
108 logErrorToConsole("The 'integrity' attribute may only be used with resou
rces on secure origins.", document); | 110 logErrorToConsole("The 'integrity' attribute may only be used with resou
rces on secure origins.", document); |
109 return false; | 111 return false; |
110 } | 112 } |
111 | 113 |
112 String integrity; | 114 String integrity; |
113 HashAlgorithm algorithm; | 115 HashAlgorithm algorithm; |
114 String attribute = element.fastGetAttribute(HTMLNames::integrityAttr); | 116 String attribute = element.fastGetAttribute(HTMLNames::integrityAttr); |
115 if (!parseIntegrityAttribute(attribute, integrity, algorithm)) { | 117 if (!parseIntegrityAttribute(attribute, integrity, algorithm, document)) { |
| 118 // An error is logged to the console during parsing; we don't need to lo
g one here. |
116 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrit
yAttribute); | 119 UseCounter::count(document, UseCounter::SRIElementWithUnparsableIntegrit
yAttribute); |
117 logErrorToConsole("The 'integrity' attribute's value '" + attribute + "
' is not valid integrity metadata.", document); | |
118 return false; | 120 return false; |
119 } | 121 } |
120 | 122 |
121 Vector<char> hashVector; | 123 Vector<char> hashVector; |
122 base64Decode(integrity, hashVector); | 124 base64Decode(integrity, hashVector); |
123 | 125 |
124 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF
::EntitiesForUnencodables); | 126 StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF
::EntitiesForUnencodables); |
125 | 127 |
126 DigestValue digest; | 128 DigestValue digest; |
127 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma
lizedSource.length(), digest); | 129 bool digestSuccess = computeDigest(algorithm, normalizedSource.data(), norma
lizedSource.length(), digest); |
(...skipping 13 matching lines...) Expand all Loading... |
141 logErrorToConsole("The computed " + algorithmToString(algorithm) + "
integrity '" + digestToString(digest) + "' does not match the 'integrity' attri
bute '" + integrity + "' for resource '" + resourceUrl.elidedString() + "'.", do
cument); | 143 logErrorToConsole("The computed " + algorithmToString(algorithm) + "
integrity '" + digestToString(digest) + "' does not match the 'integrity' attri
bute '" + integrity + "' for resource '" + resourceUrl.elidedString() + "'.", do
cument); |
142 } | 144 } |
143 } else { | 145 } else { |
144 logErrorToConsole("There was an error computing an 'integrity' value for
resource '" + resourceUrl.elidedString() + "'.", document); | 146 logErrorToConsole("There was an error computing an 'integrity' value for
resource '" + resourceUrl.elidedString() + "'.", document); |
145 } | 147 } |
146 | 148 |
147 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt
tribute); | 149 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt
tribute); |
148 return false; | 150 return false; |
149 } | 151 } |
150 | 152 |
151 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri
ng& integrity, HashAlgorithm& algorithm) | 153 // Before: |
| 154 // |
| 155 // ni:///[algorithm];[hash] |
| 156 // ^ ^ |
| 157 // position end |
| 158 // |
| 159 // After: |
| 160 // |
| 161 // ni:///[algorithm];[hash] |
| 162 // ^ ^ |
| 163 // position end |
| 164 bool SubresourceIntegrity::parseAlgorithm(const UChar*& position, const UChar* e
nd, HashAlgorithm& algorithm) |
152 { | 165 { |
153 DEFINE_STATIC_LOCAL(const String, integrityPrefix, ("ni://")); | |
154 // Any additions or subtractions from this struct should also modify the | 166 // Any additions or subtractions from this struct should also modify the |
155 // respective entries in the kAlgorithmMap array in checkDigest() as well | 167 // respective entries in the kAlgorithmMap array in checkDigest() as well |
156 // as the array in algorithmToString(). | 168 // as the array in algorithmToString(). |
157 static const struct { | 169 static const struct { |
158 const char* prefix; | 170 const char* prefix; |
159 HashAlgorithm algorithm; | 171 HashAlgorithm algorithm; |
160 } kSupportedPrefixes[] = { | 172 } kSupportedPrefixes[] = { |
161 { "sha256", HashAlgorithmSha256 }, | 173 { "sha256", HashAlgorithmSha256 }, |
162 { "sha384", HashAlgorithmSha384 }, | 174 { "sha384", HashAlgorithmSha384 }, |
163 { "sha512", HashAlgorithmSha512 } | 175 { "sha512", HashAlgorithmSha512 } |
164 }; | 176 }; |
165 Vector<UChar> characters; | |
166 attribute.stripWhiteSpace().appendTo(characters); | |
167 UChar* begin = characters.data(); | |
168 UChar* end = characters.end(); | |
169 | 177 |
170 if (characters.size() < 1) | 178 for (auto& prefix : kSupportedPrefixes) { |
171 return false; | 179 if (skipToken<UChar>(position, end, prefix.prefix)) { |
172 | 180 algorithm = prefix.algorithm; |
173 if (!equalIgnoringCase(integrityPrefix.characters8(), begin, integrityPrefix
.length())) | 181 return true; |
174 return false; | |
175 | |
176 const UChar* algorithmStart = begin + integrityPrefix.length(); | |
177 const UChar* algorithmEnd = algorithmStart; | |
178 | |
179 skipUntil<UChar>(algorithmEnd, end, ';'); | |
180 | |
181 // Instead of this sizeof() calculation to get the length of this array, | |
182 // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to | |
183 // guarantee a compile time calculation. Unfortunately, on some | |
184 // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous | |
185 // stucts, so, for now, it is necessary to resort to this sizeof | |
186 // calculation. | |
187 size_t i = 0; | |
188 size_t kSupportedPrefixesLength = sizeof(kSupportedPrefixes) / sizeof(kSuppo
rtedPrefixes[0]); | |
189 for (; i < kSupportedPrefixesLength; i++) { | |
190 if (equalIgnoringCase(kSupportedPrefixes[i].prefix, algorithmStart, strl
en(kSupportedPrefixes[i].prefix))) { | |
191 algorithm = kSupportedPrefixes[i].algorithm; | |
192 break; | |
193 } | 182 } |
194 } | 183 } |
195 | 184 |
196 if (i == kSupportedPrefixesLength) | 185 return false; |
| 186 } |
| 187 |
| 188 // Before: |
| 189 // |
| 190 // ni:///[algorithm];[hash] OR ni:///[algorithm];[hash]?[params] |
| 191 // ^ ^ ^ ^ |
| 192 // position end position end |
| 193 // |
| 194 // After: |
| 195 // |
| 196 // ni:///[algorithm];[hash] OR ni:///[algorithm];[hash]?[params] |
| 197 // ^ ^ ^ |
| 198 // position/end position end |
| 199 bool SubresourceIntegrity::parseDigest(const UChar*& position, const UChar* end,
String& digest) |
| 200 { |
| 201 const UChar* begin = position; |
| 202 skipWhile<UChar, isIntegrityCharacter>(position, end); |
| 203 |
| 204 if (position == begin || (position != end && *position != '?')) { |
| 205 digest = emptyString(); |
197 return false; | 206 return false; |
| 207 } |
198 | 208 |
199 const UChar* integrityStart = algorithmEnd; | 209 digest = String(begin, position - begin); |
200 if (!skipExactly<UChar>(integrityStart, end, ';')) | |
201 return false; | |
202 | |
203 const UChar* integrityEnd = integrityStart; | |
204 skipWhile<UChar, isIntegrityCharacter>(integrityEnd, end); | |
205 if (integrityEnd != end) | |
206 return false; | |
207 | |
208 integrity = String(integrityStart, integrityEnd - integrityStart); | |
209 return true; | 210 return true; |
210 } | 211 } |
211 | 212 |
| 213 bool SubresourceIntegrity::parseIntegrityAttribute(const String& attribute, Stri
ng& digest, HashAlgorithm& algorithm, Document& document) |
| 214 { |
| 215 Vector<UChar> characters; |
| 216 attribute.stripWhiteSpace().appendTo(characters); |
| 217 const UChar* position = characters.data(); |
| 218 const UChar* end = characters.end(); |
| 219 |
| 220 if (!skipToken<UChar>(position, end, "ni:///")) { |
| 221 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute +
"'). The value must begin with 'ni:///'.", document); |
| 222 return false; |
| 223 } |
| 224 |
| 225 if (!parseAlgorithm(position, end, algorithm)) { |
| 226 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute +
"'). The specified hash algorithm must be one of 'sha256', 'sha384', or 'sha512
'.", document); |
| 227 return false; |
| 228 } |
| 229 |
| 230 if (!skipExactly<UChar>(position, end, ';')) { |
| 231 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute +
"'). The hash algorithm must be followed by a ';' character.", document); |
| 232 return false; |
| 233 } |
| 234 |
| 235 if (!parseDigest(position, end, digest)) { |
| 236 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribute +
"'). The digest must be a valid, base64-encoded value.", document); |
| 237 return false; |
| 238 } |
| 239 |
| 240 // FIXME: Parse params in order to get content type (e.g. "?ct=application/j
avascript") |
| 241 |
| 242 return true; |
| 243 } |
| 244 |
212 } // namespace blink | 245 } // namespace blink |
OLD | NEW |