tor_keymgr/keystore/
ephemeral.rs

1//! ArtiEphemeralKeystore implementation (in-memory ephemeral key storage)
2
3pub(crate) mod err;
4
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8use tor_error::internal;
9use tor_key_forge::{EncodableItem, ErasedKey, KeystoreItem, KeystoreItemType};
10
11use crate::keystore::ephemeral::err::ArtiEphemeralKeystoreError;
12use crate::Error;
13use crate::{ArtiPath, KeyPath, KeySpecifier, Keystore, KeystoreId};
14
15/// The identifier of a key stored in the `ArtiEphemeralKeystore`.
16type KeyIdent = (ArtiPath, KeystoreItemType);
17
18/// The Ephemeral Arti key store
19///
20/// This is a purely in-memory key store. Keys written to this store
21/// are never written to disk, and are stored in-memory as [`KeystoreItem`]s.
22/// Keys saved in this Keystore do not persist between restarts!
23///
24/// While Arti never writes the keys for this key store to disk, the operating
25/// system may do so for reasons outside of this library's control. Some
26/// examples are swapping RAM to disk, generating core dumps, invoking
27/// suspend-to-disk power management, etc. This key store does not attempt to
28/// prevent this operating system behaviour.
29pub struct ArtiEphemeralKeystore {
30    /// Identifier hard-coded to 'ephemeral'
31    id: KeystoreId,
32    /// Keys stored as [`KeystoreItem`].
33    key_dictionary: Arc<Mutex<HashMap<KeyIdent, KeystoreItem>>>,
34}
35
36impl ArtiEphemeralKeystore {
37    /// Create a new [`ArtiEphemeralKeystore`]
38    pub fn new(id: String) -> Self {
39        Self {
40            id: KeystoreId(id),
41            key_dictionary: Default::default(),
42        }
43    }
44}
45
46impl Keystore for ArtiEphemeralKeystore {
47    fn id(&self) -> &KeystoreId {
48        &self.id
49    }
50
51    fn contains(
52        &self,
53        key_spec: &dyn KeySpecifier,
54        item_type: &KeystoreItemType,
55    ) -> Result<bool, Error> {
56        let arti_path = key_spec
57            .arti_path()
58            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
59        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
60        let contains_key = key_dictionary.contains_key(&(arti_path, item_type.clone()));
61        Ok(contains_key)
62    }
63
64    fn get(
65        &self,
66        key_spec: &dyn KeySpecifier,
67        item_type: &KeystoreItemType,
68    ) -> Result<Option<ErasedKey>, Error> {
69        let arti_path = key_spec
70            .arti_path()
71            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
72        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
73        match key_dictionary.get(&(arti_path.clone(), item_type.clone())) {
74            Some(key) if key.item_type()? != *item_type => {
75                // This should only happen if some external factor alters the
76                // process memory or if there's a bug in our implementation of
77                // Keystore::insert().
78                Err(internal!(
79                    "the specified KeystoreItemType does not match key type of the fetched key?!"
80                )
81                .into())
82            }
83            Some(key) => {
84                let key: KeystoreItem = key.clone();
85                let key: ErasedKey = key.into_erased()?;
86                Ok(Some(key))
87            }
88            None => Ok(None),
89        }
90    }
91
92    fn insert(&self, key: &dyn EncodableItem, key_spec: &dyn KeySpecifier) -> Result<(), Error> {
93        let arti_path = key_spec
94            .arti_path()
95            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
96        let key_data = key.as_keystore_item()?;
97        let item_type = key_data.item_type()?;
98
99        // save to dictionary
100        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
101        let _ = key_dictionary.insert((arti_path, item_type), key_data);
102        Ok(())
103    }
104
105    fn remove(
106        &self,
107        key_spec: &dyn KeySpecifier,
108        item_type: &KeystoreItemType,
109    ) -> Result<Option<()>, Error> {
110        let arti_path = key_spec
111            .arti_path()
112            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
113        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
114        match key_dictionary.remove(&(arti_path, item_type.clone())) {
115            Some(key) if key.item_type()? != *item_type => {
116                // This should only happen if some external factor alters the
117                // process memory or if there's a bug in our implementation of
118                // Keystore::insert().
119                Err(internal!(
120                    "the specified KeystoreItemType does not match key type of the removed key?!"
121                )
122                .into())
123            }
124            Some(_) => Ok(Some(())),
125            None => Ok(None),
126        }
127    }
128
129    fn list(&self) -> Result<Vec<(KeyPath, KeystoreItemType)>, Error> {
130        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
131        Ok(key_dictionary
132            .keys()
133            .map(|(arti_path, item_type)| (arti_path.clone().into(), item_type.clone()))
134            .collect())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    // @@ begin test lint list maintained by maint/add_warning @@
141    #![allow(clippy::bool_assert_comparison)]
142    #![allow(clippy::clone_on_copy)]
143    #![allow(clippy::dbg_macro)]
144    #![allow(clippy::mixed_attributes_style)]
145    #![allow(clippy::print_stderr)]
146    #![allow(clippy::print_stdout)]
147    #![allow(clippy::single_char_pattern)]
148    #![allow(clippy::unwrap_used)]
149    #![allow(clippy::unchecked_duration_subtraction)]
150    #![allow(clippy::useless_vec)]
151    #![allow(clippy::needless_pass_by_value)]
152    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
153
154    use tor_basic_utils::test_rng::{testing_rng, TestingRng};
155    use tor_error::{ErrorKind, HasKind};
156    use tor_key_forge::{KeyType, Keygen};
157    use tor_llcrypto::pk::{curve25519, ed25519};
158    use tor_llcrypto::rng::FakeEntropicRng;
159
160    use super::*;
161
162    use crate::test_utils::TestSpecifier;
163
164    // some helper methods
165
166    fn key() -> Box<dyn EncodableItem> {
167        let mut rng = testing_rng();
168        let keypair = ed25519::Keypair::generate(&mut rng);
169        Box::new(keypair)
170    }
171
172    fn key_type() -> KeystoreItemType {
173        KeyType::Ed25519Keypair.into()
174    }
175
176    fn key_bad() -> Box<dyn EncodableItem> {
177        let mut rng = FakeEntropicRng::<TestingRng>(testing_rng());
178        let keypair = curve25519::StaticKeypair::generate(&mut rng).unwrap();
179        Box::new(keypair)
180    }
181
182    fn key_type_bad() -> KeystoreItemType {
183        KeyType::X25519StaticKeypair.into()
184    }
185
186    fn key_spec() -> Box<dyn KeySpecifier> {
187        Box::<TestSpecifier>::default()
188    }
189
190    // tests!
191
192    #[test]
193    fn id() {
194        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
195
196        assert_eq!(&KeystoreId("test-ephemeral".to_string()), key_store.id());
197    }
198
199    #[test]
200    fn contains() {
201        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
202
203        // verify no key in store
204        assert!(!key_store
205            .contains(key_spec().as_ref(), &key_type())
206            .unwrap());
207
208        // insert key and verify in store
209        assert!(key_store
210            .insert(key().as_ref(), key_spec().as_ref())
211            .is_ok());
212        assert!(key_store
213            .contains(key_spec().as_ref(), &key_type())
214            .unwrap());
215    }
216
217    #[test]
218    fn get() {
219        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
220
221        // verify no result to get
222        assert!(key_store
223            .get(key_spec().as_ref(), &key_type())
224            .unwrap()
225            .is_none());
226
227        // insert and verify get is a result
228        assert!(key_store
229            .insert(key().as_ref(), key_spec().as_ref())
230            .is_ok());
231
232        let key = key_store
233            .get(key_spec().as_ref(), &key_type())
234            .unwrap()
235            .unwrap();
236
237        // Ensure the returned key is of the right type
238        assert!(key.downcast::<ed25519::Keypair>().is_ok());
239
240        // verify receiving a key of a different type results in the appropriate error
241        key_store.remove(key_spec().as_ref(), &key_type()).unwrap();
242        {
243            let mut key_dictionary = key_store.key_dictionary.lock().unwrap();
244            let _ = key_dictionary.insert(
245                (key_spec().arti_path().unwrap(), key_type()),
246                key_bad().as_keystore_item().unwrap(),
247            );
248        }
249        assert!(matches!(
250            key_store
251                .get(key_spec().as_ref(), &key_type())
252                .err()
253                .unwrap()
254                .kind(),
255            ErrorKind::Internal
256        ));
257    }
258
259    #[test]
260    fn insert() {
261        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
262
263        assert!(!key_store
264            .contains(key_spec().as_ref(), &key_type_bad())
265            .unwrap());
266        assert!(key_store
267            .get(key_spec().as_ref(), &key_type_bad())
268            .unwrap()
269            .is_none());
270        assert!(key_store.list().unwrap().is_empty());
271
272        // verify inserting a key succeeds
273        assert!(key_store
274            .insert(key().as_ref(), key_spec().as_ref())
275            .is_ok());
276
277        // further ensure correct side effects
278        assert!(key_store
279            .contains(key_spec().as_ref(), &key_type())
280            .unwrap());
281        assert!(key_store
282            .get(key_spec().as_ref(), &key_type())
283            .unwrap()
284            .is_some());
285        assert_eq!(key_store.list().unwrap().len(), 1);
286    }
287
288    #[test]
289    fn remove() {
290        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
291
292        // verify removing from an empty store returns None
293        assert!(key_store
294            .remove(key_spec().as_ref(), &key_type())
295            .unwrap()
296            .is_none());
297
298        // verify inserting and removing results in Some(())
299        assert!(key_store
300            .insert(key().as_ref(), key_spec().as_ref())
301            .is_ok());
302        assert!(key_store
303            .remove(key_spec().as_ref(), &key_type())
304            .unwrap()
305            .is_some());
306
307        // verify mismatched key type on removal results in the appropriate error
308        {
309            let mut key_dictionary = key_store.key_dictionary.lock().unwrap();
310            let _ = key_dictionary.insert(
311                (key_spec().arti_path().unwrap(), key_type()),
312                key_bad().as_keystore_item().unwrap(),
313            );
314        }
315        assert!(matches!(
316            key_store
317                .remove(key_spec().as_ref(), &key_type())
318                .err()
319                .unwrap()
320                .kind(),
321            ErrorKind::Internal
322        ));
323    }
324
325    #[test]
326    fn list() {
327        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
328
329        // verify empty by default
330        assert!(key_store.list().unwrap().is_empty());
331
332        // verify size 1 after inserting a key
333        assert!(key_store
334            .insert(key().as_ref(), key_spec().as_ref())
335            .is_ok());
336        assert_eq!(key_store.list().unwrap().len(), 1);
337    }
338}