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

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: Added tests 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..f971076f0515d5e91e85958965105f4575333527 100644
--- a/Source/core/frame/ContentSecurityPolicy.cpp
+++ b/Source/core/frame/ContentSecurityPolicy.cpp
@@ -49,6 +49,9 @@
#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 +72,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 == '/';
}
@@ -128,6 +133,10 @@ static const char formAction[] = "form-action";
static const char pluginTypes[] = "plugin-types";
static const char reflectedXSS[] = "reflected-xss";
+// Supported script hashes and their prefix name
+static const char sha1Label[] = "sha1";
+static const char sha256Label[] = "sha256";
+
bool isDirectiveName(const String& name)
{
return (equalIgnoringCase(name, connectSrc)
@@ -279,6 +288,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 +297,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);
void addSourceSelf();
void addSourceStar();
void addSourceUnsafeInline();
void addSourceUnsafeEval();
void addSourceNonce(const String& nonce);
+ void addSourceHash(const String& hash);
ContentSecurityPolicy* m_policy;
Vector<CSPSource> m_list;
@@ -301,6 +313,7 @@ private:
bool m_allowInline;
bool m_allowEval;
HashSet<String> m_nonces;
+ HashSet<String> m_hashes;
};
CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
@@ -409,6 +422,15 @@ bool CSPSourceList::parseSource(const UChar* begin, const UChar* end,
addSourceNonce(nonce);
return true;
}
+
+ String hash;
+ if (!parseHash(begin, end, hash))
+ return false;
+
+ if (!hash.isNull()) {
+ addSourceHash(hash);
+ return true;
+ }
}
const UChar* position = begin;
@@ -498,7 +520,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 +530,48 @@ 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)
+{
+ // TODO(jww) For now, we are not supporting SHA-256 (or any hashes other
+ // than SHA-1), so we need to put in a log message here to indicate that.
+ StringBuilder sha1Prefix;
+ sha1Prefix.append("'");
+ sha1Prefix.append(sha1Label);
+ sha1Prefix.append("-");
abarth-chromium 2013/10/17 02:28:09 There's no reason to use a StringBuilder here. Yo
jww 2013/10/18 22:58:04 Done.
+ bool isSha1 = equalIgnoringCase(sha1Prefix.toString().characters8(), begin, sha1Prefix.length());
+ StringBuilder sha256Prefix;
+ sha256Prefix.append("'");
+ sha256Prefix.append(sha256Label);
+ sha256Prefix.append("-");
+ bool isSha256 = equalIgnoringCase(sha256Prefix.toString().characters8(), begin, sha256Prefix.length());
abarth-chromium 2013/10/17 02:28:09 Ditto
jww 2013/10/18 22:58:04 Done.
+
+ if (!isSha1)
+ return true;
abarth-chromium 2013/10/17 02:28:09 You should call notImplemented() for the isSha256
jww 2013/10/18 22:58:04 Done.
+
+ const UChar* position = begin + (isSha1 ? sha1Prefix.length() : sha256Prefix.length());
+ const UChar* hashBegin = position;
+
+ skipWhile<UChar, isBase64EncodedCharacter>(position, end);
+ ASSERT(hashBegin <= position);
+
+ // Base64 encodings may end with exactly one or two '=' characters
+ 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 +714,11 @@ void CSPSourceList::addSourceNonce(const String& nonce)
m_nonces.add(nonce);
}
+void CSPSourceList::addSourceHash(const String& hash)
+{
+ m_hashes.add(hash);
+}
+
class CSPDirective {
public:
CSPDirective(const String& name, const String& value, ContentSecurityPolicy* policy)
@@ -766,6 +835,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 +870,7 @@ public:
bool allowBaseURI(const KURL&, ContentSecurityPolicy::ReportingStatus) const;
bool allowScriptNonce(const String&) const;
bool allowStyleNonce(const String&) const;
+ bool allowHash(const String&) const;
void gatherReportURIs(DOMStringList&) const;
const String& evalDisabledErrorMessage() const { return m_evalDisabledErrorMessage; }
@@ -828,6 +899,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 +1002,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 +1252,11 @@ bool CSPDirectiveList::allowStyleNonce(const String& nonce) const
return checkNonce(operativeDirective(m_styleSrc.get()), nonce);
}
+bool CSPDirectiveList::allowHash(const String& hash) const
+{
+ return checkHash(operativeDirective(m_scriptSrc.get()), hash);
abarth-chromium 2013/10/17 02:28:09 Notice the m_scriptSrc here.
jww 2013/10/18 22:58:04 Ah, right, that's why we have separate script and
+}
+
// policy = directive-list
// directive-list = [ directive *( ";" [ directive ] ) ]
//
@@ -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,22 @@ bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const
return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce);
}
+bool ContentSecurityPolicy::allowHash(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.
+ SHA1 sourceSha1;
+ sourceSha1.addBytes(source.utf8());
abarth-chromium 2013/10/17 02:28:09 Woah there cowboy. There are a couple of importan
jww 2013/10/18 22:58:04 Done.
+ Vector<uint8_t, 20> digest;
+ sourceSha1.computeHash(digest);
+
+ StringBuilder hash;
abarth-chromium 2013/10/17 02:28:09 Please call |hash.reserveCapacity(...)| with the e
jww 2013/10/18 22:58:04 Done.
+ hash.append(sha1Label);
+ hash.append("-");
+ hash.append(base64Encode(reinterpret_cast<char*>(digest.data()), 20));
abarth-chromium 2013/10/17 02:28:09 20 -> digest.size()
jww 2013/10/18 22:58:04 Done.
+ return isAllowedByAllWithHash<&CSPDirectiveList::allowHash>(m_policies, hash.toString());
+}
+
bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus);
@@ -1851,12 +1960,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