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