From 2081b775b5100a4b76a9acb8597f11ae5e60e0e0 Mon Sep 17 00:00:00 2001 From: Anirudh Ramachandran Date: Tue, 7 Mar 2017 00:26:12 -0800 Subject: [PATCH] Add a few more methods to OpenSSLCertUtils Summary: Add a few more getters (subject, issuer, notbefore, notafter) to OpenSSLCertUtils. Also add an additional API to AsyncSSLSocket to add application-generated alert strings during the handshake, for e.g., during certificate verification Reviewed By: knekritz Differential Revision: D4624754 fbshipit-source-id: f01998b9e0e58b88ece8c6dc51ab590988bf0a8f --- folly/io/async/AsyncSSLSocket.cpp | 8 +++ folly/io/async/AsyncSSLSocket.h | 8 +++ folly/ssl/OpenSSLCertUtils.cpp | 96 +++++++++++++++++++++++++ folly/ssl/OpenSSLCertUtils.h | 32 +++++++++ folly/ssl/test/OpenSSLCertUtilsTest.cpp | 52 ++++++++++++++ 5 files changed, 196 insertions(+) diff --git a/folly/io/async/AsyncSSLSocket.cpp b/folly/io/async/AsyncSSLSocket.cpp index 7b4e16bb..a25269d2 100644 --- a/folly/io/async/AsyncSSLSocket.cpp +++ b/folly/io/async/AsyncSSLSocket.cpp @@ -1911,6 +1911,14 @@ std::string AsyncSSLSocket::getSSLAlertsReceived() const { return ret; } +void AsyncSSLSocket::setSSLCertVerificationAlert(std::string alert) { + sslVerificationAlert_ = std::move(alert); +} + +std::string AsyncSSLSocket::getSSLCertVerificationAlert() const { + return sslVerificationAlert_; +} + void AsyncSSLSocket::getSSLSharedCiphers(std::string& sharedCiphers) const { char ciphersBuffer[1024]; ciphersBuffer[0] = '\0'; diff --git a/folly/io/async/AsyncSSLSocket.h b/folly/io/async/AsyncSSLSocket.h index 99728ddd..a8bb1e12 100644 --- a/folly/io/async/AsyncSSLSocket.h +++ b/folly/io/async/AsyncSSLSocket.h @@ -597,6 +597,13 @@ class AsyncSSLSocket : public virtual AsyncSocket { std::string getSSLAlertsReceived() const; + /* + * Save an optional alert message generated during certificate verify + */ + void setSSLCertVerificationAlert(std::string alert); + + std::string getSSLCertVerificationAlert() const; + /** * Get the list of shared ciphers between the server and the client. * Works well for only SSLv2, not so good for SSLv3 or TLSv1. @@ -858,6 +865,7 @@ class AsyncSSLSocket : public virtual AsyncSocket { std::chrono::milliseconds totalConnectTimeout_{0}; std::unique_ptr preReceivedData_; + std::string sslVerificationAlert_; }; } // namespace diff --git a/folly/ssl/OpenSSLCertUtils.cpp b/folly/ssl/OpenSSLCertUtils.cpp index ae619048..d06cdac7 100644 --- a/folly/ssl/OpenSSLCertUtils.cpp +++ b/folly/ssl/OpenSSLCertUtils.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ #include +#include +#include #include #include @@ -80,5 +82,99 @@ std::vector OpenSSLCertUtils::getSubjectAltNames(X509& x509) { } return ret; } + +Optional OpenSSLCertUtils::getSubject(X509& x509) { + auto subject = X509_get_subject_name(&x509); + if (!subject) { + return none; + } + + auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); + if (bio == nullptr) { + throw std::runtime_error("Cannot allocate bio"); + } + if (X509_NAME_print_ex(bio.get(), subject, 0, XN_FLAG_ONELINE) <= 0) { + return none; + } + + char* bioData = nullptr; + size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); + return std::string(bioData, bioLen); } + +Optional OpenSSLCertUtils::getIssuer(X509& x509) { + auto issuer = X509_get_issuer_name(&x509); + if (!issuer) { + return none; + } + + auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); + if (bio == nullptr) { + throw std::runtime_error("Cannot allocate bio"); + } + + if (X509_NAME_print_ex(bio.get(), issuer, 0, XN_FLAG_ONELINE) <= 0) { + return none; + } + + char* bioData = nullptr; + size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); + return std::string(bioData, bioLen); } + +folly::Optional OpenSSLCertUtils::toString(X509& x509) { + auto in = BioUniquePtr(BIO_new(BIO_s_mem())); + if (in == nullptr) { + throw std::runtime_error("Cannot allocate bio"); + } + + if (X509_print_ex( + in.get(), + &x509, + XN_FLAG_ONELINE, + X509_FLAG_NO_HEADER | /* A few bytes of cert and data */ + X509_FLAG_NO_PUBKEY | /* Public key */ + X509_FLAG_NO_IDS | /* Issuer/subject IDs */ + X509_FLAG_NO_AUX | /* Auxiliary info? */ + X509_FLAG_NO_SIGDUMP | /* Prints the signature */ + X509_FLAG_NO_SIGNAME /* Signature algorithms */ + ) > 0) { + char* bioData = nullptr; + size_t bioLen = BIO_get_mem_data(in.get(), &bioData); + return std::string(bioData, bioLen); + } else { + return none; + } +} + +std::string OpenSSLCertUtils::getNotAfterTime(X509& x509) { + return getDateTimeStr(X509_get_notAfter(&x509)); +} + +std::string OpenSSLCertUtils::getNotBeforeTime(X509& x509) { + return getDateTimeStr(X509_get_notBefore(&x509)); +} + +std::string OpenSSLCertUtils::getDateTimeStr(const ASN1_TIME* time) { + if (!time) { + return ""; + } + + std::array buf; + + auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); + if (bio == nullptr) { + throw std::runtime_error("Cannot allocate bio"); + } + + if (ASN1_TIME_print(bio.get(), time) <= 0) { + throw std::runtime_error("Cannot print ASN1_TIME"); + } + + char* bioData = nullptr; + size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); + return std::string(bioData, bioLen); +} + +} // ssl +} // folly diff --git a/folly/ssl/OpenSSLCertUtils.h b/folly/ssl/OpenSSLCertUtils.h index 82e30ffb..c6c3803b 100644 --- a/folly/ssl/OpenSSLCertUtils.h +++ b/folly/ssl/OpenSSLCertUtils.h @@ -31,6 +31,38 @@ class OpenSSLCertUtils { static Optional getCommonName(X509& x509); static std::vector getSubjectAltNames(X509& x509); + + /* + * Return the subject name, if any, from the cert + * @param x509 Reference to an X509 + * @return a folly::Optional, or folly::none + */ + static Optional getSubject(X509& x509); + + /* + * Return the issuer name, if any, from the cert + * @param x509 Reference to an X509 + * @return a folly::Optional, or folly::none + */ + static Optional getIssuer(X509& x509); + + /* + * Get a string representation of the not-before time on the certificate + */ + static std::string getNotBeforeTime(X509& x509); + + /* + * Get a string representation of the not-after (expiration) time + */ + static std::string getNotAfterTime(X509& x509); + + /* + * Summarize the CN, Subject, Issuer, Validity, and extensions as a string + */ + static folly::Optional toString(X509& x509); + + private: + static std::string getDateTimeStr(const ASN1_TIME* time); }; } } diff --git a/folly/ssl/test/OpenSSLCertUtilsTest.cpp b/folly/ssl/test/OpenSSLCertUtilsTest.cpp index e41d0b67..629c15a2 100644 --- a/folly/ssl/test/OpenSSLCertUtilsTest.cpp +++ b/folly/ssl/test/OpenSSLCertUtilsTest.cpp @@ -105,3 +105,55 @@ TEST(OpenSSLCertUtilsTest, TestX509Sans) { EXPECT_EQ(altNames[0], "anotherexample.com"); EXPECT_EQ(altNames[1], "*.thirdexample.com"); } + +TEST(OpenSSLCertUtilsTest, TestX509IssuerAndSubject) { + OpenSSL_add_all_algorithms(); + + 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(OpenSSLCertUtilsTest, TestX509Dates) { + OpenSSL_add_all_algorithms(); + + 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(OpenSSLCertUtilsTest, TestX509Summary) { + OpenSSL_add_all_algorithms(); + + 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"); +} -- 2.34.1