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`