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/csp/CSPSourceList.h" 7 8 #include "core/frame/csp/CSPSource.h" 9 #include "core/frame/csp/ContentSecurityPolicy.h" 10 #include "platform/ParsingUtilities.h" 11 #include "platform/weborigin/KURL.h" 12 #include "platform/weborigin/SecurityOrigin.h" 13 #include "wtf/HashSet.h" 14 #include "wtf/text/Base64.h" 15 #include "wtf/text/WTFString.h" 16 17 namespace blink { 18 19 static bool isSourceListNone(const UChar* begin, const UChar* end) 20 { 21 skipWhile<UChar, isASCIISpace>(begin, end); 22 23 const UChar* position = begin; 24 skipWhile<UChar, isSourceCharacter>(position, end); 25 if (!equalIgnoringCase("'none'", begin, position - begin)) 26 return false; 27 28 skipWhile<UChar, isASCIISpace>(position, end); 29 if (position != end) 30 return false; 31 32 return true; 33 } 34 35 CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName) 36 : m_policy(policy) 37 , m_directiveName(directiveName) 38 , m_allowSelf(false) 39 , m_allowStar(false) 40 , m_allowInline(false) 41 , m_allowEval(false) 42 , m_hashAlgorithmsUsed(0) 43 { 44 } 45 46 bool CSPSourceList::matches(const KURL& url) const 47 { 48 if (m_allowStar) 49 return true; 50 51 KURL effectiveURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url; 52 53 if (m_allowSelf && m_policy->urlMatchesSelf(effectiveURL)) 54 return true; 55 56 for (size_t i = 0; i < m_list.size(); ++i) { 57 if (m_list[i].matches(effectiveURL)) 58 return true; 59 } 60 61 return false; 62 } 63 64 bool CSPSourceList::allowInline() const 65 { 66 return m_allowInline; 67 } 68 69 bool CSPSourceList::allowEval() const 70 { 71 return m_allowEval; 72 } 73 74 bool CSPSourceList::allowNonce(const String& nonce) const 75 { 76 return !nonce.isNull() && m_nonces.contains(nonce); 77 } 78 79 bool CSPSourceList::allowHash(const CSPHashValue& hashValue) const 80 { 81 return m_hashes.contains(hashValue); 82 } 83 84 uint8_t CSPSourceList::hashAlgorithmsUsed() const 85 { 86 return m_hashAlgorithmsUsed; 87 } 88 89 bool CSPSourceList::isHashOrNoncePresent() const 90 { 91 return !m_nonces.isEmpty() || m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone; 92 } 93 94 // source-list = *WSP [ source *( 1*WSP source ) *WSP ] 95 // / *WSP "'none'" *WSP 96 // 97 void CSPSourceList::parse(const UChar* begin, const UChar* end) 98 { 99 // We represent 'none' as an empty m_list. 100 if (isSourceListNone(begin, end)) 101 return; 102 103 const UChar* position = begin; 104 while (position < end) { 105 skipWhile<UChar, isASCIISpace>(position, end); 106 if (position == end) 107 return; 108 109 const UChar* beginSource = position; 110 skipWhile<UChar, isSourceCharacter>(position, end); 111 112 String scheme, host, path; 113 int port = 0; 114 CSPSource::WildcardDisposition hostWildcard = CSPSource::NoWildcard; 115 CSPSource::WildcardDisposition portWildcard = CSPSource::NoWildcard; 116 117 if (parseSource(beginSource, position, scheme, host, port, path, hostWildcard, portWildcard)) { 118 // Wildcard hosts and keyword sources ('self', 'unsafe-inline', 119 // etc.) aren't stored in m_list, but as attributes on the source 120 // list itself. 121 if (scheme.isEmpty() && host.isEmpty()) 122 continue; 123 if (m_policy->isDirectiveName(host)) 124 m_policy->reportDirectiveAsSourceExpression(m_directiveName, host); 125 m_list.append(CSPSource(m_policy, scheme, host, port, path, hostWildcard, portWildcard)); 126 } else { 127 m_policy->reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource)); 128 } 129 130 ASSERT(position == end || isASCIISpace(*position)); 131 } 132 } 133 134 // source = scheme ":" 135 // / ( [ scheme "://" ] host [ port ] [ path ] ) 136 // / "'self'" 137 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, CSPSource::WildcardDisposition& hostWildcard, CSPSource::WildcardDisposition& portWildcard) 138 { 139 if (begin == end) 140 return false; 141 142 if (equalIgnoringCase("'none'", begin, end - begin)) 143 return false; 144 145 if (end - begin == 1 && *begin == '*') { 146 addSourceStar(); 147 return true; 148 } 149 150 if (equalIgnoringCase("'self'", begin, end - begin)) { 151 addSourceSelf(); 152 return true; 153 } 154 155 if (equalIgnoringCase("'unsafe-inline'", begin, end - begin)) { 156 addSourceUnsafeInline(); 157 return true; 158 } 159 160 if (equalIgnoringCase("'unsafe-eval'", begin, end - begin)) { 161 addSourceUnsafeEval(); 162 return true; 163 } 164 165 String nonce; 166 if (!parseNonce(begin, end, nonce)) 167 return false; 168 169 if (!nonce.isNull()) { 170 addSourceNonce(nonce); 171 return true; 172 } 173 174 DigestValue hash; 175 ContentSecurityPolicyHashAlgorithm algorithm = ContentSecurityPolicyHashAlgorithmNone; 176 if (!parseHash(begin, end, hash, algorithm)) 177 return false; 178 179 if (hash.size() > 0) { 180 addSourceHash(algorithm, hash); 181 return true; 182 } 183 184 const UChar* position = begin; 185 const UChar* beginHost = begin; 186 const UChar* beginPath = end; 187 const UChar* beginPort = 0; 188 189 skipWhile<UChar, isNotColonOrSlash>(position, end); 190 191 if (position == end) { 192 // host 193 // ^ 194 return parseHost(beginHost, position, host, hostWildcard); 195 } 196 197 if (position < end && *position == '/') { 198 // host/path || host/ || / 199 // ^ ^ ^ 200 return parseHost(beginHost, position, host, hostWildcard) && parsePath(position, end, path); 201 } 202 203 if (position < end && *position == ':') { 204 if (end - position == 1) { 205 // scheme: 206 // ^ 207 return parseScheme(begin, position, scheme); 208 } 209 210 if (position[1] == '/') { 211 // scheme://host || scheme:// 212 // ^ ^ 213 if (!parseScheme(begin, position, scheme) 214 || !skipExactly<UChar>(position, end, ':') 215 || !skipExactly<UChar>(position, end, '/') 216 || !skipExactly<UChar>(position, end, '/')) 217 return false; 218 if (position == end) 219 return false; 220 beginHost = position; 221 skipWhile<UChar, isNotColonOrSlash>(position, end); 222 } 223 224 if (position < end && *position == ':') { 225 // host:port || scheme://host:port 226 // ^ ^ 227 beginPort = position; 228 skipUntil<UChar>(position, end, '/'); 229 } 230 } 231 232 if (position < end && *position == '/') { 233 // scheme://host/path || scheme://host:port/path 234 // ^ ^ 235 if (position == beginHost) 236 return false; 237 beginPath = position; 238 } 239 240 if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, hostWildcard)) 241 return false; 242 243 if (beginPort) { 244 if (!parsePort(beginPort, beginPath, port, portWildcard)) 245 return false; 246 } else { 247 port = 0; 248 } 249 250 if (beginPath != end) { 251 if (!parsePath(beginPath, end, path)) 252 return false; 253 } 254 255 return true; 256 } 257 258 // nonce-source = "'nonce-" nonce-value "'" 259 // nonce-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) 260 // 261 bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& nonce) 262 { 263 DEFINE_STATIC_LOCAL(const String, noncePrefix, ("'nonce-")); 264 265 if (!equalIgnoringCase(noncePrefix.characters8(), begin, noncePrefix.length())) 266 return true; 267 268 const UChar* position = begin + noncePrefix.length(); 269 const UChar* nonceBegin = position; 270 271 skipWhile<UChar, isNonceCharacter>(position, end); 272 ASSERT(nonceBegin <= position); 273 274 if ((position + 1) != end || *position != '\'' || !(position - nonceBegin)) 275 return false; 276 277 nonce = String(nonceBegin, position - nonceBegin); 278 return true; 279 } 280 281 // hash-source = "'" hash-algorithm "-" hash-value "'" 282 // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512" 283 // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) 284 // 285 bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm) 286 { 287 // Any additions or subtractions from this struct should also modify the 288 // respective entries in the kAlgorithmMap array in checkDigest(). 289 static const struct { 290 const char* prefix; 291 ContentSecurityPolicyHashAlgorithm algorithm; 292 } kSupportedPrefixes[] = { 293 { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 }, 294 { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 }, 295 { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 }, 296 { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 } 297 }; 298 299 String prefix; 300 hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; 301 302 // Instead of this sizeof() calculation to get the length of this array, 303 // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to 304 // guarantee a compile time calculation. Unfortunately, on some 305 // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous 306 // stucts, so, for now, it is necessary to resort to this sizeof 307 // calculation. 308 for (size_t i = 0; i < (sizeof(kSupportedPrefixes) / sizeof(kSupportedPrefixes[0])); i++) { 309 if (equalIgnoringCase(kSupportedPrefixes[i].prefix, begin, strlen(kSupportedPrefixes[i].prefix))) { 310 prefix = kSupportedPrefixes[i].prefix; 311 hashAlgorithm = kSupportedPrefixes[i].algorithm; 312 break; 313 } 314 } 315 316 if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) 317 return true; 318 319 const UChar* position = begin + prefix.length(); 320 const UChar* hashBegin = position; 321 322 skipWhile<UChar, isBase64EncodedCharacter>(position, end); 323 ASSERT(hashBegin <= position); 324 325 // Base64 encodings may end with exactly one or two '=' characters 326 skipExactly<UChar>(position, position + 1, '='); 327 skipExactly<UChar>(position, position + 1, '='); 328 329 if ((position + 1) != end || *position != '\'' || !(position - hashBegin)) 330 return false; 331 332 Vector<char> hashVector; 333 base64Decode(hashBegin, position - hashBegin, hashVector); 334 if (hashVector.size() > kMaxDigestSize) 335 return false; 336 hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); 337 return true; 338 } 339 340 // ; <scheme> production from RFC 3986 341 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 342 // 343 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme) 344 { 345 ASSERT(begin <= end); 346 ASSERT(scheme.isEmpty()); 347 348 if (begin == end) 349 return false; 350 351 const UChar* position = begin; 352 353 if (!skipExactly<UChar, isASCIIAlpha>(position, end)) 354 return false; 355 356 skipWhile<UChar, isSchemeContinuationCharacter>(position, end); 357 358 if (position != end) 359 return false; 360 361 scheme = String(begin, end - begin); 362 return true; 363 } 364 365 // host = [ "*." ] 1*host-char *( "." 1*host-char ) 366 // / "*" 367 // host-char = ALPHA / DIGIT / "-" 368 // 369 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, CSPSource::WildcardDisposition& hostWildcard) 370 { 371 ASSERT(begin <= end); 372 ASSERT(host.isEmpty()); 373 ASSERT(hostWildcard == CSPSource::NoWildcard); 374 375 if (begin == end) 376 return false; 377 378 const UChar* position = begin; 379 380 if (skipExactly<UChar>(position, end, '*')) { 381 hostWildcard = CSPSource::HasWildcard; 382 383 if (position == end) 384 return true; 385 386 if (!skipExactly<UChar>(position, end, '.')) 387 return false; 388 } 389 390 const UChar* hostBegin = position; 391 392 while (position < end) { 393 if (!skipExactly<UChar, isHostCharacter>(position, end)) 394 return false; 395 396 skipWhile<UChar, isHostCharacter>(position, end); 397 398 if (position < end && !skipExactly<UChar>(position, end, '.')) 399 return false; 400 } 401 402 ASSERT(position == end); 403 host = String(hostBegin, end - hostBegin); 404 return true; 405 } 406 407 bool CSPSourceList::parsePath(const UChar* begin, const UChar* end, String& path) 408 { 409 ASSERT(begin <= end); 410 ASSERT(path.isEmpty()); 411 412 const UChar* position = begin; 413 skipWhile<UChar, isPathComponentCharacter>(position, end); 414 // path/to/file.js?query=string || path/to/file.js#anchor 415 // ^ ^ 416 if (position < end) 417 m_policy->reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position); 418 419 path = decodeURLEscapeSequences(String(begin, position - begin)); 420 421 ASSERT(position <= end); 422 ASSERT(position == end || (*position == '#' || *position == '?')); 423 return true; 424 } 425 426 // port = ":" ( 1*DIGIT / "*" ) 427 // 428 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, CSPSource::WildcardDisposition& portWildcard) 429 { 430 ASSERT(begin <= end); 431 ASSERT(!port); 432 ASSERT(portWildcard == CSPSource::NoWildcard); 433 434 if (!skipExactly<UChar>(begin, end, ':')) 435 ASSERT_NOT_REACHED(); 436 437 if (begin == end) 438 return false; 439 440 if (end - begin == 1 && *begin == '*') { 441 port = 0; 442 portWildcard = CSPSource::HasWildcard; 443 return true; 444 } 445 446 const UChar* position = begin; 447 skipWhile<UChar, isASCIIDigit>(position, end); 448 449 if (position != end) 450 return false; 451 452 bool ok; 453 port = charactersToIntStrict(begin, end - begin, &ok); 454 return ok; 455 } 456 457 void CSPSourceList::addSourceSelf() 458 { 459 m_allowSelf = true; 460 } 461 462 void CSPSourceList::addSourceStar() 463 { 464 m_allowStar = true; 465 } 466 467 void CSPSourceList::addSourceUnsafeInline() 468 { 469 m_allowInline = true; 470 } 471 472 void CSPSourceList::addSourceUnsafeEval() 473 { 474 m_allowEval = true; 475 } 476 477 void CSPSourceList::addSourceNonce(const String& nonce) 478 { 479 m_nonces.add(nonce); 480 } 481 482 void CSPSourceList::addSourceHash(const ContentSecurityPolicyHashAlgorithm& algorithm, const DigestValue& hash) 483 { 484 m_hashes.add(CSPHashValue(algorithm, hash)); 485 m_hashAlgorithmsUsed |= algorithm; 486 } 487 488 489 } // namespace blink 490