key-management.md
1 # Key management backend 2 3 > This document uses deprecated terminology: "client authorization" is 4 > now known as "restricted discovery". 5 6 ## Motivation 7 Arti will need to be able to manage various types of keys, including: 8 * HS client authorization keys (`KS_hsd_desc_enc`, `KS_hsc_intro_auth`) 9 * HS service keys (`KS_hs_id`, `KS_hs_desc_sign`) 10 * relay keys 11 * dirauth keys 12 * ... 13 14 This document describes a possible design for a key manager that can read, add, 15 or remove keys from persistent storage. It is based on some ideas proposed by 16 @Diziet. 17 18 See also: [#728] 19 20 ## Usage example 21 22 ```rust 23 let client_spec = HsClientSpecifier::from_str("alice")?; 24 25 let intro_auth_key_spec: HsClientSecretKeySpecifier = 26 (client_spec, hs_id, HsClientKeyRole::IntroAuth).into(); 27 28 // Get KP_hsc_intro_auth 29 // TODO #798, TODO HS: We should not use "unescorted" ed25519 secrets. 30 let sk: Option<ed25519::SecretKey> = keymgr.get::<ed25519::SecretKey>(&intro_auth_key_spec)?; 31 32 // Alternatively, instead of returning a type-erased value, KeyStore::get could return a `Key` 33 // (TODO hs: come up with a better name), `Key` being an enum over all supported key types. 34 // `Key` could then have a `Key::as` convenience function that returns the underlying key (or 35 // an error if none of the variants have the requested type): 36 // let sk: Option<ed25519::SecretKey> = keymgr.get(&key_spec)?.as::<ed25519::SecretKey>().unwrap(); 37 ``` 38 39 ## Key stores 40 41 The key manager is an interface to one or more key stores. 42 43 Supported key stores: 44 45 * C Tor key store: an on-disk store that is backwards-compatible with C Tor (new 46 keys are stored in the format used by C Tor, and any existing keys are 47 expected to be in this format too). 48 * Arti key store: an on-disk store that stores keys in OpenSSH key 49 format. 50 51 In the future we plan to also support HSM-based key stores. 52 53 ## Use cases 54 55 ### Hidden service with on-disk keys 56 57 The user makes a minimal configuration: 58 they specify the nickname of the hidden service in the Arti configuration, 59 and where to forward http(s) requests. 60 61 The default Arti keystore is instantiated, and is initially empty. 62 63 All keys including `K_hs_id` are automatically generated as needed. 64 65 `K_hs_id` is stored on disk in the default location 66 within the Arti keystore. 67 `K_hs_blind_id` are calculated as needed and aren't stored. 68 `K_hs_desc_sign` is generated and certified as needed; 69 it need not be stored 70 (so service restarts might involve a new `K_hs_desc_sign`). 71 72 ### Hidden service with on-disk keys, migration from C Tor 73 74 The user specifies, for the service: its nickname, 75 and the C Tor `HiddenServiceDirectory`. 76 77 When Arti starts up, a C Tor compatibility key store is created 78 (for that specific service, so one per HS if there are several). 79 80 The `private_key` file is `K_hs_id`. 81 Other keys (`K_hs_blind_id`, `K_hs_desc_sign`, etc.) 82 are created dynamically, 83 and not stored. 84 (These do not have a C Tor compatibility path.) 85 86 Additionally, other Arti-specific C Tor compatibility code 87 reads other information from the `HiddenServiceDirectory`. 88 Notably, it checks the `hostname` file, 89 and reads the `client_keys` directory. 90 (In the initial Arti HS release, 91 perhaps Arti doesn't know how to handle C Tor `client_keys` 92 and instead refuses to run on the grounds that it can't 93 enforce the authentication.) 94 95 ### Relay with `K_relayid` in hardware (HSM) 96 97 Suppose an HSM with the following properties: 98 99 * Limited number of keyslots, identified by number. 100 * Requires user interaction (passphrase, touch permission) for key use. 101 102 User configures or specifies: 103 104 * The HSM, giving it a keystore name 105 (there might be several distinct keystores with the same hardware driver, 106 referring to distinct hardware tokens) 107 and details needed to find it 108 * That this Arti is supposed to be a relay. 109 * Some linkage that allows the `K_relayid_*` to be found in the HSM. 110 This must link: 111 * the HSM keystore nickname 112 * that this is for the relay keys 113 * the slots within the HSM (HSM-specific value) 114 for the `K_relayid_rsa` and `K_relayid_ed`. 115 116 Perhaps the linkage is done by an entry in the HSM's config section, 117 linking the `ArtiPath`s for the keys to to the HSM keyslot numbers; 118 or perhaps it is done in the relay config section, 119 and specifies, for both the RSA and ED identities, 120 the HSM keystore nickname and 121 the keystore-specific location information (in this case, the keyslot). 122 123 A separate tool provided by Arti generates the subsidiary keys 124 `K_relaysign_*` etc., for a specified time into the future, 125 using the `K_relayid_*` via the HSM keystore. 126 This tool manages the interaction between the user and the HSM 127 (eg touch or passphrase). 128 129 The main Arti relay process picks up those subsidiary keys 130 automatically and uses them. 131 It does not need to (and cannot) use the main identity key. 132 133 ### Ephemeral hidden service 134 135 Some application that embeds Arti wishes to create an ephemeral hidden service. 136 137 It chooses a nickname which is used only for logging, 138 and makes the appropriate API calls to `TorClient`. 139 140 Arti generates a `K_hs_id` and all subsidiary keys automatically. 141 The keys are not stored anywhere. 142 (Probably, the HS code knows that this is ephemeral and doesn't call the storage APIs. 143 But maybe there is a dummy keystore that is always empty and which never stores anything.) 144 145 ### Hidden service with offline identity key 146 147 There are two hosts: the online host runs the service, but has no access to `K_hs_id`. 148 The offline host has `K_hs_id`. 149 150 The user configures, on both hosts, the HS nickname. 151 152 On the offline host the user never runs the main Arti daemon. 153 Instead, they run a special hidden service offline key management tool. 154 This key management tool works with *two* on-disk keystores: 155 the private one (which exists only on the offline host), 156 and the shared one (which exists on both). 157 The private keystore contains `K_hs_id` 158 (and the key is generated there if there isn't one already). 159 The shared keystore contains the subsidiary keys. 160 161 The offline tool generates a specified number of 162 `K_hs_desc_sign` for future time periods, 163 and certifies them with the appropriate `K_hs_blind_id`. 164 (The `K_hs_blind_id` are never stored.) 165 The `K_hs_desc_sign` are stored in the shared keystore, 166 each with its corresponding `descriptor-signing-key-cert`. 167 168 The offline tool generates a copy of `KP_hs_id` 169 and stores it in the shared keystore. 170 (`KP_hs_id` is sort-of a secret, and sort-of a certificate. 171 Technical note: 172 the protocol in rend-spec-v3 is quite close to allowing operation 173 of a hidden service whose online component *doesn't know* 174 its own `.onion` address. 175 But it is not quite there.) 176 177 The whole shared keystore is copied 178 from the offline to the online host. 179 180 On the online host, 181 Arti uses the provided keys and certificates. 182 183 If the online Arti ends up running off the end of 184 the pre-generated keys/certs, 185 it knows that it shouldn't generate a new `K_hs_id` 186 (even though it thinks it needs to, 187 since it finds it needs to sign an absent `K_hs_desc_sign`) 188 because the `KP_hs_id` is present. 189 190 The user can also configure, in the online Arti, 191 the `.onion` address for the service. 192 This will also prevent Arti from ever generating a `K_hs_id`, 193 since it would have to somehow generate the indented identity. 194 (If provided, it is checked.) 195 196 ## Proposed configuration changes 197 198 We introduce a new `[keys]` section for configuring the key stores. The 199 `[keys.permissions]` section specifies the permissions all top-level key store 200 directories are expected to have. It serves a similar purpose to 201 `[storage.permissions]`. 202 203 Initially, it will only be possible to configure two disk-backed key stores: the 204 Arti key store via the `[keys.arti_store]` section, and the C Tor key store via 205 the `[keys.ctor_store]` section. Future versions will be able to support the 206 configuration of arbitrary key store implementations. 207 208 The order of the key store sections is important, because keys are looked up in 209 each of the configured key stores, in the order they are specified in the 210 config. For example, if the Arti key store comes before the C Tor key store in 211 the config, when prompted to retrieve a key, the key manager will search 212 for the key in the Arti key store before checking the C Tor one: 213 214 ```toml 215 [keys.arti_store] 216 ... 217 218 [keys.ctor_store] 219 ... 220 ``` 221 222 Note that both key stores are optional. It is possible to run Arti without 223 configuring a key store (for example, when running Arti as a client). 224 225 TODO hs: pick reasonable default values for the various top-level key store 226 directories (`root_dir`, `client_dir`, `key_dir`). 227 228 ### Arti key store configuration 229 230 ```toml 231 # Key store options 232 [keys] 233 234 # Describe the filesystem permissions to enforce. 235 [keys.permissions] 236 # If set to true, we ignore all filesystem permissions. 237 #dangerously_trust_everyone = false 238 239 # What user (if any) is trusted to own files and directories? ":current" means 240 # to trust the current user. 241 #trust_user = ":current" 242 243 # What group (if any) is trusted to have read/write access to files and 244 # directories? ":selfnamed" means to trust the group with the same name as the 245 # current user, if that user is a member. 246 #trust_group = ":username" 247 248 # If set, gives a path prefix that will always be trusted. For example, if this 249 # option is set to "/home/", and we are checking "/home/username/.cache", then 250 # we always accept the permissions on "/" and "/home", but we check the 251 # permissions on "/home/username" and "/home/username/.cache". 252 # 253 # (This is not the default.) 254 # 255 # ignore_prefix = "/home/" 256 #ignore_prefix = "" 257 258 # The Arti key store. 259 [keys.arti_store] 260 # The root of the key store. All keys are stored somewhere in the `root_dir` 261 # hierarchy 262 root_dir = "" 263 ``` 264 265 ### C Tor key store configuration 266 267 The client and relay keys are stored in a different part of the config than the 268 onion service keys: the client/relay key directories are read from the 269 `[keys.ctor_store]` section, whereas the onion service ones are read from the 270 `[onion_service.hs_service_dirs]` of each `[[onion_service]]` section (note 271 there can be multiple `[[onion_service]]` sections, one for each hidden service 272 configured). As a result, the C Tor key store is not rooted at a specific 273 directory (unlike the Arti key store). Instead, it is configured with: 274 * (for each onion service configured) a `hs_service_dir`, for onion service keys 275 * a `client_dir`, for onion service client authorization keys. 276 * a `key_dir`, for relay and directory authority keys 277 278 The exact structure of the `[[onion_service]]` config is not yet 279 specified, see [#699]. 280 281 A downside of this approach is that there is no `CTorKeyStoreConfig` to speak 282 of: the `CTorKeyStore` is created from various bits of information taken from 283 different parts of the Arti config (`CTorKeyStore::new(client_dir, key_dir, 284 hs_service_dirs)`). 285 286 ```toml 287 # The C Tor key store. 288 [keys.ctor_store] 289 # The client authorization key directory (if running Arti as a client). 290 # 291 # This corresponds to C Tor's ClientOnionAuthDir option. 292 client_dir = "" 293 # The key directory. 294 # 295 # This corresponds to C Tor's KeyDirectory option. 296 key_dir = "" 297 298 # Hidden service options 299 [[onion_service]] 300 # This corresponds to C Tor's HiddenServiceDir option. 301 hs_service_dir = "/home/bob/hs1" 302 # The maximum number of streams per rendezvous circuit. 303 # 304 # This corresponds to C Tor's HiddenServiceMaxStreams. 305 max_streams = 0 306 # TODO arti#699: figure out what the rest of the options are 307 ... 308 309 # Hidden service options 310 [[onion_service]] 311 # This corresponds to C Tor's HiddenServiceDir option. 312 hs_service_dir = "/home/bob/hs2" 313 # The maximum number of streams per rendezvous circuit. 314 # 315 # This corresponds to C Tor's HiddenServiceMaxStreams. 316 max_streams = 9000 317 # TODO arti#699: figure out what the rest of the options are 318 ... 319 ``` 320 321 ## Key specifiers 322 323 We introduce the concept of a "key specifier" (specified for each supported key 324 type via the `KeySpecifier` trait). A "key specifier" uniquely identifies an 325 instance of a type of key. From an implementation standpoint, `KeySpecifier` 326 implementers must specify: 327 * `arti_path`: the location of the key in the Arti key store. This also serves 328 as a unique identifier for a particular instance of a key. 329 * `ctor_path`: the location of the key in the C Tor key store (optional). 330 331 For example, an Arti key store might have the following structure (note that 332 each path within the `keys.arti_store.root_dir` directory, minus the extension, 333 is the `arti_path` of a particular key): 334 ``` 335 <keys.arti_store.root_dir> 336 ├── client 337 │ ├── alice # HS client specifier "alice" 338 │ │ ├── foo.onion 339 │ │ │ ├── hsc_desc_enc.arti_priv # arti_path = "client/alice/foo.onion/hsc_desc_enc" 340 │ │ │ │ # (HS client Alice's x25519 hsc_desc_enc keypair for decrypting the HS 341 │ │ │ │ # descriptors of foo.onion") 342 │ │ │ └── hsc_intro_auth.arti_priv # arti_path = "client/alice/foo.onion/hsc_intro_auth" 343 │ │ │ # (HS client Alice's ed25519 hsc_intro_auth keypair for computing 344 │ │ │ # signatures to prove to foo.onion she is authorized") 345 │ │ │ # Note: this is not implemented in C Tor 346 │ │ └── bar.onion 347 │ │ ├── hsc_desc_enc.arti_priv # arti_path = "client/alice/foo.onion/hsc_desc_enc" 348 │ │ │ # (HS client Alice's x25519 hsc_desc_enc keypair for decrypting the HS 349 │ │ │ # descriptors of bar.onion") 350 │ │ └── hsc_intro_auth.arti_priv # arti_path = "client/alice/bar.onion/hsc_intro_auth" 351 │ │ # (HS client Alice's ed25519 hsc_intro_auth keypair for computing 352 │ │ # signatures to prove to bar.onion she is authorized") 353 │ └── bob # HS client specifier "bob" 354 │ └── foo.onion 355 │ ├── hsc_desc_enc.arti_priv # arti_path = "client/bob/foo.onion/hsc_desc_enc" 356 │ │ # (HS client Bob's x25519 hsc_desc_enc keypair for decrypting the HS 357 │ │ # descriptors of foo.onion") 358 │ └── hsc_intro_auth.arti_priv # arti_path = "client/bob/foo.onion/hsc_intro_auth" 359 │ # (HS client Bob's ed25519 hsc_intro_auth keypair for computing 360 │ # signatures to prove to foo.onion he is authorized") 361 │ # Note: this is not implemented in C Tor 362 ├── hs 363 │ └── baz.onion # Hidden service baz.onion 364 │ ├── authorized_clients # The clients authorized to access baz.onion 365 │ │ └── dan 366 │ │ └── hsc_desc_enc.arti_pub # arti_path = "hs/baz.onion/authorized_clients/dan/hsc_desc_enc" 367 │ │ # (The public part of HS client Dan's x25519 hsc_desc_enc keypair for 368 │ │ # decrypting baz.onions descriptors) 369 │ │ 370 │ │ 371 │ │ 372 │ ├── hs_id.arti_priv # arti_path = "hs/baz.onion/hs_id" (baz.onion's identity key) 373 │ └── hs_blind_id.arti_priv # arti_path = "hs/baz.onion/hs_blind_id" (baz.onion's blinded identity key) 374 │ 375 ├── relay 376 │ └── Carol # Relay Carol 377 │ └── ... 378 ... 379 ``` 380 381 ### The `KeySpecifier` trait 382 383 ```rust 384 /// The path of a key in the Arti key store, 385 /// relative to the root of the store. 386 /// This path does not contain double-dot (..) elements. 387 /// 388 /// NOTE: There is a 1:1 mapping between a value that implements 389 /// `KeySpecifier` and its corresponding `ArtiPath`. 390 /// A `KeySpecifier` can be converted to an `ArtiPath`, 391 /// but the reverse conversion is not supported. 392 // 393 // TODO hs: restrict the character set and syntax for values of this type 394 // (it should not be possible to construct an ArtiPath out of a String that 395 // uses disallowed chars, or one that is in the wrong format (TBD exactly what 396 // this format is supposed to look like) 397 pub struct ArtiPath(PathBuf); 398 399 /// The path of a key in the C Tor key store. 400 /// 401 /// To construct the path of the key on disk, the `CTorPath` is appended to the 402 /// `hs_service_dir`/`client_dir`/`key_dir` (depending on the role of the 403 /// requested key) followed by the extension. 404 /// 405 /// This path does not contain double-dot (..) elements. 406 pub struct CTorPath(PathBuf); 407 408 /// The "specifier" of a key. 409 /// 410 /// `KeySpecifier::arti_path()` uniquely identifies an instance of a key. 411 pub trait KeySpecifier { 412 /// The location of the key in the Arti key store. 413 /// 414 /// This also acts as a unique identifier for a specific key instance. 415 fn arti_path(&self) -> ArtiPath; 416 417 /// The file extension for a key of this type in an Arti key store. 418 /// 419 /// The Arti key store will ignore any files that don't have a recognized extension. 420 fn arti_extension(&self) -> &'static str; 421 422 /// The location of the key in the C Tor key store (if supported). 423 fn ctor_path(&self) -> Option<CTorPath>; 424 425 /// The file extension for a key of this type in a C Tor key store. 426 /// 427 /// The C Tor key store will ignore any files that don't have a recognized extension. 428 fn ctor_extension(&self) -> &'static str; 429 430 /// The type of user (client, service, relay, etc.) that uses this key. 431 fn user_kind(&self) -> UserKind; 432 } 433 434 /// The type of user (client, service, relay, etc.) that uses a key. 435 #[non_exhaustive] 436 pub enum UserKind { 437 Client, 438 Service(HsId), 439 Relay, 440 DirAuth, 441 ... 442 } 443 444 /// An identifier for an HS client. 445 #[derive(AsRef, Into, ...)] 446 struct HsClientSpecifier(String); 447 448 impl FromStr for HsClientSpecifier { /* check syntax rules */ } 449 450 /// The role of a HS client key. 451 enum HsClientKeyRole { 452 /// A key for deriving keys for decrypting HS descriptors (KP_hsc_desc_enc). 453 DescEnc, 454 /// A key for computing INTRODUCE1 signatures (KP_hsc_intro_auth). 455 IntroAuth, 456 } 457 458 struct HsClientSecretKeySpecifier { 459 /// The client associated with this key. 460 client_spec: HsClientSpecifier, 461 /// The hidden service this authorization key is for. 462 hs_id: HsId, 463 /// The role of the key. 464 role: HsClientKeyRole, 465 } 466 467 impl KeySpecifier for HsClientSecretKeySpecifier { 468 fn arti_path(&self) -> ArtiPath { 469 ArtiPath( 470 Path::new("client") 471 .join(self.client_spec.to_string()) 472 .join(self.hs_id.to_string()) 473 .join(self.role.to_string()), 474 ) 475 } 476 477 fn arti_extension(&self) -> &'static str { 478 // We use nonstandard extensions to prevent keys from being used in 479 // unexpected ways (e.g. if the user renames a key from 480 // KP_hsc_intro_auth.arti_priv to KP_hsc_intro_auth.arti_priv.old, the 481 // arti key store should disregard the backup file). 482 "arti_priv" 483 } 484 485 fn ctor_path(&self) -> Option<CTorPath> { 486 Some(CTorPath( 487 Path::new("client").join(self.client_spec.to_string()), 488 )) 489 } 490 491 fn ctor_extension(&self) -> &'static str { 492 "auth_private" 493 } 494 } 495 496 ``` 497 498 ## Proposed key manager API 499 500 ```rust 501 /// A key that can be stored in, or retrieved from, a `KeyStore.` 502 pub trait EncodableKey { 503 /// The underlying key type. 504 fn key_type() -> KeyType 505 where 506 Self: Sized; 507 } 508 509 // Implement `EncodableKey` for all the key types we wish to support. 510 impl EncodableKey for ed25519::SecretKey { 511 fn key_type() -> KeyType { 512 KeyType::Ed25519Private 513 } 514 } 515 ... 516 517 /// The key manager. 518 #[derive(Default)] 519 struct KeyMgr { 520 /// The underlying persistent stores. 521 key_store: Vec<Box<dyn KeyStore>>, 522 } 523 524 impl KeyMgr { 525 /// Read a key from the key store, attempting to deserialize it as `K`. 526 pub fn get<K: Any + EncodableKey>(&self, key_spec: &dyn KeySpecifier) -> Result<Option<K>> { 527 // Check if the requested key specifier exists in any of the key stores: 528 for store in &self.key_store { 529 let key = store.get(key_spec, K::key_type())?; 530 531 if key.is_some() { 532 // Found it! Now try to downcast it to the right type (the 533 // downcast should _not_ fail, because K::key_type() tells the 534 // store to return a key of type `K` constructed from the key 535 // material read from disk) 536 return key 537 .map(|k| k.downcast::<K>().map(|k| *k).map_err(|e| /* bug */ ...)) 538 .transpose(); 539 } 540 } 541 542 // Not found 543 Ok(None) 544 } 545 546 /// Insert the specified key into the appropriate key store. 547 /// 548 /// If the key bundle (key family?) of this `key` exists in one of the key stores, the key is 549 /// inserted there. Otherwise, the key is inserted into the first key store. 550 /// 551 /// If the key already exists, it is overwritten. 552 /// 553 /// TODO hs: update the API to return a Result<Option<K>> here (i.e. the old key) 554 pub fn insert<K: EncodableKey>( 555 &self, 556 key_spec: &dyn KeySpecifier, 557 key: K, 558 ) -> Result<()> { 559 for store in &self.key_store { 560 if store.has_key_bundle(key_spec) { 561 return store.insert(&key, key_spec, K::key_type()); 562 } 563 } 564 565 // None of the stores has the key bundle of key_spec, so we insert the key into the first key 566 // store. 567 if let Some(store) = self.key_store.first() { 568 return store.insert(&key, key_spec, K::key_type()); 569 } 570 571 // Bug: no key stores were configured 572 Err(...) 573 } 574 575 /// Remove the specified key. 576 /// 577 /// If the key exists in multiple key stores, this will only remove it from the first one. An 578 /// error is returned if none of the key stores contain the specified key. 579 pub fn remove(&self, key_spec: &dyn KeySpecifier) -> Result<()> { 580 for store in &self.key_store { 581 match store.remove(key_spec) { 582 Ok(()) => return Ok(()), 583 Err(e) if e is NotFound => continue, 584 Err(e) => return Err(e), 585 } 586 } 587 588 Err(not found) 589 } 590 } 591 ``` 592 593 ## Proposed key store API 594 595 The key manager reads from (and writes to) the configured key stores. The key 596 stores all implement the `KeyStore` trait: 597 598 ```rust 599 /// A generic key store. 600 pub trait KeyStore { 601 /// Retrieve the key identified by `key_spec`. 602 fn get(&self, key_spec: &dyn KeySpecifier, key_type: KeyType) -> Result<Option<ErasedKey>>; 603 604 /// Write `key` to the key store. 605 fn insert(&self, key: &dyn EncodableKey, key_spec: &dyn KeySpecifier, key_type: KeyType) -> Result<()>; 606 607 /// Remove the specified key. 608 fn remove(&self, key_spec: &dyn KeySpecifier) -> Result<()>; 609 } 610 ``` 611 612 We will initially support 2 key store implementations (one for the C Tor key 613 store, and one for the Arti store). 614 615 616 ### The Arti key store 617 618 ```rust 619 620 impl KeyStore for ArtiNativeKeyStore { 621 fn get(&self, key_spec: &dyn KeySpecifier, key_type: KeyType) -> Result<Option<ErasedKey>> { 622 let key_path = self.key_path(key_spec); 623 624 let input = match fs::read(key_path) { 625 Ok(input) => input, 626 Err(e) if matches!(e.kind(), ErrorKind::NotFound) => return Ok(None), 627 Err(e) => return Err(...), 628 }; 629 630 key_type.read_ssh_format_erased(&input).map(Some) 631 } 632 633 fn insert(&self, key: &dyn EncodableKey, key_spec: &dyn KeySpecifier, key_type: KeyType) -> Result<()> { 634 let key_path = self.key_path(key_spec); 635 636 let ssh_format = key_type.write_ssh_format(key)?; 637 fs::write(key_path, ssh_format).map_err(|_| ())?; 638 639 Ok(()) 640 } 641 642 fn remove(&self, key_spec: &dyn KeySpecifier) -> Result<()> { 643 let key_path = self.key_path(key_spec); 644 645 fs::remove_file(key_path).map_err(|e| ...)?; 646 647 Ok(()) 648 } 649 } 650 651 impl ArtiNativeKeyStore { 652 /// The path on disk of the key with the specified specifier and type. 653 fn key_path(&self, key_spec: &dyn KeySpecifier) -> PathBuf { 654 let mut arti_path = self.keystore_dir.join(key_spec.arti_path().0); 655 arti_path.set_extension(key_spec.arti_extension()); 656 657 arti_path 658 } 659 } 660 661 #[derive(Copy, Clone, ...)] 662 pub enum KeyType { 663 Ed25519Private, 664 Ed25519Public, 665 666 X25519StaticSecret, 667 X25519Public, 668 // ...plus all the other key types we're interested in. 669 } 670 671 impl KeyType { 672 /// Whether the key is public or private. 673 fn is_private(&self) -> bool { 674 match self { 675 // Secret key types 676 KeyType::Ed25519Private | KeyType::X25519StaticSecret => true, 677 // Public key types 678 KeyType::Ed25519Public | KeyType::X25519Public => false, 679 } 680 } 681 } 682 683 pub enum Algorithm { 684 Ed25519, 685 X25519, 686 ... 687 } 688 689 impl Algorithm { 690 fn as_str(&self) -> &'static str { 691 ... 692 } 693 } 694 695 ``` 696 697 The `ArtiNativeKeyStore` uses the `SshKeyType` implementation of `KeyType` 698 to read and write OpenSSH key files: 699 ```rust 700 701 pub trait SshKeyType: Send + Sync + 'static { 702 fn ssh_algorithm(&self) -> Algorithm; 703 704 /// Read an OpenSSH key, parse the key material into a known key type, returning the 705 /// type-erased value. 706 /// 707 /// The caller is expected to downcast the value returned to a concrete type. 708 fn read_ssh_format_erased(&self, input: &[u8]) -> Result<ErasedKey>; 709 710 /// Encode an OpenSSH-formatted key. 711 fn write_ssh_format(&self, key: &dyn EncodableKey) -> Result<Vec<u8>>; 712 } 713 714 impl SshKeyType for KeyType { 715 fn ssh_algorithm(&self) -> Algorithm { 716 ... 717 } 718 719 fn read_ssh_format_erased(&self, input: &[u8]) -> Result<ErasedKey> { 720 match self { 721 KeyType::Ed25519Private => { 722 let sk = ssh_key::PrivateKey::from_bytes(input).map_err(|_| ())?; 723 724 // Build the expected key type (i.e. convert ssh_key key types to the key types 725 // we use internally). 726 let sk = match sk.key_data() { 727 KeypairData::Ed25519(kp) => { 728 ed25519::SecretKey ::from_bytes(&kp.private.to_bytes())? 729 } 730 _ => { 731 // bug 732 return Err(...); 733 } 734 }; 735 736 Ok(Box::new(sk)) 737 } 738 KeyType::Ed25519Public => { 739 let pk = ssh_key::PublicKey::from_bytes(input).map_err(|_| ())?; 740 741 // Build the expected key type (i.e. convert ssh_key key types to the key types 742 // we use internally). 743 let pk = match pk.key_data() { 744 KeyData::Ed25519(pk) => ed25519::PublicKey::from_bytes(&pk.0)?, 745 _ => { 746 // bug 747 return Err(...); 748 } 749 }; 750 751 Ok(Box::new(pk)) 752 } 753 KeyType::X25519StaticSecret | KeyType::X25519Public => { 754 // The ssh-key crate doesn't support arbitrary key types. We'll probably 755 // need a more general-purpose crate for parsing OpenSSH (one that allows 756 // arbitrary values for the algorithm), or to roll our own (we 757 // could also fork ssh-key and modify it as required). 758 todo!() 759 } 760 } 761 } 762 763 fn write_ssh_format(&self, key: &dyn EncodableKey) -> Result<Vec<u8>> { 764 /* Encode `key` in SSH key format. */ 765 } 766 } 767 768 ``` 769 770 #### Versioning 771 772 As Arti evolves, it is likely we will eventually need to make changes to the 773 structure of its key store (for example, to support new key specifiers, or to 774 change something about the existing ones). This means we'll need to be able to 775 distinguish between the different supported key store versions. To achieve this, 776 the root of the Arti key store will have a `.VERSION` file that contains 2 777 version numbers (the format of the `.VERSION` file is TBD): 778 * `version`: the version of the key store 779 * `min_version`: the minimum `ArtiKeyStore` version required to 780 read/manipulate the store 781 782 The `ArtiKeyStore` won't be constructed if the `.VERSION` file of the configured 783 store is malformed, or if `ArtiKeyStore::VERSION` is less than its 784 `min_version`. This should likely be treated as a fatal error (i.e. Arti should 785 refuse to start if the keystore exists but is inaccessible or malformed). 786 787 #### Key passphrases 788 789 OpenSSH keys can have passphrases. While the first version of the key manager 790 won't be able to handle such keys, we will add passphrase support at some point 791 in the future. 792 793 ### The C Tor key store 794 795 TODO 796 797 ```rust 798 799 impl KeyStore for CTorKeyStore { 800 ... 801 } 802 803 impl CTorKeyStore { 804 /// The path on disk of the key with the specified specifier and type. 805 fn key_path(&self, key_spec: &dyn KeySpecifier) -> Option<PathBuf> { 806 let ext = key_spec.ctor_extension(); 807 let root_dir = match key_spec.user_kind() { 808 UserKind::Client => self.client_dir, 809 UserKind::Relay => self.key_dir, 810 UserKind::Service(hs_id) => { 811 // CTorKeyStore contains a mapping from hs_id to 812 // `HiddenServiceDir`. We work out which HiddenServiceDir to 813 // load keys from based on the hs_id of the KeySpecifier. 814 self.hs_service_dirs.get(hs_id)? 815 } 816 ... 817 }; 818 819 let mut ctor_path = root_dir.join(key_spec.ctor_path().0); 820 ctor_path.set_extension(ext); 821 822 Some(ctor_path) 823 } 824 } 825 826 ``` 827 828 ## Concurrent access for disk-based key stores 829 830 The key stores will allow concurrent modification by different processes. In 831 order to implement this safely without locking, the key store operations (get, 832 insert, remove) will need to be atomic. Reading and removing keys atomically is 833 trivial. To create/import a key atomically, we write the new key to a temporary 834 file before using `rename(2)` to atomically replace the existing one (this 835 ensures preexisting keys are replaced atomically). 836 837 Note: on Windows, we can't use `rename` to atomically replace an existing file 838 with a new one (`rename` returns an error if the destination path already 839 exists). As such, on Windows we will need some sort of synchronization mechanism 840 (unless it exposes some other APIs we can use for atomic renaming). 841 842 [#728]: https://gitlab.torproject.org/tpo/core/arti/-/issues/728 843 [#699]: https://gitlab.torproject.org/tpo/core/arti/-/issues/699