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