Skip to content

Commit

Permalink
CSP report-hash keyword for scripts
Browse files Browse the repository at this point in the history
Implement hash reporting for scripts as part of CSP.

PR: w3c/webappsec-csp#693

Change-Id: Ie8d97d6094ca7601d84258cc5e1bca540eb49b39
Bug: 377830102
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6038298
Reviewed-by: Antonio Sartori <antoniosartori@chromium.org>
Commit-Queue: Yoav Weiss (@Shopify) <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1392854}
  • Loading branch information
Yoav Weiss authored and chromium-wpt-export-bot committed Dec 6, 2024
1 parent d484471 commit 22b20cf
Show file tree
Hide file tree
Showing 22 changed files with 215 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy: default-src 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<head>
<meta name="timeout" content="long">
<title>Test that reports for same-origin subresources are sent with hashes</title>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src='/reporting/resources/report-helper.js'></script>
</head>
<body>
<!-- Actually run the tests. -->
<script src='resources/report-hash-test-runner.sub.js'></script>
<script>
run_tests();
</script>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy: script-src-elem 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Reporting-Endpoints: csp-endpoint2="/reporting/resources/report.py?reportID={{$id2:uuid()}}"
Content-Security-Policy: script-src-elem 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha512'; report-to csp-endpoint2
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Server-Timing: uuid2;desc="{{$id2}}",hash2;desc="sha512-hG4x56V5IhUUepZdYU/lX7UOQJ2M7f6ud2EI7os4JV3OwXSZ002P3zkb9tXQkjpOO8UbtjuEufvdcU67Qt2tlw=="

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy-Report-Only: default-src 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy-Report-Only: script-src-elem 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy-Report-Only: script-src 'none' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy-Report-Only: script-src 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
function find_server_timing(name) {
const server_timing = performance.getEntriesByType("navigation")[0].serverTiming;
for (entry of server_timing) {
if (entry.name == name) {
return entry.description;
}
}
return null;
}

const ORIGIN = "https://{{host}}:{{ports[https][0]}}";
const REMOTE_ORIGIN = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
const endpoint = `${ORIGIN}/reporting/resources/report.py`;
const id = find_server_timing("uuid");
const id2 = find_server_timing("uuid2");
const subresource_url = `${ORIGIN}/reporting/resources/comment.js`;
const crossorigin_subresource_url = `${REMOTE_ORIGIN}/reporting/resources/comment.js`;
const subresource_hash = find_server_timing("hash");
const subresource_hash2 = find_server_timing("hash2");
let counter = 0;

function reporting_observer_setup(expected_url, expected_hash) {
return new Promise(resolve => {
new ReportingObserver((reports, observer) => {
assert_unreached();
observer.disconnect();
}, { types: ["csp-hash"] }).observe();
step_timeout(resolve, 100);
});
}

async function check_reports(uuid, expected_hash, url) {
const reports = await pollReports(endpoint, uuid);
checkReportExists(reports, 'csp-hash', location.href);
const report = getReport(reports, 'csp-hash', location.href, url);
assert_not_equals(report, null);
assert_equals(report.body.hash, expected_hash);
assert_equals(report.body.type, "subresource");
assert_equals(report.body.destination, "script");
}

function report_hash_test(url, populate_script_attributes, expected_hash, expected_hash2, description) {
promise_test(async t => {
const unique_subresource_url = `${url}?${++counter}`;
const observer_promise = reporting_observer_setup(unique_subresource_url, subresource_hash);
// Trigger a script load
await new Promise(resolve => {
const script = document.createElement('script');
script.src = unique_subresource_url;
populate_script_attributes(script);
script.addEventListener('load', resolve);
document.head.appendChild(script);
});

await check_reports(id, expected_hash, unique_subresource_url);
if (id2) {
await check_reports(id2, expected_hash2, unique_subresource_url);
}
await observer_promise;
}, description);
}

