# HS client auth > This document uses deprecated terminology: "client authorization" is > now known as "restricted discovery". This based on our previous discussions from #1028, #1027, #696. It presents a simplified version of what is proposed in #1028, and an implementation plan (in the form of action items and tickets). # Proposal A (rejected) ## Generating keys ### Using the `arti hsc` subcommand Client authorization keys can be manually generated using the `arti hsc generate-key stealth` command. In addition to generating a client auth keypair in the keystore, this command also exports the public part of key, in the format specified using `--pub-format`. To generate a client authorization key for service xyz.onion in keystore foo: ``` arti hsc --config arti.toml generate-key stealth --keystore foo \ --nickname alice \ --hsid xyz.onion \ --pub-format arti # or --pub-format ctor (the format is documented in the tor manpage # under CLIENT AUTHORIZATION) ``` Some other possible names for `generate-key stealth`: * `generate-key hs-auth-stealth` * `generate-key hs-auth-desc` * `generate-hs-auth-key stealth` * .. Initially, `arti hsc generate-key` will only support `stealth` keys, for use with services running in **stealth mode** (as defined in #1028). If we implement other types of client authorization in the future, we'll likely need to also extend `arti hsc generate-key`. This command displays an error if the client already has a keypair of the specified kind. The public part of a preexisting keypair can be extracted using `arti hsc export-pubkey`. (Another possibility would be to let the service generate the client auth keys, but then we'd need to come up with a secure way for it to communicate the private part of the key to the client; we decided this is a nonstarter) #### Public key output format ##### `--pub-format ctor` With `--pub-format ctor`, `arti hsc generate-key stealth` will generate a `.auth` file, with the content in the `::` format, as per the `CLIENT AUTHORIZATION` section from `tor(1)`. ##### `--pub-format arti` Regardless of how we choose to implement client auth configuration on the service side, with `--pub-format arti`, `arti hsc generate-key stealth` will generate a `kp_hsc_desc_enc.x25519_public` file containing an OpenSSH key (using our custom `x22519@spec.torproject.org` algorithm name): ``` x25519@spec.torproject.org AAAAGngyNTUxOUBzcGVjLnRvcnByb2plY3Qub3JnAAAAIGmMjbhv/HldaPDU3zGl4YspW84XMqiEoNon1Tre14Eh ``` This file can then be shared with HS operators through a secure channel (there are many ways to peel this orange, but they're outside the scope of this document). **Suggested action items**: * [ ] Decide if `arti hsc generate-key stealth` is a good name for the command, and come up with a better one if it isn't * [ ] Implement the `arti hsc generate-key` subcommand (#1281) * [ ] Implement the `arti hsc export-pubkey` subcommand ### Auto-generating client auth keys We could also provide an HS client config option for auto-generating the client authorization keys for specific hidden services: ```toml [hs_client] # Generate client authorization keys for these services, if needed. foo_bar_onion_svc_auth = [ "foo.onion", "bar.onion" ] ``` However, I think the UX for this would be bad: * it's not obvious at all that the client might also have authorization keys for services not listed under `foo_bar_onion_svc_auth` * we will need to provide a CLI for extracting the public key in a format that can be used by an Arti or C Tor hidden service (`arti hsc extract-pub-auth-key-foo-bar`). So auto-generating the client auth keys doesn't even save us from having to invoke `arti hsc` **Suggested action items**: * [x] Do not implement this ## Configuring client authorization (client side) Clients wanting to connect to services that require client authorization don't need to be explicitly configured to do so: the presence of `client///ks_hsc_desc_enc.x25519_private` in the client keystore is enough for the client to be able to connect to `` (assuming `` is configured to allow connections from this client). We might need to revisit this decision if we implement additional types of client authentication (other types of client auth will potentially need to be enabled selectively, only for the services that expect it). If we do, we need to make sure the config changes are backwards-compatible (i.e. clients default to using their `/ks_hsc_desc_enc.x25519_private`, if any, when connecting to ``). **Suggested action items**: none ## Configuring client authorization (service side) The authorized clients will be configured using the `authorized_clients` service option. As mentioned in #1028, we might want to support dynamic HS client providers at some point, but for now we're only going to allow statically configured clients. We have several options for the static authorized clients configuration. ### Option 1: Place authorized client keys in the state dir We could put the authorized client keys in a directory within the state dir (`/authorized_clients/`). We will provide a `arti hss auth-clients` CLI (described under `Extra CLI subcommands for managing authorized clients` below) for managing client authorization. We will use the same naming convention as we do for the keys in an `ArtiNativeKeystore`, so the paths of client authorization keys will be of the form ``` /authorized_clients//kp_hsc_desc_enc.x25519_public ``` but we will additionally support keys in the format used by C Tor. So `authorized_clients` can also contain entries of the form ``` /authorized_clients//.auth ``` If both `kp_hsc_desc_enc.x25519_public` and `.auth` are present, the service will use `kp_hsc_desc_enc.x25519_public` and log a warning. In addition to provisioning the `authorized_clients` directory, HS operators wanting to enable client authorization must explicitly set `enabled = true` in the toml config: ```toml [onion_service."allium-cepa"] authorized_clients.enabled = true ``` If `authorized_clients` is empty, no clients are authorized to access the service. Alternatively, we could declare an empty directory means no authorization is required (this is what C Tor does), but that would be redundant with the `enabled` option. Pros: * this simplifies the distribution and management of client keys: service operators can grant/revoke client authorization by simply moving the client keys to/from the `authorized_clients` directory * the keys are stored in a familiar format (the same one we use in the keystore) Cons: * the presence of an empty `authorized_clients` directory can be interpreted in multiple ways ("nobody is authorized" or "everyone is authorized"). However, this is probably disambiguated by the existence `authorized_clients.enabled = true` (`authorized_clients.enabled = false` means "everyone is authorized") * services need to watch the `authorized_clients` directory for changes (and update their view of which clients are authorized accordingly) ### Option 2: Encode the authorized clients as a JSON blob #1028 suggests encoding the authorized clients in a semi-opaque format, and embedding ```toml [onion_service."allium-cepa"] authorized_clients.enabled = true authorized_clients.static = { "alice": "{...}" } ``` in the config (or reading each client's config from a separate `/authorized_clients/config.json`) However, I'm not sure I see the benefit of using JSON here. Pros: * the authorized clients can be reloaded along with the rest of the config in `watch_for_config_changes` * it might be more user-friendly (readable) than the alternative. OTOH, I'm not sure we want it to be readable (we don't want to encourage users to manually modify it) Cons: * it complicates the distribution and management of client keys: service operators have to fiddle with the config to authorize new clients (they need to paste the contents of `kp_hsc_desc_enc.x25519_public` in the config). This can be alleviated by providing an `arti hss auth-clients` subcommand for managing authorized clients (see `Extra CLI subcommands for managing authorized clients` below) **Suggested action items**: * [ ] Implement Option 1 for static authorized client configuration * [ ] Make sure the service reloads its authorized clients if there are changes to the `authorized_clients` directory ## Extra CLI subcommands for managing authorized clients We might want to provide an `arti hss auth-clients` command for managing a service's authorized clients (that is, assuming we implement `Option 1` from above). ``` NAME arti-hss-auth-clients - Manage the authorized clients of this hidden service SYNOPSIS arti hss auth-clients [SUBCOMMAND] DESCRIPTION A command for managing the authorized clients of an Arti hidden service. TODO: document how these commands are supposed to work after we reach a conclusion in #1028 SUBCOMMANDS help Print this message or the help of the given subcommand(s) list List the authorized clients import Import the public keys of a client disable Un-authorize a previously authorized client remove Purge the client authorization keys of a client, unauthorizing them enable Authorize a new client ``` For example, `arti hss auth-clients import --nickname client-foo ~/downloads/kp_hsc_desc_enc.x25519_public` would create an authorized client called `client-foo`. Since `import`, `disable`, `enable` are essentially just wrappers around `cp` and `mv`, this subcommand may not be particularly useful. OTOH, `enable` and `disable` could be useful for managing temporarily unauthorized clients: arti would maintain a separate `revoked_clients` (`disabled_clients`?) directory, and `disable` and `enable` would move keys to and from it. In addition to the commands listed under `SUBCOMMANDS` above, we might also want to provide subcommands for: * retrieving the absolute path of `/authorized_clients` * retrieving the absolute path of `/authorized_clients/` **Suggested action items**: * [ ] Make this a low-priority item (and implement it if time permits) # Proposal B ## Generating keys ### Using the `arti hsc` subcommand Client authorization keys can be manually generated using the `arti hsc prepare-restricted-mode-key` command. In addition to generating a client auth keypair in the keystore, this command also exports the public part of the key (in C Tor format). ``` arti hsc prepare-restricted-mode-key --hsid ... # no default [ --config arti.toml ] # default is default arti.toml [ --output FOO.auth ] # default is .auth, use `-` for stdout [ --overwrite ] # overwrites any existing output file; default is to refuse [ --generate=no|yes|if-needed ] # if-needed is the default; otherwise, can error ``` **Suggested action items**: * [ ] Implement the `prepare-restricted-mode-key` command (#1281) #### Public key output format For now, we will only support C Tor format for restricted mode client public keys. **Suggested action items**: * [ ] Support encoding x25519 public keys in C Tor format ## HS nickname -> HsId mapping (rejected) Clients will have nicknames for the services they have authorization keys for. This will allow clients to refer to services by nickname rather than by HsId (`torproject` vs `2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid`). The client will need to maintain a mapping from HS nickname to HsId. The reverse mapping is also going to be needed (at least conceptually), because when connecting to `.onion`, the client needs to "look up" the corresponding `` in order to be able to compute the key specifier of that particular authorization key (if it exists). During HsId rotation, clients will need to be able to connect to both the old and the new HsId. This is needed to support e.g. load balancing setups where multiple hosts run the "same" hidden service (i.e. they all use the same HsId), and race to publish/republish the descriptor. This mapping will need to be: * as persistent as the keystore * compatible with the non-disk keystore types we plan to implement in the future I don't think this mapping belongs in the state dir. Putting it there would create more opportunities for synchronization bugs where the keystore is out of sync with the state dir. Instead, I propose we encode the mapping in the `ArtiPath`s of the client keys. (Alternatively, it could be encoded in the comment of the OpenSSH key.) `ArtiPaths` of the form ``` client//ks_hsc_desc_enc.x25519_private ``` will become ``` client/+/ks_hsc_desc_enc.x25519_private ``` We will need to restrict the `` charset (for instance, it cannot include the `+` symbol), as well as its length (to avoid running into platform-specific file path length limits). Pros: * the keys associated with a given `` can be listed/removed using the `client/+*/ks_hsc_desc_enc.x25519_private` `KeyPathPattern`. In fact, the entire mapping can be derived by listing all the key specifiers matching `client/*+*/ks_hsc_desc_enc.x25519_private` * the mapping cannot go out of sync with the keystore * when asked to connect to ``, the client doesn't need to know the nickname of the service: it just needs to find the key matching `client/*+/ks_hsc_desc_enc.x25519_private`, and bail if there is more than 1 such key (in the future we might decide to allow many-to-many nickname -> hsid mappings, but for now they are forbidden) Cons: * in practice, the length of the nickname is going to be limited to about 147 characters. I think this is fine. * in the case of C Tor keystores, the mapping can't be extracted from the `CTorPath`s of the client keys alone (the HsId needs to be read from `.auth`). This asymmetry might mean we need to split `KeyMgr::list_matching` into `KeyMgr::list_matching_arti` and `KeyMgr::list_matching_ctor` (because `ArtiPath`s are going to be handled very differently from `CTorPath`s). (I think this is actually a pervasive issue that we haven't tackled yet: a number of other callsites/APIs will likely need to change when we add support for C Tor keystores). ### Handling HsId changes If a service `` running in "restricted mode" rotates its identity keys (`` -> ``), on the client side, `client/+` needs to be copied to `client/+` for the duration of the transition period. After the transition period, `client/+` can be removed. We can provide an `arti hsc` subcommand for handling HsId changes, but it will need to be run manually. This is inconvenient but unavoidable: the nickname -> HsId mapping needs to be manually updated regardless of whether it's encoded in the `ArtiPath` or stored separately. **Suggested action items**: * [x] For now, do not implement any of this. When we have a concrete use case for it, we should come up with an alternative way to map nicknames to HsIds and/or multiple HsIds to the same service identity. For context, see https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1987#note_2999114 ## Configuring client authorization (service side) The authorized clients are going be part of the service configuration. ```toml [onion_service."allium-cepa".restricted_mode] # TODO: The naming and values of this field are provisional enabled = auto | on | off [onion_service."allium-cepa".restricted_mode.authorized_clients.static] alice = "descriptor:x25519:PU63REQUH4PP464E2Y7AVQ35HBB5DXDH5XEUVUNP3KCPNOXZGIBA" bob = "descriptor:x25519:B5ZQGTPERMMUDA6VC63LHJUF5IHPOKJMUK26LY2XKSF7VG52AESQ" # Alternatively, you can specify a directory of authorized clients. # Each authorized client is represented by an .auth file, as specified # under CLIENT AUTHORIZATION in tor(1). # # [onion_service."allium-cepa".restricted_mode.authorized_clients.keydirectory] # path = "/etc/allium/authorized_clients" ``` `restricted_mode.enabled = off` disables "restricted mode", even if the list of authorized clients is non-empty. As per #1028, in the future we might extend this with support for pluggable client auth key databases: ```toml [onion_service."allium-cepa".restricted_mode] [onion_service."allium-cepa".restricted_mode.authorized_clients.static] alice = "descriptor:x25519:PU63REQUH4PP464E2Y7AVQ35HBB5DXDH5XEUVUNP3KCPNOXZGIBA" bob = "descriptor:x25519:B5ZQGTPERMMUDA6VC63LHJUF5IHPOKJMUK26LY2XKSF7VG52AESQ" [onion_service."allium-cepa".restricted_mode.provider] driver = "postgresql" database = "..." query = "SELECT nick, pubkey AS kp_desc_enc FROM clients JOIN client_keys WHERE clients.enabled" ``` If we later introduce new client auth protocols, we will also add corresponding service configuration modes: ```toml [onion_service."allium-cepa".restricted_mode] enabled = on [onion_service."allium-cepa".foobar_mode] enabled = on [onion_service."allium-cepa".foobar_mode.provider] ... ``` Each mode can be toggled on or off independently of the others. Some modes may be incompatible. The service will error if the enabled authorization modes are mutually incompatible. If we want to add an authorization mechanism that uses the "restricted mode" x25519 public keys, we can simply nest its configuration within the `restricted_mode` section: ```toml [onion_service."allium-cepa".restricted_mode] enabled = true [onion_service."allium-cepa".extra_foobar_checks] enabled = auto ... ``` **Suggested action items**: * [ ] Choose a name for the `enabled` option, and decide what values it should take (`BoolOrAuto` may not be the right type for it) * [ ] Implement the service configuration for configuring "restricted" mode with static `authorized_clients`