folly/folly/ssl/test/OpenSSLCertUtilsTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/ssl/OpenSSLCertUtils.h>

#include <folly/Format.h>
#include <folly/Range.h>
#include <folly/String.h>
#include <folly/container/Enumerate.h>
#include <folly/portability/GTest.h>
#include <folly/portability/OpenSSL.h>
#include <folly/portability/Time.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <folly/testing/TestUtil.h>

using namespace testing;
using namespace folly;
using folly::test::find_resource;

const char* kTestCertWithoutSan = "folly/ssl/test/tests-cert.pem";
const char* kTestCa = "folly/ssl/test/ca-cert.pem";

// Test key
const std::string kTestKey = folly::stripLeftMargin(R"(
  ----BEGIN EC PRIVATE KEY-----
  MHcCAQEEIBskFwVZ9miFN+SKCFZPe9WEuFGmP+fsecLUnsTN6bOcoAoGCCqGSM49
  AwEHoUQDQgAE7/f4YYOYunAM/VkmjDYDg3AWUgyyTIraWmmQZsnu0bYNV/lLLfNz
  CtHggxGSwEtEe40nNb9C8wQmHUvb7VBBlw==
  -----END EC PRIVATE KEY-----
)");
const std::string kTestCertWithSan = folly::stripLeftMargin(R"(
  -----BEGIN CERTIFICATE-----
  MIIDXDCCAkSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEL
  MAkGA1UECAwCQ0ExDTALBgNVBAoMBEFzb3gxJTAjBgNVBAMMHEFzb3ggQ2VydGlm
  aWNhdGlvbiBBdXRob3JpdHkwHhcNMTcwMjEzMjMyMTAzWhcNNDQwNzAxMjMyMTAz
  WjAwMQswCQYDVQQGEwJVUzENMAsGA1UECgwEQXNveDESMBAGA1UEAwwJMTI3LjAu
  MC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7/f4YYOYunAM/VkmjDYDg3AW
  UgyyTIraWmmQZsnu0bYNV/lLLfNzCtHggxGSwEtEe40nNb9C8wQmHUvb7VBBl6OC
  ASowggEmMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh
  dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRx1kmdZEfXHmWLHpSDI0Lh8hmfwzAf
  BgNVHSMEGDAWgBQX3ykJKb97nxp/6UZJyDvts7noezAxBgNVHREEKjAoghJhbm90
  aGVyZXhhbXBsZS5jb22CEioudGhpcmRleGFtcGxlLmNvbTB4BggrBgEFBQcBAQRs
  MGowaAYIKwYBBQUHMAKGXGh0dHBzOi8vcGhhYnJpY2F0b3IuZmIuY29tL2RpZmZ1
  c2lvbi9GQkNPREUvYnJvd3NlL21hc3Rlci90aS90ZXN0X2NlcnRzL2NhX2NlcnQu
  cGVtP3ZpZXc9cmF3MA0GCSqGSIb3DQEBCwUAA4IBAQCj3FLjLMLudaFDiYo9pAPQ
  NBYNpG27aajQCvnEsYaMAGnNBxUUhv/E4xpnJEhatiCJWlPgGebdjXkpXYkLxnFj
  38UmpfZbNcvPPKxXmjIlkpYeFwcHTAUpFmMXVHdr8FjkDSN+qWHLllMFNAAqp0U6
  4VWjDlq9xCjzNw+8fdcEpwylpPrbNyQHqSO1k+DhM2qPuQfiWPmHe2PbJv8JB3no
  HWGi9SNe0FjtJM3066L0Gj8g/bFDo/pnyKguQyGkS7PaepK5/u5Y2fMMBO/m4+U0
  b9Yb0TvatsqL688CoZcSn73A0yAjptwbD/4HmcVlG2j/y8eTVpXisugu6Xz+QQGu
  -----END CERTIFICATE-----
)");

