From d65b77365390ed615636966c225b2bf9deb564a6 Mon Sep 17 00:00:00 2001 From: Aaron Balsara Date: Fri, 15 Jan 2016 10:50:33 -0800 Subject: [PATCH] Allow SSLContext to read certificates and keys from memory Summary: Added the ability for SSLContext to load X509 Certificates and private keys from memory Reviewed By: yfeldblum Differential Revision: D2800746 fb-gh-sync-id: 14cad74f8d761b9b0f07e2827b155cec9ba27f50 --- folly/io/async/SSLContext.cpp | 66 +++++++++++++++++-- folly/io/async/SSLContext.h | 13 ++++ folly/io/async/test/AsyncSSLSocketTest.cpp | 73 ++++++++++++++++++++-- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/folly/io/async/SSLContext.cpp b/folly/io/async/SSLContext.cpp index 4e8ea69f..eb5f122a 100644 --- a/folly/io/async/SSLContext.cpp +++ b/folly/io/async/SSLContext.cpp @@ -22,6 +22,7 @@ #include #include +#include #include // --------------------------------------------------------------------- @@ -43,7 +44,13 @@ std::mutex& initMutex() { return m; } -} // anonymous namespace +inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); } +using BIO_deleter = folly::static_function_deleter; +using X509_deleter = folly::static_function_deleter; +using EVP_PKEY_deleter = + folly::static_function_deleter; + +} // anonymous namespace #ifdef OPENSSL_NPN_NEGOTIATED int SSLContext::sNextProtocolsExDataIndex_ = -1; @@ -186,10 +193,36 @@ void SSLContext::loadCertificate(const char* path, const char* format) { } } +void SSLContext::loadCertificateFromBufferPEM(folly::StringPiece cert) { + if (cert.data() == nullptr) { + throw std::invalid_argument("loadCertificate: is nullptr"); + } + + std::unique_ptr bio(BIO_new(BIO_s_mem())); + if (bio == nullptr) { + throw std::runtime_error("BIO_new: " + getErrors()); + } + + int written = BIO_write(bio.get(), cert.data(), cert.size()); + if (written != cert.size()) { + throw std::runtime_error("BIO_write: " + getErrors()); + } + + std::unique_ptr x509( + PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (x509 == nullptr) { + throw std::runtime_error("PEM_read_bio_X509: " + getErrors()); + } + + if (SSL_CTX_use_certificate(ctx_, x509.get()) == 0) { + throw std::runtime_error("SSL_CTX_use_certificate: " + getErrors()); + } +} + void SSLContext::loadPrivateKey(const char* path, const char* format) { if (path == nullptr || format == nullptr) { throw std::invalid_argument( - "loadPrivateKey: either or is nullptr"); + "loadPrivateKey: either or is nullptr"); } if (strcmp(format, "PEM") == 0) { if (SSL_CTX_use_PrivateKey_file(ctx_, path, SSL_FILETYPE_PEM) == 0) { @@ -200,10 +233,35 @@ void SSLContext::loadPrivateKey(const char* path, const char* format) { } } +void SSLContext::loadPrivateKeyFromBufferPEM(folly::StringPiece pkey) { + if (pkey.data() == nullptr) { + throw std::invalid_argument("loadPrivateKey: is nullptr"); + } + + std::unique_ptr bio(BIO_new(BIO_s_mem())); + if (bio == nullptr) { + throw std::runtime_error("BIO_new: " + getErrors()); + } + + int written = BIO_write(bio.get(), pkey.data(), pkey.size()); + if (written != pkey.size()) { + throw std::runtime_error("BIO_write: " + getErrors()); + } + + std::unique_ptr key( + PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); + if (key == nullptr) { + throw std::runtime_error("PEM_read_bio_PrivateKey: " + getErrors()); + } + + if (SSL_CTX_use_PrivateKey(ctx_, key.get()) == 0) { + throw std::runtime_error("SSL_CTX_use_PrivateKey: " + getErrors()); + } +} + void SSLContext::loadTrustedCertificates(const char* path) { if (path == nullptr) { - throw std::invalid_argument( - "loadTrustedCertificates: is nullptr"); + throw std::invalid_argument("loadTrustedCertificates: is nullptr"); } if (SSL_CTX_load_verify_locations(ctx_, path, nullptr) == 0) { throw std::runtime_error("SSL_CTX_load_verify_locations: " + getErrors()); diff --git a/folly/io/async/SSLContext.h b/folly/io/async/SSLContext.h index e20b093b..b6742bcc 100644 --- a/folly/io/async/SSLContext.h +++ b/folly/io/async/SSLContext.h @@ -37,6 +37,7 @@ #endif #include +#include namespace folly { @@ -185,6 +186,12 @@ class SSLContext { * @param format Certificate file format */ virtual void loadCertificate(const char* path, const char* format = "PEM"); + /** + * Load server certificate from memory. + * + * @param cert A PEM formatted certificate + */ + virtual void loadCertificateFromBufferPEM(folly::StringPiece cert); /** * Load private key. * @@ -192,6 +199,12 @@ class SSLContext { * @param format Private key file format */ virtual void loadPrivateKey(const char* path, const char* format = "PEM"); + /** + * Load private key from memory. + * + * @param pkey A PEM formatted key + */ + virtual void loadPrivateKeyFromBufferPEM(folly::StringPiece pkey); /** * Load trusted certificates from specified file. * diff --git a/folly/io/async/test/AsyncSSLSocketTest.cpp b/folly/io/async/test/AsyncSSLSocketTest.cpp index 350a6b57..c4ad75d6 100644 --- a/folly/io/async/test/AsyncSSLSocketTest.cpp +++ b/folly/io/async/test/AsyncSSLSocketTest.cpp @@ -24,12 +24,14 @@ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -55,10 +57,17 @@ const char* testCA = "folly/io/async/test/certs/ca-cert.pem"; constexpr size_t SSLClient::kMaxReadBufferSz; constexpr size_t SSLClient::kMaxReadsPerEvent; -TestSSLServer::TestSSLServer(SSLServerAcceptCallbackBase *acb) : -ctx_(new folly::SSLContext), - acb_(acb), - socket_(folly::AsyncServerSocket::newSocket(&evb_)) { +inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); } +using BIO_deleter = folly::static_function_deleter; +using X509_deleter = folly::static_function_deleter; +using SSL_deleter = folly::static_function_deleter; +using EVP_PKEY_deleter = + folly::static_function_deleter; + +TestSSLServer::TestSSLServer(SSLServerAcceptCallbackBase* acb) + : ctx_(new folly::SSLContext), + acb_(acb), + socket_(folly::AsyncServerSocket::newSocket(&evb_)) { // Set up the SSL context ctx_->loadCertificate(testCert); ctx_->loadPrivateKey(testKey); @@ -144,6 +153,21 @@ bool clientProtoFilterPickNone(unsigned char**, unsigned int*, return false; } +std::string getFileAsBuf(const char* fileName) { + std::string buffer; + folly::readFile(fileName, buffer); + return buffer; +} + +std::string getCommonName(X509* cert) { + X509_NAME* subject = X509_get_subject_name(cert); + std::string cn; + cn.resize(ub_common_name); + X509_NAME_get_text_by_NID( + subject, NID_commonName, const_cast(cn.data()), ub_common_name); + return cn; +} + /** * Test connecting to, writing to, reading from, and closing the * connection to the SSL server. @@ -1360,6 +1384,47 @@ TEST(AsyncSSLSocketTest, NoClientCertHandshakeError) { EXPECT_LE(0, server.handshakeTime.count()); } +TEST(AsyncSSLSocketTest, LoadCertFromMemory) { + auto cert = getFileAsBuf(testCert); + auto key = getFileAsBuf(testKey); + + std::unique_ptr certBio(BIO_new(BIO_s_mem())); + BIO_write(certBio.get(), cert.data(), cert.size()); + std::unique_ptr keyBio(BIO_new(BIO_s_mem())); + BIO_write(keyBio.get(), key.data(), key.size()); + + // Create SSL structs from buffers to get properties + std::unique_ptr certStruct( + PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr)); + std::unique_ptr keyStruct( + PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr)); + certBio = nullptr; + keyBio = nullptr; + + auto origCommonName = getCommonName(certStruct.get()); + auto origKeySize = EVP_PKEY_bits(keyStruct.get()); + certStruct = nullptr; + keyStruct = nullptr; + + auto ctx = std::make_shared(); + ctx->loadPrivateKeyFromBufferPEM(key); + ctx->loadCertificateFromBufferPEM(cert); + ctx->loadTrustedCertificates(testCA); + + std::unique_ptr ssl(ctx->createSSL()); + + auto newCert = SSL_get_certificate(ssl.get()); + auto newKey = SSL_get_privatekey(ssl.get()); + + // Get properties from SSL struct + auto newCommonName = getCommonName(newCert); + auto newKeySize = EVP_PKEY_bits(newKey); + + // Check that the key and cert have the expected properties + EXPECT_EQ(origCommonName, newCommonName); + EXPECT_EQ(origKeySize, newKeySize); +} + TEST(AsyncSSLSocketTest, MinWriteSizeTest) { EventBase eb; -- 2.34.1