diff --git a/doc/dhtnode.1 b/doc/dhtnode.1 index 69e18eb4184b1ae4e22cdc36c57d3f816ec20357..ef456e54ee18630adb38af8cb1df16642451a2bc 100644 --- a/doc/dhtnode.1 +++ b/doc/dhtnode.1 @@ -7,7 +7,7 @@ .SH SYNOPSIS .B dhtnode [-h] -.B dhtnode [-v [-l \fIlogfile\fP]] [-i] [-d] [-n \fInetwork_id\fP] [-p \fIlocal_port\fP] [-b \fIbootstrap_host\fP[:\fIport\fP]] +.B dhtnode [-v [-l \fIlogfile\fP]] [-i [--save-identity \fIfile\fP]] [-d] [-n \fInetwork_id\fP] [-p \fIlocal_port\fP] [-b \fIbootstrap_host\fP[:\fIport\fP]] [--certificate \fIfile\fP] [--privkey \fIfile\fP] [--privkey-password \fIpassword\fP] .SH DESCRIPTION @@ -60,6 +60,22 @@ Write log to syslog instead of stdout \fB-i\fP Generate cryptographic identity for the node. +.TP +\fB--save-identity\fP \fIfile\fP +Save generated identity (certificate and private key) to given file prefix. + +.TP +\fB--certificate\fP \fIfile\fP +Load identity certificate from given file. + +.TP +\fB--privkey\fP \fIfile\fP +Load identity private key from given file. + +.TP +\fB--privkey-password\fP \fIpassword\fP +Password to use for private key encryption or decryption (optional). + .TP \fB-d\fP Run the program in daemon mode (will fork in the background). diff --git a/include/opendht/crypto.h b/include/opendht/crypto.h index 6782ac7b1e8632c1a8e7ba2dffcb15feeb387937..6f66db26ca870af24f4a0ea05f62fc0cd39d26d1 100644 --- a/include/opendht/crypto.h +++ b/include/opendht/crypto.h @@ -654,6 +654,7 @@ OPENDHT_PUBLIC Identity generateIdentity(const std::string& name = "dhtnode", co OPENDHT_PUBLIC Identity generateEcIdentity(const std::string& name, const Identity& ca, bool is_ca); OPENDHT_PUBLIC Identity generateEcIdentity(const std::string& name = "dhtnode", const Identity& ca = {}); +OPENDHT_PUBLIC void saveIdentity(const Identity& id, const std::string& path, const std::string& privkey_password = {}); /** * Performs SHA512, SHA256 or SHA1, depending on hash_length. diff --git a/src/crypto.cpp b/src/crypto.cpp index 62ef4f5042ad129ed2afdf5fd1d6a3d9f06a0a40..5e0e92e9f17567ad55b890abfaf060dafcbd2141 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -31,6 +31,7 @@ extern "C" { #include <random> #include <sstream> +#include <fstream> #include <stdexcept> #include <cassert> @@ -1043,6 +1044,21 @@ generateEcIdentity(const std::string& name, const Identity& ca) { return generateEcIdentity(name, ca, !ca.first || !ca.second); } +void +saveIdentity(const Identity& id, const std::string& path, const std::string& privkey_password) +{ + { + auto ca_key_data = id.first->serialize(privkey_password); + std::ofstream key_file(path + ".pem"); + key_file.write((char*)ca_key_data.data(), ca_key_data.size()); + } + { + auto ca_key_data = id.second->getPacked(); + std::ofstream crt_file(path + ".crt"); + crt_file.write((char*)ca_key_data.data(), ca_key_data.size()); + } +} + void setValidityPeriod(gnutls_x509_crt_t cert, int64_t validity) { diff --git a/tools/dhtnode.cpp b/tools/dhtnode.cpp index 14570e1a2042c7ed5639d055ab22df6ccc23eef3..db87a5e20e7cb5724d6eda57cc72acfd11782455 100644 --- a/tools/dhtnode.cpp +++ b/tools/dhtnode.cpp @@ -56,7 +56,7 @@ void print_node_info(const std::shared_ptr<DhtRunner>& node, const dht_params& p std::cout << "port " << port4 << std::endl; else std::cout << "IPv4 port " << port4 << ", IPv6 port " << port6 << std::endl; - if (params.generate_identity) + if (params.id.first) std::cout << "Public key ID " << node->getId() << std::endl; } @@ -395,7 +395,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& node, dht_params& params node->cancelPut(id, std::stoul(rem, nullptr, 16)); } else if (op == "s") { - if (not params.generate_identity) { + if (not params.id.first) { print_id_req(); continue; } @@ -410,7 +410,7 @@ void cmd_loop(std::shared_ptr<DhtRunner>& node, dht_params& params }); } else if (op == "e") { - if (not params.generate_identity) { + if (not params.id.first) { print_id_req(); continue; } @@ -510,17 +510,20 @@ main(int argc, char **argv) auto node = std::make_shared<DhtRunner>(); try { - dht::crypto::Identity crt {}; - if (params.generate_identity) { + if (not params.id.first and params.generate_identity) { auto ca_tmp = dht::crypto::generateEcIdentity("DHT Node CA"); - crt = dht::crypto::generateIdentity("DHT Node", ca_tmp); + params.id = dht::crypto::generateIdentity("DHT Node", ca_tmp); + if (not params.save_identity.empty()) { + dht::crypto::saveIdentity(ca_tmp, params.save_identity + "_ca", params.privkey_pwd); + dht::crypto::saveIdentity(params.id, params.save_identity, params.privkey_pwd); + } } dht::DhtRunner::Config config {}; config.dht_config.node_config.network = params.network; config.dht_config.node_config.maintain_storage = false; config.dht_config.node_config.persist_path = params.persist_path; - config.dht_config.id = crt; + config.dht_config.id = params.id; config.threaded = true; config.proxy_server = params.proxyclient; config.push_node_id = "dhtnode"; diff --git a/tools/tools_common.h b/tools/tools_common.h index de16bd3946803a924ae167327b9bf63c4a4c801e..d0cb757be6d1334082feaff0f2f8c2a90bde4c03 100644 --- a/tools/tools_common.h +++ b/tools/tools_common.h @@ -23,6 +23,7 @@ #include <opendht.h> #include <opendht/log.h> +#include <opendht/crypto.h> #ifndef WIN32_NATIVE #include <getopt.h> #include <readline/readline.h> @@ -87,53 +88,79 @@ bool isInfoHash(const dht::InfoHash& h) { return true; } +std::vector<uint8_t> +loadFile(const std::string& path) +{ + std::vector<uint8_t> buffer; + std::ifstream file(path, std::ios::binary); + if (!file) + throw std::runtime_error("Can't read file: "+path); + file.seekg(0, std::ios::end); + auto size = file.tellg(); + if (size > std::numeric_limits<unsigned>::max()) + throw std::runtime_error("File is too big: "+path); + buffer.resize(size); + file.seekg(0, std::ios::beg); + if (!file.read((char*)buffer.data(), size)) + throw std::runtime_error("Can't load file: "+path); + return buffer; +} + static const constexpr in_port_t DHT_DEFAULT_PORT = 4222; struct dht_params { bool help {false}; // print help and exit bool version {false}; - bool log {false}; - std::string logfile {}; - bool syslog {false}; - in_port_t port {0}; - dht::NetId network {0}; bool generate_identity {false}; bool daemonize {false}; bool service {false}; bool peer_discovery {false}; + bool log {false}; + bool syslog {false}; + std::string logfile {}; std::pair<std::string, std::string> bootstrap {}; + dht::NetId network {0}; + in_port_t port {0}; in_port_t proxyserver {0}; std::string proxyclient {}; std::string pushserver {}; std::string devicekey {}; std::string persist_path {}; + dht::crypto::Identity id {}; + std::string privkey_pwd {}; + std::string save_identity {}; }; static const constexpr struct option long_options[] = { - {"help", no_argument , nullptr, 'h'}, - {"port", required_argument, nullptr, 'p'}, - {"net", required_argument, nullptr, 'n'}, - {"bootstrap", required_argument, nullptr, 'b'}, - {"identity", no_argument , nullptr, 'i'}, - {"verbose", no_argument , nullptr, 'v'}, - {"daemonize", no_argument , nullptr, 'd'}, - {"service", no_argument , nullptr, 's'}, - {"peer-discovery", no_argument , nullptr, 'D'}, - {"persist", required_argument, nullptr, 'f'}, - {"logfile", required_argument, nullptr, 'l'}, - {"syslog", no_argument , nullptr, 'L'}, - {"proxyserver", required_argument, nullptr, 'S'}, - {"proxyclient", required_argument, nullptr, 'C'}, - {"pushserver", required_argument, nullptr, 'y'}, - {"devicekey", required_argument, nullptr, 'z'}, - {"version", no_argument , nullptr, 'V'}, - {nullptr, 0 , nullptr, 0} + {"help", no_argument , nullptr, 'h'}, + {"port", required_argument, nullptr, 'p'}, + {"net", required_argument, nullptr, 'n'}, + {"bootstrap", required_argument, nullptr, 'b'}, + {"identity", no_argument , nullptr, 'i'}, + {"save-identity", required_argument, nullptr, 'I'}, + {"certificate", required_argument, nullptr, 'c'}, + {"privkey", required_argument, nullptr, 'k'}, + {"privkey-password", required_argument, nullptr, 'm'}, + {"verbose", no_argument , nullptr, 'v'}, + {"daemonize", no_argument , nullptr, 'd'}, + {"service", no_argument , nullptr, 's'}, + {"peer-discovery", no_argument , nullptr, 'D'}, + {"persist", required_argument, nullptr, 'f'}, + {"logfile", required_argument, nullptr, 'l'}, + {"syslog", no_argument , nullptr, 'L'}, + {"proxyserver", required_argument, nullptr, 'S'}, + {"proxyclient", required_argument, nullptr, 'C'}, + {"pushserver", required_argument, nullptr, 'y'}, + {"devicekey", required_argument, nullptr, 'z'}, + {"version", no_argument , nullptr, 'V'}, + {nullptr, 0 , nullptr, 0} }; dht_params parseArgs(int argc, char **argv) { dht_params params; int opt; + std::string privkey; while ((opt = getopt_long(argc, argv, "hidsvDp:n:b:f:l:", long_options, nullptr)) != -1) { switch (opt) { case 'p': { @@ -201,10 +228,36 @@ parseArgs(int argc, char **argv) { case 's': params.service = true; break; + case 'c': { + try { + params.id.second = std::make_shared<dht::crypto::Certificate>(loadFile(optarg)); + } catch (const std::exception& e) { + throw std::runtime_error(std::string("Error loading certificate: ") + e.what()); + } + break; + } + case 'k': + privkey = optarg; + break; + case 'm': + params.privkey_pwd = optarg; + break; + case 'I': + params.save_identity = optarg; + break; default: break; } } + if (not privkey.empty()) { + try { + params.id.first = std::make_shared<dht::crypto::PrivateKey>(loadFile(privkey), params.privkey_pwd); + } catch (const std::exception& e) { + throw std::runtime_error(std::string("Error loading private key: ") + e.what()); + } + } + if (params.save_identity.empty()) + params.privkey_pwd.clear(); return params; }