NEL report for subresource sxg header integrity mismatch

When there is a mismatch between the 'header-integrity' value of
'allowed-alt-sxg' link header of the cached main resource and the header
integrity value of the cached subresource, Chrome will send a NEL report
with "sxg.header_integrity_mismatch" type when navigating to the main
resource if NEL reporting was setup.

Bug: 1025074
Change-Id: I7c0ae0904a211e11b72bae95cfe2274eb9c5550a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2237212
Reviewed-by: Kunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#788891}
diff --git a/signed-exchange/resources/generate-test-sxgs.sh b/signed-exchange/resources/generate-test-sxgs.sh
index 73c2080..e3988ce 100755
--- a/signed-exchange/resources/generate-test-sxgs.sh
+++ b/signed-exchange/resources/generate-test-sxgs.sh
@@ -577,6 +577,22 @@
   -miRecordSize 100 \
   -responseHeader "link:<$inner_url_origin/signed-exchange/resources/sxg-subresource-script.js>;rel=allowed-alt-sxg;header-integrity=\"$header_integrity\",<$inner_url_origin/signed-exchange/resources/sxg-subresource-script.js>;rel=preload;as=script"
 
+# Generate the signed exchange file of signed exchange subresource test with
+# header integrity mismatch.
+gen-signedexchange \
+  -version $sxg_version \
+  -uri $inner_url_origin/signed-exchange/resources/sxg-subresource-sxg.html \
+  -status 200 \
+  -content sxg-subresource-sxg-inner.html \
+  -certificate $certfile \
+  -certUrl $cert_url_origin/signed-exchange/resources/$certfile.cbor \
+  -validityUrl $inner_url_origin/signed-exchange/resources/resource.validity.msg \
+  -privateKey $keyfile \
+  -date 2030-04-01T00:00:00Z \
+  -expire 168h \
+  -o sxg/sxg-subresource-header-integrity-mismatch.sxg \
+  -miRecordSize 100 \
+  -responseHeader "link:<$inner_url_origin/signed-exchange/resources/sxg-subresource-script.js>;rel=allowed-alt-sxg;header-integrity=\"sha256-$dummy_sha256\",<$inner_url_origin/signed-exchange/resources/sxg-subresource-script.js>;rel=preload;as=script"
 
 # A Signed Exchange for testing prefetch.
 # The id query value "XXX..." of prefetch-test-cert.py will be replaced with
