Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1146)

Unified Diff: Source/core/frame/ContentSecurityPolicy.cpp

Issue 26481005: Implementation of script hashes for CSP. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Rebase on tip of tree Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/core/frame/ContentSecurityPolicy.h ('k') | Source/wtf/text/Base64.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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.";
« no previous file with comments | « Source/core/frame/ContentSecurityPolicy.h ('k') | Source/wtf/text/Base64.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698