From ccfb76d9b478774669b10cb71148c02333bc4f7c Mon Sep 17 00:00:00 2001
From: Seva <seva@binarytrails.net>
Date: Wed, 25 Sep 2019 15:38:54 -0400
Subject: [PATCH] http: add client identity & fix CA

---
 include/opendht/dht_proxy_client.h |  3 +-
 include/opendht/dhtrunner.h        |  3 +-
 include/opendht/http.h             | 19 ++++---
 src/dht_proxy_client.cpp           | 37 +++++++++-----
 src/dhtrunner.cpp                  |  3 +-
 src/http.cpp                       | 82 ++++++++++++++++++------------
 tests/dhtproxytester.cpp           |  7 +--
 7 files changed, 95 insertions(+), 59 deletions(-)

diff --git a/include/opendht/dht_proxy_client.h b/include/opendht/dht_proxy_client.h
index 40918735..4f84cc7b 100644
--- a/include/opendht/dht_proxy_client.h
+++ b/include/opendht/dht_proxy_client.h
@@ -53,7 +53,7 @@ public:
     DhtProxyClient();
 
     explicit DhtProxyClient(
-        std::shared_ptr<dht::crypto::Certificate> certificate,
+        std::shared_ptr<dht::crypto::Certificate> serverCA, dht::crypto::Identity clientIdentity,
         std::function<void()> loopSignal, const std::string& serverHost,
         const std::string& pushClientId = "", std::shared_ptr<dht::Logger> logger = {});
 
@@ -327,6 +327,7 @@ private:
      */
     void cancelAllOperations();
 
+    dht::crypto::Identity clientIdentity_;
     std::shared_ptr<dht::crypto::Certificate> serverCertificate_;
     std::pair<std::string, std::string> serverHostService_;
     std::string pushClientId_;
diff --git a/include/opendht/dhtrunner.h b/include/opendht/dhtrunner.h
index 2e2590bb..1b725d71 100644
--- a/include/opendht/dhtrunner.h
+++ b/include/opendht/dhtrunner.h
@@ -63,7 +63,8 @@ public:
         std::string push_token {};
         bool peer_discovery {false};
         bool peer_publish {false};
-        std::shared_ptr<dht::crypto::Certificate> client_cert;
+        std::shared_ptr<dht::crypto::Certificate> server_ca;
+        dht::crypto::Identity client_identity;
     };
 
     struct Context {
diff --git a/include/opendht/http.h b/include/opendht/http.h
index 72476827..36656898 100644
--- a/include/opendht/http.h
+++ b/include/opendht/http.h
@@ -20,6 +20,7 @@
 
 #include "def.h"
 #include "infohash.h"
+#include "crypto.h"
 
 // some libraries may try to redefine snprintf
 // but restinio will use it in std namespace
@@ -85,17 +86,15 @@ class OPENDHT_PUBLIC Connection
 {
 public:
     Connection(asio::io_context& ctx, const bool ssl = true, std::shared_ptr<dht::Logger> l = {});
-    Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> certificate,
-               std::shared_ptr<dht::Logger> l = {});
+    Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> server_ca,
+               const dht::crypto::Identity& identity, std::shared_ptr<dht::Logger> l = {});
     ~Connection();
 
     unsigned int id();
     bool is_open();
-    bool is_v6();
     bool is_ssl();
 
-    void set_endpoint(const asio::ip::tcp::endpoint& endpoint,
-                      const asio::ssl::verify_mode verify_mode = asio::ssl::verify_none);
+    void set_ssl_verification(const asio::ip::tcp::endpoint& endpoint, const asio::ssl::verify_mode verify_mode);
 
     asio::streambuf& input();
     asio::streambuf& data();
@@ -120,7 +119,9 @@ private:
     std::unique_ptr<socket_t> socket_;
     std::shared_ptr<asio::ssl::context> ssl_ctx_;
     std::unique_ptr<ssl_socket_t> ssl_socket_;
-    std::unique_ptr<asio::const_buffer> certificate_;
+    std::unique_ptr<asio::const_buffer> server_ca_;
+    std::unique_ptr<asio::const_buffer> client_key_;
+    std::unique_ptr<asio::const_buffer> client_cert_;
 
     asio::ip::tcp::endpoint endpoint_;
 
@@ -232,7 +233,8 @@ public:
         return request_;
     }
 