const std::string kTestCertBundle = folly::stripLeftMargin(R"(
  -----BEGIN CERTIFICATE-----
  MIIDgzCCAmugAwIBAgIJAIkcS3PQcCm+MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV
  BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
  Q29tcGFueSBMdGQxFDASBgNVBAMMC3Rlc3QgY2VydCAxMB4XDTE3MTAyMzIwNTcw
  M1oXDTE4MTAyMzIwNTcwM1owWDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1
  bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEUMBIGA1UEAwwL
  dGVzdCBjZXJ0IDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCplTzR
  6shdhVNbx5HFViiYDBjRYXCWiUeR0/0+XPkyI+DPIGAQ6Mre8WD03GPebYn7j3Lr
  JwgV06BJNvVCLDy0SJbf6ToxGfKWSLEWOoip32nIpb9qxURtx44NUvhChP54hhKI
  zAf8nNlS+qKUYbmixJHeUWO//8wNpsMKDkvtfVUZ6oVV3JPOOihJ+sQ0sIc5x+xk
  3eWfa0cNoZnxu4plQg2O4RlHOv8ruMW6BttpcqQ8I+Rxq+/YOhNQhX+6GZ1+Rs+f
  ddWXYNH6tFxsLIEbgCqHhLGw7g+JRms9R+CxLCpjmhYhR2xgl6KQu/Racr2T/17z
  897VfY7X94PmamidAgMBAAGjUDBOMB0GA1UdDgQWBBRHQvRr2p3/83y1yXiiVnnS
  zObpzTAfBgNVHSMEGDAWgBRHQvRr2p3/83y1yXiiVnnSzObpzTAMBgNVHRMEBTAD
  AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAk61K1sjrS7rrLnGND1o1Q6D2ebgb1wcfU
  WX+ZnhlkUxjSS1nHmaulMftpvzbgrOt7HWZKMXIpetnDSfksrGpw6QJ3VWFIJlH5
  P4x8//pVeI5jQd4W7gIl65tZOc5cEH8aqnzkaGP8YBx6BI6N8px1gZVgePVu3ebR
  eLdrWH2l4VishWOf6rO/ltQdTwRIqj08QNsWmSrRK2d7J/DGA6R9JkdyxeLdxqmB
  2BMwJ7IVR+bWuTzD9Zk5lZseIVFcIksxmQ8jJuZXUdN8WOT/65p9UnN+Cc6+Q7F4
  rlVz+ytcdvaf5mDeqFILDK6btWcUP2Vr1EfRDt/QBrU6OjAVQD+U
  -----END CERTIFICATE-----
  -----BEGIN CERTIFICATE-----
  MIIDgzCCAmugAwIBAgIJAPzrfjTkvHezMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV
  BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
  Q29tcGFueSBMdGQxFDASBgNVBAMMC3Rlc3QgY2VydCAyMB4XDTE3MTAyMzIwNTcx
  NloXDTE4MTAyMzIwNTcxNlowWDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1
  bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEUMBIGA1UEAwwL
  dGVzdCBjZXJ0IDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzy9G/
  NM7Llp+foYxug2Dqc3r9zWtb4PvbRqoz8W0ZRy0GkL3JtOfLWtlz+RCGa//mlGMA
  HLa+Qg77nnjuhO/KCCgQS9fxHY+zcv1VBwzsKmKcju4BCscsTLPsy0SJCXBXSgnH
  S4NMR/K+YozwdikEZRbU4VLJiw44CeJ1h74r2ElHYuOL0SpL8PSlv7kJu3/xWUiV
  L2iWk+y8yKIpCRQ9I7+L0kuhylZAmVBTKtgbdcLfERqQNNWAT7D+p/6CwNmpT9ei
  G2xJ0N4bt3w8kwcZ+IkGwei8Nadix+POe3WVU9K1VXVfoLZ9nNWKRnwIFP4Bsmld
  rP4Uy2IZuhrKE4BPAgMBAAGjUDBOMB0GA1UdDgQWBBQkmeMfPQaax9wCZL16jSSG
  XigBWjAfBgNVHSMEGDAWgBQkmeMfPQaax9wCZL16jSSGXigBWjAMBgNVHRMEBTAD
  AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCXzqxYp1FqMS2M+opCSPezgPDBdE2S9g6d
  HJHV5CLptGnu1vQIlyCXy/7X9b6Qq8UzuYyFacN/37tbNw6sGyTRfL8sEeFYfFoT
  GvgSrRqSM47ZBYx5jW/Uslkc5qbq+v4zeGCq5611stQKsJYIudu0+PjJmgtNF6en
  zTx8B6eS79GRN3/M7/kFLlxeZNCQpmKwvPp8P7JE4ZHUtuzQoKtjdt/etWpS76fV
  Akx7VhCFg/lw80tmgSclq885hYRYc6DOKfUubWOacKVfmHwL4oDiSffBonI7MoH8
  SJbzsCBpVd/tkDADZpxBQplGV7AaDBoNS0qvZHfH5x9R9R5lx9M+
  -----END CERTIFICATE-----
  -----BEGIN CERTIFICATE-----
  MIIDgzCCAmugAwIBAgIJAOzqPJDDfSKDMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV
  BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
  Q29tcGFueSBMdGQxFDASBgNVBAMMC3Rlc3QgY2VydCAzMB4XDTE3MTAyMzIwNTcy
  NVoXDTE4MTAyMzIwNTcyNVowWDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1
  bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEUMBIGA1UEAwwL
  dGVzdCBjZXJ0IDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWqU2b
  eBzaOAja6od84hFfgvitOGrCYqLXMUXe0X7AlldzXV4zHaVyTKdEwDwvKDi5p9OF
  uTxSZkZ0JSPHZeH2/rHXidNMWdtiy5x/5ra1u9ctN7jHeboIxmdpfxoGq7s6cRA5
  oRh0bCNmw+Y7K+1RITmPloB7155RbrJYZR5MOFIaCnZV3j/icKjASTOg3ivXX4lx
  BoHGMYF8rl+51FIJsuXvnBgF+GhadMVSWl4Qy6gLliml1MgujlmFg9/1y/xzdWZg
  yyLI3tvw7fo/NN62u41VQBdCGdpvnVxU4ADu2/T0vhAS+Bh2CMK1OAAw61x1507S
  f68mab9s8at49qefAgMBAAGjUDBOMB0GA1UdDgQWBBQnn76Swsnld6Q1weLgpo/S
  tt0KeTAfBgNVHSMEGDAWgBQnn76Swsnld6Q1weLgpo/Stt0KeTAMBgNVHRMEBTAD
  AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCB0XANIWyP7DYROh6MFQLqeylngd9iUGNe
  BMT4pWu60p5ZX13kK/gbV/P2cayUkkWEMWpzKcIX70IkaB5y/OxVMXUXo94UupsM
  b1T736wHA0TLeL7yDj9OnMYj/qa2r8pAyEObI84KoWRGMHH9UPSRbVMVrhg/agBA
  LA6eZhwiGctkCy09kp+SFbUpv+SMyVp60UrPub6j68Hzd0FioGY01Os7nScuPNo0
  rl2S+G36bcem8Z5MOkJ0LEFi6ctK9JdLcHkr1SVavo3fsYZaIZraJxFGcYUVyLT+
  Rw7ydBokxHWsmVJczuRmEovXcTmgIphti234e7usKjw8M5mGwYfa
  -----END CERTIFICATE-----
)");

