// 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 "quiche/quic/core/crypto/proof_source_x509.h"

#include <memory>

#include "absl/strings/string_view.h"
#include "openssl/ssl.h"
#include "quiche/quic/core/crypto/certificate_view.h"
#include "quiche/quic/core/crypto/proof_source.h"
#include "quiche/quic/platform/api/quic_expect_bug.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/test_certificates.h"
#include "quiche/common/platform/api/quiche_reference_counted.h"

namespace quic {
namespace test {
namespace {

quiche::QuicheReferenceCountedPointer<ProofSource::Chain> MakeChain(
    absl::string_view cert) {
  return quiche::QuicheReferenceCountedPointer<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)) {
    QUICHE_CHECK(test_key_ != nullptr);
    QUICHE_CHECK(wildcard_key_ != nullptr);
  }

 protected:
  quiche::QuicheReferenceCountedPointer<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);
  EXPECT_QUIC_BUG((void)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.
  bool cert_matched_sni;
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "unknown.test", &cert_matched_sni)
                ->certs[0],
            kTestCertificate);
  EXPECT_FALSE(cert_matched_sni);
  // mail.example.org is explicitly a SubjectAltName in kTestCertificate.
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "mail.example.org", &cert_matched_sni)
                ->certs[0],
            kTestCertificate);
  EXPECT_TRUE(cert_matched_sni);
  // www.foo.test is in kWildcardCertificate.
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "www.foo.test", &cert_matched_sni)
                ->certs[0],
            kWildcardCertificate);
  EXPECT_TRUE(cert_matched_sni);
  // *.wildcard.test is in kWildcardCertificate.
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "www.wildcard.test", &cert_matched_sni)
                ->certs[0],
            kWildcardCertificate);
  EXPECT_TRUE(cert_matched_sni);
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "etc.wildcard.test", &cert_matched_sni)
                ->certs[0],
            kWildcardCertificate);
  EXPECT_TRUE(cert_matched_sni);
  // wildcard.test itself is not in kWildcardCertificate.
  EXPECT_EQ(proof_source
                ->GetCertChain(QuicSocketAddress(), QuicSocketAddress(),
                               "wildcard.test", &cert_matched_sni)
                ->certs[0],
            kTestCertificate);
  EXPECT_FALSE(cert_matched_sni);
}

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(), QuicSocketAddress(),
                                    "example.com", SSL_SIGN_RSA_PSS_RSAE_SHA256,
                                    "Test data", std::make_unique<Callback>());
}

}  // namespace
}  // namespace test
}  // namespace quic