-    void set_certificate(std::shared_ptr<dht::crypto::Certificate> certificate);
+    void set_certificate_authority(std::shared_ptr<dht::crypto::Certificate> certificate);
+    void set_identity(const dht::crypto::Identity& identity);
     void set_logger(std::shared_ptr<dht::Logger> logger);
 
     /**
@@ -311,7 +313,8 @@ private:
     std::unique_ptr<Callbacks> cbs_;
     State state_;
 
-    std::shared_ptr<dht::crypto::Certificate> certificate_;
+    dht::crypto::Identity client_identity_;
+    std::shared_ptr<dht::crypto::Certificate> server_ca_;
     std::string service_;
     std::string host_;
 
diff --git a/src/dht_proxy_client.cpp b/src/dht_proxy_client.cpp
index 94e793e7..e7ffad3d 100644
--- a/src/dht_proxy_client.cpp
+++ b/src/dht_proxy_client.cpp
@@ -72,20 +72,25 @@ struct DhtProxyClient::ProxySearch {
 DhtProxyClient::DhtProxyClient() {}
 
 DhtProxyClient::DhtProxyClient(
-        std::shared_ptr<dht::crypto::Certificate> certificate,
+        std::shared_ptr<dht::crypto::Certificate> serverCA, dht::crypto::Identity clientIdentity,
         std::function<void()> signal, const std::string& serverHost,
         const std::string& pushClientId, std::shared_ptr<dht::Logger> logger)
     :
-        serverCertificate_(certificate),
+        clientIdentity_(clientIdentity), serverCertificate_(serverCA),
         pushClientId_(pushClientId), loopSignal_(signal), logger_(logger)
 {
     // build http client
     serverHostService_ = splitPort(serverHost);
     serverHostService_.second = serverHostService_.second.empty() ? "80" :
                                 serverHostService_.second;
-    if (serverCertificate_ and logger_)
-        logger_->d("[proxy:client] using server certificate for ssl:\n%s",
-                   serverCertificate_->toString(false/*chain*/).c_str());
+    if (logger_){
+        if (serverCertificate_)
+            logger_->d("[proxy:client] using ca certificate for ssl:\n%s",
+                       serverCertificate_->toString(false/*chain*/).c_str());
+        if (clientIdentity_.first and clientIdentity_.second)
+            logger_->d("[proxy:client] using client certificate for ssl:\n%s",
+                       clientIdentity_.second->toString(false/*chain*/).c_str());
+    }
     // resolve once
     resolver_ = std::make_shared<http::Resolver>(httpContext_, serverHost, logger_);
     // run http client
@@ -346,8 +351,9 @@ DhtProxyClient::get(const InfoHash& key, GetCallback cb, DoneCallback donecb, Va
             }
         });
         if (serverCertificate_)
-            request->set_certificate(serverCertificate_);
-        request->set_certificate(serverCertificate_);
+            request->set_certificate_authority(serverCertificate_);
+        if (clientIdentity_.first and clientIdentity_.second)
+            request->set_identity(clientIdentity_);
         request->send();
         requests_[reqid] = request;
     }
@@ -482,8 +488,9 @@ DhtProxyClient::doPut(const InfoHash& key, Sp<Value> val, DoneCallback cb, time_
             }
         });
         if (serverCertificate_)
-            request->set_certificate(serverCertificate_);
-        request->set_certificate(serverCertificate_);
+            request->set_certificate_authority(serverCertificate_);
+        if (clientIdentity_.first and clientIdentity_.second)
+            request->set_identity(clientIdentity_);
         request->send();
         requests_[reqid] = request;
     }
@@ -679,7 +686,9 @@ DhtProxyClient::queryProxyInfo(std::shared_ptr<InfoState> infoState, const sa_fa
             return;
 
         if (serverCertificate_)
-            request->set_certificate(serverCertificate_);
+            request->set_certificate_authority(serverCertificate_);
+        if (clientIdentity_.first and clientIdentity_.second)
+            request->set_identity(clientIdentity_);
         request->send();
         requests_[reqid] = request;
     }
@@ -958,7 +967,9 @@ DhtProxyClient::handleExpireListener(const asio::error_code &ec, const InfoHash&
                     }
                 });
                 if (serverCertificate_)
-                    request->set_certificate(serverCertificate_);
+                    request->set_certificate_authority(serverCertificate_);
+                if (clientIdentity_.first and clientIdentity_.second)
+                    request->set_identity(clientIdentity_);
                 request->send();
                 requests_[reqid] = request;
             }
