#include <openssl/x509v3.h>
#include <folly/Format.h>
+#include <folly/Memory.h>
#include <folly/SpinLock.h>
// ---------------------------------------------------------------------
return m;
}
-} // anonymous namespace
+inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); }
+using BIO_deleter = folly::static_function_deleter<BIO, &BIO_free_fb>;
+using X509_deleter = folly::static_function_deleter<X509, &X509_free>;
+using EVP_PKEY_deleter =
+ folly::static_function_deleter<EVP_PKEY, &EVP_PKEY_free>;
+
+} // anonymous namespace
#ifdef OPENSSL_NPN_NEGOTIATED
int SSLContext::sNextProtocolsExDataIndex_ = -1;
}
}
+void SSLContext::loadCertificateFromBufferPEM(folly::StringPiece cert) {
+ if (cert.data() == nullptr) {
+ throw std::invalid_argument("loadCertificate: <cert> is nullptr");
+ }
+
+ std::unique_ptr<BIO, BIO_deleter> 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, X509_deleter> 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 <path> or <format> is nullptr");
+ "loadPrivateKey: either <path> or <format> is nullptr");
}
if (strcmp(format, "PEM") == 0) {
if (SSL_CTX_use_PrivateKey_file(ctx_, path, SSL_FILETYPE_PEM) == 0) {
}
}
+void SSLContext::loadPrivateKeyFromBufferPEM(folly::StringPiece pkey) {
+ if (pkey.data() == nullptr) {
+ throw std::invalid_argument("loadPrivateKey: <pkey> is nullptr");
+ }
+
+ std::unique_ptr<BIO, BIO_deleter> 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<EVP_PKEY, EVP_PKEY_deleter> 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: <path> is nullptr");
+ throw std::invalid_argument("loadTrustedCertificates: <path> is nullptr");
}
if (SSL_CTX_load_verify_locations(ctx_, path, nullptr) == 0) {
throw std::runtime_error("SSL_CTX_load_verify_locations: " + getErrors());
#endif
#include <folly/Random.h>
+#include <folly/Range.h>
namespace folly {
* @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.
*
* @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.
*
#include <folly/io/async/test/BlockingSocket.h>
+#include <fstream>
#include <gtest/gtest.h>
#include <iostream>
#include <list>
#include <set>
#include <unistd.h>
#include <fcntl.h>
+#include <openssl/bio.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
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<BIO, &BIO_free_fb>;
+using X509_deleter = folly::static_function_deleter<X509, &X509_free>;
+using SSL_deleter = folly::static_function_deleter<SSL, &SSL_free>;
+using EVP_PKEY_deleter =
+ folly::static_function_deleter<EVP_PKEY, &EVP_PKEY_free>;
+
+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);
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<char*>(cn.data()), ub_common_name);
+ return cn;
+}
+
/**
* Test connecting to, writing to, reading from, and closing the
* connection to the SSL server.
EXPECT_LE(0, server.handshakeTime.count());
}
+TEST(AsyncSSLSocketTest, LoadCertFromMemory) {
+ auto cert = getFileAsBuf(testCert);
+ auto key = getFileAsBuf(testKey);
+
+ std::unique_ptr<BIO, BIO_deleter> certBio(BIO_new(BIO_s_mem()));
+ BIO_write(certBio.get(), cert.data(), cert.size());
+ std::unique_ptr<BIO, BIO_deleter> 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<X509, X509_deleter> certStruct(
+ PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr));
+ std::unique_ptr<EVP_PKEY, EVP_PKEY_deleter> 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<SSLContext>();
+ ctx->loadPrivateKeyFromBufferPEM(key);
+ ctx->loadCertificateFromBufferPEM(cert);
+ ctx->loadTrustedCertificates(testCA);
+
+ std::unique_ptr<SSL, SSL_deleter> 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;