From de3ec9836382a219e2fc73320d3c6a25afb59906 Mon Sep 17 00:00:00 2001 From: DcruBro Date: Tue, 25 Nov 2025 00:22:52 +0100 Subject: [PATCH] Added checking of whitelisted keys on server --- CMakeLists.txt | 9 ++-- include/columnlynx/common/utils.hpp | 16 ++++--- src/client/net/tcp/tcp_client.cpp | 4 +- src/common/utils.cpp | 44 ++++++++++++++++++++ src/server/main.cpp | 17 +++++++- src/server/server/net/tcp/tcp_connection.cpp | 10 ++++- 6 files changed, 88 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1344026..7ee57a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,9 @@ else() message(FATAL_ERROR "OpenSSL not found") endif() +# json +#ind_package(nlohmann_json CONFIG REQUIRED) + # --------------------------------------------------------- # Output directories # --------------------------------------------------------- @@ -86,7 +89,7 @@ endforeach() # --------------------------------------------------------- file(GLOB_RECURSE COMMON_SRC CONFIGURE_DEPENDS src/common/*.cpp) add_library(common STATIC ${COMMON_SRC}) -target_link_libraries(common PUBLIC sodium OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(common PUBLIC sodium OpenSSL::SSL OpenSSL::Crypto nlohmann_json::nlohmann_json) target_include_directories(common PUBLIC ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include @@ -99,7 +102,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 OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(client PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto nlohmann_json::nlohmann_json) target_include_directories(client PRIVATE ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include @@ -113,7 +116,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 OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(server PRIVATE common sodium OpenSSL::SSL OpenSSL::Crypto nlohmann_json::nlohmann_json) target_include_directories(server PRIVATE ${PROJECT_SOURCE_DIR}/include ${sodium_SOURCE_DIR}/src/libsodium/include diff --git a/include/columnlynx/common/utils.hpp b/include/columnlynx/common/utils.hpp index d8026ba..4d3c3dc 100644 --- a/include/columnlynx/common/utils.hpp +++ b/include/columnlynx/common/utils.hpp @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include #ifdef _WIN32 #include @@ -25,9 +29,11 @@ namespace ColumnLynx::Utils { std::string getVersion(); unsigned short serverPort(); unsigned char protocolVersion(); + std::vector getWhitelistedKeys(); // Raw byte to hex string conversion helper std::string bytesToHexString(const uint8_t* bytes, size_t length); + std::vector hexStringToBytes(const std::string& hex); // uint8_t to raw string conversion helper template @@ -39,7 +45,7 @@ namespace ColumnLynx::Utils { return std::string(reinterpret_cast(data), length); } - inline constexpr uint64_t bswap64(uint64_t x) { + inline constexpr uint64_t cbswap64(uint64_t x) { return ((x & 0x00000000000000FFULL) << 56) | ((x & 0x000000000000FF00ULL) << 40) | ((x & 0x0000000000FF0000ULL) << 24) | @@ -50,11 +56,11 @@ namespace ColumnLynx::Utils { ((x & 0xFF00000000000000ULL) >> 56); } - inline constexpr uint64_t htobe64(uint64_t x) { - return bswap64(x); // host -> big-endian (for little-endian hosts) + inline constexpr uint64_t chtobe64(uint64_t x) { + return cbswap64(x); // host -> big-endian (for little-endian hosts) } - inline constexpr uint64_t be64toh(uint64_t x) { - return bswap64(x); // big-endian -> host (for little-endian hosts) + inline constexpr uint64_t cbe64toh(uint64_t x) { + return cbswap64(x); // big-endian -> host (for little-endian hosts) } }; \ No newline at end of file diff --git a/src/client/net/tcp/tcp_client.cpp b/src/client/net/tcp/tcp_client.cpp index b7dc53c..4e232b2 100644 --- a/src/client/net/tcp/tcp_client.cpp +++ b/src/client/net/tcp/tcp_client.cpp @@ -28,7 +28,7 @@ namespace ColumnLynx::Net::TCP { // Check if hostname or IPv4/IPv6 sockaddr_in addr4{}; sockaddr_in6 addr6{}; - self->mIsHostDomain = inet_pton(AF_INET, mHost.c_str(), (void*)(&addr4)) != 1 && inet_pton(AF_INET6, mHost.c_str(), (void*)(&addr6)) != 1; + self->mIsHostDomain = inet_pton(AF_INET, mHost.c_str(), (void*)(&addr4)) != 1 && inet_pton(AF_INET6, mHost.c_str(), (void*)(&addr6)) != 1; // Voodoo black magic std::vector payload; payload.reserve(1 + crypto_box_PUBLICKEYBYTES); @@ -248,7 +248,7 @@ namespace ColumnLynx::Net::TCP { std::memcpy(&mConnectionSessionID, decrypted.data(), sizeof(mConnectionSessionID)); std::memcpy(&mTunConfig, decrypted.data() + sizeof(mConnectionSessionID), sizeof(Protocol::TunConfig)); - mConnectionSessionID = Utils::be64toh(mConnectionSessionID); + mConnectionSessionID = Utils::cbe64toh(mConnectionSessionID); Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID)); diff --git a/src/common/utils.cpp b/src/common/utils.cpp index c62b6ec..c2efde5 100644 --- a/src/common/utils.cpp +++ b/src/common/utils.cpp @@ -61,4 +61,48 @@ namespace ColumnLynx::Utils { return hexString; } + + std::vector hexStringToBytes(const std::string& hex) { + // TODO: recover from errors + + if (hex.length() % 2 != 0) { + throw std::invalid_argument("Hex string must have even length"); + } + + auto hexValue = [](char c) -> uint8_t { + if ('0' <= c && c <= '9') return c - '0'; + if ('A' <= c && c <= 'F') return c - 'A' + 10; + if ('a' <= c && c <= 'f') return c - 'a' + 10; + throw std::invalid_argument("Invalid hex character"); + }; + + size_t len = hex.length(); + std::vector bytes; + bytes.reserve(len / 2); + + for (size_t i = 0; i < len; i += 2) { + uint8_t high = hexValue(hex[i]); + uint8_t low = hexValue(hex[i + 1]); + bytes.push_back((high << 4) | low); + } + + return bytes; + } + + std::vector getWhitelistedKeys() { + // Currently re-reads the file every time, should be fine. + // Advantage of it is that you don't need to reload the server binary after adding/removing keys. Disadvantage is re-reading the file every time. + // I might redo this part. + + std::vector out; + + std::ifstream file("whitelisted_keys"); // TODO: This is hardcoded for now, make dynamic + std::string line; + + while (std::getline(file, line)) { + out.push_back(line); + } + + return out; + } } \ No newline at end of file diff --git a/src/server/main.cpp b/src/server/main.cpp index 75dff3b..cfe0b64 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -12,6 +12,7 @@ #include #include #include +//#include using asio::ip::tcp; using namespace ColumnLynx::Utils; @@ -41,7 +42,8 @@ int main(int argc, char** argv) { options.add_options() ("h,help", "Print help") - ("4,ipv4-only", "Force IPv4 only operation", cxxopts::value()->default_value("false")); + ("4,ipv4-only", "Force IPv4 only operation", cxxopts::value()->default_value("false")) + ("c,config", "Specify config file location", cxxopts::value()->default_value("config.json")); PanicHandler::init(); @@ -53,6 +55,7 @@ int main(int argc, char** argv) { } bool ipv4Only = result["ipv4-only"].as(); + std::string configPath = result["config"].as(); log("ColumnLynx Server, Version " + getVersion()); log("This software is licensed under the GPLv2 only OR the GPLv3. See LICENSES/ for details."); @@ -64,6 +67,18 @@ int main(int argc, char** argv) { std::shared_ptr tun = std::make_shared("utun0"); log("Using virtual interface: " + tun->getName()); + /* + // Load the config + std::ifstream f(configPath); + if (!f) { + error("Could not open config."); + return 1; + } + + nlohmann::json j; + f >> j; // parse + */ + // 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)); diff --git a/src/server/server/net/tcp/tcp_connection.cpp b/src/server/server/net/tcp/tcp_connection.cpp index dd902f6..3722cce 100644 --- a/src/server/server/net/tcp/tcp_connection.cpp +++ b/src/server/server/net/tcp/tcp_connection.cpp @@ -115,6 +115,14 @@ namespace ColumnLynx::Net::TCP { Utils::log("Client protocol version " + std::to_string(clientProtoVer) + " accepted from " + reqAddr + "."); std::memcpy(mConnectionPublicKey.data(), data.data() + 1, std::min(data.size() - 1, sizeof(mConnectionPublicKey))); // Store the client's public key (for identification) + + std::vector whitelistedKeys = Utils::getWhitelistedKeys(); + + if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mConnectionPublicKey.data(), mConnectionPublicKey.size())) == whitelistedKeys.end()) { + Utils::warn("Non-whitelisted client attempted to connect, terminating. Client IP: " + reqAddr); + disconnect(); + } + mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, Utils::uint8ArrayToString(mLibSodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES)); // This public key should always exist break; } @@ -188,7 +196,7 @@ namespace ColumnLynx::Net::TCP { SessionRegistry::getInstance().lockIP(mConnectionSessionID, clientIP); - uint64_t sessionIDNet = Utils::htobe64(mConnectionSessionID); + uint64_t sessionIDNet = Utils::chtobe64(mConnectionSessionID); std::vector payload(sizeof(uint64_t) + sizeof(tunConfig)); std::memcpy(payload.data(), &sessionIDNet, sizeof(uint64_t));