diff --git a/src/core/tsi/ssl_transport_security_utils.cc b/src/core/tsi/ssl_transport_security_utils.cc index 1a6747a60a2..18404627691 100644 --- a/src/core/tsi/ssl_transport_security_utils.cc +++ b/src/core/tsi/ssl_transport_security_utils.cc @@ -18,9 +18,15 @@ #include "src/core/tsi/ssl_transport_security_utils.h" +#include #include +#include #include +#include +#include +#include #include +#include #include #include "absl/log/check.h" @@ -374,4 +380,55 @@ absl::StatusOr AkidFromCrl(X509_CRL* crl) { return ret; } +absl::StatusOr> ParsePemCertificateChain( + absl::string_view cert_chain_pem) { + if (cert_chain_pem.empty()) { + return absl::InvalidArgumentError("Cert chain PEM is empty."); + } + BIO* in = BIO_new_mem_buf(cert_chain_pem.data(), cert_chain_pem.size()); + if (in == nullptr) { + return absl::InternalError("BIO_new_mem_buf failed."); + } + std::vector certs; + while (X509* cert = PEM_read_bio_X509(in, /*x=*/nullptr, /*cb=*/nullptr, + /*u=*/nullptr)) { + certs.push_back(cert); + } + + // We always have errors at this point because in the above loop we read until + // we reach the end of |cert_chain_pem|, which generates a "no start line" + // error. Therefore, this error is OK if we have successfully parsed some + // certificate data previously. + const int last_error = ERR_peek_last_error(); + if (ERR_GET_LIB(last_error) != ERR_LIB_PEM || + ERR_GET_REASON(last_error) != PEM_R_NO_START_LINE) { + for (X509* cert : certs) { + X509_free(cert); + } + BIO_free(in); + return absl::FailedPreconditionError("Invalid PEM."); + } + ERR_clear_error(); + BIO_free(in); + if (certs.empty()) { + return absl::NotFoundError("No certificates found."); + } + return certs; +} + +absl::StatusOr ParsePemPrivateKey( + absl::string_view private_key_pem) { + BIO* in = BIO_new_mem_buf(private_key_pem.data(), private_key_pem.size()); + if (in == nullptr) { + return absl::InvalidArgumentError("Private key PEM is empty."); + } + EVP_PKEY* pkey = + PEM_read_bio_PrivateKey(in, /*x=*/nullptr, /*cb=*/nullptr, /*u=*/nullptr); + BIO_free(in); + if (pkey == nullptr) { + return absl::NotFoundError("No private key found."); + } + return pkey; +} + } // namespace grpc_core diff --git a/src/core/tsi/ssl_transport_security_utils.h b/src/core/tsi/ssl_transport_security_utils.h index 6485c542de4..5676d8ff3a9 100644 --- a/src/core/tsi/ssl_transport_security_utils.h +++ b/src/core/tsi/ssl_transport_security_utils.h @@ -19,6 +19,7 @@ #ifndef GRPC_SRC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H #define GRPC_SRC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H +#include #include #include "absl/status/status.h" @@ -168,6 +169,16 @@ absl::StatusOr AkidFromCertificate(X509* cert); // crl. // return: a std::string of the DER encoding of the AKID or a status on failure. absl::StatusOr AkidFromCrl(X509_CRL* crl); + +// Returns a vector of X509 instances parsed from the given PEM-encoded +// certificate chain. Caller takes ownership of the X509 pointers in the output +// vector. +absl::StatusOr> ParsePemCertificateChain( + absl::string_view cert_chain_pem); + +// Returns an EVP_PKEY instance parsed from the non-empty PEM private key block +// in private_key_pem. Caller takes ownership of the EVP_PKEY pointer. +absl::StatusOr ParsePemPrivateKey(absl::string_view private_key_pem); } // namespace grpc_core #endif // GRPC_SRC_CORE_TSI_SSL_TRANSPORT_SECURITY_UTILS_H diff --git a/test/core/tsi/ssl_transport_security_utils_test.cc b/test/core/tsi/ssl_transport_security_utils_test.cc index 70edc02af13..5b64dc212df 100644 --- a/test/core/tsi/ssl_transport_security_utils_test.cc +++ b/test/core/tsi/ssl_transport_security_utils_test.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,11 @@ namespace grpc_core { namespace testing { +using ::testing::ContainerEq; +using ::testing::NotNull; +using ::testing::TestWithParam; +using ::testing::ValuesIn; + const char* kValidCrl = "test/core/tsi/test_creds/crl_data/crls/current.crl"; const char* kCrlIssuer = "test/core/tsi/test_creds/crl_data/ca.pem"; const char* kModifiedSignature = @@ -60,10 +66,73 @@ const char* kCaWithAkid = "test/core/tsi/test_creds/crl_data/ca_with_akid.pem"; const char* kCrlWithAkid = "test/core/tsi/test_creds/crl_data/crl_with_akid.crl"; -using ::testing::ContainerEq; -using ::testing::NotNull; -using ::testing::TestWithParam; -using ::testing::ValuesIn; +constexpr absl::string_view kLeafCertPem = + "-----BEGIN CERTIFICATE-----\n" + "MIICZzCCAdCgAwIBAgIIN18/ctj3wpAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\n" + "ChMOR29vZ2xlIFRFU1RJTkcxDzANBgNVBAMTBnRlc3RDQTAeFw0xNTAxMDEwMDAw\n" + "MDBaFw0yNTAxMDEwMDAwMDBaMC8xFzAVBgNVBAoTDkdvb2dsZSBURVNUSU5HMRQw\n" + "EgYDVQQDDAt0ZXN0X2NlcnRfMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + "20oOyI+fNCCeHJ3DNjGooPPP43Q6emhVvuWD8ppta582Rgxq/4j1bl9cPHdoCdyy\n" + "HsWFVUZzscj2qhClmlBAMEA595OU2NX2d81nSih5dwZWLMRQkEIzyxUR7Vee3eyo\n" + "nQD4HSamaevMSv79WTUBCozEGITqWnjYA152KUbA/IsCAwEAAaOBkDCBjTAOBgNV\n" + "HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud\n" + "EwEB/wQCMAAwGQYDVR0OBBIEECnFWP/UkDrV+SoXra58k64wGwYDVR0jBBQwEoAQ\n" + "p7JSbajiTZaIRUDSV1C81jAWBgNVHREEDzANggt0ZXN0X2NlcnRfMTANBgkqhkiG\n" + "9w0BAQsFAAOBgQCpJJssfN62T3G5z+5SBB+9KCzXnGxcTHtaTJkb04KLe+19EwhV\n" + "yRY4lZadKHjcNS6GCBogd069wNFUVYOU9VI7uUiEPdcTO+VRV5MYW0wjSi1zlkBZ\n" + "e8OAfYVeGUMfvThFpJ41f8vZ6GHgg95Lwv+Zh89SL8g1J3RWll9YVG8HWw==\n" + "-----END CERTIFICATE-----"; +constexpr absl::string_view kPrivateKeyPem = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXQIBAAKBgQDbSg7Ij580IJ4cncM2Maig88/jdDp6aFW+5YPymm1rnzZGDGr/\n" + "iPVuX1w8d2gJ3LIexYVVRnOxyPaqEKWaUEAwQDn3k5TY1fZ3zWdKKHl3BlYsxFCQ\n" + "QjPLFRHtV57d7KidAPgdJqZp68xK/v1ZNQEKjMQYhOpaeNgDXnYpRsD8iwIDAQAB\n" + "AoGAbq4kZApJeo/z/dGK0/GggQxOIylo0puSm7VQMcTL8YP8asKdxrgj2D99WG+U\n" + "LVYc+PcM4wuaHWOnTBL24roaitCNhrpIsJfWDkexzHXMj622SYlUcCuwsfjYOEyw\n" + "ntoNAnh0o4S+beYAfzT5VHCh4is9G9u+mwKYiGpJXROrYUECQQD4eq4nuGq3mfYJ\n" + "B0+md30paDVVCyBsuZTAtnu3MbRjMXy5LLE+vhno5nocvVSTOv3QC7Wk6yAa8/bG\n" + "iPT/MWixAkEA4e0zqPGo8tSimVv/1ei8Chyb+YqdSx+Oj5eZpa6X/KB/C1uS1tm6\n" + "DTgHW2GUhV4ypqdGH+t8quprJUtFuzqH+wJBAMRiicSg789eouMt4RjrdYPFdela\n" + "Gu1zm4rYb10xrqV7Vl0wYoH5U5cMmdSfGvomdLX6mzzWDJDg4ti1JBWRonECQQCD\n" + "Umtq0j1QGQUCe5Vz8zoJ7qNDI61WU1t8X7Rxt9CkiW4PXgU2WYxpzp2IImpAM4bh\n" + "k+2Q9EKc3nG1VdGMiPMtAkARkQF+pL8SBrUoh8G8glCam0brh3tW/cdW8L4UGTNF\n" + "2ZKC/LFH6DQBjYs3UXjvMGJxz4k9LysyY6o2Nf1JG6/L\n" + "-----END RSA PRIVATE KEY-----"; +constexpr absl::string_view kEcPrivateKeyPem = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOM7iHjJw/N6n8HtM\n" + "bVVVRhEYXoHFF+MSaTYQxOWM1p+hRANCAASMeWC+pIJAm/1fn0Wz3yyWGQzVPm9v\n" + "LCQo5JvK0a2t+Aa6d3AtLRwo6vh1VbJ8zFZxxIwyJNis3n1jRMWal7Vo\n" + "-----END PRIVATE KEY-----"; +constexpr absl::string_view kRsaPrivateKeyPem = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqyrzsrS8mWQwz\n" + "VFudLgte2kJX/pZ3KqJQBtMrkLxpgyJJU8mVBB+quDwnfH6PnQk+sF9omTlGAAxR\n" + "JzSEe8BS1Wnxld6rr6o/381VVW/2b+2kSifCtx/gVwCQQLnf4dbjfGW7ZClu1URG\n" + "ks2lK9T9BIh9SMSnYLEKEC8sWW1LibzJxHapFjIP88GrqgpPNGdEK7ABMsqHASuU\n" + "MvQ+0w7sdX2Pdu+Gm8ChxawvLiQVSh9ehtJiPl/jWbcZ6K3caTUxMf9tn8ky0DMK\n" + "xmHHmmxu19ehegzi7KSzjHmJ4QAtrtDaB/+ud0ZJ5l+pwfk7DL1TRjFYOyPVpExb\n" + "nLcQQxzfAgMBAAECggEATc+kFygnzQ7Q0iniu0+Y+pPxmelxX8VawZ76YmTEkkWe\n" + "P04fDvcb/kmFjm/XsVJYPelY7mywfUXUVrzH3nwK+TIl3FztX8beh89M203bfqkr\n" + "2ae3Sazopuq8ZPw4MtnPb0DjkGZnwgkD3CtR6ah4lvWTwZB/l8ojnnQVKd1sP/c4\n" + "LQSlVm2aiD6+D/NxbyJ4AOMWgUFrWBKqnV30mTZ5Lwv8fjznopgWMfsUl+Nx/HzV\n" + "J1ZRtLE+Z9euFJOUeMSEG1+YFxXAA3XuRdY/4PpzvK8Rlxb2rtJvt+dHojQCz66U\n" + "6PcspPt6MOcUFnpamJ513oKDwmdR8puRg7/bk2VKYQKBgQDVHz/NQaS8czTCkx8p\n" + "jXmZcGv1DH+T3SWeNI871BXQbSSKuqrOXDfzfrIFK7uXAWtIAOKLVaZOcSEDk+Rj\n" + "kbifkqRZuMy+iLdBLj/Gw3xVfkOb3m4g7OqWc7RBlfTCTCCUTVPiQkKZLGJ/eIJx\n" + "sGvdyJP6f12MODqUobgQC2UniQKBgQDNJ0vDHdqRQYI4zz1gAYDaCruRjtwWvRSL\n" + "tcBFlcVnMXjfZpxOKnGU1xEO4wDhEXra9yLwi/6IwGFtk0Zi2C9rYptniiURReuX\n" + "TkNNf1JmyZhYuSXD9Pg1Ssa/t3ZtauFzK1rHL1R1UB/pnD8xxuB4aAl+kZKi1Ie+\n" + "E6IXHuyfJwKBgQDOac+viq503tfww/Fgm2d0lw/YbNx7Z6rxiVJYzda64ZqMyrJ3\n" + "35VJPiJJI8wyOuue90xzSuch/ivNfUWssgwwcSTAyV10BJIIjTSz283mN75fjpT3\n" + "Sr8CLNoe05AVRwoe2K4v66D5HaXgc+VTG129lnDMIuOF1UfXgLH2yDKWkQKBgQC4\n" + "ajqQiqWPLXQB3UkupCtP1ZYGooT1a8KsVBUieB+bQ72EFJktKrovMaUD3MtNhokJ\n" + "jF68HRwRkd4CwgDjmbIGtf08ddIcVN4ShSe64lkQTOfF2ak5HVyBi1ZdwG2Urh87\n" + "iB1yL/mb+wq01N95v2zIz7y5KeLGvIXJN5zda88IwQKBgFLk68ZMEDMVCLpdvywb\n" + "bRC3rOl2CTHfXFD6RY0SIv4De+w7iQkYOn+4NIyG+hMfGfj5ooOO5gbsDyGagZqV\n" + "OLc6cW5HnwN+PERByn+hSoyGq8IOk8Vn5DeV7PoqIlbbdfUmTUx69EtzvViZoa+O\n" + "O2XDljPcjgc+pobqzebPIR6R\n" + "-----END PRIVATE KEY-----"; constexpr std::size_t kMaxPlaintextBytesPerTlsRecord = 16384; constexpr std::size_t kTlsRecordOverhead = 100; @@ -117,38 +186,6 @@ class FlowTest : public TestWithParam { return absl::InvalidArgumentError( "Client and server SSL object must not be null."); } - std::string cert_pem = - "-----BEGIN CERTIFICATE-----\n" - "MIICZzCCAdCgAwIBAgIIN18/ctj3wpAwDQYJKoZIhvcNAQELBQAwKjEXMBUGA1UE\n" - "ChMOR29vZ2xlIFRFU1RJTkcxDzANBgNVBAMTBnRlc3RDQTAeFw0xNTAxMDEwMDAw\n" - "MDBaFw0yNTAxMDEwMDAwMDBaMC8xFzAVBgNVBAoTDkdvb2dsZSBURVNUSU5HMRQw\n" - "EgYDVQQDDAt0ZXN0X2NlcnRfMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" - "20oOyI+fNCCeHJ3DNjGooPPP43Q6emhVvuWD8ppta582Rgxq/4j1bl9cPHdoCdyy\n" - "HsWFVUZzscj2qhClmlBAMEA595OU2NX2d81nSih5dwZWLMRQkEIzyxUR7Vee3eyo\n" - "nQD4HSamaevMSv79WTUBCozEGITqWnjYA152KUbA/IsCAwEAAaOBkDCBjTAOBgNV\n" - "HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud\n" - "EwEB/wQCMAAwGQYDVR0OBBIEECnFWP/UkDrV+SoXra58k64wGwYDVR0jBBQwEoAQ\n" - "p7JSbajiTZaIRUDSV1C81jAWBgNVHREEDzANggt0ZXN0X2NlcnRfMTANBgkqhkiG\n" - "9w0BAQsFAAOBgQCpJJssfN62T3G5z+5SBB+9KCzXnGxcTHtaTJkb04KLe+19EwhV\n" - "yRY4lZadKHjcNS6GCBogd069wNFUVYOU9VI7uUiEPdcTO+VRV5MYW0wjSi1zlkBZ\n" - "e8OAfYVeGUMfvThFpJ41f8vZ6GHgg95Lwv+Zh89SL8g1J3RWll9YVG8HWw==\n" - "-----END CERTIFICATE-----\n"; - std::string key_pem = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIICXQIBAAKBgQDbSg7Ij580IJ4cncM2Maig88/jdDp6aFW+5YPymm1rnzZGDGr/\n" - "iPVuX1w8d2gJ3LIexYVVRnOxyPaqEKWaUEAwQDn3k5TY1fZ3zWdKKHl3BlYsxFCQ\n" - "QjPLFRHtV57d7KidAPgdJqZp68xK/v1ZNQEKjMQYhOpaeNgDXnYpRsD8iwIDAQAB\n" - "AoGAbq4kZApJeo/z/dGK0/GggQxOIylo0puSm7VQMcTL8YP8asKdxrgj2D99WG+U\n" - "LVYc+PcM4wuaHWOnTBL24roaitCNhrpIsJfWDkexzHXMj622SYlUcCuwsfjYOEyw\n" - "ntoNAnh0o4S+beYAfzT5VHCh4is9G9u+mwKYiGpJXROrYUECQQD4eq4nuGq3mfYJ\n" - "B0+md30paDVVCyBsuZTAtnu3MbRjMXy5LLE+vhno5nocvVSTOv3QC7Wk6yAa8/bG\n" - "iPT/MWixAkEA4e0zqPGo8tSimVv/1ei8Chyb+YqdSx+Oj5eZpa6X/KB/C1uS1tm6\n" - "DTgHW2GUhV4ypqdGH+t8quprJUtFuzqH+wJBAMRiicSg789eouMt4RjrdYPFdela\n" - "Gu1zm4rYb10xrqV7Vl0wYoH5U5cMmdSfGvomdLX6mzzWDJDg4ti1JBWRonECQQCD\n" - "Umtq0j1QGQUCe5Vz8zoJ7qNDI61WU1t8X7Rxt9CkiW4PXgU2WYxpzp2IImpAM4bh\n" - "k+2Q9EKc3nG1VdGMiPMtAkARkQF+pL8SBrUoh8G8glCam0brh3tW/cdW8L4UGTNF\n" - "2ZKC/LFH6DQBjYs3UXjvMGJxz4k9LysyY6o2Nf1JG6/L\n" - "-----END RSA PRIVATE KEY-----\n"; // Create the context objects. SSL_CTX* client_ctx = nullptr; @@ -161,18 +198,22 @@ class FlowTest : public TestWithParam { server_ctx = SSL_CTX_new(TLSv1_2_method()); #endif - BIO* client_cert_bio(BIO_new_mem_buf(cert_pem.c_str(), cert_pem.size())); + BIO* client_cert_bio( + BIO_new_mem_buf(kLeafCertPem.data(), kLeafCertPem.size())); X509* client_cert = PEM_read_bio_X509(client_cert_bio, /*x=*/nullptr, /*cb=*/nullptr, /*u=*/nullptr); - BIO* client_key_bio(BIO_new_mem_buf(key_pem.c_str(), key_pem.size())); + BIO* client_key_bio( + BIO_new_mem_buf(kPrivateKeyPem.data(), kPrivateKeyPem.size())); EVP_PKEY* client_key = PEM_read_bio_PrivateKey(client_key_bio, /*x=*/nullptr, /*cb=*/nullptr, /*u=*/nullptr); - BIO* server_cert_bio(BIO_new_mem_buf(cert_pem.c_str(), cert_pem.size())); + BIO* server_cert_bio( + BIO_new_mem_buf(kLeafCertPem.data(), kLeafCertPem.size())); X509* server_cert = PEM_read_bio_X509(server_cert_bio, /*x=*/nullptr, /*cb=*/nullptr, /*u=*/nullptr); - BIO* server_key_bio(BIO_new_mem_buf(key_pem.c_str(), key_pem.size())); + BIO* server_key_bio( + BIO_new_mem_buf(kPrivateKeyPem.data(), kPrivateKeyPem.size())); EVP_PKEY* server_key = PEM_read_bio_PrivateKey(server_key_bio, /*x=*/nullptr, /*cb=*/nullptr, /*u=*/nullptr); @@ -673,6 +714,128 @@ TEST_F(CrlUtils, CrlAkidNullptr) { auto akid = AkidFromCrl(nullptr); EXPECT_EQ(akid.status().code(), absl::StatusCode::kInvalidArgument); } + +TEST(ParsePemCertificateChainTest, EmptyPem) { + EXPECT_EQ(ParsePemCertificateChain(/*cert_chain_pem=*/"").status(), + absl::InvalidArgumentError("Cert chain PEM is empty.")); +} + +TEST(ParsePemCertificateChainTest, InvalidPem) { + EXPECT_EQ(ParsePemCertificateChain("invalid-pem").status(), + absl::NotFoundError("No certificates found.")); +} + +TEST(ParsePemCertificateChainTest, PartialPem) { + std::string pem(kLeafCertPem); + EXPECT_EQ(ParsePemCertificateChain(pem.substr(0, pem.length() / 2)).status(), + absl::FailedPreconditionError("Invalid PEM.")); +} + +TEST(ParsePemCertificateChainTest, SingleCertSuccess) { + absl::StatusOr> certs = + ParsePemCertificateChain(kLeafCertPem); + EXPECT_EQ(certs.status(), absl::OkStatus()); + EXPECT_EQ(certs->size(), 1); + EXPECT_NE(certs->at(0), nullptr); + X509_free(certs->at(0)); +} + +TEST(ParsePemCertificateChainTest, MultipleCertFailure) { + EXPECT_EQ(ParsePemCertificateChain(absl::StrCat(kLeafCertPem, kLeafCertPem)) + .status(), + absl::FailedPreconditionError("Invalid PEM.")); +} + +TEST(ParsePemCertificateChainTest, MultipleCertSuccess) { + absl::StatusOr> certs = + ParsePemCertificateChain(absl::StrCat(kLeafCertPem, "\n", kLeafCertPem)); + EXPECT_EQ(certs.status(), absl::OkStatus()); + EXPECT_EQ(certs->size(), 2); + EXPECT_NE(certs->at(0), nullptr); + EXPECT_NE(certs->at(1), nullptr); + X509_free(certs->at(0)); + X509_free(certs->at(1)); +} + +TEST(ParsePemCertificateChainTest, MultipleCertWithExtraMiddleLinesSuccess) { + absl::StatusOr> certs = ParsePemCertificateChain( + absl::StrCat(kLeafCertPem, "\nGarbage\n", kLeafCertPem)); + EXPECT_EQ(certs.status(), absl::OkStatus()); + EXPECT_EQ(certs->size(), 2); + EXPECT_NE(certs->at(0), nullptr); + EXPECT_NE(certs->at(1), nullptr); + X509_free(certs->at(0)); + X509_free(certs->at(1)); +} + +TEST(ParsePemCertificateChainTest, MultipleCertWitManyMiddleLinesSuccess) { + absl::StatusOr> certs = ParsePemCertificateChain( + absl::StrCat(kLeafCertPem, "\n\n\n\n\n\n\n", kLeafCertPem)); + EXPECT_EQ(certs.status(), absl::OkStatus()); + EXPECT_EQ(certs->size(), 2); + EXPECT_NE(certs->at(0), nullptr); + EXPECT_NE(certs->at(1), nullptr); + X509_free(certs->at(0)); + X509_free(certs->at(1)); +} + +TEST(ParsePemCertificateChainTest, ValidCertWithInvalidSuffix) { + EXPECT_EQ(ParsePemCertificateChain(absl::StrCat(kLeafCertPem, "invalid-pem")) + .status(), + absl::FailedPreconditionError("Invalid PEM.")); +} + +TEST(ParsePemCertificateChainTest, ValidCertWithInvalidPrefix) { + EXPECT_EQ(ParsePemCertificateChain(absl::StrCat("invalid-pem", kLeafCertPem)) + .status(), + absl::NotFoundError("No certificates found.")); +} + +TEST(ParsePemCertificateChainTest, ValidCertWithInvalidLeadingLine) { + absl::StatusOr> certs = + ParsePemCertificateChain(absl::StrCat("invalid-pem\n", kLeafCertPem)); + EXPECT_EQ(certs.status(), absl::OkStatus()); + EXPECT_EQ(certs->size(), 1); + EXPECT_NE(certs->at(0), nullptr); + X509_free(certs->at(0)); +} + +TEST(ParsePemPrivateKeyTest, EmptyPem) { + EXPECT_EQ(ParsePemPrivateKey(/*private_key_pem=*/"").status(), + absl::NotFoundError("No private key found.")); +} + +TEST(ParsePemPrivateKeyTest, InvalidPem) { + EXPECT_EQ(ParsePemPrivateKey("invalid-pem").status(), + absl::NotFoundError("No private key found.")); +} + +TEST(ParsePemPrivateKeyTest, PartialPem) { + std::string pem(kPrivateKeyPem); + EXPECT_EQ(ParsePemPrivateKey(pem.substr(0, pem.length() / 2)).status(), + absl::NotFoundError("No private key found.")); +} + +TEST(ParsePemPrivateKeyTest, RsaSuccess1) { + absl::StatusOr pkey = ParsePemPrivateKey(kPrivateKeyPem); + EXPECT_EQ(pkey.status(), absl::OkStatus()); + EXPECT_NE(*pkey, nullptr); + EVP_PKEY_free(*pkey); +} + +TEST(ParsePemPrivateKeyTest, RsaSuccess2) { + absl::StatusOr pkey = ParsePemPrivateKey(kRsaPrivateKeyPem); + EXPECT_EQ(pkey.status(), absl::OkStatus()); + EXPECT_NE(*pkey, nullptr); + EVP_PKEY_free(*pkey); +} + +TEST(ParsePemPrivateKeyTest, EcSuccess) { + absl::StatusOr pkey = ParsePemPrivateKey(kEcPrivateKeyPem); + EXPECT_EQ(pkey.status(), absl::OkStatus()); + EXPECT_NE(*pkey, nullptr); + EVP_PKEY_free(*pkey); +} } // namespace testing } // namespace grpc_core