Skip to content

Commit

Permalink
Bug 1890696 [wpt PR 45638] - Subresource Integrity support for ES mod…
Browse files Browse the repository at this point in the history
…ules, using importmaps, a=testonly

Automatic update from web-platform-tests
Subresource Integrity support for ES modules, using importmaps (#45638)

SRI support for ES modules enables using them in documents that require
SRI for certain scripts for security reasons, as well as with the move
overarching require-sri-for CSP directive.

This CL implements whatwg/html#10269
based on https://github.com/guybedford/import-maps-extensions#integrity

I2P: https://groups.google.com/a/chromium.org/g/blink-dev/c/O2UR3kb-HcI/m/7Jh7_GYsAAAJ?utm_medium=email&utm_source=footer

Change-Id: Ida563334048d013ffc658f9783f9401930dd4689
Bug: 334251999
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5441822
Reviewed-by: Domenic Denicola <domenic@chromium.org>
Commit-Queue: Yoav Weiss (@Shopify) <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1297376}

Co-authored-by: Yoav Weiss <yoavweiss@chromium.org>
--

wpt-commits: 7daf23a6329f4577bc3723d5e25eae8eae26e710
wpt-pr: 45638
  • Loading branch information
chromium-wpt-export-bot authored and moz-wptsync-bot committed May 14, 2024
1 parent 12b5d15 commit 57f4cab
Show file tree
Hide file tree
Showing 7 changed files with 502 additions and 3 deletions.
87 changes: 87 additions & 0 deletions testing/web-platform/tests/import-maps/dynamic-integrity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
let log;
</script>
<script type="importmap">
{
"imports": {
"./resources/log.js?pipe=sub&name=ResolvesToBadHash": "./resources/log.js?pipe=sub&name=BadHash",
"./resources/log.js?pipe=sub&name=ResolvesToNoHash": "./resources/log.js?pipe=sub&name=NoHash",
"./resources/log.js?pipe=sub&name=GoodHash": "./resources/log.js?pipe=sub&name=GoodHash",
"bare": "./resources/log.js?pipe=sub&name=BareURL",
"bare2": "./resources/log.js?pipe=sub&name=F"
},
"integrity": {
"./resources/log.js?pipe=sub&name=BadHash": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ResolvesToNoHash": "sha384-foobar",
"./resources/log.js?pipe=sub&name=GoodHash": "sha384-SwfgBqInhSlLziU454cYhGgwPpae+d3VHZcY+vjZIO/gxRGt2u3Jsfyvure/Ww0u",
"./resources/log.js?pipe=sub&name=InvalidExtra": "sha384-WsKk8nzJFPhk/4pWR4LYoPhEu3xaAc6PdIm4vmqoZVWqEgMYmZgOg9XJKxgD1+8v foobar-rOJN8igD0+jW6lwNN3+InhXTgQztVHlq/HJ0riswXp8kMoiIDx5JpmCwuVem6Ll9q2LFNSu1xq23bsBMMQk1rg==",
"./resources/log.js?pipe=sub&name=Suffix": "sha384-lbOWldbmji7sCHI/L8iVJ+elmFIMp41p+aYOLxqQfZMqtoFeHFVe/ASRA0IyZ1/9?foobar",
"./resources/log.js?pipe=sub&name=Multiple": "sha384-foobar sha512-rOJN8igD0+jW6lwNN3+InhXTgQztVHlq/HJ0riswXp8kMoiIDx5JpmCwuVem6Ll9q2LFNSu1xq23bsBMMQk1rg==",
"./resources/log.js?pipe=sub&name=BadHashWithNoImport": "sha384-foobar",
"./resources/log.js?pipe=sub&name=BareURL": "sha384-foobar",
"bare2": "sha384-foobar",
"resources/log.js?pipe=sub&name=Bare": "sha384-foobar"
}
}
</script>
<script type="module">
const test_not_loaded = (url, description) => {
promise_test(async t => {
log = [];
const promise = import(url);
await promise_rejects_js(t, TypeError, promise);
assert_array_equals(log, []);
}, description);
};

const test_loaded = (url, log_expectation, description) => {
promise_test(async t => {
log = [];
await import(url);
assert_array_equals(log, log_expectation);
}, description);
};

test_not_loaded("./resources/log.js?pipe=sub&name=ResolvesToBadHash",
'script was not loaded, as its resolved URL failed its integrity check');

test_loaded("./resources/log.js?pipe=sub&name=ResolvesToNoHash", ["log:NoHash"],
'script was loaded, as its resolved URL had no integrity check, despite' +
' its specifier having one');

test_loaded("./resources/log.js?pipe=sub&name=GoodHash", ["log:GoodHash"],
'script was loaded, as its integrity check passed');

test_not_loaded("./resources/log.js?pipe=sub&name=BadHashWithNoImport",
'Script with no import definition was not loaded, as it failed its' +
' integrity check');

test_not_loaded("bare",
'Bare specifier script was not loaded, as it failed its integrity check');

test_loaded("bare2", ["log:F"],
'Bare specifier used for integrity loaded, as its definition should have' +
' used the URL');

