Commit 081d150e authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

Add OCSP check for Signed Exchange

After this patch, SignedExchangeHandler starts accepting
"application/signed-exchange;v=b1" content-type in addition to v=b0.
(But Accept-Header still advertises only v=b0.)

For b1 signed exchanges, OCSP response from cert chain is passed to
CertVerifier::Verify(), and signed exchange without valid OCSP response
is rejected.

For now, b1 has minimal test coverage, but when we drop b0 support after
M68 branch cut, we'll be able to convert existing tests to b1.

This also makes IgnoreErrorsCertVerifier set OCSP results if request has
a non-empty ocsp response. This allows LayoutTests to work.

Bug: 815024
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_mojo
Change-Id: I611d68c1d4f26b1f97ea81f8f9f9b89ba3ad0d84
Reviewed-on: https://chromium-review.googlesource.com/1060933Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Reviewed-by: default avatarRyan Sleevi <rsleevi@chromium.org>
Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#561709}
parent 2778edc4
......@@ -89,10 +89,10 @@ SignedExchangeHandler::SignedExchangeHandler(
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::SignedExchangeHandler");
// Currently, only 'v=b0' is supported.
if (!SignedExchangeHeaderParser::GetVersionParamFromContentType(content_type,
&version_) ||
version_ != SignedExchangeVersion::kB0) {
(version_ != SignedExchangeVersion::kB0 &&
version_ != SignedExchangeVersion::kB1)) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SignedExchangeHandler::RunErrorCallback,
weak_factory_.GetWeakPtr(),
......@@ -101,7 +101,7 @@ SignedExchangeHandler::SignedExchangeHandler(
devtools_proxy_.get(), "SignedExchangeHandler::SignedExchangeHandler",
base::StringPrintf("Unsupported version of the content type. Currentry "
"content type must be "
"\"application/signed-exchange;v=b0\". But the "
"\"application/signed-exchange;v={b0,b1}\". But the "
"response content type was \"%s\"",
content_type.c_str()));
return;
......@@ -305,13 +305,10 @@ void SignedExchangeHandler::OnCertReceived(
net::CertVerifier* cert_verifier = g_cert_verifier_for_testing
? g_cert_verifier_for_testing
: request_context->cert_verifier();
// TODO(https://crbug.com/815024): Get the OCSP response from the
// “status_request” extension of the main-certificate, and check the lifetime
// (nextUpdate - thisUpdate) is less than 7 days.
int result = cert_verifier->Verify(
net::CertVerifier::RequestParams(
unverified_cert_chain_->cert(), header_->request_url().host(),
config.GetCertVerifyFlags(), std::string() /* ocsp_response */,
config.GetCertVerifyFlags(), unverified_cert_chain_->ocsp(),
net::CertificateList()),
net::SSLConfigService::GetCRLSet().get(), &cert_verify_result_,
base::BindRepeating(&SignedExchangeHandler::OnCertVerifyComplete,
......@@ -326,6 +323,27 @@ void SignedExchangeHandler::OnCertReceived(
"SignedExchangeHandler::OnCertReceived");
}
bool SignedExchangeHandler::CheckOCSPStatus(
const net::OCSPVerifyResult& ocsp_result) {
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cross-origin-trust
// Step 6.3 Validate that main-certificate has an ocsp property (Section 3.3)
// with a valid OCSP response whose lifetime (nextUpdate - thisUpdate) is less
// than 7 days ([RFC6960]). [spec text]
//
// OCSP verification is done in CertVerifier::Verify(), so we just check the
// result here.
// The b0 implementation checkpoint has no OCSP check.
if (version_ == SignedExchangeVersion::kB0)
return true;
if (ocsp_result.response_status != net::OCSPVerifyResult::PROVIDED ||
ocsp_result.revocation_status != net::OCSPRevocationStatus::GOOD)
return false;
return true;
}
void SignedExchangeHandler::OnCertVerifyComplete(int result) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeHandler::OnCertVerifyComplete");
......@@ -339,6 +357,17 @@ void SignedExchangeHandler::OnCertVerifyComplete(int result) {
return;
}
if (!CheckOCSPStatus(cert_verify_result_.ocsp_result)) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy_.get(), "SignedExchangeHandler::OnCertVerifyComplete",
base::StringPrintf(
"OCSP check failed. response status: %d, revocation status: %d",
cert_verify_result_.ocsp_result.response_status,
cert_verify_result_.ocsp_result.revocation_status));
RunErrorCallback(static_cast<net::Error>(net::ERR_FAILED));
return;
}
network::ResourceResponseHead response_head;
response_head.headers = header_->BuildHttpResponseHeaders();
response_head.headers->GetMimeTypeAndCharset(&response_head.mime_type,
......
......@@ -28,6 +28,7 @@ class CertVerifyResult;
class DrainableIOBuffer;
class SourceStream;
class URLRequestContextGetter;
struct OCSPVerifyResult;
} // namespace net
namespace network {
......@@ -95,6 +96,7 @@ class CONTENT_EXPORT SignedExchangeHandler {
void OnCertReceived(
std::unique_ptr<SignedExchangeCertificateChain> cert_chain);
void OnCertVerifyComplete(int result);
bool CheckOCSPStatus(const net::OCSPVerifyResult& ocsp_result);
ExchangeHeadersCallback headers_callback_;
base::Optional<SignedExchangeVersion> version_;
......
......@@ -122,6 +122,12 @@ int IgnoreErrorsCertVerifier::Verify(const RequestParams& params,
std::transform(spki_fingerprints.begin(), spki_fingerprints.end(),
std::back_inserter(verify_result->public_key_hashes),
[](const SHA256HashValue& v) { return HashValue(v); });
if (!params.ocsp_response().empty()) {
verify_result->ocsp_result.response_status =
net::OCSPVerifyResult::PROVIDED;
verify_result->ocsp_result.revocation_status =
net::OCSPRevocationStatus::GOOD;
}
return net::OK;
}
......
<!DOCTYPE html>
<title>Location of SignedHTTPExchange</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="./resources/htxg-util.js"></script>
<body>
<script>
promise_test(async (t) => {
await waitUntilDidFinishLoadForFrame;
// The timestamp of the test .sxg file is "May 15 2018 00:00 UTC" and valid
// until "May 22 2018 00:00 UTC".
await setSignedExchangeVerificationTime(new Date("May 15 2018 00:01 UTC"));
const event = await new Promise(async (resolve, reject) => {
// We can't catch the network error on iframe. So we use the timer.
t.step_timeout(() => reject('timeout'), 1000);
const frame =
await withIframe('resources/htxg-location.sxg', 'htxg_iframe');
const channel = new MessageChannel();
channel.port1.onmessage = resolve;
frame.contentWindow.postMessage(
{port: channel.port2}, '*', [channel.port2]);
});
assert_equals(event.data.location, 'https://www.127.0.0.1/test.html');
}, 'Location of SignedHTTPExchange');
</script>
</body>
......@@ -18,7 +18,7 @@ go get github.com/nyaxt/webpackage/go/signedexchange/cmd/gen-signedexchange
# Generate the certificate message file of "127.0.0.1.pem".
gen-certurl \
../../../../../../../blink/tools/blinkpy/thirdparty/wpt/certs/127.0.0.1.pem \
../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.pem \
> 127.0.0.1.pem.msg
# Generate the signed exchange file.
......@@ -29,7 +29,7 @@ gen-signedexchange \
-certificate ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.pem \
-certUrl http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.msg \
-validityUrl http://localhost:8000/loading/htxg/resources/resource.validity.msg \
-privateKey ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.key\
-privateKey ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.key \
-date 2018-04-01T00:00:00Z \
-expire 168h \
-o htxg-location.htxg \
......@@ -43,9 +43,32 @@ gen-signedexchange \
-certificate ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.pem \
-certUrl http://localhost:8000/loading/htxg/resources/not_found_cert.pem.msg \
-validityUrl http://localhost:8000/loading/htxg/resources/not_found_cert.validity.msg \
-privateKey ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.key\
-privateKey ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.key \
-date 2018-04-01T00:00:00Z \
-expire 168h \
-o htxg-cert-not-found.htxg \
-miRecordSize 100
# Install gen-certurl command from the original WICG repository [1].
# (Note: this overwrites gen-certurl fetched from [2] in the above.)
go get github.com/WICG/webpackage/go/signedexchange/cmd/gen-certurl
# Generate the certificate chain CBOR of "127.0.0.1.pem".
gen-certurl \
-pem ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.pem \
> 127.0.0.1.pem.cbor
# Generate the b1 version of signed exchange file.
gen-signedexchange \
-uri https://www.127.0.0.1/test.html \
-status 200 \
-content htxg-location.html \
-certificate ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.pem \
-certUrl http://localhost:8000/loading/htxg/resources/127.0.0.1.pem.cbor \
-validityUrl http://localhost:8000/loading/htxg/resources/resource.validity.msg \
-privateKey ../../../../../../../blink/tools/blinkpy/third_party/wpt/certs/127.0.0.1.key \
-date 2018-05-15T00:00:00Z \
-expire 168h \
-o htxg-location.sxg \
-miRecordSize 100
```
main frame - didStartProvisionalLoadForFrame
main frame - didCommitLoadForFrame
main frame - didReceiveTitle: Location of SignedHTTPExchange
main frame - didFinishDocumentLoadForFrame
main frame - didHandleOnloadEventsForFrame
main frame - didFinishLoadForFrame
frame "htxg_iframe" - didReceiveTitle:
frame "htxg_iframe" - didStartProvisionalLoadForFrame
frame "htxg_iframe" - didFailProvisionalLoadWithError
This is a testharness.js-based test.
FAIL Location of SignedHTTPExchange promise_test: Unhandled rejection with value: "timeout"
Harness: the test ran to completion.
main frame - didStartProvisionalLoadForFrame
main frame - didCommitLoadForFrame
main frame - didReceiveTitle: Location of SignedHTTPExchange
main frame - didFinishDocumentLoadForFrame
main frame - didHandleOnloadEventsForFrame
main frame - didFinishLoadForFrame
frame "htxg_iframe" - didReceiveTitle:
frame "htxg_iframe" - didStartProvisionalLoadForFrame
frame "htxg_iframe" - didFailProvisionalLoadWithError
This is a testharness.js-based test.
FAIL Location of SignedHTTPExchange promise_test: Unhandled rejection with value: "timeout"
Harness: the test ran to completion.
......@@ -85,6 +85,7 @@ application/sgml
application/sgml-open-catalog
application/sieve
application/signed-exchange;v=b0 htxg
application/signed-exchange;v=b1 sxg
application/slate
application/smil smi smil
application/srgs gram
......
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