From 705962e5ce4cb505a90828683843e70ab9ed1092 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Tue, 11 Nov 2025 13:19:59 +0100 Subject: [PATCH] Added partial verification of server public key on client side - needs hostname verification. Added startup flag to ignore verification fail. --- CMakeLists.txt | 19 +++++--- .../columnlynx/client/net/tcp/tcp_client.hpp | 5 ++- .../columnlynx/common/libsodium_wrapper.hpp | 44 +++++++++++++++++++ src/client/main.cpp | 7 ++- src/client/net/tcp/tcp_client.cpp | 24 +++++++--- src/common/utils.cpp | 2 +- src/server/main.cpp | 2 +- 7 files changed, 88 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f4fb42..53fc39d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16) # If MAJOR is 0, and MINOR > 0, Version is BETA project(ColumnLynx - VERSION 0.0.1 + VERSION 0.0.3 LANGUAGES CXX ) @@ -25,7 +25,7 @@ include(FetchContent) if(APPLE) # Build universal (arm64 + x86_64), or limit to one arch if you prefer # e.g., set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "" FORCE) - set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures" FORCE) + set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Build architectures" FORCE) endif() # --------------------------------------------------------- @@ -58,6 +58,15 @@ set(SODIUM_CMAKE_ARGS FetchContent_MakeAvailable(Sodium) +# OpenSSL +find_package(OpenSSL REQUIRED) +if(OPENSSL_FOUND) + message(STATUS "Found OpenSSL version ${OPENSSL_VERSION}") + include_directories(${OPENSSL_INCLUDE_DIR}) +else() + message(FATAL_ERROR "OpenSSL not found") +endif() + # --------------------------------------------------------- # Output directories # --------------------------------------------------------- @@ -77,7 +86,7 @@ endforeach() # --------------------------------------------------------- file(GLOB_RECURSE COMMON_SRC CONFIGURE_DEPENDS src/common/*.cpp) add_library(common STATIC ${COMMON_SRC}) -target_link_libraries(common PUBLIC sodium) +target_link_libraries(common PUBLIC sodium OpenSSL::SSL OpenSSL::Crypto) target_include_directories(common PUBLIC ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include @@ -90,7 +99,7 @@ target_compile_definitions(common PUBLIC ASIO_STANDALONE) # --------------------------------------------------------- file(GLOB_RECURSE CLIENT_SRC CONFIGURE_DEPENDS src/client/*.cpp) add_executable(client ${CLIENT_SRC}) -target_link_libraries(client PRIVATE common sodium) +target_link_libraries(client PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto) target_include_directories(client PRIVATE ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include @@ -104,7 +113,7 @@ set_target_properties(client PROPERTIES OUTPUT_NAME "columnlynx_client") # --------------------------------------------------------- file(GLOB_RECURSE SERVER_SRC CONFIGURE_DEPENDS src/server/*.cpp) add_executable(server ${SERVER_SRC}) -target_link_libraries(server PRIVATE common sodium) +target_link_libraries(server PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto) target_include_directories(server PRIVATE ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include diff --git a/include/columnlynx/client/net/tcp/tcp_client.hpp b/include/columnlynx/client/net/tcp/tcp_client.hpp index 28001ee..4a2193e 100644 --- a/include/columnlynx/client/net/tcp/tcp_client.hpp +++ b/include/columnlynx/client/net/tcp/tcp_client.hpp @@ -24,7 +24,8 @@ namespace ColumnLynx::Net::TCP { const std::string& port, Utils::LibSodiumWrapper* sodiumWrapper, std::array* aesKey, - uint64_t* sessionIDRef) + uint64_t* sessionIDRef, + bool* insecureMode) : mResolver(ioContext), mSocket(ioContext), @@ -33,6 +34,7 @@ namespace ColumnLynx::Net::TCP { mLibSodiumWrapper(sodiumWrapper), mGlobalKeyRef(aesKey), mSessionIDRef(sessionIDRef), + mInsecureMode(insecureMode), mHeartbeatTimer(mSocket.get_executor()), mLastHeartbeatReceived(std::chrono::steady_clock::now()), mLastHeartbeatSent(std::chrono::steady_clock::now()) @@ -62,6 +64,7 @@ namespace ColumnLynx::Net::TCP { SymmetricKey mConnectionAESKey; std::array* mGlobalKeyRef; // Reference to global AES key uint64_t* mSessionIDRef; // Reference to global Session ID + bool* mInsecureMode; // Reference to insecure mode flag asio::steady_timer mHeartbeatTimer; std::chrono::steady_clock::time_point mLastHeartbeatReceived; std::chrono::steady_clock::time_point mLastHeartbeatSent; diff --git a/include/columnlynx/common/libsodium_wrapper.hpp b/include/columnlynx/common/libsodium_wrapper.hpp index 1d99989..331855e 100644 --- a/include/columnlynx/common/libsodium_wrapper.hpp +++ b/include/columnlynx/common/libsodium_wrapper.hpp @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include namespace ColumnLynx { using PublicKey = std::array; // Ed25519 @@ -178,6 +181,47 @@ namespace ColumnLynx::Utils { return plaintext; } + static inline bool verifyCertificateWithSystemCAs(const std::vector& cert_der) { + // Parse DER-encoded certificate + const unsigned char* p = cert_der.data(); + std::unique_ptr cert( + d2i_X509(nullptr, &p, cert_der.size()), X509_free + ); + if (!cert) { + return false; + } + + // Create a certificate store + std::unique_ptr store( + X509_STORE_new(), X509_STORE_free + ); + if (!store) { + return false; + } + + // Load system default CA paths (/etc/ssl/certs, etc.) + if (X509_STORE_set_default_paths(store.get()) != 1) { + return false; + } + + // Create a verification context + std::unique_ptr ctx( + X509_STORE_CTX_new(), X509_STORE_CTX_free + ); + if (!ctx) { + return false; + } + + // Initialize verification context + if (X509_STORE_CTX_init(ctx.get(), store.get(), cert.get(), nullptr) != 1) { + return false; + } + + // Perform the actual certificate verification + int result = X509_verify_cert(ctx.get()); + return result == 1; + } + private: std::array mPublicKey; std::array mPrivateKey; diff --git a/src/client/main.cpp b/src/client/main.cpp index 7c6edd1..c182fe0 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -38,7 +38,10 @@ int main(int argc, char** argv) { options.add_options() ("h,help", "Print help") ("s,server", "Server address", cxxopts::value()->default_value("127.0.0.1")) - ("p,port", "Server port", cxxopts::value()->default_value(std::to_string(serverPort()))); + ("p,port", "Server port", cxxopts::value()->default_value(std::to_string(serverPort()))) + ("as,allow-selfsigned", "Allow self-signed certificates", cxxopts::value()->default_value("false")); + + bool insecureMode = options.parse(argc, argv).count("allow-selfsigned") > 0; auto result = options.parse(argc, argv); if (result.count("help")) { @@ -59,7 +62,7 @@ int main(int argc, char** argv) { uint64_t sessionID = 0; asio::io_context io; - auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID); + auto client = std::make_shared(io, host, port, &sodiumWrapper, &aesKey, &sessionID, &insecureMode); auto udpClient = std::make_shared(io, host, port, &aesKey, &sessionID); client->start(); diff --git a/src/client/net/tcp/tcp_client.cpp b/src/client/net/tcp/tcp_client.cpp index 7532bc8..4ca2016 100644 --- a/src/client/net/tcp/tcp_client.cpp +++ b/src/client/net/tcp/tcp_client.cpp @@ -128,12 +128,26 @@ namespace ColumnLynx::Net::TCP { void TCPClient::mHandleMessage(ServerMessageType type, const std::string& data) { switch (type) { - case ServerMessageType::HANDSHAKE_IDENTIFY: - Utils::log("Received server identity: " + data); - std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey))); + case ServerMessageType::HANDSHAKE_IDENTIFY: { + Utils::log("Received server identity: " + data); + std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey))); - // Generate and send challenge - { + // Convert key (uint8_t raw array) to vector + std::vector serverPublicKeyVec(std::begin(mServerPublicKey), std::end(mServerPublicKey)); + + // Verify server public key + // TODO: Verify / Match hostname of public key to hostname of server + if (!Utils::LibSodiumWrapper::verifyCertificateWithSystemCAs(serverPublicKeyVec)) { + if (!(*mInsecureMode)) { + Utils::error("Server public key verification failed. Terminating connection."); + disconnect(); + return; + } + + Utils::log("Warning: Server public key verification failed, but continuing due to insecure mode."); + } + + // Generate and send challenge Utils::log("Sending challenge to server."); mSubmittedChallenge = Utils::LibSodiumWrapper::generateRandom256Bit(); // Temporarily store the challenge to verify later mHandler->sendMessage(ClientMessageType::HANDSHAKE_CHALLENGE, Utils::uint8ArrayToString(mSubmittedChallenge)); diff --git a/src/common/utils.cpp b/src/common/utils.cpp index 9feca23..7003ebf 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -37,7 +37,7 @@ namespace ColumnLynx::Utils { } std::string getVersion() { - return "a0.2"; + return "a0.3"; } unsigned short serverPort() { diff --git a/src/server/main.cpp b/src/server/main.cpp index b4c85fd..fdddac3 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -42,7 +42,7 @@ int main(int argc, char** argv) { // Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory) LibSodiumWrapper sodiumWrapper = LibSodiumWrapper(); log("Server public key: " + bytesToHexString(sodiumWrapper.getPublicKey(), crypto_sign_PUBLICKEYBYTES)); - log("Server private key: " + bytesToHexString(sodiumWrapper.getPrivateKey(), crypto_sign_SECRETKEYBYTES)); // TEMP, remove later + //log("Server private key: " + bytesToHexString(sodiumWrapper.getPrivateKey(), crypto_sign_SECRETKEYBYTES)); // TEMP, remove later bool hostRunning = true;