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."; |