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(<m, 0, sizeof(ltm)); + lt = <m; + } + + /* 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