Add ProofSourceX509, a ProofSource that uses X.509 certificates supplied to it. This will be used by the public API, and also hopefully to make ProofSourceForTesting platform-independent at some point. gfe-relnote: n/a (not used in production) NOKEYCHECK NOKEYCHECK=True PiperOrigin-RevId: 308302732 Change-Id: I3c68ebd0269f033db060cb28bdee992ae7cf91b3
diff --git a/quic/core/crypto/proof_source_x509.cc b/quic/core/crypto/proof_source_x509.cc new file mode 100644 index 0000000..6caf384 --- /dev/null +++ b/quic/core/crypto/proof_source_x509.cc
@@ -0,0 +1,132 @@ +// Copyright 2020 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 "net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h" + +#include <memory> + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h" +#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" +#include "net/third_party/quiche/src/quic/core/quic_data_writer.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_endian.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" + +namespace quic { + +std::unique_ptr<ProofSourceX509> ProofSourceX509::Create( + QuicReferenceCountedPointer<Chain> default_chain, + CertificatePrivateKey default_key) { + std::unique_ptr<ProofSourceX509> result(new ProofSourceX509()); + if (!result->AddCertificateChain(default_chain, std::move(default_key))) { + return nullptr; + } + result->default_certificate_ = &result->certificates_.front(); + return result; +} + +void ProofSourceX509::GetProof( + const QuicSocketAddress& /*server_address*/, + const std::string& hostname, + const std::string& server_config, + QuicTransportVersion /*transport_version*/, + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr<ProofSource::Callback> callback) { + QuicCryptoProof proof; + + size_t payload_size = sizeof(kProofSignatureLabel) + sizeof(uint32_t) + + chlo_hash.size() + server_config.size(); + auto payload = std::make_unique<char[]>(payload_size); + QuicDataWriter payload_writer(payload_size, payload.get(), + quiche::Endianness::HOST_BYTE_ORDER); + bool success = payload_writer.WriteBytes(kProofSignatureLabel, + sizeof(kProofSignatureLabel)) && + payload_writer.WriteUInt32(chlo_hash.size()) && + payload_writer.WriteStringPiece(chlo_hash) && + payload_writer.WriteStringPiece(server_config); + if (!success) { + callback->Run(/*ok=*/false, nullptr, proof, nullptr); + return; + } + + Certificate* certificate = GetCertificate(hostname); + proof.signature = certificate->key.Sign( + quiche::QuicheStringPiece(payload.get(), payload_size), + SSL_SIGN_RSA_PSS_RSAE_SHA256); + callback->Run(/*ok=*/!proof.signature.empty(), certificate->chain, proof, + nullptr); +} + +QuicReferenceCountedPointer<ProofSource::Chain> ProofSourceX509::GetCertChain( + const QuicSocketAddress& /*server_address*/, + const std::string& hostname) { + return GetCertificate(hostname)->chain; +} + +void ProofSourceX509::ComputeTlsSignature( + const QuicSocketAddress& /*server_address*/, + const std::string& hostname, + uint16_t signature_algorithm, + quiche::QuicheStringPiece in, + std::unique_ptr<ProofSource::SignatureCallback> callback) { + std::string signature = + GetCertificate(hostname)->key.Sign(in, signature_algorithm); + callback->Run(/*ok=*/!signature.empty(), signature, nullptr); +} + +ProofSource::TicketCrypter* ProofSourceX509::SessionTicketCrypter() { + return nullptr; +} + +bool ProofSourceX509::AddCertificateChain( + QuicReferenceCountedPointer<Chain> chain, + CertificatePrivateKey key) { + if (chain->certs.empty()) { + QUIC_BUG << "Empty certificate chain supplied."; + return false; + } + + std::unique_ptr<CertificateView> leaf = + CertificateView::ParseSingleCertificate(chain->certs[0]); + if (leaf == nullptr) { + QUIC_BUG << "Unable to parse X.509 leaf certificate in the supplied chain."; + return false; + } + if (!key.MatchesPublicKey(*leaf)) { + QUIC_BUG << "Private key does not match the leaf certificate."; + return false; + } + + certificates_.push_front(Certificate{ + .chain = chain, + .key = std::move(key), + }); + Certificate* certificate = &certificates_.front(); + + for (quiche::QuicheStringPiece host : leaf->subject_alt_name_domains()) { + certificate_map_[std::string(host)] = certificate; + } + return true; +} + +ProofSourceX509::Certificate* ProofSourceX509::GetCertificate( + const std::string& hostname) const { + auto it = certificate_map_.find(hostname); + if (it != certificate_map_.end()) { + return it->second; + } + auto dot_pos = hostname.find('.'); + if (dot_pos != std::string::npos) { + std::string wildcard = quiche::QuicheStrCat("*", hostname.substr(dot_pos)); + it = certificate_map_.find(wildcard); + if (it != certificate_map_.end()) { + return it->second; + } + } + return default_certificate_; +} + +} // namespace quic
diff --git a/quic/core/crypto/proof_source_x509.h b/quic/core/crypto/proof_source_x509.h new file mode 100644 index 0000000..1fdb81b --- /dev/null +++ b/quic/core/crypto/proof_source_x509.h
@@ -0,0 +1,73 @@ +// Copyright 2020 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_PROOF_SOURCE_X509_H_ +#define QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_X509_H_ + +#include <forward_list> +#include <memory> + +#include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h" +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_macros.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" + +namespace quic { + +// ProofSourceX509 accepts X.509 certificates with private keys and picks a +// certificate internally based on its SubjectAltName value. +class QUIC_EXPORT_PRIVATE ProofSourceX509 : public ProofSource { + public: + // Creates a proof source that uses |default_chain| when no SubjectAltName + // value matches. Returns nullptr if |default_chain| is invalid. + static std::unique_ptr<ProofSourceX509> Create( + QuicReferenceCountedPointer<Chain> default_chain, + CertificatePrivateKey default_key); + + // ProofSource implementation. + void GetProof(const QuicSocketAddress& server_address, + const std::string& hostname, + const std::string& server_config, + QuicTransportVersion transport_version, + quiche::QuicheStringPiece chlo_hash, + std::unique_ptr<Callback> callback) override; + QuicReferenceCountedPointer<Chain> GetCertChain( + const QuicSocketAddress& server_address, + const std::string& hostname) override; + void ComputeTlsSignature( + const QuicSocketAddress& server_address, + const std::string& hostname, + uint16_t signature_algorithm, + quiche::QuicheStringPiece in, + std::unique_ptr<SignatureCallback> callback) override; + TicketCrypter* SessionTicketCrypter() override; + + // Adds a certificate chain to the verifier. Returns false if the chain is + // not valid. Newer certificates will override older certificates with the + // same SubjectAltName value. + QUIC_MUST_USE_RESULT bool AddCertificateChain( + QuicReferenceCountedPointer<Chain> chain, + CertificatePrivateKey key); + + private: + ProofSourceX509() = default; + + struct QUIC_EXPORT_PRIVATE Certificate { + QuicReferenceCountedPointer<Chain> chain; + CertificatePrivateKey key; + }; + + // Looks up certficiate for hostname, returns the default if no certificate is + // found. + Certificate* GetCertificate(const std::string& hostname) const; + + std::forward_list<Certificate> certificates_; + Certificate* default_certificate_; + QuicUnorderedMap<std::string, Certificate*> certificate_map_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_CRYPTO_PROOF_SOURCE_X509_H_
diff --git a/quic/core/crypto/proof_source_x509_test.cc b/quic/core/crypto/proof_source_x509_test.cc new file mode 100644 index 0000000..f4c9146 --- /dev/null +++ b/quic/core/crypto/proof_source_x509_test.cc
@@ -0,0 +1,124 @@ +// Copyright 2020 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 "net/third_party/quiche/src/quic/core/crypto/proof_source_x509.h" + +#include <memory> + +#include "third_party/boringssl/src/include/openssl/ssl.h" +#include "net/third_party/quiche/src/quic/core/crypto/certificate_view.h" +#include "net/third_party/quiche/src/quic/core/crypto/proof_source.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_reference_counted.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/test_tools/test_certificates.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" + +namespace quic { +namespace test { +namespace { + +QuicReferenceCountedPointer<ProofSource::Chain> MakeChain( + quiche::QuicheStringPiece cert) { + return QuicReferenceCountedPointer<ProofSource::Chain>( + new ProofSource::Chain(std::vector<std::string>{std::string(cert)})); +} + +class ProofSourceX509Test : public QuicTest { + public: + ProofSourceX509Test() + : test_chain_(MakeChain(kTestCertificate)), + wildcard_chain_(MakeChain(kWildcardCertificate)), + test_key_( + CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey)), + wildcard_key_(CertificatePrivateKey::LoadFromDer( + kWildcardCertificatePrivateKey)) { + CHECK(test_key_ != nullptr); + CHECK(wildcard_key_ != nullptr); + } + + protected: + QuicReferenceCountedPointer<ProofSource::Chain> test_chain_, wildcard_chain_; + std::unique_ptr<CertificatePrivateKey> test_key_, wildcard_key_; +}; + +TEST_F(ProofSourceX509Test, AddCertificates) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + EXPECT_TRUE(proof_source->AddCertificateChain(wildcard_chain_, + std::move(*wildcard_key_))); +} + +TEST_F(ProofSourceX509Test, AddCertificateKeyMismatch) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + test_key_ = CertificatePrivateKey::LoadFromDer(kTestCertificatePrivateKey); + bool result; + EXPECT_QUIC_BUG(result = proof_source->AddCertificateChain( + wildcard_chain_, std::move(*test_key_)), + "Private key does not match"); +} + +TEST_F(ProofSourceX509Test, CertificateSelection) { + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + ASSERT_TRUE(proof_source->AddCertificateChain(wildcard_chain_, + std::move(*wildcard_key_))); + + // Default certificate. + EXPECT_EQ( + proof_source->GetCertChain(QuicSocketAddress(), "unknown.test")->certs[0], + kTestCertificate); + // mail.example.org is explicitly a SubjectAltName in kTestCertificate. + EXPECT_EQ(proof_source->GetCertChain(QuicSocketAddress(), "mail.example.org") + ->certs[0], + kTestCertificate); + // www.foo.test is in kWildcardCertificate. + EXPECT_EQ( + proof_source->GetCertChain(QuicSocketAddress(), "www.foo.test")->certs[0], + kWildcardCertificate); + // *.wildcard.test is in kWildcardCertificate. + EXPECT_EQ(proof_source->GetCertChain(QuicSocketAddress(), "www.wildcard.test") + ->certs[0], + kWildcardCertificate); + EXPECT_EQ(proof_source->GetCertChain(QuicSocketAddress(), "etc.wildcard.test") + ->certs[0], + kWildcardCertificate); + // wildcard.test itself is not in kWildcardCertificate. + EXPECT_EQ(proof_source->GetCertChain(QuicSocketAddress(), "wildcard.test") + ->certs[0], + kTestCertificate); +} + +TEST_F(ProofSourceX509Test, TlsSignature) { + class Callback : public ProofSource::SignatureCallback { + public: + void Run(bool ok, + std::string signature, + std::unique_ptr<ProofSource::Details> /*details*/) override { + ASSERT_TRUE(ok); + std::unique_ptr<CertificateView> view = + CertificateView::ParseSingleCertificate(kTestCertificate); + EXPECT_TRUE(view->VerifySignature("Test data", signature, + SSL_SIGN_RSA_PSS_RSAE_SHA256)); + } + }; + + std::unique_ptr<ProofSourceX509> proof_source = + ProofSourceX509::Create(test_chain_, std::move(*test_key_)); + ASSERT_TRUE(proof_source != nullptr); + + proof_source->ComputeTlsSignature(QuicSocketAddress(), "example.com", + SSL_SIGN_RSA_PSS_RSAE_SHA256, "Test data", + std::make_unique<Callback>()); +} + +} // namespace +} // namespace test +} // namespace quic