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 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