@@ -1065,7 +1076,9 @@ DhtProxyClient::sendListen(const restinio::http_request_header_t header,
             }
         });
         if (serverCertificate_)
-            request->set_certificate(serverCertificate_);
+            request->set_certificate_authority(serverCertificate_);
+        if (clientIdentity_.first and clientIdentity_.second)
+            request->set_identity(clientIdentity_);
         request->send();
         requests_[reqid] = request;
     }
diff --git a/src/dhtrunner.cpp b/src/dhtrunner.cpp
index 1187e50b..98edb344 100644
--- a/src/dhtrunner.cpp
+++ b/src/dhtrunner.cpp
@@ -979,7 +979,8 @@ DhtRunner::enableProxy(bool proxify)
         // Init the proxy client
         auto dht_via_proxy = std::unique_ptr<DhtInterface>(
             new DhtProxyClient(
-                config_.client_cert,
+                config_.server_ca,
+                config_.client_identity,
                 [this]{
                     if (config_.threaded) {
                         {
diff --git a/src/http.cpp b/src/http.cpp
index 9b60c890..f52b459a 100644
--- a/src/http.cpp
+++ b/src/http.cpp
@@ -97,23 +97,40 @@ Connection::Connection(asio::io_context& ctx, const bool ssl, std::shared_ptr<dh
     }
 }
 
-Connection::Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> certificate,
-                       std::shared_ptr<dht::Logger> l)
+Connection::Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certificate> server_ca,
+                       const dht::crypto::Identity& identity, std::shared_ptr<dht::Logger> l)
     : id_(Connection::ids_++), ctx_(ctx), logger_(l)
 {
     ssl_ctx_ = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
     ssl_ctx_->set_default_verify_paths();
-
     asio::error_code ec;
-    auto cert = certificate->toString(false/*chain*/);
-    certificate_ = std::make_unique<asio::const_buffer>(static_cast<const void*>(cert.data()),
-                                                       (std::size_t) cert.size());
-    ssl_ctx_->use_certificate(*certificate_, asio::ssl::context::file_format::pem, ec);
-    if (ec)
-        throw std::runtime_error("Error setting certificate: " + ec.message());
-    else if (logger_)
-        logger_->d("[http:client]  [connection:%i] start https session with %s", id_, certificate->getUID().c_str());
-
+    if (server_ca){
+        auto ca = server_ca->toString(false/*chain*/);
+        server_ca_ = std::make_unique<asio::const_buffer>(static_cast<const void*>(ca.data()),
+                                                      (std::size_t) ca.size());
+        ssl_ctx_->add_certificate_authority(*server_ca_, ec);
+        if (ec)
+            throw std::runtime_error("Error adding certificate authority: " + ec.message());
+        else if (logger_)
+            logger_->d("[http:client]  [connection:%i] certficate authority %s", id_, server_ca->getUID().c_str());
+    }
+    if (identity.first){
+        auto pk = identity.first->serialize();
+        client_key_ = std::make_unique<asio::const_buffer>(static_cast<void*>(pk.data()), (std::size_t) pk.size());
+        ssl_ctx_->use_private_key(*client_key_, asio::ssl::context::file_format::pem, ec);
+        if (ec)
+            throw std::runtime_error("Error setting client private key: " + ec.message());
+    }
+    if (identity.second){
+        auto c = identity.second->toString(false/*chain*/);
+        client_cert_ = std::make_unique<asio::const_buffer>(static_cast<const void*>(c.data()),
+                                                    (std::size_t) c.size());
+        ssl_ctx_->use_certificate(*client_cert_, asio::ssl::context::file_format::pem, ec);
+        if (ec)
+            throw std::runtime_error("Error adding client certificate: " + ec.message());
+        else if (logger_)
+            logger_->d("[http:client]  [connection:%i] client certificate %s", id_, identity.second->getUID().c_str());
+    }
     ssl_ctx_->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
     ssl_socket_ = std::make_unique<ssl_socket_t>(ctx_, ssl_ctx_);
 }
@@ -152,12 +169,6 @@ Connection::is_open()
         return socket_->is_open();
 }
 
-bool
-Connection::is_v6()
-{
-    return endpoint_.address().is_v6();
-}
-
 bool
 Connection::is_ssl()
 {
@@ -165,20 +176,21 @@ Connection::is_ssl()
 }
 
 void
-Connection::set_endpoint(const asio::ip::tcp::endpoint& endpoint, const asio::ssl::verify_mode verify_mode)
+Connection::set_ssl_verification(const asio::ip::tcp::endpoint& endpoint, const asio::ssl::verify_mode verify_mode)
 {
-    endpoint_ = endpoint;
     if (ssl_ctx_ and verify_mode != asio::ssl::verify_none){
-        auto hostname = endpoint_.address().to_string();
+        auto hostname = endpoint.address().to_string();
         ssl_socket_->asio_ssl_stream().set_verify_mode(verify_mode);
         ssl_socket_->asio_ssl_stream().set_verify_callback(
             [this, hostname](bool preverified, asio::ssl::verify_context& ctx) -> bool {
+                if (preverified)
+                    return preverified;
+                // starts from CA and goes down the presented chain
                 auto verifier = asio::ssl::rfc2818_verification(hostname);
                 bool verified = verifier(preverified, ctx);
                 auto verify_ec = X509_STORE_CTX_get_error(ctx.native_handle());
-                if (verify_ec == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT /*18*/
-                    || verify_ec == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN /*19*/)
-                    verified = true;
+                if (verified != 0 /*X509_V_OK*/ and logger_)
+                    logger_->e("[http::connection:%i] ssl verification error=%i", id_, verify_ec);
                 return verified;
             }
         );
@@ -492,9 +504,15 @@ Request::get_connection() const
 }
 
 void
-Request::set_certificate(std::shared_ptr<dht::crypto::Certificate> certificate)
+Request::set_certificate_authority(std::shared_ptr<dht::crypto::Certificate> certificate)
+{
+    server_ca_ = certificate;
+}
+
+void
+Request::set_identity(const dht::crypto::Identity& identity)
 {
-    certificate_ = certificate;
+    client_identity_ = identity;
 }
 
 void
@@ -726,8 +744,8 @@ Request::connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb)
         logger_->d("[http:client]  [request:%i] connect begin: %s", id_, eps.c_str());
     }
     if (get_url().protocol == "https"){
-        if (certificate_)
-            conn_ = std::make_shared<Connection>(ctx_, certificate_, logger_);
+        if (server_ca_)
+            conn_ = std::make_shared<Connection>(ctx_, server_ca_, client_identity_, logger_);
         else
             conn_ = std::make_shared<Connection>(ctx_, true/*ssl*/, logger_);
     }
