Key loading from files
This commit is contained in:
@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
# If MAJOR is 0, and MINOR > 0, Version is BETA
|
# If MAJOR is 0, and MINOR > 0, Version is BETA
|
||||||
|
|
||||||
project(ColumnLynx
|
project(ColumnLynx
|
||||||
VERSION 1.1.1
|
VERSION 1.2.0
|
||||||
LANGUAGES CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -55,7 +55,22 @@ openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32
|
|||||||
# Output example: 1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e
|
# Output example: 1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then set these keys accordingly in the **server_config** and **client_config** files.
|
Write the hex output into two separate files in the matching config directory:
|
||||||
|
|
||||||
|
- `public.key` for the public key
|
||||||
|
- `private.key` for the private key seed
|
||||||
|
|
||||||
|
The files should contain only the hex ASCII characters, optionally followed by a trailing newline. Hex parsing is case-insensitive.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf '%s' '1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e' > /etc/columnlynx/public.key
|
||||||
|
printf '%s' '9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e' > /etc/columnlynx/private.key
|
||||||
|
chmod 600 /etc/columnlynx/private.key
|
||||||
|
```
|
||||||
|
|
||||||
|
On Unix-like systems, the software will warn if the private key file is too permissive and recommend tightening it with `chmod 600`.
|
||||||
|
|
||||||
### Server Setup (Linux Server ONLY)
|
### Server Setup (Linux Server ONLY)
|
||||||
|
|
||||||
@@ -157,20 +172,19 @@ sudo nft add rule nat postroute ip saddr 10.10.0.0/24 oifname "eth0" masquerade
|
|||||||
|
|
||||||
"**server_config**" is a file that contains the server configuration, **one variable per line**. These are the current configuration available variables:
|
"**server_config**" is a file that contains the server configuration, **one variable per line**. These are the current configuration available variables:
|
||||||
|
|
||||||
- **SERVER_PUBLIC_KEY** (Hex String): The public key to be used - Used for verification
|
|
||||||
- **SERVER_PRIVATE_KEY** (Hex String): The private key seed to be used
|
|
||||||
- **NETWORK** (IPv4 Format): The network IPv4 to be used (Server Interface still needs to be configured manually)
|
- **NETWORK** (IPv4 Format): The network IPv4 to be used (Server Interface still needs to be configured manually)
|
||||||
- **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked)
|
- **SUBNET_MASK** (Integer): The subnet mask to be used (ensure proper length, it will not be checked)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```
|
```
|
||||||
SERVER_PUBLIC_KEY=1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e
|
|
||||||
SERVER_PRIVATE_KEY=9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e
|
|
||||||
NETWORK=10.10.0.0
|
NETWORK=10.10.0.0
|
||||||
SUBNET_MASK=24
|
SUBNET_MASK=24
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The server keypair must now live in the same directory as `server_config`, stored in `public.key` and `private.key`.
|
||||||
|
`server_config` no longer stores key material.
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
"**whitelisted_keys**" is a file that **public keys of clients that are allowed to connect to the server, one key per line**.
|
"**whitelisted_keys**" is a file that **public keys of clients that are allowed to connect to the server, one key per line**.
|
||||||
@@ -184,17 +198,9 @@ SUBNET_MASK=24
|
|||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
||||||
"**client_config**" is a file that contains the client configuration, **one variable per line**. These are the current configuration available variables:
|
"**client_config**" is a file that contains the client configuration, **one variable per line**. Key material is no longer stored here; if you do not have any client-only settings yet, this file can stay empty.
|
||||||
|
|
||||||
- **CLIENT_PUBLIC_KEY** (Hex String): The public key to be used - Used for verification
|
The client keypair must now live in the same directory as `client_config`, stored in `public.key` and `private.key`.
|
||||||
- **CLIENT_PRIVATE_KEY** (Hex String): The private key seed to be used
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
|
|
||||||
```
|
|
||||||
CLIENT_PUBLIC_KEY=1c9d4f7a3b2e8a6d0f5c9b1e4d8a7f3c6e2b1a9d5f4c8e0a7b3d6c9f2e
|
|
||||||
CLIENT_PRIVATE_KEY=9f3a2b6c0f8e4d1a7c3e9a4b5d2f8c6e1a9d0b7e3f4c2a8e6d5b1f0a3c4e
|
|
||||||
```
|
|
||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <columnlynx/common/net/protocol_structs.hpp>
|
#include <columnlynx/common/net/protocol_structs.hpp>
|
||||||
#include <columnlynx/common/net/virtual_interface.hpp>
|
#include <columnlynx/common/net/virtual_interface.hpp>
|
||||||
@@ -27,48 +26,7 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
public:
|
public:
|
||||||
TCPClient(asio::io_context& ioContext,
|
TCPClient(asio::io_context& ioContext,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& port)
|
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())
|
|
||||||
{
|
|
||||||
// Get initial client config
|
|
||||||
std::string configPath = ClientSession::getInstance().getConfigPath();
|
|
||||||
std::shared_ptr<Utils::LibSodiumWrapper> mLibSodiumWrapper = ClientSession::getInstance().getSodiumWrapper();
|
|
||||||
|
|
||||||
// Preload the config map
|
|
||||||
mRawClientConfig = Utils::getConfigMap(configPath + "client_config");
|
|
||||||
|
|
||||||
auto itPubkey = mRawClientConfig.find("CLIENT_PUBLIC_KEY");
|
|
||||||
auto itPrivkey = mRawClientConfig.find("CLIENT_PRIVATE_KEY");
|
|
||||||
|
|
||||||
if (itPubkey != mRawClientConfig.end() && itPrivkey != mRawClientConfig.end()) {
|
|
||||||
Utils::log("Loading keypair from config file.");
|
|
||||||
|
|
||||||
PublicKey pk;
|
|
||||||
PrivateSeed seed;
|
|
||||||
|
|
||||||
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), seed.size(), seed.begin()); // This is extremely stupid, but the C++ compiler has forced my hand (I would've just used to_array, but fucking asio decls)
|
|
||||||
std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin());
|
|
||||||
|
|
||||||
if (!mLibSodiumWrapper->recomputeKeys(seed, pk)) {
|
|
||||||
throw std::runtime_error("Failed to recompute keypair from config file values!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::debug("Newly-Loaded Public Key: " + Utils::bytesToHexString(mLibSodiumWrapper->getPublicKey(), 32));
|
|
||||||
} else {
|
|
||||||
#if defined(DEBUG)
|
|
||||||
Utils::warn("No keypair found in config file! Using random key.");
|
|
||||||
#else
|
|
||||||
throw std::runtime_error("No keypair found in config file! Cannot start client without keys.");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts the TCP Client and initiaties the handshake
|
// Starts the TCP Client and initiaties the handshake
|
||||||
void start();
|
void start();
|
||||||
@@ -106,6 +64,5 @@ namespace ColumnLynx::Net::TCP {
|
|||||||
int mMissedHeartbeats = 0;
|
int mMissedHeartbeats = 0;
|
||||||
bool mIsHostDomain;
|
bool mIsHostDomain;
|
||||||
Protocol::TunConfig mTunConfig;
|
Protocol::TunConfig mTunConfig;
|
||||||
std::unordered_map<std::string, std::string> mRawClientConfig;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -79,4 +79,15 @@ namespace ColumnLynx::Utils {
|
|||||||
|
|
||||||
// Returns the config file in an unordered_map format. This purely reads the config file, you still need to parse it manually.
|
// Returns the config file in an unordered_map format. This purely reads the config file, you still need to parse it manually.
|
||||||
std::unordered_map<std::string, std::string> getConfigMap(std::string path, std::vector<std::string> requiredKeys = {});
|
std::unordered_map<std::string, std::string> getConfigMap(std::string path, std::vector<std::string> requiredKeys = {});
|
||||||
|
|
||||||
|
// Load a hex-encoded file, validate its byte length, and return the decoded bytes.
|
||||||
|
std::vector<uint8_t> loadHexBytesFromFile(const std::string& path, size_t expectedBytes, const std::string& description = "key", bool warnOnInsecurePermissions = false);
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
inline std::array<uint8_t, N> loadHexArrayFromFile(const std::string& path, const std::string& description = "key", bool warnOnInsecurePermissions = false) {
|
||||||
|
auto bytes = loadHexBytesFromFile(path, N, description, warnOnInsecurePermissions);
|
||||||
|
std::array<uint8_t, N> out{};
|
||||||
|
std::copy_n(bytes.begin(), N, out.begin());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -127,6 +127,28 @@ int main(int argc, char** argv) {
|
|||||||
initialState.virtualInterface = tun;
|
initialState.virtualInterface = tun;
|
||||||
|
|
||||||
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
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("Public Key: " + Utils::bytesToHexString(sodiumWrapper->getPublicKey(), 32));
|
||||||
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64));
|
debug("Private Key: " + Utils::bytesToHexString(sodiumWrapper->getPrivateKey(), 64));
|
||||||
initialState.sodiumWrapper = sodiumWrapper;
|
initialState.sodiumWrapper = sodiumWrapper;
|
||||||
|
|||||||
@@ -6,6 +6,20 @@
|
|||||||
//#include <arpa/inet.h>
|
//#include <arpa/inet.h>
|
||||||
|
|
||||||
namespace ColumnLynx::Net::TCP {
|
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() {
|
void TCPClient::start() {
|
||||||
auto self = shared_from_this();
|
auto self = shared_from_this();
|
||||||
mResolver.async_resolve(mHost, mPort,
|
mResolver.async_resolve(mHost, mPort,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace ColumnLynx::Utils {
|
namespace ColumnLynx::Utils {
|
||||||
std::string unixMillisToISO8601(uint64_t unixMillis, bool local) {
|
std::string unixMillisToISO8601(uint64_t unixMillis, bool local) {
|
||||||
@@ -86,7 +87,7 @@ namespace ColumnLynx::Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string getVersion() {
|
std::string getVersion() {
|
||||||
return "1.1.1";
|
return "1.2.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned short serverPort() {
|
unsigned short serverPort() {
|
||||||
@@ -251,4 +252,78 @@ namespace ColumnLynx::Utils {
|
|||||||
|
|
||||||
return config;
|
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 <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <columnlynx/common/utils.hpp>
|
#include <columnlynx/common/utils.hpp>
|
||||||
#include <columnlynx/common/panic_handler.hpp>
|
#include <columnlynx/common/panic_handler.hpp>
|
||||||
@@ -93,8 +94,7 @@ int main(int argc, char** argv) {
|
|||||||
#if defined(DEBUG)
|
#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
|
#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" });
|
||||||
std::unordered_map<std::string, std::string> config = Utils::getConfigMap(configPath + "server_config", { "NETWORK", "SUBNET_MASK", "SERVER_PUBLIC_KEY", "SERVER_PRIVATE_KEY" });
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
serverState.serverConfig = config;
|
serverState.serverConfig = config;
|
||||||
@@ -105,29 +105,27 @@ 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)
|
// Store a reference to the tun in the serverState, it will increment and keep a safe reference (we love shared_ptrs)
|
||||||
serverState.virtualInterface = tun;
|
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>();
|
std::shared_ptr<LibSodiumWrapper> sodiumWrapper = std::make_shared<LibSodiumWrapper>();
|
||||||
|
|
||||||
auto itPubkey = config.find("SERVER_PUBLIC_KEY");
|
const std::string serverPublicKeyPath = configPath + "public.key";
|
||||||
auto itPrivkey = config.find("SERVER_PRIVATE_KEY");
|
const std::string serverPrivateKeyPath = configPath + "private.key";
|
||||||
|
|
||||||
if (itPubkey != config.end() && itPrivkey != config.end()) {
|
namespace fs = std::filesystem;
|
||||||
log("Loading keypair from config file.");
|
bool serverKeyFilesPresent = fs::exists(serverPublicKeyPath) && fs::exists(serverPrivateKeyPath);
|
||||||
|
if (serverKeyFilesPresent) {
|
||||||
|
log("Loading server keypair from key files.");
|
||||||
|
|
||||||
PublicKey pk;
|
PublicKey pk = Utils::loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>(serverPublicKeyPath, "server public key");
|
||||||
PrivateSeed seed;
|
PrivateSeed seed = Utils::loadHexArrayFromFile<crypto_sign_SEEDBYTES>(serverPrivateKeyPath, "server private key", true);
|
||||||
|
|
||||||
std::copy_n(Utils::hexStringToBytes(itPrivkey->second).begin(), seed.size(), seed.begin());
|
|
||||||
std::copy_n(Utils::hexStringToBytes(itPubkey->second).begin(), pk.size(), pk.begin());
|
|
||||||
|
|
||||||
if (!sodiumWrapper->recomputeKeys(seed, pk)) {
|
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 {
|
} else {
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
warn("No keypair found in config file! Using random key.");
|
warn("No server keypair files found! Using random key.");
|
||||||
#else
|
#else
|
||||||
throw std::runtime_error("No keypair found in config file! Cannot start server without keys.");
|
throw std::runtime_error("No server keypair files found! Cannot start server without keys.");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
62
tests/test_key_file_loading.cpp
Normal file
62
tests/test_key_file_loading.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Tests for hex key file loading helpers
|
||||||
|
#include <cassert>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <columnlynx/common/utils.hpp>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using namespace ColumnLynx::Utils;
|
||||||
|
|
||||||
|
fs::path tempDir = fs::temp_directory_path() / "columnlynx_key_loader_test";
|
||||||
|
fs::remove_all(tempDir);
|
||||||
|
fs::create_directories(tempDir);
|
||||||
|
|
||||||
|
auto publicKeyPath = tempDir / "public.key";
|
||||||
|
auto privateKeyPath = tempDir / "private.key";
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream pub(publicKeyPath);
|
||||||
|
pub << "00112233445566778899aabbccddeeff00112233445566778899AABBCCDDEEFF";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream priv(privateKeyPath);
|
||||||
|
priv << "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pk = loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>(publicKeyPath.string(), "public key");
|
||||||
|
auto sk = loadHexArrayFromFile<crypto_sign_SEEDBYTES>(privateKeyPath.string(), "private key", true);
|
||||||
|
|
||||||
|
assert(pk[0] == 0x00 && pk[1] == 0x11 && pk.back() == 0xFF);
|
||||||
|
assert(sk[0] == 0xFF && sk[1] == 0xEE && sk.back() == 0x00);
|
||||||
|
|
||||||
|
bool threwMissing = false;
|
||||||
|
try {
|
||||||
|
(void)loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>((tempDir / "missing.key").string(), "missing key");
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
threwMissing = true;
|
||||||
|
}
|
||||||
|
assert(threwMissing && "Missing key file should throw");
|
||||||
|
|
||||||
|
auto badLengthPath = tempDir / "bad-length.key";
|
||||||
|
{
|
||||||
|
std::ofstream bad(badLengthPath);
|
||||||
|
bad << "abcd";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool threwBadLength = false;
|
||||||
|
try {
|
||||||
|
(void)loadHexArrayFromFile<crypto_sign_PUBLICKEYBYTES>(badLengthPath.string(), "bad length key");
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
threwBadLength = true;
|
||||||
|
}
|
||||||
|
assert(threwBadLength && "Wrong-length key file should throw");
|
||||||
|
|
||||||
|
std::cout << "Key file loader tests passed\n";
|
||||||
|
fs::remove_all(tempDir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user