const std::map<std::string, std::string> testCertWithSanExts{
    {"1.3.6.1.5.5.7.1.1",
     "0h\x6\b+\x6\x1\x5\x5\a0\x2\x86\\https://phabricator.fb.com/diffusion/"
     "FBCODE/browse/master/ti/test_certs/ca_cert.pem?view=raw"},
    {"2.5.29.35",
     "\x80\x14\x17\xDF\x29\x9\x29\xBF\x7B\x9F\x1A\x7F\xE9\x46\x49\xC8\x3B\xED"
     "\xB3\xB9\xE8\x7B"},
    {"2.5.29.19", "\x0"},
    {"2.16.840.1.113730.1.13", "OpenSSL Generated Certificate"},
    {"2.5.29.17",
     "\x82\x12"
     "anotherexample.com\x82\x12*.thirdexample.com"},
    {"2.5.29.14",
     "\x71\xD6\x49\x9D\x64\x47\xD7\x1E\x65\x8B\x1E\x94\x83\x23\x42\xE1\xF2\x19"
     "\x9F\xC3"},
    {"1.2.3.4.1", "Custom Extension 1"},
    {"1.2.3.4.2", "Custom Extension 2"},
};

class OpenSSLCertUtilsTest : public TestWithParam<bool> {
 public:
  void SetUp() override {
    if (GetParam()) {
      // Run the test with an polluted error stack.
      SSLerr(SSL_F_SSL3_READ_BYTES, SSL_R_SSL_HANDSHAKE_FAILURE);
    }
  }
};

INSTANTIATE_TEST_SUITE_P(OpenSSLCertUtilsTest, OpenSSLCertUtilsTest, Bool());

