tor-keymgr: Support reading C Tor keys.
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 CTorClientKeystoreConfig
s. The asymmetry is unavoidable: hidden service keys must be explicitly associated with the HsNickname
of one of the configured onion-service
s, 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 HsNickname
s 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:
- Does the config make sense? What about the
ctor_services
/ctor_clients
naming? Initially, the plan was to have a configurablekind = "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 thestorage.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"],
]
- 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 keystorekind = "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 itskind
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 (closed)