/ doc / dev / notes / key-management.md
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)
374375  ├── 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