static folly::ssl::X509UniquePtr readCertFromFile(const std::string& filename) {
  auto path = find_resource(filename);
  folly::ssl::BioUniquePtr bio(BIO_new(BIO_s_file()));
  if (!bio) {
    throw std::runtime_error("Couldn't create BIO");
  }

  if (BIO_read_filename(bio.get(), path.c_str()) != 1) {
    throw std::runtime_error("Couldn't read cert file: " + filename);
  }
  return folly::ssl::X509UniquePtr(
      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
}

static folly::ssl::X509UniquePtr readCertFromData(
    const folly::StringPiece data) {
  folly::ssl::BioUniquePtr bio(BIO_new_mem_buf(data.data(), data.size()));
  if (!bio) {
    throw std::runtime_error("Couldn't create BIO");
  }
  return folly::ssl::X509UniquePtr(
      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
}

// Validate the certs parsed from kTestCertBundle buffer.
static void validateTestCertBundle(
    const std::vector<folly::ssl::X509UniquePtr>& certs) {
  EXPECT_EQ(certs.size(), 3);
  for (auto i : folly::enumerate(certs)) {
    auto cn = folly::ssl::OpenSSLCertUtils::getCommonName(**i);
    EXPECT_TRUE(cn);
    EXPECT_EQ(*cn, folly::sformat("test cert {}", i.index + 1));
  }
}

// Validate parsed cert from kTestCertWithSan.
static void validateTestCertWithSAN(X509* x509) {
  ASSERT_NE(nullptr, x509);
  auto cn = folly::ssl::OpenSSLCertUtils::getCommonName(*x509);
  EXPECT_EQ("127.0.0.1", cn.value());
  auto altNames = folly::ssl::OpenSSLCertUtils::getSubjectAltNames(*x509);
  EXPECT_EQ(2, altNames.size());
  EXPECT_EQ("anotherexample.com", altNames[0]);
  EXPECT_EQ("*.thirdexample.com", altNames[1]);
}

static void addCustomExt(
    X509* x509, const std::string& oid, const std::string& data) {
  std::string extValue("\x0C");
  extValue.push_back(static_cast<char>(data.length()));
  extValue.append(data);
  folly::ssl::ASN1StrUniquePtr asn1String(ASN1_UTF8STRING_new());
  ASN1_STRING_set(asn1String.get(), extValue.c_str(), extValue.size());
  folly::ssl::ASN1ObjUniquePtr object(OBJ_txt2obj(oid.c_str(), 1));
  folly::ssl::X509ExtensionUniquePtr ext(X509_EXTENSION_create_by_OBJ(
      nullptr, object.get(), false, asn1String.get()));
  if (!ext) {
    throw std::runtime_error(
        folly::to<std::string>("Could not create extension ", oid));
  }
  if (!X509_add_ext(x509, ext.get(), -1)) {
    throw std::runtime_error(
        folly::to<std::string>("Could not add extension ", oid));
  }
}

TEST_P(OpenSSLCertUtilsTest, TestX509CN) {
  auto x509 = readCertFromFile(kTestCertWithoutSan);
  EXPECT_NE(x509, nullptr);
  auto cn = folly::ssl::OpenSSLCertUtils::getCommonName(*x509);
  EXPECT_EQ(cn.value(), "Asox Company");
  auto sans = folly::ssl::OpenSSLCertUtils::getSubjectAltNames(*x509);
  EXPECT_EQ(sans.size(), 0);
}

TEST_P(OpenSSLCertUtilsTest, TestX509Sans) {
  auto x509 = readCertFromData(kTestCertWithSan);
  validateTestCertWithSAN(x509.get());
}

TEST_P(OpenSSLCertUtilsTest, TestX509IssuerAndSubject) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  auto issuer = folly::ssl::OpenSSLCertUtils::getIssuer(*x509);
  EXPECT_EQ(
      issuer.value(),
      "C = US, ST = CA, O = Asox, CN = Asox Certification Authority");
  auto subj = folly::ssl::OpenSSLCertUtils::getSubject(*x509);
  EXPECT_EQ(subj.value(), "C = US, O = Asox, CN = 127.0.0.1");
}

TEST_P(OpenSSLCertUtilsTest, TestX509Dates) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  auto notBefore = folly::ssl::OpenSSLCertUtils::getNotBeforeTime(*x509);
  EXPECT_EQ(notBefore, "Feb 13 23:21:03 2017 GMT");
  auto notAfter = folly::ssl::OpenSSLCertUtils::getNotAfterTime(*x509);
  EXPECT_EQ(notAfter, "Jul  1 23:21:03 2044 GMT");
}