test_loaded("./resources/log.js?pipe=sub&name=InvalidExtra",
["log:InvalidExtra"],
'script was loaded, as its integrity check passed, despite having an extra' +
' invalid hash');

test_loaded("./resources/log.js?pipe=sub&name=Suffix", ["log:Suffix"],
'script was loaded, as its integrity check passed, despite having an' +
' invalid suffix');

test_loaded("./resources/log.js?pipe=sub&name=Multiple", ["log:Multiple"],
'script was loaded, as its integrity check passed given multiple hashes.' +
' This also makes sure that the larger hash is picked.');

test_loaded("./resources/log.js?pipe=sub&name=Bare",["log:Bare"],
'script was loaded, as its integrity check was ignored, as it was defined' +
' using a URL that looks like a bare specifier');
</script>

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
let log = [];
</script>
<script type="importmap">
{
"integrity": {
"./resources/log.js?pipe=sub&name=NoReferencingScriptValidCheck": "sha384-5eRmXQSBE6H5ENdymdZxcyiIfJL1dxtH8p+hOelZY7Jzk+gt0gYyemrGY0cEaThF"
}
}
</script>
<script>
let promiseResolve;
let promiseReject;
let promise = new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
</script>
</head>
<body>
<img src="/images/green.png?2"
onload="import('./resources/log.js?pipe=sub&name=NoReferencingScriptValidCheck').then(promiseResolve).catch(promiseReject)">
<script>
promise_test(async () => {
await promise;
assert_equals(log.length, 1);
assert_equals(log[0], "log:NoReferencingScriptValidCheck");
}, "Script was loaded as its valid integrity check passed");
</script>
</body>
</html>

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
let log = [];
</script>
<script type="importmap">
{
"integrity": {
"./resources/log.js?pipe=sub&name=NoReferencingScriptInvalidCheck": "sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
}
}
</script>
<script>
let promiseResolve;
let promiseReject;
let promise = new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
</script>
</head>
<body>
<img src="/images/green.png"
onload="import('./resources/log.js?pipe=sub&name=NoReferencingScriptInvalidCheck').then(promiseResolve).catch(promiseReject)">
<script type="module">
promise_test(async t => {
await promise_rejects_js(t, TypeError, promise);
}, "Script was not loaded as its integrity check failed");
</script>
</body>
</html>
161 changes: 161 additions & 0 deletions testing/web-platform/tests/import-maps/nonimport-integrity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!DOCTYPE html>
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
let log = [];
</script>
<script type="importmap">
{
"integrity": {
"./resources/log.js?pipe=sub&name=ModuleNoIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModuleIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModuleEmptyIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModuleBadIntegrityAttribute": "sha384-COhDkp+ybIZ9wz9hUaSJ5NzKcn8wOMZMpsACZfTeEdBRtNcX5yWJnFn+lIK77Tay",
"./resources/log.js?pipe=sub&name=ModulePreloadNoIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModulePreloadIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModulePreloadEmptyIntegrity": "sha384-foobar",
"./resources/log.js?pipe=sub&name=ModulePreloadBadIntegrityAttribute": "sha384-026dlUs9+KSmPb0Uc7oUPOlWBO67o7vSFdfLJZWEVTvKCly5NXO8+CsOXl54ZBqJ",
"./resources/log.js?pipe=sub&name=NonModule": "sha384-foobar",
"/images/green.png": "sha384-foobar"
}
}
</script>
<script type="module">
promise_test(async t => {
log = [];
const script = document.createElement("script");
script.type = "module";
script.src = "./resources/log.js?pipe=sub&name=ModuleNoIntegrity";
const promise = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => { reject(Error()); };
});
document.head.appendChild(script);
await promise_rejects_js(t, Error, promise);
}, "Script was not loaded as its integrity check was not ignored");

promise_test(async () => {
log = [];
const script = document.createElement("script");
script.type = "module";
script.integrity = "sha384-QtZrhNFOSmHASHnBdmGg+zrVz5hjukCBakaqwT2pcG7w+QTa/niK16csP6kXAeXI";
script.src = "./resources/log.js?pipe=sub&name=ModuleIntegrity";
const promise = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
document.head.appendChild(script);
await promise;
assert_equals(log.length, 1);
assert_equals(log[0], "log:ModuleIntegrity");
}, "Script was loaded as its correct integrity attribute was not ignored");

promise_test(async () => {
log = [];
const script = document.createElement("script");
script.type = "module";
script.integrity = "";
script.src = "./resources/log.js?pipe=sub&name=ModuleEmptyIntegrity";
const promise = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
document.head.appendChild(script);
await promise;
assert_equals(log.length, 1);
assert_equals(log[0], "log:ModuleEmptyIntegrity");
}, "Script was loaded as its empty integrity attribute was not ignored");

promise_test(async t => {
log = [];
const script = document.createElement("script");
script.type = "module";
script.integrity = "sha384-foobar";
script.src = "./resources/log.js?pipe=sub&name=ModuleBadIntegrityAttribute";
const promise = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => { reject(Error()); };
});
document.head.appendChild(script);
await promise_rejects_js(t, Error, promise);
}, "Script was not loaded as its bad integrity attribute was not overridden");

