From 9cc010e0f094c52f71b10b235c3a449c730ff183 Mon Sep 17 00:00:00 2001
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Date: Tue, 27 Oct 2020 17:22:25 -0400
Subject: [PATCH] http: load ca certs from platform specific stores into x509
 store

---
 CMakeLists.txt                          |  26 ++-
 {compat => src/compat}/msvc/unistd.h    |   0
 {compat => src/compat}/msvc/wingetopt.c |   0
 {compat => src/compat}/msvc/wingetopt.h |   0
 src/compat/os_cert.cpp                  | 221 ++++++++++++++++++++++++
 src/compat/os_cert.h                    |  38 ++++
 src/http.cpp                            |  14 +-
 tools/dhtchat.cpp                       |   6 +-
 tools/dhtscanner.cpp                    |   4 +-
 tools/perftest.cpp                      |   4 +-
 10 files changed, 289 insertions(+), 24 deletions(-)
 rename {compat => src/compat}/msvc/unistd.h (100%)
 rename {compat => src/compat}/msvc/wingetopt.c (100%)
 rename {compat => src/compat}/msvc/wingetopt.h (100%)
 create mode 100644 src/compat/os_cert.cpp
 create mode 100644 src/compat/os_cert.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4bbf899..a9e9f1f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,9 +36,6 @@ option (OPENDHT_C "Build C bindings" OFF)
 find_package(Doxygen)
 option (OPENDHT_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen)" ${DOXYGEN_FOUND})
 
-# native windows build
-set (MSVC (CMAKE_CXX_COMPILER_ID MATCHES "MSVC"))
-
 # Dependencies
 list (APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
 if (NOT MSVC)
@@ -98,10 +95,6 @@ if (NOT MSVC)
         set(OPENDHT_PROXY_OPENSSL OFF)
     endif ()
 else ()
-    add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS
-                    -D_CRT_SECURE_NO_WARNINGS
-                    -DWIN32_LEAN_AND_MEAN)
-    
     set (WIN32_DEP_DIR ${PROJECT_SOURCE_DIR}/../)
     include_directories(${WIN32_DEP_DIR}/../msvc/include) # SMP gnutls
     include_directories(${WIN32_DEP_DIR}/argon2/include)
@@ -141,7 +134,12 @@ if (NOT MSVC)
         set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fstack-protector-strong")
     endif ()
 else ()
-    set(DISABLE_MSC_WARNINGS "/wd4101 /wd4244 /wd4267 /wd4273 /wd4804 /wd4834")
+    add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS
+                    -D_CRT_SECURE_NO_WARNINGS
+                    -DWIN32_LEAN_AND_MEAN
+                    -DSTATIC_GETOPT
+                    -DGNUTLS_INTERNAL_BUILD)
+    set(DISABLE_MSC_WARNINGS "/wd4101 /wd4244 /wd4267 /wd4273 /wd4804 /wd4834 /wd4996")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DISABLE_MSC_WARNINGS}")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
 endif ()
@@ -328,10 +326,13 @@ if(OPENDHT_ARGON2)
     include_directories(argon2/include/)
 endif()
 
+# cross-platform compatibility sources
+set (COMPAT_DIR ${PROJECT_SOURCE_DIR}/src/compat/)
+list (APPEND opendht_SOURCES ${COMPAT_DIR}/os_cert.cpp)
+
 if (MSVC)
-    set (MSC_COMPAT_DIR ${PROJECT_SOURCE_DIR}/compat/msvc/)
-    list (APPEND opendht_HEADERS ${MSC_COMPAT_DIR}/unistd.h)
-    #include_directories(${PROJECT_SOURCE_DIR}/compat/msvc/)
+    set (MSC_COMPAT_DIR ${COMPAT_DIR}/msvc/)
+    list (APPEND opendht_SOURCES ${MSC_COMPAT_DIR}/unistd.h)
 endif ()
 
 # Targets
@@ -368,13 +369,11 @@ if (OPENDHT_STATIC)
             )
             if (OPENDHT_HTTP)
                 add_obj_lib (win32_fmt ${WIN32_DEP_DIR}/fmt/msvc/Release/fmt.lib)
-                #add_obj_lib (win32_asio ${WIN32_DEP_DIR}/asio/asio/msvc/x64/Release/asio.lib)
                 add_obj_lib (win32_http_parser ${WIN32_DEP_DIR}/http_parser/x64/Release/http-parser.lib)
                 add_obj_lib (win32_ssl ${WIN32_DEP_DIR}/openssl/libssl_static.lib)
                 add_obj_lib (win32_crypto ${WIN32_DEP_DIR}/openssl/libcrypto_static.lib)
                 list (APPEND obj_libs
                     $<TARGET_OBJECTS:win32_fmt>
-                    #$<TARGET_OBJECTS:win32_asio>
                     $<TARGET_OBJECTS:win32_http_parser>
                     $<TARGET_OBJECTS:win32_ssl>
                     $<TARGET_OBJECTS:win32_crypto>
@@ -389,7 +388,6 @@ if (OPENDHT_STATIC)
             list (APPEND win32_Libs
                 ${PROJECT_SOURCE_DIR}/../fmt/msvc/Release/fmt.lib
                 ${PROJECT_SOURCE_DIR}/../http_parser/x64/Release/http-parser.lib
-                ${PROJECT_SOURCE_DIR}/../asio/asio/msvc/x64/Release/asio.lib
                 ${PROJECT_SOURCE_DIR}/../openssl/libssl.lib
                 ${PROJECT_SOURCE_DIR}/../openssl/libcrypto.lib
             )
diff --git a/compat/msvc/unistd.h b/src/compat/msvc/unistd.h
similarity index 100%
rename from compat/msvc/unistd.h
rename to src/compat/msvc/unistd.h
diff --git a/compat/msvc/wingetopt.c b/src/compat/msvc/wingetopt.c
similarity index 100%
rename from compat/msvc/wingetopt.c
rename to src/compat/msvc/wingetopt.c
diff --git a/compat/msvc/wingetopt.h b/src/compat/msvc/wingetopt.h
similarity index 100%
rename from compat/msvc/wingetopt.h
rename to src/compat/msvc/wingetopt.h
diff --git a/src/compat/os_cert.cpp b/src/compat/os_cert.cpp
new file mode 100644
index 00000000..10999749
--- /dev/null
+++ b/src/compat/os_cert.cpp
@@ -0,0 +1,221 @@
+/*
+ *  Copyright (C) 2020 Savoir-faire Linux Inc.
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "os_cert.h"
+
+#include "log_enable.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32.lib")
+#include <openssl/ssl.h>
+#undef X509_NAME
+#undef OCSP_REQUEST
+#undef OCSP_RESPONSE
+#endif /*WIN32*/
+
+#ifdef __APPLE__
+#include <Security/Security.h>
+#endif /*__APPLE__*/
+
+#ifdef _WIN32
+#define GENTIME_LENGTH 15
+#define UTCTIME_LENGTH 13
+#define ATOI2(ar)      ((ar) += 2, ((ar)[-2] - '0') * 10 + ((ar)[-1] - '0'))
+// Taken from libressl/src/crypto/asn1/a_time_tm.c
+/*
+ * Parse an RFC 5280 format ASN.1 time string.
+ *
+ * mode must be:
+ * 0 if we expect to parse a time as specified in RFC 5280 for an X509 object.
+ * V_ASN1_UTCTIME if we wish to parse an RFC5280 format UTC time.
+ * V_ASN1_GENERALIZEDTIME if we wish to parse an RFC5280 format Generalized time.
+ *
+ * Returns:
+ * -1 if the string was invalid.
+ * V_ASN1_UTCTIME if the string validated as a UTC time string.
+ * V_ASN1_GENERALIZEDTIME if the string validated as a Generalized time string.
+ *
+ * Fills in *tm with the corresponding time if tm is non NULL.
+ */
+int
+ASN1_time_parse(const char* bytes, size_t len, struct tm* tm, int mode)
+{
+    size_t i;
+    int type = 0;
+    struct tm ltm;
+    struct tm* lt;
+    const char* p;
+
+    if (bytes == NULL)
+        return (-1);
+
+    /* Constrain to valid lengths. */
+    if (len != UTCTIME_LENGTH && len != GENTIME_LENGTH)
+        return (-1);
+
+    lt = tm;
+    if (lt == NULL) {
+        memset(&ltm, 0, sizeof(ltm));
+        lt = &ltm;
+    }
+
+    /* Timezone is required and must be GMT (Zulu). */
+    if (bytes[len - 1] != 'Z')
+        return (-1);
+
+    /* Make sure everything else is digits. */
+    for (i = 0; i < len - 1; i++) {
+        if (isdigit((unsigned char) bytes[i]))
+            continue;
+        return (-1);
+    }
+
+    /*
+     * Validate and convert the time
+     */
+    p = bytes;
+    switch (len) {
+    case GENTIME_LENGTH:
+        if (mode == V_ASN1_UTCTIME)
+            return (-1);
+        lt->tm_year = (ATOI2(p) * 100) - 1900; /* cc */
+        type = V_ASN1_GENERALIZEDTIME;
+        /* FALLTHROUGH */
+    case UTCTIME_LENGTH:
+        if (type == 0) {
+            if (mode == V_ASN1_GENERALIZEDTIME)
+                return (-1);
+            type = V_ASN1_UTCTIME;
+        }
+        lt->tm_year += ATOI2(p); /* yy */
+        if (type == V_ASN1_UTCTIME) {
+            if (lt->tm_year < 50)
+                lt->tm_year += 100;
+        }
+        lt->tm_mon = ATOI2(p) - 1; /* mm */
+        if (lt->tm_mon < 0 || lt->tm_mon > 11)
+            return (-1);
+        lt->tm_mday = ATOI2(p); /* dd */
+        if (lt->tm_mday < 1 || lt->tm_mday > 31)
+            return (-1);
+        lt->tm_hour = ATOI2(p); /* HH */
+        if (lt->tm_hour < 0 || lt->tm_hour > 23)
+            return (-1);
+        lt->tm_min = ATOI2(p); /* MM */
+        if (lt->tm_min < 0 || lt->tm_min > 59)
+            return (-1);
+        lt->tm_sec = ATOI2(p); /* SS */
+        /* Leap second 60 is not accepted. Reconsider later? */
+        if (lt->tm_sec < 0 || lt->tm_sec > 59)
+            return (-1);
+        break;
+    default:
+        return (-1);
+    }
+
+    return (type);
+}
+#endif /*_WIN32*/
+
+namespace dht {
+namespace http {
+
+void
+addSystemCaCertificates(SSL_CTX* ctx, const std::shared_ptr<Logger>& logger)
+{
+    X509_STORE* store = SSL_CTX_get_cert_store(ctx);
+    if (store == nullptr) {
+        if (logger)
+            logger->e("couldn't get the context cert store");
+        return;
+    }
+
+    X509* x509cert;
+#ifdef _WIN32
+    PCCERT_CONTEXT pContext = NULL;
+    HCERTSTORE hSystemStore;
+    hSystemStore = CertOpenSystemStore(NULL, "ROOT");
+    if (!hSystemStore) {
+        if (logger)
+            logger->e("couldn't open the system cert store");
+        return;
+    }
+    while (pContext = CertEnumCertificatesInStore(hSystemStore, pContext)) {
+        x509cert = nullptr;
+        const uint8_t* encoded_cert = pContext->pbCertEncoded;
+        x509cert = d2i_X509(nullptr, &encoded_cert, pContext->cbCertEncoded);
+        if (x509cert) {
+            if (X509_STORE_add_cert(store, x509cert) == 1) {
+                if (logger)
+                    logger->d("local certificate added");
+            }
+            X509_free(x509cert);
+        }
+    }
+    CertCloseStore(hSystemStore, 0);
+#elif __APPLE__
+    CFArrayRef result = NULL;
+    OSStatus osStatus;
+
+    if ((osStatus = SecTrustCopyAnchorCertificates(&result)) != 0) {
+        if (logger) {
+            CFStringRef statusString = SecCopyErrorMessageString(osStatus, NULL);
+            logger->d("Error enumerating certificates: %s",
+                      CFStringGetCStringPtr(statusString, kCFStringEncodingASCII));
+            CFRelease(statusString);
+        }
+        if (result != NULL) {
+            CFRelease(result);
+        }
+        return;
+    }
+    CFDataRef rawData = NULL;
+    for (CFIndex i = 0; i < CFArrayGetCount(result); i++) {
+        x509cert = nullptr;
+        SecCertificateRef cert = (SecCertificateRef) CFArrayGetValueAtIndex(result, i);
+        rawData = SecCertificateCopyData(cert);
+        if (!rawData) {
+            if (logger)
+                logger->e("couldn't copy raw certificate data");
+            break;
+        }
+        const uint8_t* rawDataPtr = CFDataGetBytePtr(rawData);
+        x509cert = d2i_X509(nullptr, &rawDataPtr, CFDataGetLength(rawData));
+        if (x509cert) {
+            if (X509_STORE_add_cert(store, x509cert) == 1) {
+                if (logger)
+                    logger->d("local certificate added");
+            }
+            X509_free(x509cert);
+        }
+        CFRelease(rawData);
+        rawData = NULL;
+    }
+    if (result != NULL) {
+        CFRelease(result);
+    }
+    if (rawData != NULL) {
+        CFRelease(rawData);
+    }
+#endif /*__APPLE__, _WIN32*/
+}
+
+} /*namespace http*/
+} /*namespace dht*/
diff --git a/src/compat/os_cert.h b/src/compat/os_cert.h
new file mode 100644
index 00000000..b6dc883c
--- /dev/null
+++ b/src/compat/os_cert.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (C) 2020 Savoir-faire Linux Inc.
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "log_enable.h"
+
+#include <memory>
+
+#include <openssl/x509.h>
+
+#ifdef _WIN32
+#define V_ASN1_UTCTIME         23
+#define V_ASN1_GENERALIZEDTIME 24
+#define timegm                 _mkgmtime
+int ASN1_time_parse(const char* bytes, size_t len, struct tm* tm, int mode);
+#endif /*_WIN32*/
+
+namespace dht {
+namespace http {
+void addSystemCaCertificates(SSL_CTX* ctx, const std::shared_ptr<Logger>& logger);
+}
+} // namespace dht
diff --git a/src/http.cpp b/src/http.cpp
index 23005c74..0cb50a77 100644
--- a/src/http.cpp
+++ b/src/http.cpp
@@ -20,6 +20,7 @@
 #include "log_enable.h"
 #include "crypto.h"
 #include "base64.h"
+#include "compat/os_cert.h"
 
 #include <asio.hpp>
 #include <restinio/impl/tls_socket.hpp>
@@ -30,6 +31,8 @@
 #include <openssl/ssl.h>
 #include <openssl/asn1.h>
 
+#include <crypto/x509.h>
+
 #define MAXAGE_SEC (14*24*60*60)
 #define JITTER_SEC (60)
 #define OCSP_MAX_RESPONSE_SIZE (20480)
@@ -105,6 +108,8 @@ Connection::Connection(asio::io_context& ctx, const bool ssl, std::shared_ptr<dh
         ssl_ctx_->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
 #ifdef __ANDROID__
         ssl_ctx_->add_verify_path("/system/etc/security/cacerts");
+#elif defined(WIN32) || defined(__APPLE__)
+        addSystemCaCertificates(ssl_ctx_->native_handle(), l);
 #else
         ssl_ctx_->set_default_verify_paths();
 #endif
@@ -126,9 +131,11 @@ Connection::Connection(asio::io_context& ctx, std::shared_ptr<dht::crypto::Certi
     ssl_ctx_ = std::make_shared<asio::ssl::context>(asio::ssl::context::tls_client);
     ssl_ctx_->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
 #ifdef __ANDROID__
-        ssl_ctx_->add_verify_path("/system/etc/security/cacerts");
+    ssl_ctx_->add_verify_path("/system/etc/security/cacerts");
+#elif WIN32
+    addSystemCaCertificates(ssl_ctx_->native_handle(), l);
 #else
-        ssl_ctx_->set_default_verify_paths();
+    ssl_ctx_->set_default_verify_paths();
 #endif
     asio::error_code ec;
     if (server_ca){
@@ -457,6 +464,7 @@ Connection::set_ssl_verification(const std::string& hostname, const asio::ssl::v
                         X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 1024);
                         logger->d("[connection:%i] verify %s compliance to RFC 2818:\n%s", id, hostname.c_str(), subject_name);
                     }
+
                     // starts from CA and goes down the presented chain
                     auto verifier = asio::ssl::rfc2818_verification(hostname);
                     bool verified = verifier(preverified, ctx);
@@ -553,7 +561,7 @@ Connection::async_handshake(HandlerCb cb)
                     else if (verify_ec != X509_V_OK)
                         this_.logger_->e("[connection:%i] verify handshake error: %i", this_.id_, verify_ec);
                     else
-                        this_.logger_->e("[connection:%i] verify handshake success", this_.id_);
+                        this_.logger_->w("[connection:%i] verify handshake success", this_.id_);
                 }
             }
             if (cb)
diff --git a/tools/dhtchat.cpp b/tools/dhtchat.cpp
index 3f9853c7..66d455ce 100644
--- a/tools/dhtchat.cpp
+++ b/tools/dhtchat.cpp
@@ -54,7 +54,7 @@ main(int argc, char **argv)
         print_usage();
         return 0;
     }
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_init();
 #endif
 
@@ -76,7 +76,7 @@ main(int argc, char **argv)
 
         const InfoHash myid = dht.getId();
 
-#ifndef WIN32_NATIVE
+#ifndef _MSC_VER
         // using the GNU History API
         using_history();
 #endif
@@ -144,7 +144,7 @@ main(int argc, char **argv)
 
     std::cout << std::endl <<  "Stopping node..." << std::endl;
     dht.join();
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_deinit();
 #endif
     return 0;
diff --git a/tools/dhtscanner.cpp b/tools/dhtscanner.cpp
index b6dbf27c..66006398 100644
--- a/tools/dhtscanner.cpp
+++ b/tools/dhtscanner.cpp
@@ -77,7 +77,7 @@ step(DhtRunner& dht, std::atomic_uint& done, std::shared_ptr<NodeSet> all_nodes,
 int
 main(int argc, char **argv)
 {
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_init();
 #endif
     auto params = parseArgs(argc, argv);
@@ -123,7 +123,7 @@ main(int argc, char **argv)
     }
 
     dht.join();
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_deinit();
 #endif
     return 0;
diff --git a/tools/perftest.cpp b/tools/perftest.cpp
index 187124f9..59faf7ac 100644
--- a/tools/perftest.cpp
+++ b/tools/perftest.cpp
@@ -132,7 +132,7 @@ benchPingPong(unsigned netSize, unsigned n_parallel) {
 int
 main(int argc, char **argv)
 {
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_init();
 #endif
     auto params = parseArgs(argc, argv);
@@ -173,7 +173,7 @@ main(int argc, char **argv)
     std::cout << print_duration(totalTime/totalOps) << " per rt, "
             << totalOps/std::chrono::duration<double>(totalTime).count() << " ping per s" << std::endl << std::endl;
 
-#ifdef WIN32_NATIVE
+#ifdef _MSC_VER
     gnutls_global_deinit();
 #endif
     return 0;
-- 
GitLab