TEST_P(OpenSSLCertUtilsTest, TestASN1TimeToTimePoint) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  std::tm tm = {};
  strptime("Feb 13 23:21:03 2017", "%b %d %H:%M:%S %Y", &tm);
  auto expected = std::chrono::system_clock::from_time_t(timegm(&tm));
  auto notBefore = X509_get_notBefore(x509.get());
  auto result = folly::ssl::OpenSSLCertUtils::asnTimeToTimepoint(notBefore);
  EXPECT_EQ(
      std::chrono::time_point_cast<std::chrono::seconds>(expected),
      std::chrono::time_point_cast<std::chrono::seconds>(result));
}

TEST_P(OpenSSLCertUtilsTest, TestX509Summary) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  auto summary = folly::ssl::OpenSSLCertUtils::toString(*x509);
  EXPECT_EQ(
      summary.value(),
      "        Version: 3 (0x2)\n        Serial Number: 2 (0x2)\n"
      "        Issuer: C = US, ST = CA, O = Asox, CN = Asox Certification Authority\n"
      "        Validity\n            Not Before: Feb 13 23:21:03 2017 GMT\n"
      "            Not After : Jul  1 23:21:03 2044 GMT\n"
      "        Subject: C = US, O = Asox, CN = 127.0.0.1\n"
      "        X509v3 extensions:\n"
      "            X509v3 Basic Constraints: \n"
      "                CA:FALSE\n"
      "            Netscape Comment: \n"
      "                OpenSSL Generated Certificate\n"
      "            X509v3 Subject Key Identifier: \n"
      "                71:D6:49:9D:64:47:D7:1E:65:8B:1E:94:83:23:42:E1:F2:19:9F:C3\n"
      "            X509v3 Authority Key Identifier: \n"
      "                keyid:17:DF:29:09:29:BF:7B:9F:1A:7F:E9:46:49:C8:3B:ED:B3:B9:E8:7B\n\n"
      "            X509v3 Subject Alternative Name: \n"
      "                DNS:anotherexample.com, DNS:*.thirdexample.com\n"
      "            Authority Information Access: \n"
      "                CA Issuers - URI:https://phabricator.fb.com/diffusion/FBCODE/browse/master/ti/test_certs/ca_cert.pem?view=raw\n\n");
}

TEST_P(OpenSSLCertUtilsTest, TestDerEncodeDecode) {
  auto x509 = readCertFromData(kTestCertWithSan);

  auto der = folly::ssl::OpenSSLCertUtils::derEncode(*x509);
  auto decoded = folly::ssl::OpenSSLCertUtils::derDecode(der->coalesce());

  EXPECT_EQ(
      folly::ssl::OpenSSLCertUtils::toString(*x509),
      folly::ssl::OpenSSLCertUtils::toString(*decoded));
}

TEST_P(OpenSSLCertUtilsTest, TestDerDecodeJunkData) {
  StringPiece junk{"MyFakeCertificate"};
  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::derDecode(junk), std::runtime_error);
}

TEST_P(OpenSSLCertUtilsTest, TestDerDecodeTooShort) {
  auto x509 = readCertFromData(kTestCertWithSan);

  auto der = folly::ssl::OpenSSLCertUtils::derEncode(*x509);
  der->trimEnd(1);
  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::derDecode(der->coalesce()),
      std::runtime_error);
}

TEST_P(OpenSSLCertUtilsTest, TestReadCertsFromBuffer) {
  auto certs = folly::ssl::OpenSSLCertUtils::readCertsFromBuffer(
      StringPiece(kTestCertBundle));
  validateTestCertBundle(certs);
}

// readCertsFromBuffer() should manage to read certs from a buffer that contain
// both cert and private key.
TEST_P(OpenSSLCertUtilsTest, TestReadCertsFromMixedBuffer) {
  std::vector<std::string> bufs(
      {folly::to<std::string>(kTestCertWithSan, "\n\n", kTestKey, "\n"),
       folly::to<std::string>(kTestKey, "\n\n", kTestCertWithSan, "\n")});
  for (auto& buf : bufs) {
    auto certs = folly::ssl::OpenSSLCertUtils::readCertsFromBuffer(
        folly::StringPiece(buf));
    ASSERT_EQ(1, certs.size());
    validateTestCertWithSAN(certs.front().get());
  }
}

