| Index: Source/core/frame/ContentSecurityPolicy.cpp
|
| diff --git a/Source/core/frame/ContentSecurityPolicy.cpp b/Source/core/frame/ContentSecurityPolicy.cpp
|
| index 4dcac4cec748fd0c3a6de2df577622995e90aae5..bc17809cb5c8f34a6f3910a7a91048c51454aba8 100644
|
| --- a/Source/core/frame/ContentSecurityPolicy.cpp
|
| +++ b/Source/core/frame/ContentSecurityPolicy.cpp
|
| @@ -42,6 +42,7 @@
|
| #include "core/loader/PingLoader.h"
|
| #include "core/page/UseCounter.h"
|
| #include "platform/JSONValues.h"
|
| +#include "platform/NotImplemented.h"
|
| #include "platform/ParsingUtilities.h"
|
| #include "platform/network/FormData.h"
|
| #include "platform/network/ResourceResponse.h"
|
| @@ -50,11 +51,30 @@
|
| #include "weborigin/SchemeRegistry.h"
|
| #include "weborigin/SecurityOrigin.h"
|
| #include "wtf/HashSet.h"
|
| +#include "wtf/SHA1.h"
|
| +#include "wtf/StringHasher.h"
|
| +#include "wtf/text/Base64.h"
|
| +#include "wtf/text/StringBuilder.h"
|
| #include "wtf/text/TextPosition.h"
|
| #include "wtf/text/WTFString.h"
|
|
|
| +namespace WTF {
|
| +
|
| +struct VectorIntHash {
|
| + static unsigned hash(const Vector<uint8_t>& v) { return StringHasher::computeHash(v.data(), v.size()); }
|
| + static bool equal(const Vector<uint8_t>& a, const Vector<uint8_t>& b) { return a == b; };
|
| + static const bool safeToCompareToEmptyOrDeleted = true;
|
| +};
|
| +template<> struct DefaultHash<Vector<uint8_t> > {
|
| + typedef VectorIntHash Hash;
|
| +};
|
| +
|
| +} // namespace WTF
|
| +
|
| namespace WebCore {
|
|
|
| +typedef std::pair<unsigned, Vector<uint8_t> > SourceHashValue;
|
| +
|
| // Normally WebKit uses "static" for internal linkage, but using "static" for
|
| // these functions causes a compile error because these functions are used as
|
| // template parameters.
|
| @@ -70,7 +90,9 @@ bool isDirectiveValueCharacter(UChar c)
|
| return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR
|
| }
|
|
|
| -bool isNonceCharacter(UChar c)
|
| +// Only checks for general Base64 encoded chars, not '=' chars since '=' is
|
| +// positional and may only appear at the end of a Base64 encoded string.
|
| +bool isBase64EncodedCharacter(UChar c)
|
| {
|
| return isASCIIAlphanumeric(c) || c == '+' || c == '/';
|
| }
|
| @@ -276,6 +298,8 @@ public:
|
| bool allowInline() const { return m_allowInline; }
|
| bool allowEval() const { return m_allowEval; }
|
| bool allowNonce(const String& nonce) const { return !nonce.isNull() && m_nonces.contains(nonce); }
|
| + bool allowHash(const SourceHashValue& hashValue) const { return m_hashes.contains(hashValue); }
|
| + uint8_t hashAlgorithmsUsed() const { return m_hashAlgorithmsUsed; }
|
|
|
| private:
|
| bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard);
|
| @@ -284,12 +308,14 @@ private:
|
| bool parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard);
|
| bool parsePath(const UChar* begin, const UChar* end, String& path);
|
| bool parseNonce(const UChar* begin, const UChar* end, String& nonce);
|
| + bool parseHash(const UChar* begin, const UChar* end, Vector<uint8_t>& hash, ContentSecurityPolicy::HashAlgorithms&);
|
|
|
| void addSourceSelf();
|
| void addSourceStar();
|
| void addSourceUnsafeInline();
|
| void addSourceUnsafeEval();
|
| void addSourceNonce(const String& nonce);
|
| + void addSourceHash(const ContentSecurityPolicy::HashAlgorithms&, const Vector<uint8_t>& hash);
|
|
|
| ContentSecurityPolicy* m_policy;
|
| Vector<CSPSource> m_list;
|
| @@ -298,6 +324,8 @@ private:
|
| bool m_allowInline;
|
| bool m_allowEval;
|
| HashSet<String> m_nonces;
|
| + HashSet<SourceHashValue> m_hashes;
|
| + uint8_t m_hashAlgorithmsUsed;
|
| };
|
|
|
| CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
|
| @@ -306,6 +334,7 @@ CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& direct
|
| , m_allowStar(false)
|
| , m_allowInline(false)
|
| , m_allowEval(false)
|
| + , m_hashAlgorithmsUsed(0)
|
| {
|
| }
|
|
|
| @@ -404,6 +433,16 @@ bool CSPSourceList::parseSource(const UChar* begin, const UChar* end, String& sc
|
| addSourceNonce(nonce);
|
| return true;
|
| }
|
| +
|
| + Vector<uint8_t> hash;
|
| + ContentSecurityPolicy::HashAlgorithms algorithm = ContentSecurityPolicy::HashAlgorithmsNone;
|
| + if (!parseHash(begin, end, hash, algorithm))
|
| + return false;
|
| +
|
| + if (hash.size() > 0) {
|
| + addSourceHash(algorithm, hash);
|
| + return true;
|
| + }
|
| }
|
|
|
| const UChar* position = begin;
|
| @@ -493,16 +532,54 @@ bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& non
|
| const UChar* position = begin + noncePrefix.length();
|
| const UChar* nonceBegin = position;
|
|
|
| - skipWhile<UChar, isNonceCharacter>(position, end);
|
| + skipWhile<UChar, isBase64EncodedCharacter>(position, end);
|
| ASSERT(nonceBegin <= position);
|
|
|
| - if (((position + 1) != end && *position != '\'') || !(position - nonceBegin))
|
| + if ((position + 1) != end || *position != '\'' || !(position - nonceBegin))
|
| return false;
|
|
|
| nonce = String(nonceBegin, position - nonceBegin);
|
| return true;
|
| }
|
|
|
| +// hash-source = "'" hash-algorithm "-" hash-value "'"
|
| +// hash-algorithm = "sha1" / "sha256"
|
| +// hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
|
| +//
|
| +bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, Vector<uint8_t>& hash, ContentSecurityPolicy::HashAlgorithms& hashAlgorithm)
|
| +{
|
| + DEFINE_STATIC_LOCAL(const String, sha1Prefix, ("'sha1-"));
|
| + DEFINE_STATIC_LOCAL(const String, sha256Prefix, ("'sha256-"));
|
| +
|
| + String prefix;
|
| + if (equalIgnoringCase(sha1Prefix.characters8(), begin, sha1Prefix.length())) {
|
| + prefix = sha1Prefix;
|
| + hashAlgorithm = ContentSecurityPolicy::HashAlgorithmsSha1;
|
| + } else if (equalIgnoringCase(sha256Prefix.characters8(), begin, sha256Prefix.length())) {
|
| + notImplemented();
|
| + } else {
|
| + return true;
|
| + }
|
| +
|
| + const UChar* position = begin + prefix.length();
|
| + const UChar* hashBegin = position;
|
| +
|
| + skipWhile<UChar, isBase64EncodedCharacter>(position, end);
|
| + ASSERT(hashBegin <= position);
|
| +
|
| + // Base64 encodings may end with exactly one or two '=' characters
|
| + skipExactly<UChar>(position, position + 1, '=');
|
| + skipExactly<UChar>(position, position + 1, '=');
|
| +
|
| + if ((position + 1) != end || *position != '\'' || !(position - hashBegin))
|
| + return false;
|
| +
|
| + Vector<char> hashVector;
|
| + base64Decode(hashBegin, position - hashBegin, hashVector);
|
| + hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size());
|
| + return true;
|
| +}
|
| +
|
| // ; <scheme> production from RFC 3986
|
| // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
| //
|
| @@ -645,6 +722,12 @@ void CSPSourceList::addSourceNonce(const String& nonce)
|
| m_nonces.add(nonce);
|
| }
|
|
|
| +void CSPSourceList::addSourceHash(const ContentSecurityPolicy::HashAlgorithms& algorithm, const Vector<uint8_t>& hash)
|
| +{
|
| + m_hashes.add(SourceHashValue(algorithm, hash));
|
| + m_hashAlgorithmsUsed |= algorithm;
|
| +}
|
| +
|
| class CSPDirective {
|
| public:
|
| CSPDirective(const String& name, const String& value, ContentSecurityPolicy* policy)
|
| @@ -761,6 +844,9 @@ public:
|
| bool allowInline() const { return m_sourceList.allowInline(); }
|
| bool allowEval() const { return m_sourceList.allowEval(); }
|
| bool allowNonce(const String& nonce) const { return m_sourceList.allowNonce(nonce.stripWhiteSpace()); }
|
| + bool allowHash(const SourceHashValue& hashValue) const { return m_sourceList.allowHash(hashValue); }
|
| +
|
| + uint8_t hashAlgorithmsUsed() const { return m_sourceList.hashAlgorithmsUsed(); }
|
|
|
| private:
|
| CSPSourceList m_sourceList;
|
| @@ -795,6 +881,7 @@ public:
|
| bool allowBaseURI(const KURL&, ContentSecurityPolicy::ReportingStatus) const;
|
| bool allowScriptNonce(const String&) const;
|
| bool allowStyleNonce(const String&) const;
|
| + bool allowScriptHash(const SourceHashValue&) const;
|
|
|
| void gatherReportURIs(DOMStringList&) const;
|
| const String& evalDisabledErrorMessage() const { return m_evalDisabledErrorMessage; }
|
| @@ -823,6 +910,7 @@ private:
|
| bool checkEval(SourceListDirective*) const;
|
| bool checkInline(SourceListDirective*) const;
|
| bool checkNonce(SourceListDirective*, const String&) const;
|
| + bool checkHash(SourceListDirective*, const SourceHashValue&) const;
|
| bool checkSource(SourceListDirective*, const KURL&) const;
|
| bool checkMediaType(MediaListDirective*, const String& type, const String& typeAttribute) const;
|
|
|
| @@ -925,6 +1013,11 @@ bool CSPDirectiveList::checkNonce(SourceListDirective* directive, const String&
|
| return !directive || directive->allowNonce(nonce);
|
| }
|
|
|
| +bool CSPDirectiveList::checkHash(SourceListDirective* directive, const SourceHashValue& hashValue) const
|
| +{
|
| + return !directive || directive->allowHash(hashValue);
|
| +}
|
| +
|
| bool CSPDirectiveList::checkSource(SourceListDirective* directive, const KURL& url) const
|
| {
|
| return !directive || directive->allows(url);
|
| @@ -1167,6 +1260,11 @@ bool CSPDirectiveList::allowStyleNonce(const String& nonce) const
|
| return checkNonce(operativeDirective(m_styleSrc.get()), nonce);
|
| }
|
|
|
| +bool CSPDirectiveList::allowScriptHash(const SourceHashValue& hashValue) const
|
| +{
|
| + return checkHash(operativeDirective(m_scriptSrc.get()), hashValue);
|
| +}
|
| +
|
| // policy = directive-list
|
| // directive-list = [ directive *( ";" [ directive ] ) ]
|
| //
|
| @@ -1354,6 +1452,7 @@ void CSPDirectiveList::addDirective(const String& name, const String& value)
|
| setCSPDirective<SourceListDirective>(name, value, m_defaultSrc);
|
| } else if (equalIgnoringCase(name, scriptSrc)) {
|
| setCSPDirective<SourceListDirective>(name, value, m_scriptSrc);
|
| + m_policy->usesScriptHashAlgorithms(m_scriptSrc->hashAlgorithmsUsed());
|
| } else if (equalIgnoringCase(name, objectSrc)) {
|
| setCSPDirective<SourceListDirective>(name, value, m_objectSrc);
|
| } else if (equalIgnoringCase(name, frameSrc)) {
|
| @@ -1391,6 +1490,7 @@ void CSPDirectiveList::addDirective(const String& name, const String& value)
|
| ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContextClient* client)
|
| : m_client(client)
|
| , m_overrideInlineStyleAllowed(false)
|
| + , m_sourceHashAlgorithmsUsed(HashAlgorithmsNone)
|
| {
|
| }
|
|
|
| @@ -1518,6 +1618,17 @@ bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const Strin
|
| }
|
| return true;
|
| }
|
| +
|
| +template<bool (CSPDirectiveList::*allowed)(const SourceHashValue&) const>
|
| +bool isAllowedByAllWithHash(const CSPDirectiveListVector& policies, const SourceHashValue& hashValue)
|
| +{
|
| + for (size_t i = 0; i < policies.size(); ++i) {
|
| + if (!(policies[i].get()->*allowed)(hashValue))
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| template<bool (CSPDirectiveList::*allowFromURL)(const KURL&, ContentSecurityPolicy::ReportingStatus) const>
|
| bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus)
|
| {
|
| @@ -1591,6 +1702,28 @@ bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const
|
| return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce);
|
| }
|
|
|
| +bool ContentSecurityPolicy::allowScriptHash(const String& source) const
|
| +{
|
| + // TODO(jww) We don't currently have a WTF SHA256 implementation. Once we
|
| + // have that, we should implement a proper check for sha256 hash values here.
|
| + if (HashAlgorithmsSha1 & m_sourceHashAlgorithmsUsed) {
|
| + Vector<uint8_t, 20> digest;
|
| + SHA1 sourceSha1;
|
| + sourceSha1.addBytes(UTF8Encoding().normalizeAndEncode(source, WTF::EntitiesForUnencodables));
|
| + sourceSha1.computeHash(digest);
|
| +
|
| + if (isAllowedByAllWithHash<&CSPDirectiveList::allowScriptHash>(m_policies, SourceHashValue(HashAlgorithmsSha1, Vector<uint8_t>(digest))))
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +void ContentSecurityPolicy::usesScriptHashAlgorithms(uint8_t algorithms)
|
| +{
|
| + m_sourceHashAlgorithmsUsed |= algorithms;
|
| +}
|
| +
|
| bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
|
| {
|
| return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus);
|
| @@ -1846,12 +1979,6 @@ void ContentSecurityPolicy::reportInvalidPathCharacter(const String& directiveNa
|
| logToConsole(message);
|
| }
|
|
|
| -void ContentSecurityPolicy::reportInvalidNonce(const String& nonce) const
|
| -{
|
| - String message = "Ignoring invalid Content Security Policy script nonce: '" + nonce + "'.\n";
|
| - logToConsole(message);
|
| -}
|
| -
|
| void ContentSecurityPolicy::reportInvalidSourceExpression(const String& directiveName, const String& source) const
|
| {
|
| String message = "The source list for Content Security Policy directive '" + directiveName + "' contains an invalid source: '" + source + "'. It will be ignored.";
|
|
|