tor_key_forge/
key_type.rs

1//! This module defines the key types that can be written to a [`Keystore`](tor-keymgr::Keystore).
2
3// @Diziet's notes regarding why we shouldn't be storing public keys in the key store:
4//
5// "Let me talk through, a bit, why we have public keys here.
6//
7// ISTM that primarily, a keystore is a store of secrets (ie, things we use to demonstrate to other
8// people).  Ie it corresponds to our identities.  It's not a registry of public data (including
9// instructions about who to trust for what).
10//
11// But we do need to store some ancillary data with some of our identities.  Where this data is
12// small or convenient, we can put it into the keystore.  And when we do that we can use the same
13// storage format as we use for private keys?  (That's not actually true: openssh private keys and
14// openssh public keys are different formats.)
15//
16// Which public keys are you anticipating storing here?
17//
18// I would like to rule out, at this stage, using the Arti keystore to store the public keys of
19// clients for our HS.  That is, the HS client discovery keys for a particular HS should be
20// stored, for that HS, outside the keystore.  (IIRC C Tor does keep the client discovery public keys
21// for an HS in its keystore, so we need to be compatible with that, but that doesn't necessary
22// have to be done in Arti via the keystore API.  Perhaps the "C Tor keystore" object could
23// implement both the keystore trait and an "HS client public keys" trait.)
24//
25// I should explain why I have this opinion:
26// Basically, (private) keystores are awkward.  They have to handle private key material, deal with
27// privsep (possibly including offline hosts); they have to be transparent and manipulable, but
28// also secure.  They might need to be implemented by or associated with HSMs.  All of these things
29// make the keystore's APIs (both the caller API and the visible filesystem interface) compromises
30// with nontrivial downsides.
31//
32// Whereas data about who we should trust is totally different.  It can live in normal
33// configuration land; it doesn't need to be associated with HSMs.  It doesn't want or need (the
34// possibility of) privsep.  And the user might want to override/supplement it in totally different
35// ways.  For example, it should be possible for an HS server to look up client discovery keys
36// in a database.  But we don't need or want to write a generic "look up stuff in a database" API;
37// that can be (at least for now) a bespoke API just for HS restricted discovery."
38
39use ssh_key::private::KeypairData;
40use ssh_key::public::KeyData;
41use ssh_key::Algorithm;
42use tor_error::{bad_api_usage, internal, Bug};
43
44use crate::ssh::{ED25519_EXPANDED_ALGORITHM_NAME, X25519_ALGORITHM_NAME};
45use crate::Result;
46
47use std::fmt;
48use std::result::Result as StdResult;
49
50/// Declare and implement the `KeyType` enum.
51///
52/// Each of the `variant`s is mapped to the specified `str_repr`.
53///
54/// `str_repr` is returned from [`KeyType::arti_extension`].
55///
56/// The `str_repr` is also used for implementing `From<&str>` for `KeyType`.
57/// Note `KeyType` implements `From<&str>` rather than `FromStr`,
58/// because the conversion from string is infallible
59/// (unrecognized strings are mapped to `KeyType::Unknown`)
60macro_rules! declare_item_type {
61    {
62        $(#[$enum_meta:meta])*
63        $vis:vis enum KeyType {
64            $(
65                $(#[$meta:meta])*
66                $variant:ident => $str_repr:expr,
67            )*
68        }
69
70        $(#[$cert_enum_meta:meta])*
71        $cert_vis:vis enum CertType {
72            $(
73                $(#[$cert_meta:meta])*
74                $cert_variant:ident => $cert_str_repr:expr,
75            )*
76        }
77    } => {
78
79        $(#[$enum_meta])*
80        $vis enum KeyType {
81            $(
82                $(#[$meta])*
83                $variant,
84            )*
85        }
86
87        $(#[$cert_enum_meta])*
88        $cert_vis enum CertType {
89            $(
90                $(#[$cert_meta])*
91                $cert_variant,
92            )*
93        }
94
95        /// A type of item stored in a keystore.
96        #[derive(Clone, PartialEq, Eq, Hash)]
97        #[non_exhaustive]
98        pub enum KeystoreItemType {
99            /// A key
100            Key(KeyType),
101            /// A key certificate
102            Cert(CertType),
103            /// An unrecognized entry type
104            Unknown {
105                /// The extension used for entries of this type in an Arti keystore.
106                arti_extension: String,
107            },
108        }
109
110        impl KeyType {
111            /// The file extension for a key of this type.
112            pub fn arti_extension(&self) -> String {
113                use KeyType::*;
114
115                match self {
116                    $(
117                        $variant => $str_repr.into(),
118                    )*
119                }
120            }
121        }
122
123        impl KeystoreItemType {
124            /// The file extension for an item of this type.
125            pub fn arti_extension(&self) -> String {
126                use KeyType::*;
127                use CertType::*;
128
129                match self {
130                    $(
131                        Self::Key($variant) => $str_repr.into(),
132                    )*
133                    $(
134                        Self::Cert($cert_variant) => $cert_str_repr.into(),
135                    )*
136                        Self::Unknown { arti_extension } => arti_extension.into(),
137                }
138            }
139
140            /// Try to get the inner [`KeyType`], if this is a [`KeystoreItemType::Key`].
141            ///
142            /// Returns an error if this is not a key type.
143            pub fn key_type(&self) -> StdResult<&KeyType, Bug> {
144                match self {
145                    KeystoreItemType::Key(key_type) => Ok(key_type),
146                    _ => Err(bad_api_usage!("{:?} is not a key type", self)),
147                }
148            }
149        }
150
151        impl From<&str> for KeystoreItemType {
152            fn from(key_type: &str) -> Self {
153                use KeyType::*;
154                use CertType::*;
155
156                match key_type {
157                    $(
158                        $str_repr => Self::Key($variant),
159                    )*
160                    $(
161                        $cert_str_repr => Self::Cert($cert_variant),
162                    )*
163                    _ => Self::Unknown {
164                        arti_extension: key_type.into(),
165                    },
166                }
167            }
168        }
169
170        impl fmt::Debug for KeystoreItemType {
171            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172                match self {
173                    KeystoreItemType::Key(key_type) => write!(f, "{:?}", key_type),
174                    KeystoreItemType::Cert(cert_type) => write!(f, "{:?}", cert_type),
175                    KeystoreItemType::Unknown { arti_extension } => {
176                        write!(f, "unknown item type (extension={arti_extension})")
177                    }
178                }
179            }
180        }
181
182        impl From<KeyType> for KeystoreItemType {
183            fn from(key: KeyType) -> Self {
184                Self::Key(key)
185            }
186        }
187
188        impl From<CertType> for KeystoreItemType {
189            fn from(key: CertType) -> Self {
190                Self::Cert(key)
191            }
192        }
193
194        #[cfg(test)]
195        mod tests {
196            // @@ begin test lint list maintained by maint/add_warning @@
197            #![allow(clippy::bool_assert_comparison)]
198            #![allow(clippy::clone_on_copy)]
199            #![allow(clippy::dbg_macro)]
200            #![allow(clippy::mixed_attributes_style)]
201            #![allow(clippy::print_stderr)]
202            #![allow(clippy::print_stdout)]
203            #![allow(clippy::single_char_pattern)]
204            #![allow(clippy::unwrap_used)]
205            #![allow(clippy::unchecked_duration_subtraction)]
206            #![allow(clippy::useless_vec)]
207            #![allow(clippy::needless_pass_by_value)]
208            //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
209            use super::*;
210
211            #[test]
212            fn unknown_item_types() {
213                const UNKNOWN_KEY_TYPE: &str = "rsa";
214
215                let unknown_key_ty = KeystoreItemType::from(UNKNOWN_KEY_TYPE);
216                assert_eq!(
217                    unknown_key_ty,
218                    KeystoreItemType::Unknown {
219                        arti_extension: UNKNOWN_KEY_TYPE.into()
220                    }
221                );
222                assert_eq!(unknown_key_ty.arti_extension(), UNKNOWN_KEY_TYPE);
223            }
224
225            #[test]
226            fn recognized_item_types() {
227                $(
228                    let key_ty = KeystoreItemType::from($str_repr);
229                    assert_eq!(
230                        key_ty,
231                        KeystoreItemType::Key(KeyType::$variant)
232                    );
233                    assert_eq!(key_ty.arti_extension(), $str_repr);
234                )*
235                $(
236                    let cert_ty = KeystoreItemType::from($cert_str_repr);
237                    assert_eq!(
238                        cert_ty,
239                        KeystoreItemType::Cert(CertType::$cert_variant)
240                    );
241                    assert_eq!(cert_ty.arti_extension(), $cert_str_repr);
242                )*
243            }
244        }
245    }
246}
247
248impl KeyType {
249    /// Return the `KeyType` of the specified [`KeyData`].
250    ///
251    /// Returns an error if the [`KeyData`] is of an unsupported type.
252    pub(crate) fn try_from_key_data(key: &KeyData) -> Result<KeyType> {
253        match key.algorithm() {
254            Algorithm::Ed25519 => Ok(KeyType::Ed25519PublicKey),
255            Algorithm::Other(algo) if algo.as_str() == X25519_ALGORITHM_NAME => {
256                Ok(KeyType::X25519PublicKey)
257            }
258            _ => Err(internal!("invalid key data").into()),
259        }
260    }
261
262    /// Return the `KeyType` of the specified [`KeypairData`].
263    ///
264    /// Returns an error if the [`KeypairData`] is of an unsupported type.
265    pub(crate) fn try_from_keypair_data(key: &KeypairData) -> Result<KeyType> {
266        let algo = key.algorithm().map_err(|e| internal!("invalid algr {e}"))?;
267        match algo {
268            Algorithm::Ed25519 => Ok(KeyType::Ed25519Keypair),
269            Algorithm::Other(algo) if algo.as_str() == X25519_ALGORITHM_NAME => {
270                Ok(KeyType::X25519StaticKeypair)
271            }
272            Algorithm::Other(algo) if algo.as_str() == ED25519_EXPANDED_ALGORITHM_NAME => {
273                Ok(KeyType::Ed25519ExpandedKeypair)
274            }
275            _ => Err(internal!("invalid keypair data").into()),
276        }
277    }
278}
279
280declare_item_type! {
281    /// A type of key stored in the key store.
282    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
283    #[non_exhaustive]
284    pub enum KeyType {
285        /// An Ed25519 keypair.
286        Ed25519Keypair => "ed25519_private",
287        /// An Ed25519 public key.
288        Ed25519PublicKey => "ed25519_public",
289        /// A Curve25519 keypair.
290        X25519StaticKeypair => "x25519_private",
291        /// A Curve25519 public key.
292        X25519PublicKey => "x25519_public",
293        /// An expanded Ed25519 keypair.
294        Ed25519ExpandedKeypair => "ed25519_expanded_private",
295    }
296
297    /// A type of certificate stored in the keystore.
298    ///
299    /// The purpose and meaning of a certificate, as well as the algorithms
300    /// of the subject and signing keys, are specified by its `CertType`
301    ///
302    /// More specifically, the `CertType` of a certificate determines
303    ///  * The cryptographic algorithms of the subject key and the signing key
304    ///  * How the subject key value and its properties are encoded before
305    ///    the signing key key makes its signature
306    ///  * How the signature and the other information is encoded for storage.
307    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
308    #[non_exhaustive]
309    pub enum CertType {
310        /// A Tor Ed25519 certificate.
311        ///
312        /// See <https://spec.torproject.org/cert-spec.html#ed-certs>
313        Ed25519TorCert => "tor_ed25519_cert",
314    }
315}