/ doc / dev / notes / client-auth.md
client-auth.md
  1  # HS client auth
  2  
  3  > This document uses deprecated terminology: "client authorization" is
  4  > now known as "restricted discovery".
  5  
  6  This based on our previous discussions from #1028, #1027, #696.
  7  
  8  It presents a simplified version of what is proposed in #1028, and an
  9  implementation plan (in the form of action items and tickets).
 10  
 11  # Proposal A (rejected)
 12  
 13  ## Generating keys
 14  
 15  ### Using the `arti hsc` subcommand
 16  
 17  Client authorization keys can be manually generated using the `arti hsc
 18  generate-key stealth` command. In addition to generating a client auth
 19  keypair in the keystore, this command also exports the public part of key,
 20  in the format specified using `--pub-format`.
 21  
 22  To generate a client authorization key for service xyz.onion in keystore foo:
 23  
 24  ```
 25   arti hsc --config arti.toml generate-key stealth --keystore foo  \
 26       --nickname alice                                             \
 27       --hsid xyz.onion                                             \
 28       --pub-format arti
 29       # or --pub-format ctor (the format is documented in the tor manpage
 30       # under CLIENT AUTHORIZATION)
 31  ```
 32  
 33  Some other possible names for `generate-key stealth`:
 34    * `generate-key hs-auth-stealth`
 35    * `generate-key hs-auth-desc`
 36    * `generate-hs-auth-key stealth`
 37    * ..
 38  
 39  Initially, `arti hsc generate-key` will only support `stealth` keys, for use
 40  with services running in **stealth mode** (as defined in #1028). If we implement
 41  other types of client authorization in the future, we'll likely need to also
 42  extend `arti hsc generate-key`.
 43  
 44  This command displays an error if the client already has a keypair of the
 45  specified kind. The public part of a preexisting keypair can be extracted using
 46  `arti hsc export-pubkey`.
 47  
 48  (Another possibility would be to let the service generate the client auth keys,
 49  but then we'd need to come up with a secure way for it to communicate the
 50  private part of the key to the client; we decided this is a nonstarter)
 51  
 52  #### Public key output format
 53  
 54  ##### `--pub-format ctor`
 55  
 56  With `--pub-format ctor`, `arti hsc generate-key stealth` will generate a
 57  `<client_nickname>.auth` file, with the content in the
 58  `<auth-type>:<key-type>:<base32-encoded-public-key>` format, as per the `CLIENT
 59  AUTHORIZATION` section from `tor(1)`.
 60  
 61  ##### `--pub-format arti`
 62  
 63  Regardless of how we choose to implement client auth configuration on the
 64  service side, with `--pub-format arti`, `arti hsc generate-key stealth` will
 65  generate a `kp_hsc_desc_enc.x25519_public` file containing an OpenSSH key (using
 66  our custom `x22519@spec.torproject.org` algorithm name):
 67  
 68  ```
 69  x25519@spec.torproject.org AAAAGngyNTUxOUBzcGVjLnRvcnByb2plY3Qub3JnAAAAIGmMjbhv/HldaPDU3zGl4YspW84XMqiEoNon1Tre14Eh
 70  ```
 71  
 72  This file can then be shared with HS operators through a secure channel (there
 73  are many ways to peel this orange, but they're outside the scope of this
 74  document).
 75  
 76  **Suggested action items**:
 77    * [ ] Decide if `arti hsc generate-key stealth` is a good name for the
 78      command, and come up with a better one if it isn't
 79    * [ ] Implement the `arti hsc generate-key` subcommand (#1281)
 80    * [ ] Implement the `arti hsc export-pubkey` subcommand
 81  
 82  ### Auto-generating client auth keys
 83  
 84  We could also provide an HS client config option for auto-generating the client
 85  authorization keys for specific hidden services:
 86  
 87  ```toml
 88     [hs_client]
 89     # Generate client authorization keys for these services, if needed.
 90     foo_bar_onion_svc_auth = [
 91         "foo.onion",
 92         "bar.onion"
 93     ]
 94  ```
 95  
 96  However, I think the UX for this would be bad:
 97    * it's not obvious at all that the client might also have authorization keys for
 98      services not listed under `foo_bar_onion_svc_auth`
 99    * we will need to provide a CLI for extracting the public key in a format that
100      can be used by an Arti or C Tor hidden service (`arti hsc
101      extract-pub-auth-key-foo-bar`). So auto-generating the
102      client auth keys doesn't even save us from having to invoke `arti hsc`
103  
104  **Suggested action items**:
105    * [x] Do not implement this
106  
107  ## Configuring client authorization (client side)
108  
109  Clients wanting to connect to services that require client authorization don't
110  need to be explicitly configured to do so: the presence of
111  `client/<client_id>/<hsid>/ks_hsc_desc_enc.x25519_private` in the client
112  keystore is enough for the client to be able to connect to `<hsid>` (assuming `<hsid>`
113  is configured to allow connections from this client).
114  
115  We might need to revisit this decision if we implement additional types of
116  client authentication (other types of client auth will potentially need to be
117  enabled selectively, only for the services that expect it). If we do, we need to
118  make sure the config changes are backwards-compatible (i.e. clients default to
119  using their `<hsid>/ks_hsc_desc_enc.x25519_private`, if any, when connecting to
120  `<hsid>`).
121  
122  **Suggested action items**: none
123  
124  ## Configuring client authorization (service side)
125  
126  The authorized clients will be configured using the `authorized_clients` service
127  option. As mentioned in #1028, we might want to support dynamic HS client
128  providers at some point, but for now we're only going to allow statically
129  configured clients.
130  
131  We have several options for the static authorized clients configuration.
132  
133  ### Option 1: Place authorized client keys in the state dir
134  
135  We could put the authorized client keys in a directory
136  within the state dir (`<state_dir>/authorized_clients/<client_nick>`).
137  
138  We will provide a `arti hss auth-clients` CLI (described under `Extra CLI
139  subcommands for managing authorized clients` below) for managing client
140  authorization.
141  
142  We will use the same naming convention as we do for the keys in an
143  `ArtiNativeKeystore`, so the paths of client authorization keys
144  will be of the form
145  ```
146  <state_dir>/authorized_clients/<client_nick>/kp_hsc_desc_enc.x25519_public
147  ```
148  
149  but we will additionally support keys in the format used by C Tor. So
150  `authorized_clients` can also contain entries of the form
151  ```
152  <state_dir>/authorized_clients/<client_nick>/<client_nick>.auth
153  ```
154  
155  If both `kp_hsc_desc_enc.x25519_public` and `<client_nick>.auth` are present,
156  the service will use `kp_hsc_desc_enc.x25519_public` and log a warning.
157  
158  In addition to provisioning the `authorized_clients` directory, HS operators
159  wanting to enable client authorization must explicitly set `enabled = true` in
160  the toml config:
161  
162  ```toml
163  [onion_service."allium-cepa"]
164  authorized_clients.enabled = true
165  ```
166  
167  
168  If `authorized_clients` is empty, no clients are authorized to access the
169  service. Alternatively, we could declare an empty directory means no
170  authorization is required (this is what C Tor does), but that would be redundant
171  with the `enabled` option.
172  
173  Pros:
174    * this simplifies the distribution and management of client keys: service
175      operators can grant/revoke client authorization by simply moving the client
176      keys to/from the `authorized_clients` directory
177    * the keys are stored in a familiar format (the same one we use in the
178      keystore)
179  
180  Cons:
181    * the presence of an empty `authorized_clients` directory can be interpreted
182      in multiple ways ("nobody is authorized" or "everyone is authorized").
183      However, this is probably disambiguated by the existence
184      `authorized_clients.enabled = true` (`authorized_clients.enabled = false`
185      means "everyone is authorized")
186    * services need to watch the `authorized_clients` directory for changes (and
187      update their view of which clients are authorized accordingly)
188  
189  
190  ### Option 2: Encode the authorized clients as a JSON blob
191  
192  #1028 suggests encoding the authorized clients in a semi-opaque format, and
193  embedding
194  
195  ```toml
196  [onion_service."allium-cepa"]
197  authorized_clients.enabled = true
198  
199  authorized_clients.static = {
200    "alice": "{...}"
201  }
202  ```
203  
204  in the config (or reading each client's config from a separate
205  `<state_dir>/authorized_clients/config.json`)
206  
207  However, I'm not sure I see the benefit of using JSON here.
208  
209  Pros:
210    * the authorized clients can be reloaded along with the rest of the config in
211      `watch_for_config_changes`
212    * it might be more user-friendly (readable) than the alternative. OTOH, I'm
213      not sure we want it to be readable (we don't want to encourage users to
214      manually modify it)
215  
216  Cons:
217    * it complicates the distribution and management of client keys: service
218      operators have to fiddle with the config to authorize new clients (they need
219      to paste the contents of `kp_hsc_desc_enc.x25519_public` in the config).
220      This can be alleviated by providing an `arti hss auth-clients` subcommand
221      for managing authorized clients (see `Extra CLI subcommands for managing
222      authorized clients` below)
223  
224  **Suggested action items**:
225    * [ ] Implement Option 1 for static authorized client configuration
226    * [ ] Make sure the service reloads its authorized clients if there are
227      changes to the `authorized_clients` directory
228  
229  ## Extra CLI subcommands for managing authorized clients
230  
231  We might want to provide an `arti hss auth-clients` command for managing a
232  service's authorized clients (that is, assuming we implement `Option 1` from
233  above).
234  
235  ```
236  NAME
237         arti-hss-auth-clients - Manage the authorized clients of this hidden service
238  
239  SYNOPSIS
240         arti hss auth-clients [SUBCOMMAND]
241  
242  DESCRIPTION
243        A command for managing the authorized clients of an Arti hidden service.
244  
245        TODO: document how these commands are supposed to work after we reach a
246        conclusion in #1028
247  
248  SUBCOMMANDS
249         help                  Print this message or the help of the given subcommand(s)
250         list                  List the authorized clients
251         import                Import the public keys of a client
252         disable               Un-authorize a previously authorized client
253         remove                Purge the client authorization keys of a client, unauthorizing them
254         enable                Authorize a new client
255  ```
256  
257  For example, `arti hss auth-clients import --nickname client-foo
258  ~/downloads/kp_hsc_desc_enc.x25519_public` would create an authorized client
259  called `client-foo`.
260  
261  Since `import`, `disable`, `enable` are essentially just wrappers around `cp`
262  and `mv`, this subcommand may not be particularly useful. OTOH, `enable` and
263  `disable` could be useful for managing temporarily unauthorized clients: arti
264  would maintain a separate `revoked_clients` (`disabled_clients`?) directory, and
265  `disable` and `enable` would move keys to and from it.
266  
267  In addition to the commands listed under `SUBCOMMANDS` above, we might also want
268  to provide subcommands for:
269    * retrieving the absolute path of `<state_dir>/authorized_clients`
270    * retrieving the absolute path of
271      `<state_dir>/authorized_clients/<client_nick>`
272  
273  **Suggested action items**:
274    * [ ] Make this a low-priority item (and implement it if time permits)
275  
276  
277  # Proposal B
278  
279  ## Generating keys
280  
281  ### Using the `arti hsc` subcommand
282  
283  Client authorization keys can be manually generated using the `arti hsc
284  prepare-restricted-mode-key` command. In addition to generating a client auth
285  keypair in the keystore, this command also exports the public part of the key
286  (in C Tor format).
287  
288  ```
289  arti hsc prepare-restricted-mode-key
290     --hsid ...                 # no default
291     [ --config arti.toml ]     # default is default arti.toml
292     [ --output FOO.auth ]      # default is <hs-nickname>.auth, use `-` for stdout
293     [ --overwrite ]            # overwrites any existing output file; default is to refuse
294     [ --generate=no|yes|if-needed ]     # if-needed is the default; otherwise, can error
295  ```
296  
297  **Suggested action items**:
298    * [ ] Implement the `prepare-restricted-mode-key` command (#1281)
299  
300  #### Public key output format
301  
302  For now, we will only support C Tor format for restricted mode client public keys.
303  
304  **Suggested action items**:
305    * [ ] Support encoding x25519 public keys in C Tor format
306  
307  ## HS nickname -> HsId mapping (rejected)
308  
309  Clients will have nicknames for the services they have authorization keys for.
310  This will allow clients to refer to services by nickname rather than
311  by HsId (`torproject` vs
312  `2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid`).
313  
314  The client will need to maintain a mapping from HS nickname to HsId. The reverse
315  mapping is also going to be needed (at least conceptually), because when
316  connecting to `<hsid>.onion`, the client needs to "look up" the corresponding
317  `<hs-nickname>` in order to be able to compute the key specifier of that
318  particular authorization key (if it exists).
319  
320  During HsId rotation, clients will need to be able to connect to both the old
321  and the new HsId. This is needed to support e.g. load balancing setups where
322  multiple hosts run the "same" hidden service (i.e. they all use the same HsId),
323  and race to publish/republish the descriptor.
324  
325  This mapping will need to be:
326    * as persistent as the keystore
327    * compatible with the non-disk keystore types we plan to implement in
328      the future
329  
330  I don't think this mapping belongs in the state dir. Putting it there would
331  create more opportunities for synchronization bugs where the keystore is out of
332  sync with the state dir.
333  
334  Instead, I propose we encode the mapping in the `ArtiPath`s of the client keys.
335  (Alternatively, it could be encoded in the comment of the OpenSSH key.)
336  
337  `ArtiPaths` of the form
338  ```
339  client/<hsid>/ks_hsc_desc_enc.x25519_private
340  ```
341  will become
342  ```
343  client/<hs-nickname>+<hsid>/ks_hsc_desc_enc.x25519_private
344  ```
345  
346  We will need to restrict the `<hs-nickname>` charset (for instance, it cannot
347  include the `+` symbol), as well as its length (to avoid running into
348  platform-specific file path length limits).
349  
350  Pros:
351    * the keys associated with a given `<hs-nickname>` can be listed/removed using
352      the `client/<hs-nickname>+*/ks_hsc_desc_enc.x25519_private`
353      `KeyPathPattern`. In fact, the entire mapping can be derived by listing
354      all the key specifiers matching `client/*+*/ks_hsc_desc_enc.x25519_private`
355    * the mapping cannot go out of sync with the keystore
356    * when asked to connect to `<hsid.onion>`, the client doesn't need to know the
357      nickname of the service: it just needs to find the key matching
358      `client/*+<hsid>/ks_hsc_desc_enc.x25519_private`, and bail if there is more
359      than 1 such key (in the future we might decide to allow many-to-many
360      nickname -> hsid mappings, but for now they are forbidden)
361  
362  Cons:
363    * in practice, the length of the nickname is going to be limited to about 147
364      characters. I think this is fine.
365    * in the case of C Tor keystores, the mapping can't be extracted from the
366      `CTorPath`s of the client keys alone (the HsId needs to be read from
367      `<hs-nickname>.auth`). This asymmetry might mean we need to split
368      `KeyMgr::list_matching`  into `KeyMgr::list_matching_arti` and
369      `KeyMgr::list_matching_ctor` (because `ArtiPath`s are going to be handled
370      very differently from `CTorPath`s). (I think this is actually a pervasive
371      issue that we haven't tackled yet: a number of other callsites/APIs will
372      likely need to change when we add support for C Tor keystores).
373  
374  ### Handling HsId changes
375  
376  If a service `<hsid1>` running in "restricted mode" rotates its identity keys
377  (`<hsid1>` -> `<hsid2>`), on the client side, `client/<hs-nickname>+<hsid1>`
378  needs to be copied to `client/<hs-nickname>+<hsid2>` for the duration of the
379  transition period. After the transition period, `client/<hs-nickname>+<hsid1>`
380  can be removed. We can provide an `arti hsc` subcommand for handling HsId
381  changes, but it will need to be run manually.
382  
383  This is inconvenient but unavoidable: the nickname -> HsId mapping needs to be
384  manually updated regardless of whether it's encoded in the `ArtiPath` or stored
385  separately.
386  
387  **Suggested action items**:
388    * [x] For now, do not implement any of this. When we have a concrete use case
389      for it, we should come up with an alternative way to map nicknames to
390      HsIds and/or multiple HsIds to the same service identity. For context, see
391      https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1987#note_2999114
392  
393  ## Configuring client authorization (service side)
394  
395  The authorized clients are going be part of the service configuration.
396  
397  ```toml
398  [onion_service."allium-cepa".restricted_mode]
399  # TODO: The naming and values of this field are provisional
400  enabled = auto | on | off
401  
402  [onion_service."allium-cepa".restricted_mode.authorized_clients.static]
403  alice = "descriptor:x25519:PU63REQUH4PP464E2Y7AVQ35HBB5DXDH5XEUVUNP3KCPNOXZGIBA"
404  bob   = "descriptor:x25519:B5ZQGTPERMMUDA6VC63LHJUF5IHPOKJMUK26LY2XKSF7VG52AESQ"
405  
406  # Alternatively, you can specify a directory of authorized clients.
407  # Each authorized client is represented by an .auth file, as specified
408  # under CLIENT AUTHORIZATION in tor(1).
409  #
410  # [onion_service."allium-cepa".restricted_mode.authorized_clients.keydirectory]
411  # path = "/etc/allium/authorized_clients"
412  ```
413  
414  `restricted_mode.enabled = off` disables "restricted mode", even if the
415  list of authorized clients is non-empty.
416  
417  As per #1028, in the future we might extend this with support for pluggable
418  client auth key databases:
419  ```toml
420  [onion_service."allium-cepa".restricted_mode]
421  
422  [onion_service."allium-cepa".restricted_mode.authorized_clients.static]
423  alice = "descriptor:x25519:PU63REQUH4PP464E2Y7AVQ35HBB5DXDH5XEUVUNP3KCPNOXZGIBA"
424  bob   = "descriptor:x25519:B5ZQGTPERMMUDA6VC63LHJUF5IHPOKJMUK26LY2XKSF7VG52AESQ"
425  
426  [onion_service."allium-cepa".restricted_mode.provider]
427  driver = "postgresql"
428  database = "..."
429  query = "SELECT nick, pubkey AS kp_desc_enc FROM clients JOIN client_keys WHERE clients.enabled"
430  ```
431  
432  If we later introduce new client auth protocols, we will also add
433  corresponding service configuration modes:
434  ```toml
435  [onion_service."allium-cepa".restricted_mode]
436  enabled = on
437  
438  [onion_service."allium-cepa".foobar_mode]
439  enabled = on
440  
441  [onion_service."allium-cepa".foobar_mode.provider]
442  ...
443  ```
444  
445  Each mode can be toggled on or off independently of the others. Some modes may
446  be incompatible. The service will error if the enabled authorization modes are
447  mutually incompatible.
448  
449  If we want to add an authorization mechanism that uses the "restricted mode"
450  x25519 public keys, we can simply nest its configuration within the
451  `restricted_mode` section:
452  
453  ```toml
454  [onion_service."allium-cepa".restricted_mode]
455  enabled = true
456  
457  [onion_service."allium-cepa".extra_foobar_checks]
458  enabled = auto
459  ...
460  ```
461  
462  **Suggested action items**:
463    * [  ] Choose a name for the `enabled` option, and decide what values it
464      should take (`BoolOrAuto` may not be the right type for it)
465    * [  ] Implement the service configuration for configuring "restricted" mode
466      with static `authorized_clients`