Skip to main content

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