Commit fc16d3a8 authored by Antonio Sartori's avatar Antonio Sartori Committed by Commit Bot

Add IsValidRequiredCSPAttr to network CSP parser

This CL implements the check of whether a string is a valid 'csp'
attribute according to
https://w3c.github.io/webappsec-cspee/#iframe-csp-valid-attribute-value
in the network CSP parser.

The check is still incomplete, since it relies on the Subsume
algorithm that will be implemented in future CLs.

This function will be used by the AncestorThrottle.

Bug: 1094909
Change-Id: Id325b203ba4269e542fd7b9b144da55c3e4e52d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2315683
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791763}
parent 0d911b2d
......@@ -944,6 +944,61 @@ void UpgradeInsecureRequest(GURL* url) {
*url = url->ReplaceComponents(replacements);
}
bool IsValidRequiredCSPAttr(
const std::vector<mojom::ContentSecurityPolicyPtr>& policy,
const mojom::ContentSecurityPolicy* context,
std::string& error_message) {
DCHECK(policy.size() == 1);
if (!policy[0]->parsing_errors.empty()) {
error_message =
"Parsing the csp attribute into a Content-Security-Policy returned one "
"or more parsing errors: " +
base::JoinString(policy[0]->parsing_errors, " ");
return false;
}
if (!policy[0]->report_endpoints.empty()) {
error_message =
"The csp attribute cannot contain the directives 'report-to' or "
"'report-uri'.";
return false;
}
if (context && !Subsumes(*context, policy)) {
error_message =
"The csp attribute Content-Security-Policy is not subsumed by the "
"frame's parent csp attribute Content-Security-Policy.";
return false;
}
return true;
}
bool Subsumes(const mojom::ContentSecurityPolicy& policy_a,
const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b) {
if (policy_a.directives.empty())
return true;
if (policy_a.header->type == mojom::ContentSecurityPolicyType::kReport)
return true;
// TODO(antoniosartori): Complete the implementation of this function
return util::ranges::all_of(
policy_a.directives, [&policies_b](const auto& directive_a) {
return util::ranges::any_of(
policies_b, [&directive_a](const auto& policy_b) {
if (policy_b->header->type ==
mojom::ContentSecurityPolicyType::kReport) {
return false;
}
auto value_b = policy_b->directives.find(directive_a.first);
return value_b != policy_b->directives.end() &&
value_b->second == directive_a.second;
});
});
}
CSPDirectiveName ToCSPDirectiveName(const std::string& name) {
if (name == "base-uri")
return CSPDirectiveName::BaseURI;
......
......@@ -71,6 +71,22 @@ bool ShouldTreatAsPublicAddress(
COMPONENT_EXPORT(NETWORK_CPP)
void UpgradeInsecureRequest(GURL* url);
// Checks whether |policy| is a valid required CSP attribute according to
// https://w3c.github.io/webappsec-cspee/#iframe-csp-valid-attribute-value.
// |policy| must be a vector containing exactly one entry.
// The context can be null.
COMPONENT_EXPORT(NETWORK_CPP)
bool IsValidRequiredCSPAttr(
const std::vector<mojom::ContentSecurityPolicyPtr>& policy,
const mojom::ContentSecurityPolicy* context,
std::string& error_message);
// Checks whether |policy_a| subsumes the policy list |policies_b| according to
// the algorithm https://w3c.github.io/webappsec-cspee/#subsume-policy-list.
COMPONENT_EXPORT(NETWORK_CPP)
bool Subsumes(const mojom::ContentSecurityPolicy& policy_a,
const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b);
COMPONENT_EXPORT(NETWORK_CPP)
mojom::CSPDirectiveName ToCSPDirectiveName(const std::string& name);
......
......@@ -1134,4 +1134,78 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) {
}
}
TEST(ContentSecurityPolicy, IsValidRequiredCSPAttr) {
struct TestCase {
const char* csp;
bool expected;
std::string expected_error;
} cases[] = {{"script-src 'none'", true, ""},
{"script-src 'none'; invalid-directive", false,
"Parsing the csp attribute into a Content-Security-Policy "
"returned one or more parsing errors: Unrecognized "
"Content-Security-Policy directive 'invalid-directive'."},
{"script-src 'none'; report-uri https://www.example.com", false,
"The csp attribute cannot contain the directives 'report-to' "
"or 'report-uri'."}};
for (auto& test : cases) {
SCOPED_TRACE(test.csp);
std::vector<mojom::ContentSecurityPolicyPtr> csp;
auto required_csp_headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
required_csp_headers->SetHeader("Content-Security-Policy", test.csp);
AddContentSecurityPolicyFromHeaders(*required_csp_headers,
GURL("https://example.com/"), &csp);
std::string out;
EXPECT_EQ(test.expected, IsValidRequiredCSPAttr(csp, nullptr, out));
EXPECT_EQ(test.expected_error, out);
}
}
TEST(ContentSecurityPolicy, Subsumes) {
struct TestCase {
std::string name;
std::string required_csp;
std::string returned_csp;
bool expected;
} cases[] = {
{
"No required csp",
"",
"script-src 'none'",
true,
},
{
"Same CSPs",
"script-src 'none'",
"script-src 'none'",
true,
},
};
for (auto& test : cases) {
SCOPED_TRACE(test.name);
std::vector<mojom::ContentSecurityPolicyPtr> required_csp;
if (test.required_csp.empty()) {
required_csp.push_back(mojom::ContentSecurityPolicy::New());
} else {
auto required_csp_headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
required_csp_headers->SetHeader("Content-Security-Policy",
test.required_csp);
AddContentSecurityPolicyFromHeaders(
*required_csp_headers, GURL("https://example.com/"), &required_csp);
}
auto returned_csp_headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
returned_csp_headers->AddHeader("Content-Security-Policy",
test.returned_csp);
std::vector<mojom::ContentSecurityPolicyPtr> returned_csp;
AddContentSecurityPolicyFromHeaders(
*returned_csp_headers, GURL("https://example.com/"), &returned_csp);
EXPECT_EQ(test.expected, Subsumes(*required_csp[0], returned_csp));
}
}
} // namespace network
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment