Skip to content

Draft: tor-keymgr: Support reading C Tor keys.

gabi-250 requested to merge gabi-250/arti:secondary-keystores into main

This extends tor-keymgr with a couple of new (and experimental) read-only keystore implementations: CTorServiceKeystore, for C Tor hidden service keys, and CTorClientKeystore, for C Tor client restricted discovery keys.

The two can be configured using CTorServiceKeystoreConfig and CTorClientKeystoreConfig.

In TOML format, the service config is of the form:

[storage.keystore.ctor_services."allium-cepa"]
# This should be set to the `HiddenServiceDirectory` of your hidden service.
# Arti will read `HiddenServiceDirectory/hostname`
# and `HiddenServiceDirectory/private_key`.
# (Note: if your service is running in restricted discovery mode, you must also set the
# `[[onion_services."<the nickname of your svc>".restricted_discovery.key_dirs]]`
# to `HiddenServiceDirectory/client_keys`).
path = "/var/lib/tor/allium_cepa"

# The identifier of this keystore.
id = "foo"

[storage.keystore.ctor_services."another-onion-svc"]
id = "coo"
...

Whereas the client keystore config looks like this:

[[storage.keystore.ctor_clients]]
# The identifier of this keystore.
id = "bar"

# This should be set to the `ClientOnionAuthDir` of your client.
# If Arti is configured to run as a client (i.e. if it runs in SOCKS proxy mode),
# it will read the client restricted discovery keys from this path.
#
# The key files are expected to have the `.auth_private` extension,
# and their content **must** be of the form:
# `<56-char-onion-addr-without-.onion-part>:descriptor:x25519:<x25519 private key in base32>`.
#
# Malformed files, and files that don't have the `.auth_private` extension, will be ignored.
path = "/var/lib/tor/onion_auth"

[[storage.keystore.ctor_clients]]
id = "baz"
...

The two look quite different, because ctor_services is a map (from HsNickname to CTorServiceKeystoreConfig), while ctor_clients is a list of CTorClientKeystoreConfigs. The asymmetry is unavoidable: hidden service keys must be explicitly associated with the HsNickname of one of the configured onion-services, while clients don't (we don't have the concept of separate clients within the same arti process, i.e. client keys are not currently namespaced).

Important: ideally, we should cross-check the HsNicknames from the ctor_services map against the nicknames configured in the onion-services section, bailing if any of the ctor_services doesn't have an associated service. However, I haven't had a chance to implement this; I will open a ticket so we don't forget to implement it later.

Questions for the reviewer:

  1. Does the config make sense? What about the ctor_services/ctor_clients naming? Initially, the plan was to have a configurable kind = "client" | "service" for each ctor store, but I ultimately decided against it, because the two configs are actually quite different (the client config doesn't have a nickname, for example). I also wanted the storage.keystore.ctor_services config to mirror the onion service config (because the nicknames from the two sections need to match up):
[storage.keystore.ctor_services."allium-cepa"]
#This should be set to the `HiddenServiceDirectory` of your hidden service.
#Arti will read `HiddenServiceDirectory/hostname`
#and `HiddenServiceDirectory/private_key`.
#(Note: if your service is running in restricted discovery mode, you must also set the
#`[[onion_services."<the nickname of your svc>".restricted_discovery.key_dirs]]`
#to `HiddenServiceDirectory/client_keys`).
path = "/var/lib/tor/allium_cepa"

#The identifier of this keystore.
id = "foo"

[onion_services."allium-cepa"]
proxy_ports = [
   ["80", "127.0.0.1:10080"],
]
  1. Note that the user must manually specify the keystore IDs, and they all need to be unique. Does this seem right, or should we internally derive the keystore IDs? I thought manually specifying them made more sense, because many of our public APIs take a KeystoreSelector that can be used to specify the ID of a keystore to perform an action on (so from that point of view, it makes sense to expose these to the user). On the other hand, there is a design smell here: the primary keystore gets an auto-generated ID, which can clash with the ids specified by the user in the config. So the user can craft a broken config by making the primary keystore kind = "ephemeral", and setting the ID of one of the secondary keystores to "ephemeral", which happens to be the keystore ID we set internally for the primary store, if its kind is "ephemeral". This is something that will need to be addressed before we make the ctor stores non-experimental (so IOW, I propose we defer this to a follow-up MR)

This branch got quite big, but it should be reviewable commit by commit (the commits are incremental, and I've done by best to document every decision and code-movement).

Closes #858

Merge request reports