TEST_P(OpenSSLCertUtilsTest, TestX509Digest) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);

  auto sha1Digest = folly::ssl::OpenSSLCertUtils::getDigestSha1(*x509);
  EXPECT_EQ(
      folly::hexlify(folly::range(sha1Digest)),
      "692c24cbc0f595b668480a8b62e5cd6bf5041c64");

  auto sha2Digest = folly::ssl::OpenSSLCertUtils::getDigestSha256(*x509);
  EXPECT_EQ(
      folly::hexlify(folly::range(sha2Digest)),
      "82f800877c004fc8e5d89d6646b15d135d2d4677d56dfcdf29a4698fce1c257f");
}

TEST_P(OpenSSLCertUtilsTest, TestX509Store) {
  auto store = folly::ssl::OpenSSLCertUtils::readStoreFromFile(
      find_resource(kTestCa).string());
  EXPECT_NE(store, nullptr);

  auto x509 = readCertFromFile(kTestCertWithoutSan);
  folly::ssl::X509StoreCtxUniquePtr ctx(X509_STORE_CTX_new());
  auto rc = X509_STORE_CTX_init(ctx.get(), store.get(), x509.get(), nullptr);
  EXPECT_EQ(rc, 1);
  rc = X509_verify_cert(ctx.get());
  EXPECT_EQ(rc, 1);
}

TEST_P(OpenSSLCertUtilsTest, TestProcessMalformedCertBuf) {
  std::string badCert =
      "-----BEGIN CERTIFICATE-----\n"
      "yo\n"
      "-----END CERTIFICATE-----\n";

  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::readCertsFromBuffer(
          folly::StringPiece(badCert)),
      std::runtime_error);

  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::readStoreFromBuffer(
          folly::StringPiece(badCert)),
      std::runtime_error);

  std::string bufWithBadCert =
      folly::to<std::string>(badCert, "\n", kTestCertBundle);

  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::readCertsFromBuffer(
          folly::StringPiece(bufWithBadCert)),
      std::runtime_error);

  EXPECT_THROW(
      folly::ssl::OpenSSLCertUtils::readStoreFromBuffer(
          folly::StringPiece(bufWithBadCert)),
      std::runtime_error);
}

TEST_P(OpenSSLCertUtilsTest, TestReadStoreDuplicate) {
  auto dupBundle =
      folly::to<std::string>(kTestCertBundle, "\n\n", kTestCertBundle);

  auto store = folly::ssl::OpenSSLCertUtils::readStoreFromBuffer(
      folly::StringPiece(dupBundle));
  EXPECT_NE(store, nullptr);
  EXPECT_EQ(ERR_get_error(), 0);
}

TEST_P(OpenSSLCertUtilsTest, TestAllExtensions) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  // Adding a couple of curtom extensions
  addCustomExt(x509.get(), "1.2.3.4.1", "Custom Extension 1");
  addCustomExt(x509.get(), "1.2.3.4.2", "Custom Extension 2");

  std::vector<std::pair<std::string, std::string>> extensions =
      folly::ssl::OpenSSLCertUtils::getAllExtensions(*x509);
  for (auto const& pair : extensions) {
    std::string name = pair.first;
    std::string value = pair.second;
    if (testCertWithSanExts.find(name) != testCertWithSanExts.end()) {
      EXPECT_EQ(value, testCertWithSanExts.find(name)->second);
    }
  }
}

TEST_P(OpenSSLCertUtilsTest, TestGetExtension) {
  auto x509 = readCertFromData(kTestCertWithSan);
  EXPECT_NE(x509, nullptr);
  addCustomExt(x509.get(), "1.2.3.4.1", "Custom Extension 1");
  addCustomExt(x509.get(), "1.2.3.4.2", "Custom Extension 2");
  for (const auto& [name, value] : testCertWithSanExts) {
    std::vector<std::string> extensionValues =
        folly::ssl::OpenSSLCertUtils::getExtension(*x509, name);
    EXPECT_EQ(extensionValues.size(), 1);
    EXPECT_EQ(extensionValues[0], value);
  }
}