oxen-io / lokinet

Lokinet is an anonymous, decentralized and IP based overlay network for the internet.
https://lokinet.org/
GNU General Public License v3.0
1.71k stars 221 forks source link

Add Support for Encrypted/Authenticated Listeners #2129

Open dr7ana opened 1 year ago

dr7ana commented 1 year ago

User can specify encrypted listener addresses and comma-delimited public keys to be accepted by exposed encrypted listener. Keys must be attached to a listener address.

Example:

bind_curve=tcp://0.0.0.0:1234|pubkeyA,pubkeyB bind_curve=tcp://0.0.0.0:5678|pubkeyC,pubkeyD

In the given example above, port 1234 is only accessible by whitelisted pubkeys A and B, while 5678 is accessible by C and D.

Fixes: #2122

jagerman commented 1 year ago

This is going to need some deeper changes in lokinet to make it work w.r.t. to lokinet-side "server" pubkey, so for right now let's put this on hold until we can come back to it (likely after some other refactors are merged). Some notes on what is required to make this work follow.


The main issue is where we specify the OMQ listener keypair: right now, we default-construct the OxenMQ instance during Router construction, which results in OxenMQ generating a new random keypair at every startup. For a curve listener this is fairly useless because it means you'd have to go get this pubkey (which right now we don't even log, much less expose in any useful way) every time you want to use the client: you couldn't, for instance, set up a script that periodically polls lokinet for stats because every time the server restarts you'd have to (manually) obtain the new server pubkey and update it in the script.

What we need here is two pieces:

  1. For a service node, we want to use the service node's x25519 keypair itself. This is the same key as the ed25519 keypair we use for the router already, just converted into x25519 keyspace. (We could call on libsodium to convert, but it's probably easier to just use the pre-converted x25519 keypair that oxend gives us through the get_service_privkey endpoint that we already call to get the ed25519 keypair).

This gets complicated, though, because we have to re-create the OxenMQ instance after we get this keypair and give it the new keypair we got from oxend.

In storage server, we solve this problem by using two OxenMQ instances: we create a temporary one during instantiation that only has one job: connecting to oxend and getting the keys. This basically blocks startup until it succeeds, at which point we destroy that object and reconstruct a new OxenMQ using the just-fetched private key.

Lokinet likely needs to do something similar, but currently that is difficult because of how the OxenMQ instance is embedded in the Router instance (and this is the instance that we use to get the keys from oxend). Likely a solution here is going to require us setting up a temporary OxenMQ instance to extract the keys very early in the startup (before we construct the router god object) and passing those keys into Router construction so that the OxenMQ instance that Router owns can get constructed with the keys.

  1. For lokinet clients, we want to store a persistent x25519 private key in a generated file in the data directory. We can generate and store a new key if this doesn't already exist, and (when curve listening is enabled) we should log the x25519 pubkey (not privkey!) in the startup logging. Then it's just a matter of getting this once, putting into your logging script (or whatever is connecting to your lokinet rpc) and you're done.

This file-backed private key we then feed into the Router construction (similar to the snode case) so that it creates the OxenMQ instance with this key.

jagerman commented 1 year ago

An alternative here would be to update OxenMQ to support a per-socket key in the listen_curve call. Currently it just uses a master key, but there's no ZMQ requirement for that: we could set up the listened ZMQ socket with a distinct keypair.