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