@@ -748,9 +766,9 @@ Request::connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb)
                 logger_->d("[http:client]  [request:%i] connect success", id_);
 
             if (get_url().protocol == "https"){
-                if (certificate_)
-                    conn_->set_endpoint(endpoint, asio::ssl::verify_peer
-                                                  | asio::ssl::verify_fail_if_no_peer_cert);
+                if (server_ca_)
+                    conn_->set_ssl_verification(endpoint, asio::ssl::verify_peer
+                                                          | asio::ssl::verify_fail_if_no_peer_cert);
 
                 if (conn_ and conn_->is_open() and conn_->is_ssl()){
                     conn_->async_handshake([this, cb](const asio::error_code& ec){
@@ -768,8 +786,6 @@ Request::connect(std::vector<asio::ip::tcp::endpoint>&& endpoints, HandlerCb cb)
                     cb(asio::error::operation_aborted);
                 return;
             }
-            else
-                conn_->set_endpoint(endpoint, asio::ssl::verify_none);
         }
         if (cb)
             cb(ec);
diff --git a/tests/dhtproxytester.cpp b/tests/dhtproxytester.cpp
index e118ce88..41241f34 100644
--- a/tests/dhtproxytester.cpp
+++ b/tests/dhtproxytester.cpp
@@ -46,11 +46,12 @@ DhtProxyTester::setUp() {
 
     serverProxy = std::unique_ptr<dht::DhtProxyServer>(
         new dht::DhtProxyServer(
-            ///*http*/dht::crypto::Identity{},
-            /*https*/serverIdentity,
+            //dht::crypto::Identity{}, // http
+            serverIdentity,            // https
             nodeProxy, 8080, /*pushServer*/"127.0.0.1:8090", logger));
 
-    clientConfig.client_cert = serverIdentity.second;
+    clientConfig.server_ca = serverCAIdentity.second;
+    clientConfig.client_identity = dht::crypto::generateIdentity("DhtProxyTester");
     clientConfig.dht_config.node_config.maintain_storage = false;
     clientConfig.threaded = true;
     clientConfig.push_node_id = "dhtnode";
-- 
GitLab