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}