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

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: Fixed abarth's comments, added tests, and added short circuit for unused hashes. 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
Index: Source/core/frame/ContentSecurityPolicy.cpp
diff --git a/Source/core/frame/ContentSecurityPolicy.cpp b/Source/core/frame/ContentSecurityPolicy.cpp
index e141aa91cb904e121d2d03d7abf32e53fb9c6ee0..f3e9b67b7b82c459026edde3d0e03cb2d2f24fa0 100644
--- a/Source/core/frame/ContentSecurityPolicy.cpp
+++ b/Source/core/frame/ContentSecurityPolicy.cpp
@@ -44,11 +44,15 @@
#include "core/platform/network/FormData.h"
#include "core/platform/network/ResourceResponse.h"
#include "platform/JSONValues.h"
+#include "platform/NotImplemented.h"
#include "weborigin/KURL.h"
#include "weborigin/KnownPorts.h"
#include "weborigin/SchemeRegistry.h"
#include "weborigin/SecurityOrigin.h"
#include "wtf/HashSet.h"
+#include "wtf/SHA1.h"
+#include "wtf/text/Base64.h"
+#include "wtf/text/StringBuilder.h"
#include "wtf/text/TextPosition.h"
#include "wtf/text/WTFString.h"
@@ -69,7 +73,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 == '/';
}
@@ -279,6 +285,7 @@ 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 String& hash) const { return !hash.isNull() && m_hashes.contains(hash); }
private:
bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard);
@@ -287,12 +294,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, String& hash, ContentSecurityPolicy::HashFunctions&);
void addSourceSelf();
void addSourceStar();
void addSourceUnsafeInline();
void addSourceUnsafeEval();
void addSourceNonce(const String& nonce);
+ void addSourceScriptHash(const String& hash, const ContentSecurityPolicy::HashFunctions&);
ContentSecurityPolicy* m_policy;
Vector<CSPSource> m_list;
@@ -301,6 +310,7 @@ private:
bool m_allowInline;
bool m_allowEval;
HashSet<String> m_nonces;
+ HashSet<String> m_hashes;
Mike West 2013/10/21 07:11:55 I don't think we need two sets of hashes here (tho
jww 2013/10/21 19:18:04 My only objection is that, in theory, there are va
Mike West 2013/10/21 19:28:48 You'd have a policy like `script-src '<hash goes h
};
CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
@@ -409,6 +419,16 @@ bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
addSourceNonce(nonce);
return true;
}
+
+ String hash;
+ ContentSecurityPolicy::HashFunctions hashFunction = ContentSecurityPolicy::HashFunctionsNone;
+ if (!parseHash(begin, end, hash, hashFunction))
+ return false;
+
+ if (!hash.isNull()) {
+ addSourceScriptHash(hash, hashFunction);
+ return true;
+ }
}
const UChar* position = begin;
@@ -498,7 +518,7 @@ 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))
@@ -508,6 +528,45 @@ bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& non
return true;
}
+// hash-source = "'" hash-algorithm "-" hash-value "'"
+// hash-algorithm = "sha1" / "sha256"
+// hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
Mike West 2013/10/21 07:11:55 Spec: If we want to be explicit about this being b
+//
+bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, String& hash, ContentSecurityPolicy::HashFunctions& hashFunction)
+{
+ 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;
+ hashFunction = ContentSecurityPolicy::HashFunctionsSha1;
+ } 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
Mike West 2013/10/21 07:11:55 Might be clearer if you "skipExactly" twice (ignor
jww 2013/10/21 19:18:04 Done.
+ for (size_t i = 0; i < 2 && position != end; i++) {
+ if (*position != '=')
+ break;
+ position = position + 1;
+ }
+
+ if (((position + 1) != end && *position != '\'') || !(position - hashBegin))
+ return false;
+
+ hash = String(begin + 1, position - begin - 1);
+ return true;
+}
+
// ; <scheme> production from RFC 3986
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
//
@@ -650,6 +709,12 @@ void CSPSourceList::addSourceNonce(const String& nonce)
m_nonces.add(nonce);
}
+void CSPSourceList::addSourceScriptHash(const String& hash, const ContentSecurityPolicy::HashFunctions& hashFunction)
+{
+ m_hashes.add(hash);
+ m_policy->usesScriptHashFunction(hashFunction);
+}
+
class CSPDirective {
public:
CSPDirective(const String& name, const String& value, ContentSecurityPolicy* policy)
@@ -766,6 +831,7 @@ 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 String& hash) const { return m_sourceList.allowHash(hash); }
private:
CSPSourceList m_sourceList;
@@ -800,6 +866,7 @@ public:
bool allowBaseURI(const KURL&, ContentSecurityPolicy::ReportingStatus) const;
bool allowScriptNonce(const String&) const;
bool allowStyleNonce(const String&) const;
+ bool allowScriptHash(const String&) const;
void gatherReportURIs(DOMStringList&) const;
const String& evalDisabledErrorMessage() const { return m_evalDisabledErrorMessage; }
@@ -828,6 +895,7 @@ private:
bool checkEval(SourceListDirective*) const;
bool checkInline(SourceListDirective*) const;
bool checkNonce(SourceListDirective*, const String&) const;
+ bool checkHash(SourceListDirective*, const String&) const;
bool checkSource(SourceListDirective*, const KURL&) const;
bool checkMediaType(MediaListDirective*, const String& type, const String& typeAttribute) const;
@@ -930,6 +998,11 @@ bool CSPDirectiveList::checkNonce(SourceListDirective* directive, const String&
return !directive || directive->allowNonce(nonce);
}
+bool CSPDirectiveList::checkHash(SourceListDirective* directive, const String& hash) const
+{
+ return !directive || directive->allowHash(hash);
+}
+
bool CSPDirectiveList::checkSource(SourceListDirective* directive, const KURL& url) const
{
return !directive || directive->allows(url);
@@ -1175,6 +1248,11 @@ bool CSPDirectiveList::allowStyleNonce(const String& nonce) const
return checkNonce(operativeDirective(m_styleSrc.get()), nonce);
}
+bool CSPDirectiveList::allowScriptHash(const String& hash) const
+{
+ return checkHash(operativeDirective(m_scriptSrc.get()), hash);
+}
+
// policy = directive-list
// directive-list = [ directive *( ";" [ directive ] ) ]
//
@@ -1399,6 +1477,7 @@ void CSPDirectiveList::addDirective(const String& name, const String& value)
ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContext* executionContext)
: m_executionContext(executionContext)
, m_overrideInlineStyleAllowed(false)
+ , m_sourceHashFunctionsUsed(HashFunctionsNone)
{
}
@@ -1524,6 +1603,17 @@ bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const Strin
}
return true;
}
+
+template<bool (CSPDirectiveList::*allowed)(const String&) const>
+bool isAllowedByAllWithHash(const CSPDirectiveListVector& policies, const String& hash)
+{
+ for (size_t i = 0; i < policies.size(); ++i) {
+ if (!(policies[i].get()->*allowed)(hash))
+ return false;
+ }
+ return true;
+}
+
template<bool (CSPDirectiveList::*allowFromURL)(const KURL&, ContentSecurityPolicy::ReportingStatus) const>
bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus)
{
@@ -1597,6 +1687,34 @@ bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const
return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce);
}
+bool ContentSecurityPolicy::allowScriptHash(const String& source) const
+{
+ DEFINE_STATIC_LOCAL(const String, sha1Prefix, ("sha1-"));
+
+ // 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 (HashFunctionsSha1 & m_sourceHashFunctionsUsed) {
+ SHA1 sourceSha1;
+ sourceSha1.addBytes(UTF8Encoding().normalizeAndEncode(source, WTF::EntitiesForUnencodables));
Mike West 2013/10/21 07:11:55 This all looks quite sane to me, but I'd like Adam
jww 2013/10/21 19:18:04 sgtm
+ Vector<uint8_t, 20> digest;
+ sourceSha1.computeHash(digest);
+
+ StringBuilder hash;
+ hash.reserveCapacity(sha1Prefix.length() + digest.size());
+ hash.append(sha1Prefix);
+ hash.append(base64Encode(reinterpret_cast<char*>(digest.data()), digest.size()));
+ if (isAllowedByAllWithHash<&CSPDirectiveList::allowScriptHash>(m_policies, hash.toString()))
Mike West 2013/10/21 07:11:55 Ok, this makes sense. You put the hash types on th
jww 2013/10/21 19:18:04 Done.
+ return true;
+ }
+
+ return false;
+}
+
+void ContentSecurityPolicy::usesScriptHashFunction(HashFunctions hashFunction)
+{
+ m_sourceHashFunctionsUsed |= hashFunction;
+}
+
bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus);
@@ -1851,12 +1969,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.";
« Source/core/frame/ContentSecurityPolicy.h ('K') | « Source/core/frame/ContentSecurityPolicy.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698