Commit 3f832700 authored by Kunihiko Sakamoto's avatar Kunihiko Sakamoto Committed by Commit Bot

Add support for new signed-exchange cert chain format

This adds a parser for the CBOR certificate chain format defined in [1].
SignedExchangeCertificateChain::Parse() takes a version enum and selects
a parser to use. For now, the new parser is used only by tests.

[1] https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cert-chain-format

Bug: 815024,815025
Change-Id: Ia554ad3d086dbecd20294bdb6db03f37b60d67d9
Reviewed-on: https://chromium-review.googlesource.com/1002412
Commit-Queue: Kunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Reviewed-by: default avatarTsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#558182}
parent 7edf04f6
...@@ -179,9 +179,12 @@ void SignedExchangeCertFetcher::OnDataComplete() { ...@@ -179,9 +179,12 @@ void SignedExchangeCertFetcher::OnDataComplete() {
body_.reset(); body_.reset();
handle_watcher_ = nullptr; handle_watcher_ = nullptr;
// TODO(https://crbug.com/803774): Take SignedExchangeVersion as a
// parameter of CreateAndStart() and use it here.
std::unique_ptr<SignedExchangeCertificateChain> cert_chain = std::unique_ptr<SignedExchangeCertificateChain> cert_chain =
SignedExchangeCertificateChain::Parse( SignedExchangeCertificateChain::Parse(
base::as_bytes(base::make_span(body_string_))); SignedExchangeVersion::kB0,
base::as_bytes(base::make_span(body_string_)), devtools_proxy_);
body_string_.clear(); body_string_.clear();
if (!cert_chain) { if (!cert_chain) {
signed_exchange_utils::ReportErrorAndEndTraceEvent( signed_exchange_utils::ReportErrorAndEndTraceEvent(
......
...@@ -5,9 +5,13 @@ ...@@ -5,9 +5,13 @@
#include "content/browser/web_package/signed_exchange_certificate_chain.h" #include "content/browser/web_package/signed_exchange_certificate_chain.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/format_macros.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "components/cbor/cbor_reader.h"
#include "content/browser/web_package/signed_exchange_consts.h" #include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "net/cert/x509_certificate.h" #include "net/cert/x509_certificate.h"
...@@ -39,22 +43,195 @@ bool Consume3Bytes(base::span<const uint8_t>* data, uint32_t* out) { ...@@ -39,22 +43,195 @@ bool Consume3Bytes(base::span<const uint8_t>* data, uint32_t* out) {
return true; return true;
} }
} // namespace std::unique_ptr<SignedExchangeCertificateChain> ParseB0(
base::span<const uint8_t> message,
// static SignedExchangeDevToolsProxy* devtools_proxy) {
std::unique_ptr<SignedExchangeCertificateChain> TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
SignedExchangeCertificateChain::Parse( "SignedExchangeCertificateChain::ParseB0");
base::span<const uint8_t> cert_response_body) {
base::Optional<std::vector<base::StringPiece>> der_certs = base::Optional<std::vector<base::StringPiece>> der_certs =
GetCertChainFromMessage(cert_response_body); SignedExchangeCertificateChain::GetCertChainFromMessage(message);
if (!der_certs) if (!der_certs) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB0",
"Failed to parse the response as a TLS 1.3 Certificate message.");
return nullptr; return nullptr;
}
scoped_refptr<net::X509Certificate> cert = scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromDERCertChain(*der_certs); net::X509Certificate::CreateFromDERCertChain(*der_certs);
if (!cert) if (!cert) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB0",
"X509Certificate::CreateFromDERCertChain failed.");
return nullptr;
}
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertificateChain::ParseB1");
// The V0 certificate format doesn't support OCSP nor SCT.
return base::WrapUnique(
new SignedExchangeCertificateChain(cert, "" /* ocsp */, "" /* sct */));
}
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cert-chain-format
std::unique_ptr<SignedExchangeCertificateChain> ParseB1(
base::span<const uint8_t> message,
SignedExchangeDevToolsProxy* devtools_proxy) {
TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertificateChain::ParseB1");
cbor::CBORReader::DecoderError error;
base::Optional<cbor::CBORValue> value =
cbor::CBORReader::Read(message, &error);
if (!value.has_value()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf("Failed to decode CBORValue. CBOR error: %s",
cbor::CBORReader::ErrorCodeToString(error)));
return nullptr;
}
if (!value->is_array()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf(
"Expected top-level CBORValue to be an array. Actual type: %d",
static_cast<int>(value->type())));
return nullptr;
}
const cbor::CBORValue::ArrayValue& top_level_array = value->GetArray();
// Expect at least 2 elements (magic string and main certificate).
if (top_level_array.size() < 2) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf(
"Expected top-level array to have at least 2 elements."
"Actual element count: %" PRIuS,
top_level_array.size()));
return nullptr;
}
if (!top_level_array[0].is_string() ||
top_level_array[0].GetString() != kCertChainCborMagic) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"First element of cert chain CBOR does not match the magic string.");
return nullptr;
}
std::vector<base::StringPiece> der_certs;
der_certs.reserve(top_level_array.size() - 1);
std::string ocsp;
std::string sct;
for (size_t i = 1; i < top_level_array.size(); i++) {
if (!top_level_array[i].is_map()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf(
"Expected certificate map, got non-map type at index %zu."
" Actual type: %d",
i, static_cast<int>(top_level_array[i].type())));
return nullptr;
}
const cbor::CBORValue::MapValue& cert_map = top_level_array[i].GetMap();
// Step 1. Each cert value MUST be a DER-encoded X.509v3 certificate
// ([RFC5280]). Other key/value pairs in the same array item define
// properties of this certificate. [spec text]
auto cert_iter = cert_map.find(cbor::CBORValue(kCertKey));
if (cert_iter == cert_map.end() || !cert_iter->second.is_bytestring()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf(
"cert is not found or not a bytestring, at index %zu.", i));
return nullptr;
}
der_certs.push_back(cert_iter->second.GetBytestringAsString());
auto ocsp_iter = cert_map.find(cbor::CBORValue(kOcspKey));
if (i == 1) {
// Step 2. The first certificate’s ocsp value if any MUST be a complete,
// DER-encoded OCSP response for that certificate (using the ASN.1 type
// OCSPResponse defined in [RFC2560]). ... [spec text]
if (ocsp_iter == cert_map.end() || !ocsp_iter->second.is_bytestring()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"ocsp is not a bytestring, or not found in the first cert map.");
return nullptr;
}
ocsp = ocsp_iter->second.GetBytestringAsString().as_string();
if (ocsp.empty()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"ocsp must not be empty.");
return nullptr;
}
} else if (ocsp_iter != cert_map.end()) {
// Step 2. ... Subsequent certificates MUST NOT have an ocsp value. [spec
// text]
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
base::StringPrintf(
"ocsp value found in a subsequent cert map, at index %zu.", i));
return nullptr;
}
// Step 3. Each certificate’s sct value MUST be a
// SignedCertificateTimestampList for that certificate as defined by Section
// 3.3 of [RFC6962]. [spec text]
//
// We use SCTs only of the main certificate.
// TODO(crbug.com/815025): Update the spec text once
// https://github.com/WICG/webpackage/issues/175 is resolved.
if (i == 1) {
auto sct_iter = cert_map.find(cbor::CBORValue(kSctKey));
if (sct_iter != cert_map.end()) {
if (!sct_iter->second.is_bytestring()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"sct is not a bytestring.");
return nullptr;
}
sct = sct_iter->second.GetBytestringAsString().as_string();
if (sct.empty()) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"sct must not be empty.");
return nullptr;
}
}
}
}
scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromDERCertChain(der_certs);
if (!cert) {
signed_exchange_utils::ReportErrorAndEndTraceEvent(
devtools_proxy, "SignedExchangeCertificateChain::ParseB1",
"X509Certificate::CreateFromDERCertChain failed.");
return nullptr; return nullptr;
return base::WrapUnique(new SignedExchangeCertificateChain(cert)); }
TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SignedExchangeCertificateChain::ParseB1");
return base::WrapUnique(new SignedExchangeCertificateChain(cert, ocsp, sct));
}
} // namespace
// static
std::unique_ptr<SignedExchangeCertificateChain>
SignedExchangeCertificateChain::Parse(
SignedExchangeVersion version,
base::span<const uint8_t> cert_response_body,
SignedExchangeDevToolsProxy* devtools_proxy) {
switch (version) {
case SignedExchangeVersion::kB0:
return ParseB0(cert_response_body, devtools_proxy);
case SignedExchangeVersion::kB1:
return ParseB1(cert_response_body, devtools_proxy);
}
NOTREACHED();
return nullptr;
} }
// static // static
...@@ -115,8 +292,10 @@ SignedExchangeCertificateChain::GetCertChainFromMessage( ...@@ -115,8 +292,10 @@ SignedExchangeCertificateChain::GetCertChainFromMessage(
} }
SignedExchangeCertificateChain::SignedExchangeCertificateChain( SignedExchangeCertificateChain::SignedExchangeCertificateChain(
scoped_refptr<net::X509Certificate> cert) scoped_refptr<net::X509Certificate> cert,
: cert_(cert) { const std::string& ocsp,
const std::string& sct)
: cert_(cert), ocsp_(ocsp), sct_(sct) {
DCHECK(cert); DCHECK(cert);
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/string_piece_forward.h" #include "base/strings/string_piece_forward.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
namespace net { namespace net {
...@@ -19,13 +20,23 @@ class X509Certificate; ...@@ -19,13 +20,23 @@ class X509Certificate;
namespace content { namespace content {
class SignedExchangeDevToolsProxy;
// SignedExchangeCertificateChain contains all information in signed exchange // SignedExchangeCertificateChain contains all information in signed exchange
// certificate chain. // certificate chain.
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cert-chain-format // https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cert-chain-format
class CONTENT_EXPORT SignedExchangeCertificateChain { class CONTENT_EXPORT SignedExchangeCertificateChain {
public: public:
static std::unique_ptr<SignedExchangeCertificateChain> Parse( static std::unique_ptr<SignedExchangeCertificateChain> Parse(
base::span<const uint8_t> cert_response_body); SignedExchangeVersion version,
base::span<const uint8_t> cert_response_body,
SignedExchangeDevToolsProxy* devtools_proxy);
// Regular consumers should use the static Parse() rather than directly
// calling this.
SignedExchangeCertificateChain(scoped_refptr<net::X509Certificate> cert,
const std::string& ocsp,
const std::string& sct);
// Parses a TLS 1.3 Certificate message containing X.509v3 certificates and // Parses a TLS 1.3 Certificate message containing X.509v3 certificates and
// returns a vector of cert_data. Returns nullopt when failed to parse. // returns a vector of cert_data. Returns nullopt when failed to parse.
...@@ -35,12 +46,15 @@ class CONTENT_EXPORT SignedExchangeCertificateChain { ...@@ -35,12 +46,15 @@ class CONTENT_EXPORT SignedExchangeCertificateChain {
~SignedExchangeCertificateChain(); ~SignedExchangeCertificateChain();
const scoped_refptr<net::X509Certificate>& cert() const { return cert_; } const scoped_refptr<net::X509Certificate>& cert() const { return cert_; }
const std::string& ocsp() const { return ocsp_; }
const std::string& sct() const { return sct_; }
private: private:
explicit SignedExchangeCertificateChain(
scoped_refptr<net::X509Certificate> cert);
scoped_refptr<net::X509Certificate> cert_; scoped_refptr<net::X509Certificate> cert_;
// Version b1 specific fields:
std::string ocsp_;
std::string sct_;
}; };
} // namespace content } // namespace content
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
namespace content { namespace content {
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
SignedExchangeCertificateChain::GetCertChainFromMessage( SignedExchangeCertificateChain::Parse(SignedExchangeVersion::kB0,
base::make_span(data, size)); base::make_span(data, size), nullptr);
SignedExchangeCertificateChain::Parse(SignedExchangeVersion::kB1,
base::make_span(data, size), nullptr);
return 0; return 0;
} }
......
...@@ -10,6 +10,8 @@ namespace content { ...@@ -10,6 +10,8 @@ namespace content {
constexpr char kAcceptHeaderSignedExchangeSuffix[] = constexpr char kAcceptHeaderSignedExchangeSuffix[] =
",application/signed-exchange;v=b0"; ",application/signed-exchange;v=b0";
enum class SignedExchangeVersion { kB0, kB1 };
// Field names defined in the application/signed-exchange content type: // Field names defined in the application/signed-exchange content type:
// https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#application-signed-exchange // https://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#application-signed-exchange
...@@ -22,6 +24,10 @@ constexpr char kSignature[] = "signature"; ...@@ -22,6 +24,10 @@ constexpr char kSignature[] = "signature";
constexpr char kStatusKey[] = ":status"; constexpr char kStatusKey[] = ":status";
constexpr char kUrlKey[] = ":url"; constexpr char kUrlKey[] = ":url";
constexpr char kValidityUrlKey[] = "validityUrl"; constexpr char kValidityUrlKey[] = "validityUrl";
constexpr char kCertChainCborMagic[] = u8"\U0001F4DC\u26D3"; // "📜⛓"
constexpr char kCertKey[] = "cert";
constexpr char kOcspKey[] = "ocsp";
constexpr char kSctKey[] = "sct";
} // namespace content } // namespace content
......
...@@ -65,7 +65,8 @@ class MockSignedExchangeCertFetcherFactory ...@@ -65,7 +65,8 @@ class MockSignedExchangeCertFetcherFactory
EXPECT_EQ(cert_url, expected_cert_url_); EXPECT_EQ(cert_url, expected_cert_url_);
auto cert_chain = SignedExchangeCertificateChain::Parse( auto cert_chain = SignedExchangeCertificateChain::Parse(
base::as_bytes(base::make_span(cert_str_))); SignedExchangeVersion::kB0, base::as_bytes(base::make_span(cert_str_)),
devtools_proxy);
EXPECT_TRUE(cert_chain); EXPECT_TRUE(cert_chain);
base::SequencedTaskRunnerHandle::Get()->PostTask( base::SequencedTaskRunnerHandle::Get()->PostTask(
......
...@@ -76,3 +76,15 @@ gen-signedexchange \ ...@@ -76,3 +76,15 @@ gen-signedexchange \
-responseHeader 'Content-Type: text/plain; charset=iso-8859-1' \ -responseHeader 'Content-Type: text/plain; charset=iso-8859-1' \
-date 2018-03-12T05:53:20Z \ -date 2018-03-12T05:53:20Z \
-o test.example.org_hello.txt.htxg -o test.example.org_hello.txt.htxg
# 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
# Currently OCSP and SCT are just dummy data.
echo -n OCSP >/tmp/ocsp; echo -n SCT >/tmp/sct
# Generate the certificate chain CBOR for "*.example.org".
gen-certurl -pem /tmp/wildcard_example.org.public.pem \
-ocsp /tmp/ocsp -sct /tmp/sct > wildcard_example.org.public.pem.cbor
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