Allow SSLContext to read certificates and keys from memory
authorAaron Balsara <abalsara@fb.com>
Fri, 15 Jan 2016 18:50:33 +0000 (10:50 -0800)
committerfacebook-github-bot-0 <folly-bot@fb.com>
Fri, 15 Jan 2016 19:20:26 +0000 (11:20 -0800)
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
folly/io/async/SSLContext.h
folly/io/async/test/AsyncSSLSocketTest.cpp

index 4e8ea69f5a4b5d82a1d73b416fa33fbcf7785c51..eb5f122a07eba55e02c8314f613cfb454b2249f7 100644 (file)
@@ -22,6 +22,7 @@
 #include <openssl/x509v3.h>
 
 #include <folly/Format.h>
+#include <folly/Memory.h>
 #include <folly/SpinLock.h>
 
 // ---------------------------------------------------------------------
@@ -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<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;
@@ -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: <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) {
@@ -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: <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());
index e20b093b4007839c95722e47e931f35ce49adac7..b6742bcc16a47dbe8c86977468992a9560d112ef 100644 (file)
@@ -37,6 +37,7 @@
 #endif
 
 #include <folly/Random.h>
+#include <folly/Range.h>
 
 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.
    *
index 350a6b57e40eb9a7039e0518202a8a2d8abf3b5c..c4ad75d6cc849b2ec9ef2356cd68b71a1cc1f2aa 100644 (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>
@@ -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<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);
@@ -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<char*>(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<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;