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