diff --git a/include/columnlynx/client/client_session.hpp b/include/columnlynx/client/client_session.hpp new file mode 100644 index 0000000..a456ed1 --- /dev/null +++ b/include/columnlynx/client/client_session.hpp @@ -0,0 +1,71 @@ +// client_session.hpp - Client Session data for ColumnLynx +// Copyright (C) 2026 DcruBro +// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details. + +#pragma once + +#include +#include +#include +#include +#include + +namespace ColumnLynx { + struct ClientState { + std::shared_ptr sodiumWrapper; + SymmetricKey aesKey; + bool insecureMode; + std::string configPath; + std::shared_ptr virtualInterface; + uint64_t sessionID; + + ~ClientState() { sodium_memzero(aesKey.data(), aesKey.size()); } + ClientState(const ClientState&) = delete; + ClientState& operator=(const ClientState&) = delete; + ClientState(ClientState&&) = default; + ClientState& operator=(ClientState&&) = default; + + explicit ClientState() = default; + + explicit ClientState(std::shared_ptr sodium, SymmetricKey& k, bool insecure, + std::string& config, std::shared_ptr tun, uint64_t session) + : sodiumWrapper(sodium), aesKey(k), insecureMode(insecure), configPath(config), virtualInterface(tun), sessionID(session) {} + }; + + class ClientSession { + public: + // Return a reference to the Client Session instance + static ClientSession& getInstance() { static ClientSession instance; return instance; } + + // Return the current client state + std::shared_ptr getClientState() const; + + // Set the client state + void setClientState(std::shared_ptr state); + + // Get the wrapper for libsodium + const std::shared_ptr& getSodiumWrapper() const; + // Get the AES key + const SymmetricKey& getAESKey() const; + // Get whether insecure mode is enabled + bool isInsecureMode() const; + // Get the config path + const std::string& getConfigPath() const; + // Get the virtual interface + const std::shared_ptr& getVirtualInterface() const; + // Get the session ID + uint64_t getSessionID() const; + + // Setters + void setSodiumWrapper(std::shared_ptr sodiumWrapper); + void setAESKey(const SymmetricKey& aesKey); + void setInsecureMode(bool insecureMode); + void setConfigPath(const std::string& configPath); + void setVirtualInterface(std::shared_ptr virtualInterface); + void setSessionID(uint64_t sessionID); + + private: + mutable std::shared_mutex mMutex; + std::shared_ptr mClientState{nullptr}; + }; +} \ No newline at end of file diff --git a/include/columnlynx/client/net/tcp/tcp_client.hpp b/include/columnlynx/client/net/tcp/tcp_client.hpp index 219e708..b9f011d 100644 --- a/include/columnlynx/client/net/tcp/tcp_client.hpp +++ b/include/columnlynx/client/net/tcp/tcp_client.hpp @@ -17,6 +17,7 @@ #include #include #include +#include using asio::ip::tcp; @@ -25,28 +26,20 @@ namespace ColumnLynx::Net::TCP { public: TCPClient(asio::io_context& ioContext, const std::string& host, - const std::string& port, - std::shared_ptr sodiumWrapper, - std::shared_ptr> aesKey, - std::shared_ptr sessionIDRef, - bool insecureMode, - std::string& configPath, - std::shared_ptr tun = nullptr) + const std::string& port) : mResolver(ioContext), mSocket(ioContext), mHost(host), mPort(port), - mLibSodiumWrapper(sodiumWrapper), - mGlobalKeyRef(aesKey), - mSessionIDRef(sessionIDRef), - mInsecureMode(insecureMode), mHeartbeatTimer(mSocket.get_executor()), mLastHeartbeatReceived(std::chrono::steady_clock::now()), - mLastHeartbeatSent(std::chrono::steady_clock::now()), - mTun(tun), - mConfigDirPath(configPath) + mLastHeartbeatSent(std::chrono::steady_clock::now()) { + // Get initial client config + std::string configPath = ClientSession::getInstance().getConfigPath(); + std::shared_ptr mLibSodiumWrapper = ClientSession::getInstance().getSodiumWrapper(); + // Preload the config map mRawClientConfig = Utils::getConfigMap(configPath + "client_config"); @@ -104,20 +97,14 @@ namespace ColumnLynx::Net::TCP { std::string mHost, mPort; uint8_t mServerPublicKey[32]; // Assuming 256-bit public key std::array mSubmittedChallenge{}; - std::shared_ptr mLibSodiumWrapper; uint64_t mConnectionSessionID; SymmetricKey mConnectionAESKey; - std::shared_ptr> mGlobalKeyRef; // Reference to global AES key - std::shared_ptr mSessionIDRef; // Reference to global Session ID - bool mInsecureMode; // Insecure mode flag asio::steady_timer mHeartbeatTimer; std::chrono::steady_clock::time_point mLastHeartbeatReceived; std::chrono::steady_clock::time_point mLastHeartbeatSent; int mMissedHeartbeats = 0; bool mIsHostDomain; Protocol::TunConfig mTunConfig; - std::shared_ptr mTun = nullptr; std::unordered_map mRawClientConfig; - std::string mConfigDirPath; }; } \ No newline at end of file diff --git a/include/columnlynx/client/net/udp/udp_client.hpp b/include/columnlynx/client/net/udp/udp_client.hpp index 1c42add..af613d9 100644 --- a/include/columnlynx/client/net/udp/udp_client.hpp +++ b/include/columnlynx/client/net/udp/udp_client.hpp @@ -10,17 +10,15 @@ #include #include #include +#include namespace ColumnLynx::Net::UDP { class UDPClient { public: UDPClient(asio::io_context& ioContext, const std::string& host, - const std::string& port, - std::shared_ptr> aesKeyRef, - std::shared_ptr sessionIDRef, - std::shared_ptr tunRef = nullptr) - : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port), mAesKeyRef(aesKeyRef), mSessionIDRef(sessionIDRef), mTunRef(tunRef) + const std::string& port) + : mSocket(ioContext), mResolver(ioContext), mHost(host), mPort(port) { mStartReceive(); } @@ -43,9 +41,6 @@ namespace ColumnLynx::Net::UDP { asio::ip::udp::endpoint mRemoteEndpoint; std::string mHost; std::string mPort; - std::shared_ptr> mAesKeyRef; - std::shared_ptr mSessionIDRef; - std::shared_ptr mTunRef = nullptr; std::array mRecvBuffer; // Adjust size as needed }; } \ No newline at end of file diff --git a/src/client/client_session.cpp b/src/client/client_session.cpp new file mode 100644 index 0000000..ce193bd --- /dev/null +++ b/src/client/client_session.cpp @@ -0,0 +1,71 @@ +// client_session.cpp - Client Session data for ColumnLynx +// Copyright (C) 2026 DcruBro +// Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details. + +#include + +namespace ColumnLynx { + std::shared_ptr ClientSession::getClientState() const { + std::shared_lock lock(mMutex); + return mClientState; + } + + void ClientSession::setClientState(std::shared_ptr state) { + std::unique_lock lock(mMutex); + mClientState = state; + } + + const std::shared_ptr& ClientSession::getSodiumWrapper() const { + return getClientState()->sodiumWrapper; + } + + const SymmetricKey& ClientSession::getAESKey() const { + return getClientState()->aesKey; + } + + bool ClientSession::isInsecureMode() const { + return getClientState()->insecureMode; + } + + const std::string& ClientSession::getConfigPath() const { + return getClientState()->configPath; + } + + const std::shared_ptr& ClientSession::getVirtualInterface() const { + return getClientState()->virtualInterface; + } + + uint64_t ClientSession::getSessionID() const { + return getClientState()->sessionID; + } + + void ClientSession::setSodiumWrapper(std::shared_ptr sodiumWrapper) { + std::unique_lock lock(mMutex); + mClientState->sodiumWrapper = sodiumWrapper; + } + + void ClientSession::setAESKey(const SymmetricKey& aesKey) { + std::unique_lock lock(mMutex); + mClientState->aesKey = aesKey; + } + + void ClientSession::setInsecureMode(bool insecureMode) { + std::unique_lock lock(mMutex); + mClientState->insecureMode = insecureMode; + } + + void ClientSession::setConfigPath(const std::string& configPath) { + std::unique_lock lock(mMutex); + mClientState->configPath = configPath; + } + + void ClientSession::setVirtualInterface(std::shared_ptr virtualInterface) { + std::unique_lock lock(mMutex); + mClientState->virtualInterface = virtualInterface; + } + + void ClientSession::setSessionID(uint64_t sessionID) { + std::unique_lock lock(mMutex); + mClientState->sessionID = sessionID; + } +} \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index e0b7a08..7419b35 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #if defined(__WIN32__) #include @@ -99,24 +101,34 @@ int main(int argc, char** argv) { #endif } + struct ClientState initialState{}; + initialState.configPath = configPath; + initialState.insecureMode = insecureMode; + std::shared_ptr tun = std::make_shared(optionsObj["interface"].as()); log("Using virtual interface: " + tun->getName()); + initialState.virtualInterface = tun; std::shared_ptr sodiumWrapper = std::make_shared(); debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32)); debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64)); + initialState.sodiumWrapper = sodiumWrapper; - std::shared_ptr> aesKey = std::make_shared>(); - aesKey->fill(0); // Defualt zeroed state until modified by handshake - std::shared_ptr sessionID = std::make_shared(0); + std::array aesKey = std::array(); + aesKey.fill(0); // Defualt zeroed state until modified by handshake + uint64_t sessionID = 0; + initialState.aesKey = aesKey; + initialState.sessionID = sessionID; + + ColumnLynx::ClientSession::getInstance().setClientState(std::make_shared(std::move(initialState))); // Set initial state if (insecureMode) { warn("You have started the client with the --ignore-whitelist. This means that the client will NOT attempt to verify the server's public key. This is INSECURE and SHOULDN'T be used!"); } asio::io_context io; - auto client = std::make_shared(io, host, port, sodiumWrapper, aesKey, sessionID, insecureMode, configPath, tun); - auto udpClient = std::make_shared(io, host, port, aesKey, sessionID, tun); + auto client = std::make_shared(io, host, port); // TODO: Move to ClientSession state + auto udpClient = std::make_shared(io, host, port); client->start(); udpClient->start(); diff --git a/src/client/net/tcp/tcp_client.cpp b/src/client/net/tcp/tcp_client.cpp index b070a94..7e04960 100644 --- a/src/client/net/tcp/tcp_client.cpp +++ b/src/client/net/tcp/tcp_client.cpp @@ -50,6 +50,8 @@ namespace ColumnLynx::Net::TCP { mLibSodiumWrapper->getXPublicKey(), mLibSodiumWrapper->getXPublicKey() + crypto_box_PUBLICKEYBYTES );*/ + + const auto& mLibSodiumWrapper = ClientSession::getInstance().getSodiumWrapper(); payload.insert(payload.end(), mLibSodiumWrapper->getPublicKey(), mLibSodiumWrapper->getPublicKey() + crypto_sign_PUBLICKEYBYTES @@ -130,11 +132,9 @@ namespace ColumnLynx::Net::TCP { mHandler->socket().shutdown(tcp::socket::shutdown_both, ec); mHandler->socket().close(ec); mConnected = false; - - mGlobalKeyRef = nullptr; - if (mSessionIDRef) { - *mSessionIDRef = 0; - } + + ClientSession::getInstance().setAESKey({}); // Clear AES key with all zeros + ClientSession::getInstance().setSessionID(0); return; } @@ -155,9 +155,10 @@ namespace ColumnLynx::Net::TCP { Utils::log("Received server identity. Public Key: " + hexServerPub); // Verify pubkey against whitelisted_keys - std::vector whitelistedKeys = Utils::getWhitelistedKeys(mConfigDirPath); + const std::string& configPath = ClientSession::getInstance().getConfigPath(); + std::vector whitelistedKeys = Utils::getWhitelistedKeys(configPath); if (std::find(whitelistedKeys.begin(), whitelistedKeys.end(), Utils::bytesToHexString(mServerPublicKey, 32)) == whitelistedKeys.end()) { // Key verification is handled in later steps of the handshake - if (!mInsecureMode) { + if (!ClientSession::getInstance().isInsecureMode()) { Utils::error("Server public key not in whitelisted_keys. Terminating connection."); disconnect(); return; @@ -193,16 +194,14 @@ namespace ColumnLynx::Net::TCP { // Generate AES key and send confirmation mConnectionAESKey = Utils::LibSodiumWrapper::generateRandom256Bit(); - if (mGlobalKeyRef) { // Copy to the global reference - std::copy(mConnectionAESKey.begin(), mConnectionAESKey.end(), mGlobalKeyRef->begin()); - } + ClientSession::getInstance().setAESKey(mConnectionAESKey); AsymNonce nonce{}; randombytes_buf(nonce.data(), nonce.size()); // TODO: This is pretty redundant, it should return the required type directly std::array arrayPrivateKey; - std::copy(mLibSodiumWrapper->getXPrivateKey(), - mLibSodiumWrapper->getXPrivateKey() + 32, + std::copy(ClientSession::getInstance().getSodiumWrapper()->getXPrivateKey(), + ClientSession::getInstance().getSodiumWrapper()->getXPrivateKey() + 32, arrayPrivateKey.begin()); std::vector encr = Utils::LibSodiumWrapper::encryptAsymmetric( @@ -249,15 +248,14 @@ namespace ColumnLynx::Net::TCP { Utils::log("Connection established with Session ID: " + std::to_string(mConnectionSessionID)); - if (mSessionIDRef) { // Copy to the global reference - *mSessionIDRef = mConnectionSessionID; - } + ClientSession::getInstance().setSessionID(mConnectionSessionID); uint32_t clientIP = ntohl(mTunConfig.clientIP); uint32_t serverIP = ntohl(mTunConfig.serverIP); uint8_t prefixLen = mTunConfig.prefixLength; uint16_t mtu = mTunConfig.mtu; + const auto& mTun = ClientSession::getInstance().getVirtualInterface(); if (mTun) { mTun->configureIP(clientIP, serverIP, prefixLen, mtu); } diff --git a/src/client/net/udp/udp_client.cpp b/src/client/net/udp/udp_client.cpp index 10fba23..e5e6702 100644 --- a/src/client/net/udp/udp_client.cpp +++ b/src/client/net/udp/udp_client.cpp @@ -48,7 +48,7 @@ namespace ColumnLynx::Net::UDP { UDPPacketHeader hdr{}; randombytes_buf(hdr.nonce.data(), hdr.nonce.size()); - if (mAesKeyRef == nullptr || mSessionIDRef == nullptr) { + if (ClientSession::getInstance().getAESKey().empty() || ClientSession::getInstance().getSessionID() == 0) { Utils::error("UDP Client AES key or Session ID reference is null!"); return; } @@ -57,7 +57,7 @@ namespace ColumnLynx::Net::UDP { auto encryptedPayload = Utils::LibSodiumWrapper::encryptMessage( reinterpret_cast(data.data()), data.size(), - *mAesKeyRef, hdr.nonce, "udp-data" + ClientSession::getInstance().getAESKey(), hdr.nonce, "udp-data" //std::string(reinterpret_cast(&mSessionIDRef), sizeof(uint64_t)) ); @@ -67,9 +67,10 @@ namespace ColumnLynx::Net::UDP { reinterpret_cast(&hdr), reinterpret_cast(&hdr) + sizeof(UDPPacketHeader) ); + uint64_t sessionID = ClientSession::getInstance().getSessionID(); packet.insert(packet.end(), - reinterpret_cast(mSessionIDRef.get()), - reinterpret_cast(mSessionIDRef.get()) + sizeof(uint64_t) + reinterpret_cast(&sessionID), + reinterpret_cast(&sessionID) + sizeof(uint64_t) ); packet.insert(packet.end(), encryptedPayload.begin(), encryptedPayload.end()); @@ -120,8 +121,8 @@ namespace ColumnLynx::Net::UDP { uint64_t sessionID; std::memcpy(&sessionID, mRecvBuffer.data() + sizeof(UDPPacketHeader), sizeof(uint64_t)); - if (sessionID != *mSessionIDRef) { - Utils::warn("Got packet that isn't for me! Dropping!"); + if (sessionID != ClientSession::getInstance().getSessionID()) { + Utils::warn("This packet that isn't for me! Dropping!"); return; } @@ -131,13 +132,13 @@ namespace ColumnLynx::Net::UDP { mRecvBuffer.begin() + bytes ); - if (mAesKeyRef == nullptr) { + if (ClientSession::getInstance().getAESKey().empty()) { Utils::error("UDP Client AES key reference is null!"); return; } std::vector plaintext = Utils::LibSodiumWrapper::decryptMessage( - ciphertext.data(), ciphertext.size(), *mAesKeyRef, hdr.nonce, "udp-data" + ciphertext.data(), ciphertext.size(), ClientSession::getInstance().getAESKey(), hdr.nonce, "udp-data" //std::string(reinterpret_cast(&mSessionIDRef), sizeof(uint64_t)) ); @@ -149,6 +150,7 @@ namespace ColumnLynx::Net::UDP { Utils::debug("UDP Client received packet from " + mRemoteEndpoint.address().to_string() + " - Packet size: " + std::to_string(bytes)); // Write to TUN + const auto& mTunRef = ClientSession::getInstance().getVirtualInterface(); if (mTunRef) { mTunRef->writePacket(plaintext); }