Key loading from files
This commit is contained in:
@@ -127,6 +127,28 @@ int main(int argc, char** argv) {
|
||||
initialState.virtualInterface = tun;
|
||||
|
||||
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
||||
const std::string clientPublicKeyPath = configPath + "public.key";
|
||||
const std::string clientPrivateKeyPath = configPath + "private.key";
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
bool clientKeyFilesPresent = fs::exists(clientPublicKeyPath) && fs::exists(clientPrivateKeyPath);
|
||||
if (clientKeyFilesPresent) {
|
||||
Utils::log("Loading client keypair from key files.");
|
||||
|
||||
PublicKey pk = Utils::loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>(clientPublicKeyPath, "client public key");
|
||||
PrivateSeed seed = Utils::loadHexArrayFromFile<crypto_sign_SEEDBYTES>(clientPrivateKeyPath, "client private key", true);
|
||||
|
||||
if (!sodiumWrapper->recomputeKeys(seed, pk)) {
|
||||
throw std::runtime_error("Failed to recompute client keypair from key files!");
|
||||
}
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
Utils::warn("No client keypair files found! Using random key.");
|
||||
#else
|
||||
throw std::runtime_error("No client keypair files found! Cannot start client without keys.");
|
||||
#endif
|
||||
}
|
||||
|
||||
debug("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32));
|
||||
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64));
|
||||
initialState.sodiumWrapper = sodiumWrapper;
|
||||
|
||||
@@ -6,6 +6,20 @@
|
||||
//#include <arpa/inet.h>
|
||||
|
||||
namespace ColumnLynx::Net::TCP {
|
||||
TCPClient::TCPClient(asio::io_context& ioContext, const std::string& host, const std::string& port)
|
||||
: mResolver(ioContext),
|
||||
mSocket(ioContext),
|
||||
mHost(host),
|
||||
mPort(port),
|
||||
mHeartbeatTimer(mSocket.get_executor()),
|
||||
mLastHeartbeatReceived(std::chrono::steady_clock::now()),
|
||||
mLastHeartbeatSent(std::chrono::steady_clock::now())
|
||||
{
|
||||
if (!ClientSession::getInstance().getSodiumWrapper()) {
|
||||
throw std::runtime_error("ClientSession sodium wrapper is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
void TCPClient::start() {
|
||||
auto self = shared_from_this();
|
||||
mResolver.async_resolve(mHost, mPort,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <columnlynx/common/utils.hpp>
|
||||
#include <filesystem>
|
||||
#include <cctype>
|
||||
|
||||
namespace ColumnLynx::Utils {
|
||||
std::string unixMillisToISO8601(uint64_t unixMillis, bool local) {
|
||||
@@ -86,7 +87,7 @@ namespace ColumnLynx::Utils {
|
||||
}
|
||||
|
||||
std::string getVersion() {
|
||||
return "1.1.1";
|
||||
return "1.2.0";
|
||||
}
|
||||
|
||||
unsigned short serverPort() {
|
||||
@@ -251,4 +252,78 @@ namespace ColumnLynx::Utils {
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> loadHexBytesFromFile(const std::string& path, size_t expectedBytes, const std::string& description, bool warnOnInsecurePermissions) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
|
||||
fs::path p(path);
|
||||
fs::path abs = fs::absolute(p, ec);
|
||||
if (ec) {
|
||||
throw std::runtime_error("loadHexBytesFromFile(): failed to resolve path: " + path + " - " + ec.message());
|
||||
}
|
||||
|
||||
if (!fs::exists(abs, ec) || ec) {
|
||||
throw std::runtime_error("loadHexBytesFromFile(): file does not exist: " + abs.string());
|
||||
}
|
||||
|
||||
fs::path canon = fs::canonical(abs, ec);
|
||||
if (ec) {
|
||||
throw std::runtime_error("loadHexBytesFromFile(): failed to canonicalize path: " + abs.string());
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (warnOnInsecurePermissions) {
|
||||
ec.clear();
|
||||
fs::file_status status = fs::status(canon, ec);
|
||||
if (ec) {
|
||||
warn("loadHexBytesFromFile(): failed to inspect permissions for " + canon.string() + " - " + ec.message());
|
||||
} else {
|
||||
auto perms = status.permissions();
|
||||
if ((perms & (fs::perms::group_all | fs::perms::others_all)) != fs::perms::none) {
|
||||
warn(description + " file permissions are too permissive: " + canon.string() + " (recommend chmod 600)");
|
||||
}
|
||||
|
||||
if (!fs::is_regular_file(status)) {
|
||||
warn(description + " path is not a regular file: " + canon.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::ifstream file(canon);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open " + description + " file at path: " + canon.string());
|
||||
}
|
||||
|
||||
std::string hex((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
|
||||
auto trim = [](std::string& s) {
|
||||
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) {
|
||||
s.pop_back();
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
while (start < s.size() && std::isspace(static_cast<unsigned char>(s[start]))) {
|
||||
++start;
|
||||
}
|
||||
|
||||
if (start > 0) {
|
||||
s.erase(0, start);
|
||||
}
|
||||
};
|
||||
|
||||
trim(hex);
|
||||
|
||||
if (hex.empty()) {
|
||||
throw std::runtime_error(description + " file is empty: " + canon.string());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes = hexStringToBytes(hex);
|
||||
if (bytes.size() != expectedBytes) {
|
||||
throw std::runtime_error(description + " file must contain exactly " + std::to_string(expectedBytes * 2) + " hex characters (" + std::to_string(expectedBytes) + " bytes), got " + std::to_string(bytes.size()) + " bytes");
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
#include <columnlynx/common/utils.hpp>
|
||||
#include <columnlynx/common/panic_handler.hpp>
|
||||
@@ -91,10 +92,9 @@ int main(int argc, char** argv) {
|
||||
serverState.configPath = configPath;
|
||||
|
||||
#if defined(DEBUG)
|
||||
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK" });
|
||||
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK" });
|
||||
#else
|
||||
// A production server should never use random keys. If the config file cannot be read or does not contain keys, the server will fail to start.
|
||||
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK", "SERVER_PUBLIC_KEY", "SERVER_PRIVATE_KEY" });
|
||||
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK" });
|
||||
#endif
|
||||
|
||||
serverState.serverConfig = config;
|
||||
@@ -105,30 +105,28 @@ int main(int argc, char** argv) {
|
||||
// Store a reference to the tun in the serverState, it will increment and keep a safe reference (we love shared_ptrs)
|
||||
serverState.virtualInterface = tun;
|
||||
|
||||
// Generate a temporary keypair, replace with actual CA signed keys later (Note, these are stored in memory)
|
||||
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
||||
|
||||
auto itPubkey = config.find("SERVER_PUBLIC_KEY");
|
||||
auto itPrivkey = config.find("SERVER_PRIVATE_KEY");
|
||||
const std::string serverPublicKeyPath = configPath + "public.key";
|
||||
const std::string serverPrivateKeyPath = configPath + "private.key";
|
||||
|
||||
if (itPubkey != config.end() && itPrivkey != config.end()) {
|
||||
log("Loading keypair from config file.");
|
||||
namespace fs = std::filesystem;
|
||||
bool serverKeyFilesPresent = fs::exists(serverPublicKeyPath) && fs::exists(serverPrivateKeyPath);
|
||||
if (serverKeyFilesPresent) {
|
||||
log("Loading server keypair from key files.");
|
||||
|
||||
PublicKey pk;
|
||||
PrivateSeed seed;
|
||||
|
||||
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), seed.size(), seed.begin());
|
||||
std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin());
|
||||
PublicKey pk = Utils::loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>(serverPublicKeyPath, "server public key");
|
||||
PrivateSeed seed = Utils::loadHexArrayFromFile<crypto_sign_SEEDBYTES>(serverPrivateKeyPath, "server private key", true);
|
||||
|
||||
if (!sodiumWrapper->recomputeKeys(seed, pk)) {
|
||||
throw std::runtime_error("Failed to recompute keypair from config file values!");
|
||||
throw std::runtime_error("Failed to recompute keypair from key files!");
|
||||
}
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
warn("No keypair found in config file! Using random key.");
|
||||
#else
|
||||
throw std::runtime_error("No keypair found in config file! Cannot start server without keys.");
|
||||
#endif
|
||||
#if defined(DEBUG)
|
||||
warn("No server keypair files found! Using random key.");
|
||||
#else
|
||||
throw std::runtime_error("No server keypair files found! Cannot start server without keys.");
|
||||
#endif
|
||||
}
|
||||
|
||||
log("Server public key: " + bytesToHexString(sodiumWrapper->getPublicKey(), crypto_sign_PUBLICKEYBYTES));
|
||||
|
||||
Reference in New Issue
Block a user