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

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: More fixes from mkwst comments 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..879ea4f62706e51b8a04b3224a8e5448d4aa7281 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,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 String& hash) const { return !hash.isNull() && m_hashes.contains(hash); }
+ uint8_t getHashFunctionsUsed() const { return m_hashFunctionsUsed; }
abarth-chromium 2013/10/22 17:46:49 getHashFunctionsUsed -> hashFunctionsUsed (The "g
jww 2013/10/28 19:36:23 Done.
private:
bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard);
@@ -287,12 +295,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 addSourceHash(const String& hash, const ContentSecurityPolicy::HashFunctions&);
ContentSecurityPolicy* m_policy;
Vector<CSPSource> m_list;
@@ -301,6 +311,8 @@ private:
bool m_allowInline;
bool m_allowEval;
HashSet<String> m_nonces;
+ HashSet<String> m_hashes;
+ uint8_t m_hashFunctionsUsed;
abarth-chromium 2013/10/22 17:46:49 Don't we need to keep track of which hash function
jww 2013/10/28 19:36:23 Fixed by other changes.
};
CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
@@ -309,6 +321,7 @@ CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& direct
, m_allowStar(false)
, m_allowInline(false)
, m_allowEval(false)
+ , m_hashFunctionsUsed(0)
{
}
@@ -409,6 +422,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()) {
+ addSourceHash(hash, hashFunction);
+ return true;
+ }
}
const UChar* position = begin;
@@ -498,7 +521,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 +531,42 @@ 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 / "+" / "/" / "=" )
+//
+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
+ skipExactly<UChar>(position, position + 1, '=');
+ skipExactly<UChar>(position, position + 1, '=');
+
+ if (((position + 1) != end && *position != '\'') || !(position - hashBegin))
+ return false;
abarth-chromium 2013/10/22 17:46:49 Is this logic right? I would have expected: if (
jww 2013/10/28 19:36:23 Done.
+
+ hash = String(begin + 1, position - begin - 1);
abarth-chromium 2013/10/22 17:46:49 Why begin + 1 rather than hashBegin? I would have
jww 2013/10/28 19:36:23 Hashes are now stored as a struct that contain the
+ 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::addSourceHash(const String& hash, const ContentSecurityPolicy::HashFunctions& hashFunction)
+{
+ m_hashes.add(hash);
+ m_hashFunctionsUsed |= hashFunction;
+}
+
class CSPDirective {
public:
CSPDirective(const String& name, const String& value, ContentSecurityPolicy* policy)
@@ -766,6 +831,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 String& hash) const { return m_sourceList.allowHash(hash); }
+
+ uint8_t getHashFunctionsUsed() const { return m_sourceList.getHashFunctionsUsed(); }
private:
CSPSourceList m_sourceList;
@@ -800,6 +868,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 +897,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 +1000,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 +1250,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 ] ) ]
//
@@ -1358,29 +1438,30 @@ void CSPDirectiveList::addDirective(const String& name, const String& value)
{
ASSERT(!name.isEmpty());
- if (equalIgnoringCase(name, defaultSrc))
+ if (equalIgnoringCase(name, defaultSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_defaultSrc);
- else if (equalIgnoringCase(name, scriptSrc))
+ } else if (equalIgnoringCase(name, scriptSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_scriptSrc);
- else if (equalIgnoringCase(name, objectSrc))
+ m_policy->usesScriptHashFunctions(m_scriptSrc->getHashFunctionsUsed());
+ } else if (equalIgnoringCase(name, objectSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_objectSrc);
- else if (equalIgnoringCase(name, frameSrc))
+ } else if (equalIgnoringCase(name, frameSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_frameSrc);
- else if (equalIgnoringCase(name, imgSrc))
+ } else if (equalIgnoringCase(name, imgSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_imgSrc);
- else if (equalIgnoringCase(name, styleSrc))
+ } else if (equalIgnoringCase(name, styleSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_styleSrc);
- else if (equalIgnoringCase(name, fontSrc))
+ } else if (equalIgnoringCase(name, fontSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_fontSrc);
- else if (equalIgnoringCase(name, mediaSrc))
+ } else if (equalIgnoringCase(name, mediaSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_mediaSrc);
- else if (equalIgnoringCase(name, connectSrc))
+ } else if (equalIgnoringCase(name, connectSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_connectSrc);
- else if (equalIgnoringCase(name, sandbox))
+ } else if (equalIgnoringCase(name, sandbox)) {
applySandboxPolicy(name, value);
- else if (equalIgnoringCase(name, reportURI))
+ } else if (equalIgnoringCase(name, reportURI)) {
parseReportURI(name, value);
- else if (m_policy->experimentalFeaturesEnabled()) {
+ } else if (m_policy->experimentalFeaturesEnabled()) {
if (equalIgnoringCase(name, baseURI))
setCSPDirective<SourceListDirective>(name, value, m_baseURI);
else if (equalIgnoringCase(name, formAction))
@@ -1391,14 +1472,15 @@ void CSPDirectiveList::addDirective(const String& name, const String& value)
parseReflectedXSS(name, value);
else
m_policy->reportUnsupportedDirective(name);
- }
- else
+ } else {
m_policy->reportUnsupportedDirective(name);
+ }
}
ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContext* executionContext)
: m_executionContext(executionContext)
, m_overrideInlineStyleAllowed(false)
+ , m_sourceHashFunctionsUsed(HashFunctionsNone)
{
}
@@ -1524,6 +1606,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 +1690,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));
+ 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()));
abarth-chromium 2013/10/22 17:46:49 What about the trailing = characters? Do we need
jww 2013/10/28 19:36:23 Addressed by other changes (now storing the hash a
+ if (isAllowedByAllWithHash<&CSPDirectiveList::allowScriptHash>(m_policies, hash.toString()))
+ return true;
+ }
+
+ return false;
+}
+
+void ContentSecurityPolicy::usesScriptHashFunctions(uint8_t hashFunctions)
+{
+ m_sourceHashFunctionsUsed |= hashFunctions;
+}
+
bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus);
@@ -1851,12 +1972,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