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/csp/SourceListDirective.h" | 5 #include "core/frame/csp/SourceListDirective.h" |
6 | 6 |
7 #include "core/frame/csp/CSPSourceList.h" | 7 #include "core/frame/csp/CSPSource.h" |
8 #include "core/frame/csp/ContentSecurityPolicy.h" | 8 #include "core/frame/csp/ContentSecurityPolicy.h" |
9 #include "platform/network/ContentSecurityPolicyParsers.h" | 9 #include "platform/network/ContentSecurityPolicyParsers.h" |
10 #include "platform/weborigin/KURL.h" | 10 #include "platform/weborigin/KURL.h" |
11 #include "platform/weborigin/SecurityOrigin.h" | |
12 #include "wtf/HashSet.h" | |
13 #include "wtf/text/Base64.h" | |
14 #include "wtf/text/ParsingUtilities.h" | |
15 #include "wtf/text/StringToNumber.h" | |
11 #include "wtf/text/WTFString.h" | 16 #include "wtf/text/WTFString.h" |
12 | 17 |
13 namespace blink { | 18 namespace blink { |
14 | 19 |
15 SourceListDirective::SourceListDirective(const String& name, | 20 SourceListDirective::SourceListDirective(const String& name, |
16 const String& value, | 21 const String& value, |
17 ContentSecurityPolicy* policy) | 22 ContentSecurityPolicy* policy) |
18 : CSPDirective(name, value, policy), m_sourceList(policy, name) { | 23 : CSPDirective(name, value, policy), |
24 m_policy(policy), | |
25 m_directiveName(name), | |
26 m_allowSelf(false), | |
27 m_allowStar(false), | |
28 m_allowInline(false), | |
29 m_allowEval(false), | |
30 m_allowDynamic(false), | |
31 m_allowHashedAttributes(false), | |
32 m_hashAlgorithmsUsed(0) { | |
19 Vector<UChar> characters; | 33 Vector<UChar> characters; |
20 value.appendTo(characters); | 34 value.appendTo(characters); |
21 | 35 parse(characters.data(), characters.data() + characters.size()); |
22 m_sourceList.parse(characters.data(), characters.data() + characters.size()); | 36 } |
37 | |
38 static bool isSourceListNone(const UChar* begin, const UChar* end) { | |
39 skipWhile<UChar, isASCIISpace>(begin, end); | |
40 | |
41 const UChar* position = begin; | |
42 skipWhile<UChar, isSourceCharacter>(position, end); | |
43 if (!equalIgnoringCase("'none'", StringView(begin, position - begin))) | |
44 return false; | |
45 | |
46 skipWhile<UChar, isASCIISpace>(position, end); | |
47 if (position != end) | |
48 return false; | |
49 | |
50 return true; | |
23 } | 51 } |
24 | 52 |
25 bool SourceListDirective::allows( | 53 bool SourceListDirective::allows( |
26 const KURL& url, | 54 const KURL& url, |
27 ResourceRequest::RedirectStatus redirectStatus) const { | 55 ResourceRequest::RedirectStatus redirectStatus) const { |
28 return m_sourceList.matches(url, redirectStatus); | 56 // Wildcards match network schemes ('http', 'https', 'ftp', 'ws', 'wss'), and |
57 // the scheme of the protected resource: | |
58 // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression. Other | |
59 // schemes, including custom schemes, must be explicitly listed in a source | |
60 // list. | |
61 if (m_allowStar) { | |
62 if (url.protocolIsInHTTPFamily() || url.protocolIs("ftp") || | |
63 url.protocolIs("ws") || url.protocolIs("wss") || | |
64 m_policy->protocolMatchesSelf(url)) | |
65 return true; | |
66 | |
67 return hasSourceMatchInList(url, redirectStatus); | |
68 } | |
69 | |
70 KURL effectiveURL = | |
71 m_policy->selfMatchesInnerURL() && SecurityOrigin::shouldUseInnerURL(url) | |
72 ? SecurityOrigin::extractInnerURL(url) | |
73 : url; | |
74 | |
75 if (m_allowSelf && m_policy->urlMatchesSelf(effectiveURL)) | |
76 return true; | |
77 | |
78 return hasSourceMatchInList(effectiveURL, redirectStatus); | |
29 } | 79 } |
30 | 80 |
31 bool SourceListDirective::allowInline() const { | 81 bool SourceListDirective::allowInline() const { |
32 return m_sourceList.allowInline(); | 82 return m_allowInline; |
33 } | 83 } |
34 | 84 |
35 bool SourceListDirective::allowEval() const { | 85 bool SourceListDirective::allowEval() const { |
36 return m_sourceList.allowEval(); | 86 return m_allowEval; |
37 } | 87 } |
38 | 88 |
39 bool SourceListDirective::allowDynamic() const { | 89 bool SourceListDirective::allowDynamic() const { |
40 return m_sourceList.allowDynamic(); | 90 return m_allowDynamic; |
41 } | 91 } |
42 | 92 |
43 bool SourceListDirective::allowNonce(const String& nonce) const { | 93 bool SourceListDirective::allowNonce(const String& nonce) const { |
44 return m_sourceList.allowNonce(nonce.stripWhiteSpace()); | 94 String nonceStripped = nonce.stripWhiteSpace(); |
amalika
2016/10/27 12:34:19
Changed this to strip white space inside!
| |
95 return !nonceStripped.isNull() && m_nonces.contains(nonceStripped); | |
45 } | 96 } |
46 | 97 |
47 bool SourceListDirective::allowHash(const CSPHashValue& hashValue) const { | 98 bool SourceListDirective::allowHash(const CSPHashValue& hashValue) const { |
48 return m_sourceList.allowHash(hashValue); | 99 return m_hashes.contains(hashValue); |
49 } | 100 } |
50 | 101 |
51 bool SourceListDirective::allowHashedAttributes() const { | 102 bool SourceListDirective::allowHashedAttributes() const { |
52 return m_sourceList.allowHashedAttributes(); | 103 return m_allowHashedAttributes; |
104 } | |
105 | |
106 uint8_t SourceListDirective::hashAlgorithmsUsed() const { | |
107 return m_hashAlgorithmsUsed; | |
53 } | 108 } |
54 | 109 |
55 bool SourceListDirective::isHashOrNoncePresent() const { | 110 bool SourceListDirective::isHashOrNoncePresent() const { |
56 return m_sourceList.isHashOrNoncePresent(); | 111 return !m_nonces.isEmpty() || |
57 } | 112 m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone; |
58 | 113 } |
59 uint8_t SourceListDirective::hashAlgorithmsUsed() const { | 114 |
60 return m_sourceList.hashAlgorithmsUsed(); | 115 // source-list = *WSP [ source *( 1*WSP source ) *WSP ] |
116 // / *WSP "'none'" *WSP | |
117 // | |
118 void SourceListDirective::parse(const UChar* begin, const UChar* end) { | |
119 // We represent 'none' as an empty m_list. | |
120 if (isSourceListNone(begin, end)) | |
121 return; | |
122 | |
123 const UChar* position = begin; | |
124 while (position < end) { | |
125 skipWhile<UChar, isASCIISpace>(position, end); | |
126 if (position == end) | |
127 return; | |
128 | |
129 const UChar* beginSource = position; | |
130 skipWhile<UChar, isSourceCharacter>(position, end); | |
131 | |
132 String scheme, host, path; | |
133 int port = 0; | |
134 CSPSource::WildcardDisposition hostWildcard = CSPSource::NoWildcard; | |
135 CSPSource::WildcardDisposition portWildcard = CSPSource::NoWildcard; | |
136 | |
137 if (parseSource(beginSource, position, scheme, host, port, path, | |
138 hostWildcard, portWildcard)) { | |
139 // Wildcard hosts and keyword sources ('self', 'unsafe-inline', | |
140 // etc.) aren't stored in m_list, but as attributes on the source | |
141 // list itself. | |
142 if (scheme.isEmpty() && host.isEmpty()) | |
143 continue; | |
144 if (m_policy->isDirectiveName(host)) | |
145 m_policy->reportDirectiveAsSourceExpression(m_directiveName, host); | |
146 m_list.append(new CSPSource(m_policy, scheme, host, port, path, | |
147 hostWildcard, portWildcard)); | |
148 } else { | |
149 m_policy->reportInvalidSourceExpression( | |
150 m_directiveName, String(beginSource, position - beginSource)); | |
151 } | |
152 | |
153 DCHECK(position == end || isASCIISpace(*position)); | |
154 } | |
155 } | |
156 | |
157 // source = scheme ":" | |
158 // / ( [ scheme "://" ] host [ port ] [ path ] ) | |
159 // / "'self'" | |
160 bool SourceListDirective::parseSource( | |
161 const UChar* begin, | |
162 const UChar* end, | |
163 String& scheme, | |
164 String& host, | |
165 int& port, | |
166 String& path, | |
167 CSPSource::WildcardDisposition& hostWildcard, | |
168 CSPSource::WildcardDisposition& portWildcard) { | |
169 if (begin == end) | |
170 return false; | |
171 | |
172 StringView token(begin, end - begin); | |
173 | |
174 if (equalIgnoringCase("'none'", token)) | |
175 return false; | |
176 | |
177 if (end - begin == 1 && *begin == '*') { | |
178 addSourceStar(); | |
179 return true; | |
180 } | |
181 | |
182 if (equalIgnoringCase("'self'", token)) { | |
183 addSourceSelf(); | |
184 return true; | |
185 } | |
186 | |
187 if (equalIgnoringCase("'unsafe-inline'", token)) { | |
188 addSourceUnsafeInline(); | |
189 return true; | |
190 } | |
191 | |
192 if (equalIgnoringCase("'unsafe-eval'", token)) { | |
193 addSourceUnsafeEval(); | |
194 return true; | |
195 } | |
196 | |
197 if (equalIgnoringCase("'strict-dynamic'", token)) { | |
198 addSourceStrictDynamic(); | |
199 return true; | |
200 } | |
201 | |
202 if (equalIgnoringCase("'unsafe-hashed-attributes'", token)) { | |
203 addSourceUnsafeHashedAttributes(); | |
204 return true; | |
205 } | |
206 | |
207 String nonce; | |
208 if (!parseNonce(begin, end, nonce)) | |
209 return false; | |
210 | |
211 if (!nonce.isNull()) { | |
212 addSourceNonce(nonce); | |
213 return true; | |
214 } | |
215 | |
216 DigestValue hash; | |
217 ContentSecurityPolicyHashAlgorithm algorithm = | |
218 ContentSecurityPolicyHashAlgorithmNone; | |
219 if (!parseHash(begin, end, hash, algorithm)) | |
220 return false; | |
221 | |
222 if (hash.size() > 0) { | |
223 addSourceHash(algorithm, hash); | |
224 return true; | |
225 } | |
226 | |
227 const UChar* position = begin; | |
228 const UChar* beginHost = begin; | |
229 const UChar* beginPath = end; | |
230 const UChar* beginPort = 0; | |
231 | |
232 skipWhile<UChar, isNotColonOrSlash>(position, end); | |
233 | |
234 if (position == end) { | |
235 // host | |
236 // ^ | |
237 return parseHost(beginHost, position, host, hostWildcard); | |
238 } | |
239 | |
240 if (position < end && *position == '/') { | |
241 // host/path || host/ || / | |
242 // ^ ^ ^ | |
243 return parseHost(beginHost, position, host, hostWildcard) && | |
244 parsePath(position, end, path); | |
245 } | |
246 | |
247 if (position < end && *position == ':') { | |
248 if (end - position == 1) { | |
249 // scheme: | |
250 // ^ | |
251 return parseScheme(begin, position, scheme); | |
252 } | |
253 | |
254 if (position[1] == '/') { | |
255 // scheme://host || scheme:// | |
256 // ^ ^ | |
257 if (!parseScheme(begin, position, scheme) || | |
258 !skipExactly<UChar>(position, end, ':') || | |
259 !skipExactly<UChar>(position, end, '/') || | |
260 !skipExactly<UChar>(position, end, '/')) | |
261 return false; | |
262 if (position == end) | |
263 return false; | |
264 beginHost = position; | |
265 skipWhile<UChar, isNotColonOrSlash>(position, end); | |
266 } | |
267 | |
268 if (position < end && *position == ':') { | |
269 // host:port || scheme://host:port | |
270 // ^ ^ | |
271 beginPort = position; | |
272 skipUntil<UChar>(position, end, '/'); | |
273 } | |
274 } | |
275 | |
276 if (position < end && *position == '/') { | |
277 // scheme://host/path || scheme://host:port/path | |
278 // ^ ^ | |
279 if (position == beginHost) | |
280 return false; | |
281 beginPath = position; | |
282 } | |
283 | |
284 if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, | |
285 hostWildcard)) | |
286 return false; | |
287 | |
288 if (beginPort) { | |
289 if (!parsePort(beginPort, beginPath, port, portWildcard)) | |
290 return false; | |
291 } else { | |
292 port = 0; | |
293 } | |
294 | |
295 if (beginPath != end) { | |
296 if (!parsePath(beginPath, end, path)) | |
297 return false; | |
298 } | |
299 | |
300 return true; | |
301 } | |
302 | |
303 // nonce-source = "'nonce-" nonce-value "'" | |
304 // nonce-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) | |
305 // | |
306 bool SourceListDirective::parseNonce(const UChar* begin, | |
307 const UChar* end, | |
308 String& nonce) { | |
309 size_t nonceLength = end - begin; | |
310 StringView prefix("'nonce-"); | |
311 | |
312 // TODO(esprehn): Should be StringView(begin, nonceLength).startsWith(prefix). | |
313 if (nonceLength <= prefix.length() || | |
314 !equalIgnoringCase(prefix, StringView(begin, prefix.length()))) | |
315 return true; | |
316 | |
317 const UChar* position = begin + prefix.length(); | |
318 const UChar* nonceBegin = position; | |
319 | |
320 DCHECK(position < end); | |
321 skipWhile<UChar, isNonceCharacter>(position, end); | |
322 DCHECK(nonceBegin <= position); | |
323 | |
324 if (position + 1 != end || *position != '\'' || position == nonceBegin) | |
325 return false; | |
326 | |
327 nonce = String(nonceBegin, position - nonceBegin); | |
328 return true; | |
329 } | |
330 | |
331 // hash-source = "'" hash-algorithm "-" hash-value "'" | |
332 // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512" | |
333 // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) | |
334 // | |
335 bool SourceListDirective::parseHash( | |
336 const UChar* begin, | |
337 const UChar* end, | |
338 DigestValue& hash, | |
339 ContentSecurityPolicyHashAlgorithm& hashAlgorithm) { | |
340 // Any additions or subtractions from this struct should also modify the | |
341 // respective entries in the kAlgorithmMap array in checkDigest(). | |
342 static const struct { | |
343 const char* prefix; | |
344 ContentSecurityPolicyHashAlgorithm type; | |
345 } kSupportedPrefixes[] = { | |
346 // FIXME: Drop support for SHA-1. It's not in the spec. | |
347 {"'sha1-", ContentSecurityPolicyHashAlgorithmSha1}, | |
348 {"'sha256-", ContentSecurityPolicyHashAlgorithmSha256}, | |
349 {"'sha384-", ContentSecurityPolicyHashAlgorithmSha384}, | |
350 {"'sha512-", ContentSecurityPolicyHashAlgorithmSha512}, | |
351 {"'sha-256-", ContentSecurityPolicyHashAlgorithmSha256}, | |
352 {"'sha-384-", ContentSecurityPolicyHashAlgorithmSha384}, | |
353 {"'sha-512-", ContentSecurityPolicyHashAlgorithmSha512}}; | |
354 | |
355 StringView prefix; | |
356 hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; | |
357 size_t hashLength = end - begin; | |
358 | |
359 for (const auto& algorithm : kSupportedPrefixes) { | |
360 prefix = algorithm.prefix; | |
361 // TODO(esprehn): Should be StringView(begin, end - | |
362 // begin).startsWith(prefix). | |
363 if (hashLength > prefix.length() && | |
364 equalIgnoringCase(prefix, StringView(begin, prefix.length()))) { | |
365 hashAlgorithm = algorithm.type; | |
366 break; | |
367 } | |
368 } | |
369 | |
370 if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) | |
371 return true; | |
372 | |
373 const UChar* position = begin + prefix.length(); | |
374 const UChar* hashBegin = position; | |
375 | |
376 DCHECK(position < end); | |
377 skipWhile<UChar, isBase64EncodedCharacter>(position, end); | |
378 DCHECK(hashBegin <= position); | |
379 | |
380 // Base64 encodings may end with exactly one or two '=' characters | |
381 if (position < end) | |
382 skipExactly<UChar>(position, position + 1, '='); | |
383 if (position < end) | |
384 skipExactly<UChar>(position, position + 1, '='); | |
385 | |
386 if (position + 1 != end || *position != '\'' || position == hashBegin) | |
387 return false; | |
388 | |
389 Vector<char> hashVector; | |
390 // We accept base64url-encoded data here by normalizing it to base64. | |
391 base64Decode(normalizeToBase64(String(hashBegin, position - hashBegin)), | |
392 hashVector); | |
393 if (hashVector.size() > kMaxDigestSize) | |
394 return false; | |
395 hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); | |
396 return true; | |
397 } | |
398 | |
399 // ; <scheme> production from RFC 3986 | |
400 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) | |
401 // | |
402 bool SourceListDirective::parseScheme(const UChar* begin, | |
403 const UChar* end, | |
404 String& scheme) { | |
405 DCHECK(begin <= end); | |
406 DCHECK(scheme.isEmpty()); | |
407 | |
408 if (begin == end) | |
409 return false; | |
410 | |
411 const UChar* position = begin; | |
412 | |
413 if (!skipExactly<UChar, isASCIIAlpha>(position, end)) | |
414 return false; | |
415 | |
416 skipWhile<UChar, isSchemeContinuationCharacter>(position, end); | |
417 | |
418 if (position != end) | |
419 return false; | |
420 | |
421 scheme = String(begin, end - begin); | |
422 return true; | |
423 } | |
424 | |
425 // host = [ "*." ] 1*host-char *( "." 1*host-char ) | |
426 // / "*" | |
427 // host-char = ALPHA / DIGIT / "-" | |
428 // | |
429 bool SourceListDirective::parseHost( | |
430 const UChar* begin, | |
431 const UChar* end, | |
432 String& host, | |
433 CSPSource::WildcardDisposition& hostWildcard) { | |
434 DCHECK(begin <= end); | |
435 DCHECK(host.isEmpty()); | |
436 DCHECK(hostWildcard == CSPSource::NoWildcard); | |
437 | |
438 if (begin == end) | |
439 return false; | |
440 | |
441 const UChar* position = begin; | |
442 | |
443 if (skipExactly<UChar>(position, end, '*')) { | |
444 hostWildcard = CSPSource::HasWildcard; | |
445 | |
446 if (position == end) | |
447 return true; | |
448 | |
449 if (!skipExactly<UChar>(position, end, '.')) | |
450 return false; | |
451 } | |
452 | |
453 const UChar* hostBegin = position; | |
454 | |
455 while (position < end) { | |
456 if (!skipExactly<UChar, isHostCharacter>(position, end)) | |
457 return false; | |
458 | |
459 skipWhile<UChar, isHostCharacter>(position, end); | |
460 | |
461 if (position < end && !skipExactly<UChar>(position, end, '.')) | |
462 return false; | |
463 } | |
464 | |
465 DCHECK(position == end); | |
466 host = String(hostBegin, end - hostBegin); | |
467 return true; | |
468 } | |
469 | |
470 bool SourceListDirective::parsePath(const UChar* begin, | |
471 const UChar* end, | |
472 String& path) { | |
473 DCHECK(begin <= end); | |
474 DCHECK(path.isEmpty()); | |
475 | |
476 const UChar* position = begin; | |
477 skipWhile<UChar, isPathComponentCharacter>(position, end); | |
478 // path/to/file.js?query=string || path/to/file.js#anchor | |
479 // ^ ^ | |
480 if (position < end) { | |
481 m_policy->reportInvalidPathCharacter(m_directiveName, | |
482 String(begin, end - begin), *position); | |
483 } | |
484 | |
485 path = decodeURLEscapeSequences(String(begin, position - begin)); | |
486 | |
487 DCHECK(position <= end); | |
488 DCHECK(position == end || (*position == '#' || *position == '?')); | |
489 return true; | |
490 } | |
491 | |
492 // port = ":" ( 1*DIGIT / "*" ) | |
493 // | |
494 bool SourceListDirective::parsePort( | |
495 const UChar* begin, | |
496 const UChar* end, | |
497 int& port, | |
498 CSPSource::WildcardDisposition& portWildcard) { | |
499 DCHECK(begin <= end); | |
500 DCHECK(!port); | |
501 DCHECK(portWildcard == CSPSource::NoWildcard); | |
502 | |
503 if (!skipExactly<UChar>(begin, end, ':')) | |
504 NOTREACHED(); | |
505 | |
506 if (begin == end) | |
507 return false; | |
508 | |
509 if (end - begin == 1 && *begin == '*') { | |
510 port = 0; | |
511 portWildcard = CSPSource::HasWildcard; | |
512 return true; | |
513 } | |
514 | |
515 const UChar* position = begin; | |
516 skipWhile<UChar, isASCIIDigit>(position, end); | |
517 | |
518 if (position != end) | |
519 return false; | |
520 | |
521 bool ok; | |
522 port = charactersToIntStrict(begin, end - begin, &ok); | |
523 return ok; | |
524 } | |
525 | |
526 void SourceListDirective::addSourceSelf() { | |
527 m_allowSelf = true; | |
528 } | |
529 | |
530 void SourceListDirective::addSourceStar() { | |
531 m_allowStar = true; | |
532 } | |
533 | |
534 void SourceListDirective::addSourceUnsafeInline() { | |
535 m_allowInline = true; | |
536 } | |
537 | |
538 void SourceListDirective::addSourceUnsafeEval() { | |
539 m_allowEval = true; | |
540 } | |
541 | |
542 void SourceListDirective::addSourceStrictDynamic() { | |
543 m_allowDynamic = true; | |
544 } | |
545 | |
546 void SourceListDirective::addSourceUnsafeHashedAttributes() { | |
547 m_allowHashedAttributes = true; | |
548 } | |
549 | |
550 void SourceListDirective::addSourceNonce(const String& nonce) { | |
551 m_nonces.add(nonce); | |
552 } | |
553 | |
554 void SourceListDirective::addSourceHash( | |
555 const ContentSecurityPolicyHashAlgorithm& algorithm, | |
556 const DigestValue& hash) { | |
557 m_hashes.add(CSPHashValue(algorithm, hash)); | |
558 m_hashAlgorithmsUsed |= algorithm; | |
559 } | |
560 | |
561 bool SourceListDirective::hasSourceMatchInList( | |
562 const KURL& url, | |
563 ResourceRequest::RedirectStatus redirectStatus) const { | |
564 for (size_t i = 0; i < m_list.size(); ++i) { | |
565 if (m_list[i]->matches(url, redirectStatus)) | |
566 return true; | |
567 } | |
568 | |
569 return false; | |
61 } | 570 } |
62 | 571 |
63 DEFINE_TRACE(SourceListDirective) { | 572 DEFINE_TRACE(SourceListDirective) { |
64 visitor->trace(m_sourceList); | 573 visitor->trace(m_policy); |
574 visitor->trace(m_list); | |
65 CSPDirective::trace(visitor); | 575 CSPDirective::trace(visitor); |
66 } | 576 } |
67 | 577 |
68 } // namespace blink | 578 } // namespace blink |
OLD | NEW |