Add quic::CreateSelfSignedCert function to create a self-signed, der-encoded X.509 certificate. This can be used by unit tests as well as client certs. PiperOrigin-RevId: 418057683
diff --git a/quic/core/crypto/boring_utils.h b/quic/core/crypto/boring_utils.h index 2927b89..d623fa2 100644 --- a/quic/core/crypto/boring_utils.h +++ b/quic/core/crypto/boring_utils.h
@@ -23,6 +23,12 @@ return result; } +inline QUIC_EXPORT_PRIVATE bool AddStringToCbb(CBB* cbb, + absl::string_view piece) { + return 1 == CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(piece.data()), + piece.size()); +} + } // namespace quic #endif // QUICHE_QUIC_CORE_CRYPTO_BORING_UTILS_H_
diff --git a/quic/core/crypto/certificate_util.cc b/quic/core/crypto/certificate_util.cc new file mode 100644 index 0000000..550adff --- /dev/null +++ b/quic/core/crypto/certificate_util.cc
@@ -0,0 +1,280 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/crypto/certificate_util.h" + +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec_key.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/pkcs7.h" +#include "third_party/boringssl/src/include/openssl/pool.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" +#include "third_party/boringssl/src/include/openssl/stack.h" +#include "quic/core/crypto/boring_utils.h" +#include "quic/platform/api/quic_logging.h" + +namespace quic { +namespace { +bool AddEcdsa256SignatureAlgorithm(CBB* cbb) { + // See RFC 5758. This is the encoding of OID 1.2.840.10045.4.3.2. + static const uint8_t kEcdsaWithSha256[] = {0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02}; + + // An AlgorithmIdentifier is described in RFC 5280, 4.1.1.2. + CBB sequence, oid; + if (!CBB_add_asn1(cbb, &sequence, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&sequence, &oid, CBS_ASN1_OBJECT)) { + return false; + } + + if (!CBB_add_bytes(&oid, kEcdsaWithSha256, sizeof(kEcdsaWithSha256))) { + return false; + } + + // RFC 5758, section 3.2: ecdsa-with-sha256 MUST omit the parameters field. + return CBB_flush(cbb); +} + +// Adds an X.509 Name with the specified distinguished name to |cbb|. +bool AddName(CBB* cbb, absl::string_view name) { + // See RFC 4519. + static const uint8_t kCommonName[] = {0x55, 0x04, 0x03}; + static const uint8_t kCountryName[] = {0x55, 0x04, 0x06}; + static const uint8_t kOrganizationName[] = {0x55, 0x04, 0x0a}; + static const uint8_t kOrganizationalUnitName[] = {0x55, 0x04, 0x0b}; + + std::vector<std::string> attributes = + absl::StrSplit(name, ',', absl::SkipEmpty()); + + if (attributes.empty()) { + QUIC_LOG(ERROR) << "Missing DN or wrong format"; + return false; + } + + // See RFC 5280, section 4.1.2.4. + CBB rdns; + if (!CBB_add_asn1(cbb, &rdns, CBS_ASN1_SEQUENCE)) { + return false; + } + + for (const std::string& attribute : attributes) { + std::vector<std::string> parts = + absl::StrSplit(absl::StripAsciiWhitespace(attribute), '='); + if (parts.size() != 2) { + QUIC_LOG(ERROR) << "Wrong DN format at " + attribute; + return false; + } + + const std::string& type_string = parts[0]; + const std::string& value_string = parts[1]; + absl::Span<const uint8_t> type_bytes; + if (type_string == "CN") { + type_bytes = kCommonName; + } else if (type_string == "C") { + type_bytes = kCountryName; + } else if (type_string == "O") { + type_bytes = kOrganizationName; + } else if (type_string == "OU") { + type_bytes = kOrganizationalUnitName; + } else { + QUIC_LOG(ERROR) << "Unrecognized type " + type_string; + return false; + } + + CBB rdn, attr, type, value; + if (!CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET) || + !CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&type, type_bytes.data(), type_bytes.size()) || + !CBB_add_asn1(&attr, &value, + type_string == "C" ? CBS_ASN1_PRINTABLESTRING + : CBS_ASN1_UTF8STRING) || + !AddStringToCbb(&value, value_string) || !CBB_flush(&rdns)) { + return false; + } + } + if (!CBB_flush(cbb)) { + return false; + } + return true; +} + +bool CBBAddTime(CBB* cbb, const CertificateTimestamp& timestamp) { + CBB child; + std::string formatted_time; + + // Per RFC 5280, 4.1.2.5, times which fit in UTCTime must be encoded as + // UTCTime rather than GeneralizedTime. + const bool is_utc_time = (1950 <= timestamp.year && timestamp.year < 2050); + if (is_utc_time) { + uint16_t year = timestamp.year - 1900; + if (year >= 100) { + year -= 100; + } + formatted_time = absl::StrFormat("%02d", year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_UTCTIME)) { + return false; + } + } else { + formatted_time = absl::StrFormat("%04d", timestamp.year); + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_GENERALIZEDTIME)) { + return false; + } + } + + absl::StrAppendFormat(&formatted_time, "%02d%02d%02d%02d%02dZ", + timestamp.month, timestamp.day, timestamp.hour, + timestamp.minute, timestamp.second); + + static const size_t kGeneralizedTimeLength = 15; + static const size_t kUTCTimeLength = 13; + QUICHE_DCHECK_EQ(formatted_time.size(), + is_utc_time ? kUTCTimeLength : kGeneralizedTimeLength); + + return AddStringToCbb(&child, formatted_time) && CBB_flush(cbb); +} + +bool CBBAddExtension(CBB* extensions, absl::Span<const uint8_t> oid, + bool critical, absl::Span<const uint8_t> contents) { + CBB extension, cbb_oid, cbb_contents; + if (!CBB_add_asn1(extensions, &extension, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&extension, &cbb_oid, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&cbb_oid, oid.data(), oid.size()) || + (critical && !CBB_add_asn1_bool(&extension, 1)) || + !CBB_add_asn1(&extension, &cbb_contents, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&cbb_contents, contents.data(), contents.size()) || + !CBB_flush(extensions)) { + return false; + } + + return true; +} + +bool IsEcdsa256Key(const EVP_PKEY& evp_key) { + if (EVP_PKEY_id(&evp_key) != EVP_PKEY_EC) { + return false; + } + const EC_KEY* key = EVP_PKEY_get0_EC_KEY(&evp_key); + if (key == nullptr) { + return false; + } + const EC_GROUP* group = EC_KEY_get0_group(key); + if (group == nullptr) { + return false; + } + return EC_GROUP_get_curve_name(group) == NID_X9_62_prime256v1; +} + +} // namespace + +bssl::UniquePtr<EVP_PKEY> MakeKeyPairForSelfSignedCertificate() { + bssl::UniquePtr<EVP_PKEY_CTX> context( + EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); + if (!context) { + return nullptr; + } + if (EVP_PKEY_keygen_init(context.get()) != 1) { + return nullptr; + } + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context.get(), + NID_X9_62_prime256v1) != 1) { + return nullptr; + } + EVP_PKEY* raw_key = nullptr; + if (EVP_PKEY_keygen(context.get(), &raw_key) != 1) { + return nullptr; + } + return bssl::UniquePtr<EVP_PKEY>(raw_key); +} + +std::string CreateSelfSignedCertificate(EVP_PKEY& key, + const CertificateOptions& options) { + std::string error; + if (!IsEcdsa256Key(key)) { + QUIC_LOG(ERROR) << "CreateSelfSignedCert only accepts ECDSA P-256 keys"; + return error; + } + + // See RFC 5280, section 4.1. First, construct the TBSCertificate. + bssl::ScopedCBB cbb; + CBB tbs_cert, version, validity; + uint8_t* tbs_cert_bytes; + size_t tbs_cert_len; + + if (!CBB_init(cbb.get(), 64) || + !CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&tbs_cert, &version, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBB_add_asn1_uint64(&version, 2) || // X.509 version 3 + !CBB_add_asn1_uint64(&tbs_cert, options.serial_number) || + !AddEcdsa256SignatureAlgorithm(&tbs_cert) || // signature algorithm + !AddName(&tbs_cert, options.subject) || // issuer + !CBB_add_asn1(&tbs_cert, &validity, CBS_ASN1_SEQUENCE) || + !CBBAddTime(&validity, options.validity_start) || + !CBBAddTime(&validity, options.validity_end) || + !AddName(&tbs_cert, options.subject) || // subject + !EVP_marshal_public_key(&tbs_cert, &key)) { // subjectPublicKeyInfo + return error; + } + + CBB outer_extensions, extensions; + if (!CBB_add_asn1(&tbs_cert, &outer_extensions, + 3 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED) || + !CBB_add_asn1(&outer_extensions, &extensions, CBS_ASN1_SEQUENCE)) { + return error; + } + + // Key Usage + constexpr uint8_t kKeyUsageOid[] = {0x55, 0x1d, 0x0f}; + constexpr uint8_t kKeyUsageContent[] = { + 0x3, // BIT STRING + 0x2, // Length + 0x0, // Unused bits + 0x80, // bit(0): digitalSignature + }; + CBBAddExtension(&extensions, kKeyUsageOid, true, kKeyUsageContent); + + // TODO(wub): Add more extensions here if needed. + + if (!CBB_finish(cbb.get(), &tbs_cert_bytes, &tbs_cert_len)) { + return error; + } + + bssl::UniquePtr<uint8_t> delete_tbs_cert_bytes(tbs_cert_bytes); + + // Sign the TBSCertificate and write the entire certificate. + CBB cert, signature; + bssl::ScopedEVP_MD_CTX ctx; + uint8_t* sig_out; + size_t sig_len; + uint8_t* cert_bytes; + size_t cert_len; + if (!CBB_init(cbb.get(), tbs_cert_len) || + !CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE) || + !CBB_add_bytes(&cert, tbs_cert_bytes, tbs_cert_len) || + !AddEcdsa256SignatureAlgorithm(&cert) || + !CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING) || + !CBB_add_u8(&signature, 0 /* no unused bits */) || + !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, &key) || + // Compute the maximum signature length. + !EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_reserve(&signature, &sig_out, sig_len) || + // Actually sign the TBSCertificate. + !EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes, + tbs_cert_len) || + !CBB_did_write(&signature, sig_len) || + !CBB_finish(cbb.get(), &cert_bytes, &cert_len)) { + return error; + } + bssl::UniquePtr<uint8_t> delete_cert_bytes(cert_bytes); + return std::string(reinterpret_cast<char*>(cert_bytes), cert_len); +} + +} // namespace quic
diff --git a/quic/core/crypto/certificate_util.h b/quic/core/crypto/certificate_util.h new file mode 100644 index 0000000..ebc1cf4 --- /dev/null +++ b/quic/core/crypto/certificate_util.h
@@ -0,0 +1,46 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ +#define QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "quic/core/quic_time.h" +#include "quic/platform/api/quic_export.h" + +namespace quic { + +struct QUIC_NO_EXPORT CertificateTimestamp { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct QUIC_NO_EXPORT CertificateOptions { + absl::string_view subject; + uint64_t serial_number; + CertificateTimestamp validity_start; // a.k.a not_valid_before + CertificateTimestamp validity_end; // a.k.a not_valid_after +}; + +// Creates a ECDSA P-256 key pair. +QUIC_EXPORT_PRIVATE bssl::UniquePtr<EVP_PKEY> +MakeKeyPairForSelfSignedCertificate(); + +// Creates a self-signed, DER-encoded X.509 certificate. +// |key| must be a ECDSA P-256 key. +// This is mostly stolen from Chromium's net/cert/x509_util.h, with +// modifications to make it work in QUICHE. +QUIC_EXPORT_PRIVATE std::string CreateSelfSignedCertificate( + EVP_PKEY& key, const CertificateOptions& options); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_CERTIFICATE_UTIL_H_
diff --git a/quic/core/crypto/certificate_util_test.cc b/quic/core/crypto/certificate_util_test.cc new file mode 100644 index 0000000..1ffe6f5 --- /dev/null +++ b/quic/core/crypto/certificate_util_test.cc
@@ -0,0 +1,49 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/crypto/certificate_util.h" + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "quic/core/crypto/certificate_view.h" +#include "quic/platform/api/quic_test.h" +#include "quic/platform/api/quic_test_output.h" + +namespace quic { +namespace test { +namespace { + +TEST(CertificateUtilTest, CreateSelfSignedCertificate) { + bssl::UniquePtr<EVP_PKEY> key = MakeKeyPairForSelfSignedCertificate(); + ASSERT_NE(key, nullptr); + + CertificatePrivateKey cert_key(std::move(key)); + + CertificateOptions options; + options.subject = "CN=subject"; + options.serial_number = 0x12345678; + options.validity_start = {2020, 1, 1, 0, 0, 0}; + options.validity_end = {2049, 12, 31, 0, 0, 0}; + std::string der_cert = + CreateSelfSignedCertificate(*cert_key.private_key(), options); + ASSERT_FALSE(der_cert.empty()); + + QuicSaveTestOutput("CertificateUtilTest_CreateSelfSignedCert.crt", der_cert); + + std::unique_ptr<CertificateView> cert_view = + CertificateView::ParseSingleCertificate(der_cert); + ASSERT_NE(cert_view, nullptr); + EXPECT_EQ(cert_view->public_key_type(), PublicKeyType::kP256); + + absl::optional<std::string> subject = cert_view->GetHumanReadableSubject(); + ASSERT_TRUE(subject.has_value()); + EXPECT_EQ(*subject, options.subject); + + EXPECT_TRUE( + cert_key.ValidForSignatureAlgorithm(SSL_SIGN_ECDSA_SECP256R1_SHA256)); + EXPECT_TRUE(cert_key.MatchesPublicKey(*cert_view)); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quic/core/tls_server_handshaker_test.cc b/quic/core/tls_server_handshaker_test.cc index 214866c..01fb7ab 100644 --- a/quic/core/tls_server_handshaker_test.cc +++ b/quic/core/tls_server_handshaker_test.cc
@@ -10,6 +10,7 @@ #include "absl/base/macros.h" #include "absl/strings/string_view.h" +#include "quic/core/crypto/certificate_util.h" #include "quic/core/crypto/client_proof_source.h" #include "quic/core/crypto/proof_source.h" #include "quic/core/crypto/quic_random.h" @@ -49,21 +50,6 @@ const char kServerHostname[] = "test.example.com"; const uint16_t kServerPort = 443; -QuicReferenceCountedPointer<ClientProofSource::Chain> TestClientCertChain() { - return QuicReferenceCountedPointer<ClientProofSource::Chain>( - new ClientProofSource::Chain({std::string(kTestCertificate)})); -} - -CertificatePrivateKey TestClientCertPrivateKey() { - CBS private_key_cbs; - CBS_init(&private_key_cbs, - reinterpret_cast<const uint8_t*>(kTestCertificatePrivateKey.data()), - kTestCertificatePrivateKey.size()); - - return CertificatePrivateKey( - bssl::UniquePtr<EVP_PKEY>(EVP_parse_private_key(&private_key_cbs))); -} - struct TestParams { ParsedQuicVersion version; bool disable_resumption; @@ -400,6 +386,34 @@ } protected: + // Setup the client to send a (self-signed) client cert to the server, if + // requested. InitializeFakeClient() must be called after this to take effect. + bool SetupClientCert() { + auto client_proof_source = std::make_unique<DefaultClientProofSource>(); + + CertificatePrivateKey client_cert_key( + MakeKeyPairForSelfSignedCertificate()); + + CertificateOptions options; + options.subject = "CN=subject"; + options.serial_number = 0x12345678; + options.validity_start = {2020, 1, 1, 0, 0, 0}; + options.validity_end = {2049, 12, 31, 0, 0, 0}; + std::string der_cert = + CreateSelfSignedCertificate(*client_cert_key.private_key(), options); + + QuicReferenceCountedPointer<ClientProofSource::Chain> client_cert_chain( + new ClientProofSource::Chain({der_cert})); + + if (!client_proof_source->AddCertAndKey({"*"}, client_cert_chain, + std::move(client_cert_key))) { + return false; + } + + client_crypto_config_->set_proof_source(std::move(client_proof_source)); + return true; + } + // Every connection gets its own MockQuicConnectionHelper and // MockAlarmFactory, tracked separately from the server and client state so // their lifetimes persist through the whole test. @@ -901,10 +915,7 @@ } TEST_P(TlsServerHandshakerTest, RequestClientCert) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); initial_client_cert_mode_ = ClientCertMode::kRequest; @@ -924,10 +935,7 @@ } TEST_P(TlsServerHandshakerTest, RequestClientCertByDelayedSslConfig) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); QuicDelayedSSLConfig delayed_ssl_config; @@ -966,10 +974,7 @@ } TEST_P(TlsServerHandshakerTest, RequestAndRequireClientCert) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); initial_client_cert_mode_ = ClientCertMode::kRequire; @@ -990,10 +995,7 @@ } TEST_P(TlsServerHandshakerTest, RequestAndRequireClientCertByDelayedSslConfig) { - auto client_proof_source = std::make_unique<DefaultClientProofSource>(); - ASSERT_TRUE(client_proof_source->AddCertAndKey({"*"}, TestClientCertChain(), - TestClientCertPrivateKey())); - client_crypto_config_->set_proof_source(std::move(client_proof_source)); + ASSERT_TRUE(SetupClientCert()); InitializeFakeClient(); QuicDelayedSSLConfig delayed_ssl_config;