network_engine: encrypt the DHT protocol

Nodes using transport encryption derive their node ID from their X25519
transport key, advertise it in plaintext messages ("k") and in node
lists ("k4"/"k6"), and encrypt all traffic with nodes whose key is
known. Keys are bound to node IDs, so they can't be forged, and
plaintext messages never move a node known to support encryption.
Legacy nodes keep talking in plaintext.

Private networks (TransportConfig::authorities) only accept nodes with
a credential issued by one of the network authorities, never send or
accept plaintext, and introduce themselves to addresses with a Hello.
This commit is contained in:
Adrien Beraud committed 2026-10-29 23:28:48 -04:00
1 parent 523dda8b3d
commit 7bc287fe2f
6 files changed
+439 -207

No files matched your search

+18 -1
View File
@@ -1987,6 +1987,18 @@ fromDhtConfig(const Config& config)
netConf.max_peer_req_per_sec = config.max_peer_req_per_sec ? config.max_peer_req_per_sec
: netConf.max_req_per_sec / 8;
netConf.is_client = config.client_mode;
netConf.transport = config.transport;
auto& transport = netConf.transport;
if (transport.isPrivate() and not transport.enabled)
throw DhtException("Private networks require transport encryption");
if (transport.enabled and not transport.key)
transport.key = transport::KeyPair::generate();
if (transport.isPrivate()) {
if (transport.credential.key != transport.key.pub)
throw DhtException("Transport credential doesn't match the transport key");
if (not transport.credential.verify(transport.authorities))
throw DhtException("Invalid or expired transport credential");
}
return netConf;
}
@@ -2024,6 +2036,8 @@ Dht::Dht(std::unique_ptr<net::DatagramSocket>&& sock,
, maintain_storage(config.maintain_storage)
, public_stable(config.public_stable)
{
if (auto key = network_engine.getTransportKey())
myid = transport::nodeId(key->pub);
scheduler.syncTime();
auto s = network_engine.getSocket();
if (not s or (not s->hasIPv4() and not s->hasIPv6()))
@@ -2053,6 +2067,9 @@ Dht::Dht(std::unique_ptr<net::DatagramSocket>&& sock,
if (logger_)
logger_->debug("DHT node initialised with ID {:s}", myid);
if (config.transport.isPrivate() and logger_
and config.transport.credential.expiration < std::chrono::system_clock::now() + std::chrono::hours(24 * 7))
logger_->warn("Transport credential expires in less than a week");
}
bool
@@ -2948,7 +2965,7 @@ Dht::loadState(const std::string& path)
auto state = oh.get().as<DhtState>();
if (logger_)
logger_->debug("Importing {} nodes", state.nodes.size());
if (state.id)
if (state.id and not network_engine.getTransportKey())
myid = state.id;
std::vector<Sp<Node>> tmpNodes;
tmpNodes.reserve(state.nodes.size());