diff --git a/signed-exchange/resources/sxg-subresource-mismatch-iframe.html b/signed-exchange/resources/sxg-subresource-mismatch-iframe.html
new file mode 100644
index 0000000..f05fcc9
--- /dev/null
+++ b/signed-exchange/resources/sxg-subresource-mismatch-iframe.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<body>
+<script>
+(async () => {
+  const sxg_path = 'sxg/sxg-subresource-header-integrity-mismatch.sxg';
+  const scipt_sxg_path = 'sxg/sxg-subresource-script.sxg';
+  const scipt_path = 'sxg-subresource-script.js';
+  const wait_for_prefetch = new Promise((resolve) => {
+    new PerformanceObserver((list) => {
+      for (let e of list.getEntries()) {
+        if (e.name.endsWith(scipt_sxg_path)) {
+          resolve();
+        } else if (e.name.endsWith(scipt_path)) {
+          window.parent.postMessage(
+            scipt_path + ' should not be prefetched', '*');
+        }
+      }
+    }).observe({ entryTypes: ['resource'] });
+  });
+
+  const link = document.createElement('link');
+  link.rel = 'prefetch';
+  link.href = sxg_path;
+  document.body.appendChild(link);
+  await wait_for_prefetch;
+  location.href = sxg_path;
+})()
+</script>
+</body>
diff --git a/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg b/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg
new file mode 100644
index 0000000..ace89dd
--- /dev/null
+++ b/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg
Binary files differ
diff --git a/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg.sub.headers b/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg.sub.headers
new file mode 100644
index 0000000..8f1b47e
--- /dev/null
+++ b/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg.sub.headers
@@ -0,0 +1 @@
+Link: <https://{{hosts[alt][]}}:{{ports[https][0]}}/signed-exchange/resources/sxg/sxg-subresource-script.sxg>;rel=alternate;type="application/signed-exchange;v=b3";anchor="https://127.0.0.1:8444/signed-exchange/resources/sxg-subresource-script.js";
diff --git a/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative.html b/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative.html
new file mode 100644
index 0000000..8a26b21
--- /dev/null
+++ b/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<title>Subresource signed exchange prefetch.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/network-error-logging/support/nel.sub.js"></script>
+<script src="../resources/sxg-util.js"></script>
+<body>
+<script>
+nel_test(async t => {
+  const alt_origin = get_host_info().HTTPS_NOTSAMESITE_ORIGIN;
+  const test_origin = get_host_info().HTTPS_ORIGIN;
+  await fetchResourceWithBasicPolicy();
+  const iframe_path =
+      alt_origin +
+      '/signed-exchange/resources/sxg-subresource-mismatch-iframe.html';
+  const wait_message = (new Promise((resolve) => {
+    const on_message = (event) => {
+      window.removeEventListener('message', on_message);
+      resolve(event.data);
+    };
+    window.addEventListener('message', on_message);
+  }));
+  withIframe(iframe_path);
+  const message = await wait_message;
+  assert_equals(message, 'from server');
+  const cert_url = test_origin + '/signed-exchange/resources/127.0.0.1.sxg.pem.cbor';
+
+  const main_outer_url = alt_origin + '/signed-exchange/resources/sxg/sxg-subresource-header-integrity-mismatch.sxg';
+  const main_inner_url = innerURLOrigin() + '/signed-exchange/resources/sxg-subresource-sxg.html';
+  const sub_outer_url = alt_origin + '/signed-exchange/resources/sxg/sxg-subresource-script.sxg';
+  const sub_inner_url = innerURLOrigin() + '/signed-exchange/resources/sxg-subresource-script.js';
+  const iframe_url = alt_origin + '/signed-exchange/resources/sxg-subresource-mismatch-iframe.html';
+  assert_true(await reportsExist([
+    // Normal NEL report for the iframe's HTML.
+    {
+      url: iframe_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "application",
+        type: "ok",
+        status_code: 200,
+        referrer: location.href
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+    // Normal NEL report for the main resource signed exchange.
+    {
+      url: main_outer_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "application",
+        type: "ok",
+        status_code: 200,
+        referrer: iframe_url,
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+    // Signed Exchange NEL report for the main resource signed exchange.
+    {
+      url: main_outer_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "sxg",
+        type: "ok",
+        status_code: 200,
+        referrer: iframe_url,
+        sxg: {
+          outer_url: main_outer_url,
+          inner_url: main_inner_url,
+          cert_url: [cert_url]
+        }
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+    // Signed Exchange NEL report for the subresource signed exchange header
+    // integrity mismatch.
+    {
+      url: sub_outer_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "sxg",
+        type: "sxg.header_integrity_mismatch",
+        status_code: 200,
+        referrer: main_outer_url,
+        sxg: {
+          outer_url: sub_outer_url,
+          inner_url: sub_inner_url,
+          cert_url: [cert_url]
+        }
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+    // Normal NEL report for the main resource signed exchange.
+    {
+      url: sub_outer_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "application",
+        type: "ok",
+        status_code: 200,
+        referrer: iframe_url,
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+    // Signed Exchange NEL report for the sub resource signed exchange.
+    {
+      url: sub_outer_url,
+      user_agent: navigator.userAgent,
+      type: "network-error",
+      body: {
+        phase: "sxg",
+        type: "ok",
+        status_code: 200,
+        referrer: iframe_url,
+        sxg: {
+          outer_url: sub_outer_url,
+          inner_url: sub_inner_url,
+          cert_url: [cert_url]
+        }
+      },
+      metadata: {
+        content_type: "application/reports+json",
+      },
+    },
+  ]));
+}, 'Subresource signed exchange prefetch.');
+</script>
+</body>