promise_test(async t => {
const link = document.createElement("link");
link.rel = "modulepreload";
link.href = "./resources/log.js?pipe=sub&name=ModulePreloadNoIntegrity";
const promise = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = () => { reject(Error()); };
});
document.head.appendChild(link);
await promise_rejects_js(t, Error, promise);
}, "Modulepreload was not loaded as its integrity check was not ignored");

promise_test(async () => {
const link = document.createElement("link");
link.rel = "modulepreload";
link.integrity = "sha384-iDG3WysExtjWvD9QwQrC7nGXRvO0jM+r7Z2cOLMDO2geMlEtmN9j9xfqHfzT45+9";
link.href = "./resources/log.js?pipe=sub&name=ModulePreloadIntegrity";
const promise = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
document.head.appendChild(link);
await promise;
}, "Modulepreload was loaded as its correct integrity attribute was not ignored");

promise_test(async () => {
const link = document.createElement("link");
link.rel = "modulepreload";
link.integrity = "";
link.href = "./resources/log.js?pipe=sub&name=ModulePreloadEmptyIntegrity";
const promise = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
document.head.appendChild(link);
await promise;
}, "Modulepreload was loaded as its empty integrity attribute was not ignored");

promise_test(async t => {
const link = document.createElement("link");
link.rel = "modulepreload";
link.integrity = "sha384-foobar";
link.href = "./resources/log.js?pipe=sub&name=ModulePreloadBadIntegrityAttribute";
const promise = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = () => { reject(Error()); };
});
document.head.appendChild(link);
await promise_rejects_js(t, Error, promise);
}, "Modulepreload was not loaded as its bad integrity attribute was not ignored");

promise_test(async () => {
log = [];
const script = document.createElement("script");
script.src = "./resources/log.js?pipe=sub&name=NonModule";
const promise = new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
document.head.appendChild(script);
await promise;
assert_equals(log.length, 1);
assert_equals(log[0], "log:NonModule");
}, "Classic script was loaded as its integrity check was ignored");

promise_test(async () => {
const img = document.createElement("img");
const promise = new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
img.src = "/images/green.png";
document.head.appendChild(img);
await promise;
}, "Image was loaded as its integrity check was ignored");
</script>
</head>
68 changes: 68 additions & 0 deletions testing/web-platform/tests/import-maps/static-integrity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
let log = [];
</script>
<script type="importmap">
{
"imports": {
"./resources/log.js?pipe=sub&name=A": "./resources/log.js?pipe=sub&name=B",
"./resources/log.js?pipe=sub&name=C": "./resources/log.js?pipe=sub&name=D"
},
"integrity": {
"./resources/log.js?pipe=sub&name=B": "sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7",
"./resources/log.js?pipe=sub&name=D": "sha384-rxZqznFuOnvObm6JJKVmwzBXrsRG25IepqKDFHGhtitRu9YPjxPpRPMIu2hzvtxF",
"./resources/log.js?pipe=sub&name=X": "sha384-mCon9M46vUfNK2Wb3yjvBmpBw/3hwB+wMYS8IzDBng+7//R5Qao35E1azo4gFVzx",
"./resources/log.js?pipe=sub&name=Y": "sha384-u0yaFlBF39Au++qcn+MGL/Ml7UmuVfLymNJAz6Yyi4RqyUfWelcuAzVyE8Shs9xn",
"./resources/log.js?pipe=sub&name=Z": "sha384-u0yaFlBF39Au++qcn+MGL/Ml7UmuVfLymNJAz6Yyi4RqyUfWelcuAzVyE8Shs9xn"
}
}
</script>
<script type="module">
import './resources/log.js?pipe=sub&name=A';
</script>
<script type="module">
test(t => {
assert_array_equals(log, []);
}, 'Static script did not load as it failed its integrity check');
log = [];
</script>
<script type="module">
import './resources/log.js?pipe=sub&name=C';
</script>
<script type="module">
test(t => {
assert_array_equals(log, ["log:D"]);
}, 'Static script loaded as its integrity check passed');
log = [];
</script>
<script type="module">
import './resources/log.js?pipe=sub&name=X';
</script>
<script type="module">
test(t => {
assert_array_equals(log, []);
}, 'Static script did not load as it failed its integrity check, even' +
' without an import defined');
log = [];
</script>
<script type="module">
import './resources/log.js?pipe=sub&name=Y';
</script>
<script type="module">
test(t => {
assert_array_equals(log, ["log:Y"]);
}, 'Static script loaded as its integrity check passed without an import' +
' defined');
log = [];
</script>
<script type="module" src="./resources/log.js?pipe=sub&name=Z">;
</script>
<script type="module">
test(t => {
assert_array_equals(log, []);
}, 'HTML-based module script did not load as its integrity check failed.');
log = [];
</script>

0 comments on commit 57f4cab

Please sign in to comment.