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