function no_report_test(create_element, description) {
promise_test(async t => {
const unique_subresource_url = `${subresource_url}?${++counter}`;
// Trigger a script load
await new Promise(resolve => {
const elem = create_element(unique_subresource_url);
elem.addEventListener('load', resolve);
elem.addEventListener('error', resolve);
document.head.appendChild(elem);
});

// Wait for report to be received.
const reports = await pollReports(endpoint, id);
const report = getReport(reports, 'csp-hash', location.href, unique_subresource_url);
assert_equals(report, null);
}, description);
};

function run_tests() {
report_hash_test(subresource_url, script => {
script.crossOrigin = "anonymous";
}, subresource_hash, subresource_hash2,
"Reporting endpoints received hash for same-origin CORS script.");

report_hash_test(subresource_url, script => {
}, subresource_hash, subresource_hash2,
"Reporting endpoints received hash for same-origin no-CORS script.");

report_hash_test(crossorigin_subresource_url, script => {
script.crossOrigin = "anonymous";
}, subresource_hash, subresource_hash2,
"Reporting endpoints received hash for cross-origin CORS script.");

report_hash_test(crossorigin_subresource_url, script => {
}, /*expected_hash=*/"", /*expected_hash2=*/"",
"Reporting endpoints received no hash for cross-origin no-CORS script.");

report_hash_test(subresource_url, script => {
script.crossOrigin = "anonymous";
script.integrity = "sha512-hG4x56V5IhUUepZdYU/lX7UOQJ2M7f6ud2EI7os4JV3OwXSZ002P3zkb9tXQkjpOO8UbtjuEufvdcU67Qt2tlw==";
}, subresource_hash, subresource_hash2,
"Reporting endpoints received the right hash for same-origin CORS script with integrity.");

no_report_test(url => {
const script = document.createElement('script');
script.src = url;
script.crossOrigin = "anonymous"
script.integrity = "sha256-foobar";
return script;
}, "Reporting endpoints received no report for failed integrity check with sha256.");

no_report_test(url => {
const script = document.createElement('script');
script.src = url;
script.crossOrigin = "anonymous"
script.integrity = "sha512-foobar";
return script;
}, "Reporting endpoints received no report for failed integrity check with sha512.");

no_report_test(url => {
const link = document.createElement('link');
link.href = url;
link.crossOrigin = "anonymous"
link.rel = "stylesheet"
return link;
}, "Reporting endpoints received no report for CORS stylesheet.");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy: script-src-elem 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy: script-src 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha512' 'report-sha384' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha512-hG4x56V5IhUUepZdYU/lX7UOQJ2M7f6ud2EI7os4JV3OwXSZ002P3zkb9tXQkjpOO8UbtjuEufvdcU67Qt2tlw=="
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// META: script=/reporting/resources/report-helper.js
// META: script=resources/report-hash-test-runner.sub.js
run_tests();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID={{$id:uuid()}}"
Content-Security-Policy: script-src 'self' {{hosts[alt][www]}}:{{ports[https][0]}} 'unsafe-inline' 'report-sha256'; report-to csp-endpoint
Server-Timing: uuid;desc="{{$id}}",hash;desc="sha256-1XF/E08XndkoxwN6eIa5J89hYn3OVZ/UyB8BrU5jgzk="
1 change: 1 addition & 0 deletions reporting/resources/comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Ceci n'est pas un commentaire
1 change: 1 addition & 0 deletions reporting/resources/comment.js.headers
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Access-Control-Allow-Origin: *
9 changes: 9 additions & 0 deletions reporting/resources/report-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ function checkReportExists(reports, type, url) {
}
assert_unreached(`A report of ${type} from ${url} is not found.`);
}

function getReport(reports, type, document_url, subresource_url) {
for (const report of reports) {
if (report.type !== type) continue;
if (report.body.documentURL === document_url && report.body.subresourceURL == subresource_url) return report;
}
return null;
}

0 comments on commit 22b20cf

Please sign in to comment.