Home | History | Annotate | Download | only in csp
      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