Browse Source

Pass certs and keys from memory (#432)

* SSLServer: add constructor to pass ssl-certificates and key from memory

* SSLClient: add constructor to pass ssl-certificates and key from memory

* add TestCase for passing certificates from memory to SSLClient/SSLServer
Daniel Ottiger 5 years ago
parent
commit
2ece5f116b
2 changed files with 139 additions and 2 deletions
  1. 65 2
      httplib.h
  2. 74 0
      test/test.cc

+ 65 - 2
httplib.h

@@ -848,6 +848,9 @@ public:
             const char *client_ca_cert_file_path = nullptr,
             const char *client_ca_cert_dir_path = nullptr);
 
+  SSLServer(X509 *cert, EVP_PKEY *private_key,
+            X509_STORE *client_ca_cert_store = nullptr);
+
   ~SSLServer() override;
 
   bool is_valid() const override;
@@ -865,6 +868,9 @@ public:
                      const std::string &client_cert_path = std::string(),
                      const std::string &client_key_path = std::string());
 
+  SSLClient(const std::string &host, int port, X509 *client_cert,
+            EVP_PKEY *client_key);
+
   ~SSLClient() override;
 
   bool is_valid() const override;
@@ -872,6 +878,8 @@ public:
   void set_ca_cert_path(const char *ca_ceert_file_path,
                         const char *ca_cert_dir_path = nullptr);
 
+  void set_ca_cert_store(X509_STORE *ca_cert_store);
+
   void enable_server_certificate_verification(bool enabled);
 
   long get_openssl_verify_result() const;
@@ -897,6 +905,7 @@ private:
 
   std::string ca_cert_file_path_;
   std::string ca_cert_dir_path_;
+  X509_STORE *ca_cert_store_ = nullptr;
   bool server_certificate_verification_ = false;
   long verify_result_ = 0;
 };
@@ -4654,6 +4663,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
   }
 }
 
+inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
+                            X509_STORE *client_ca_cert_store) {
+  ctx_ = SSL_CTX_new(SSLv23_server_method());
+
+  if (ctx_) {
+    SSL_CTX_set_options(ctx_,
+                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                            SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+    if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+        SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    } else if (client_ca_cert_store) {
+
+      SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+
+      SSL_CTX_set_verify(
+          ctx_,
+          SSL_VERIFY_PEER |
+              SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
+          nullptr);
+    }
+  }
+}
+
 inline SSLServer::~SSLServer() {
   if (ctx_) { SSL_CTX_free(ctx_); }
 }
@@ -4693,6 +4729,24 @@ inline SSLClient::SSLClient(const std::string &host, int port,
   }
 }
 
+inline SSLClient::SSLClient(const std::string &host, int port,
+                            X509 *client_cert, EVP_PKEY *client_key)
+    : Client(host, port) {
+  ctx_ = SSL_CTX_new(SSLv23_client_method());
+
+  detail::split(&host_[0], &host_[host_.size()], '.',
+                [&](const char *b, const char *e) {
+                  host_components_.emplace_back(std::string(b, e));
+                });
+  if (client_cert != nullptr && client_key != nullptr) {
+    if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
+        SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    }
+  }
+}
+
 inline SSLClient::~SSLClient() {
   if (ctx_) { SSL_CTX_free(ctx_); }
 }
@@ -4705,6 +4759,10 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
   if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
 }
 
+void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
+  if (ca_cert_store) { ca_cert_store_ = ca_cert_store; }
+}
+
 inline void SSLClient::enable_server_certificate_verification(bool enabled) {
   server_certificate_verification_ = enabled;
 }
@@ -4728,14 +4786,19 @@ inline bool SSLClient::process_and_close_socket(
              true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
              ctx_, ctx_mutex_,
              [&](SSL *ssl) {
-               if (ca_cert_file_path_.empty()) {
+               if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) {
                  SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
-               } else {
+               } else if (!ca_cert_file_path_.empty()) {
                  if (!SSL_CTX_load_verify_locations(
                          ctx_, ca_cert_file_path_.c_str(), nullptr)) {
                    return false;
                  }
                  SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
+               } else if (ca_cert_store_ != nullptr) {
+                 if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) {
+                   SSL_CTX_set_cert_store(ctx_, ca_cert_store_);
+                 }
+                 SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
                }
 
                if (SSL_connect(ssl) != 1) { return false; }

+ 74 - 0
test/test.cc

@@ -2530,6 +2530,80 @@ TEST(SSLClientServerTest, ClientCertPresent) {
   t.join();
 }
 
+TEST(SSLClientServerTest, MemoryClientCertPresent) {
+  X509 *server_cert;
+  EVP_PKEY *server_private_key;
+  X509_STORE *client_ca_cert_store;
+  X509 *client_cert;
+  EVP_PKEY *client_private_key;
+
+  FILE *f = fopen(SERVER_CERT_FILE, "r+");
+  server_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
+  fclose(f);
+
+  f = fopen(SERVER_PRIVATE_KEY_FILE, "r+");
+  server_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
+  fclose(f);
+
+  f = fopen(CLIENT_CA_CERT_FILE, "r+");
+  client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
+  client_ca_cert_store = X509_STORE_new();
+  X509_STORE_add_cert(client_ca_cert_store, client_cert);
+  X509_free(client_cert);
+  fclose(f);
+
+  f = fopen(CLIENT_CERT_FILE, "r+");
+  client_cert = PEM_read_X509(f, nullptr, nullptr, nullptr);
+  fclose(f);
+
+  f = fopen(CLIENT_PRIVATE_KEY_FILE, "r+");
+  client_private_key = PEM_read_PrivateKey(f, nullptr, nullptr, nullptr);
+  fclose(f);
+
+  SSLServer svr(server_cert, server_private_key, client_ca_cert_store);
+  ASSERT_TRUE(svr.is_valid());
+
+  svr.Get("/test", [&](const Request &req, Response &res) {
+    res.set_content("test", "text/plain");
+    svr.stop();
+    ASSERT_TRUE(true);
+
+    auto peer_cert = SSL_get_peer_certificate(req.ssl);
+    ASSERT_TRUE(peer_cert != nullptr);
+
+    auto subject_name = X509_get_subject_name(peer_cert);
+    ASSERT_TRUE(subject_name != nullptr);
+
+    std::string common_name;
+    {
+      char name[BUFSIZ];
+      auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
+                                                name, sizeof(name));
+      common_name.assign(name, static_cast<size_t>(name_len));
+    }
+
+    EXPECT_EQ("Common Name", common_name);
+
+    X509_free(peer_cert);
+  });
+
+  thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
+  std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+  httplib::SSLClient cli(HOST, PORT, client_cert, client_private_key);
+  auto res = cli.Get("/test");
+  cli.set_timeout_sec(30);
+  ASSERT_TRUE(res != nullptr);
+  ASSERT_EQ(200, res->status);
+
+  X509_free(server_cert);
+  EVP_PKEY_free(server_private_key);
+  X509_free(client_cert);
+  EVP_PKEY_free(client_private_key);
+
+  t.join();
+}
+
 TEST(SSLClientServerTest, ClientCertMissing) {
   SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
                 CLIENT_CA_CERT_DIR);