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    #![allow(clippy::string_slice)] // See arti#2571
199    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
200
201    use tor_basic_utils::test_rng::{TestingRng, testing_rng};
202    use tor_error::{ErrorKind, HasKind};
203    use tor_key_forge::{KeyType, Keygen};
204    use tor_llcrypto::pk::{curve25519, ed25519};
205    use tor_llcrypto::rng::FakeEntropicRng;
206
207    use super::*;
208
209    use crate::test_utils::TestSpecifier;
210
211    // some helper methods
212
213    fn key() -> Box<dyn EncodableItem> {
214        let mut rng = testing_rng();
215        let keypair = ed25519::Keypair::generate(&mut rng);
216        Box::new(keypair)
217    }
218
219    fn key_type() -> KeystoreItemType {
220        KeyType::Ed25519Keypair.into()
221    }
222
223    fn key_bad() -> Box<dyn EncodableItem> {
224        let mut rng = FakeEntropicRng::<TestingRng>(testing_rng());
225        let keypair = curve25519::StaticKeypair::generate(&mut rng).unwrap();
226        Box::new(keypair)
227    }
228
229    fn key_type_bad() -> KeystoreItemType {
230        KeyType::X25519StaticKeypair.into()
231    }
232
233    fn key_spec() -> Box<dyn KeySpecifier> {
234        Box::<TestSpecifier>::default()
235    }
236
237    // tests!
238
239    #[test]
240    fn id() {
241        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
242
243        assert_eq!(&KeystoreId("test-ephemeral".to_string()), key_store.id());
244    }
245
246    #[test]
247    fn contains() {
248        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
249
250        // verify no key in store
251        assert!(
252            !key_store
253                .contains(key_spec().as_ref(), &key_type())
254                .unwrap()
255        );
256
257        // insert key and verify in store
258        assert!(
259            key_store
260                .insert(key().as_ref(), key_spec().as_ref())
261                .is_ok()
262        );
263        assert!(
264            key_store
265                .contains(key_spec().as_ref(), &key_type())
266                .unwrap()
267        );
268    }
269
270    #[test]
271    fn get() {
272        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
273
274        // verify no result to get
275        assert!(
276            key_store
277                .get(key_spec().as_ref(), &key_type())
278                .unwrap()
279                .is_none()
280        );
281
282        // insert and verify get is a result
283        assert!(
284            key_store
285                .insert(key().as_ref(), key_spec().as_ref())
286                .is_ok()
287        );
288
289        let key = key_store
290            .get(key_spec().as_ref(), &key_type())
291            .unwrap()
292            .unwrap();
293
294        // Ensure the returned key is of the right type
295        assert!(key.downcast::<ed25519::Keypair>().is_ok());
296
297        // verify receiving a key of a different type results in the appropriate error
298        key_store.remove(key_spec().as_ref(), &key_type()).unwrap();
299        {
300            let mut key_dictionary = key_store.key_dictionary.lock().unwrap();
301            let _ = key_dictionary.insert(
302                (key_spec().arti_path().unwrap(), key_type()),
303                key_bad().as_keystore_item().unwrap(),
304            );
305        }
306        assert!(matches!(
307            key_store
308                .get(key_spec().as_ref(), &key_type())
309                .err()
310                .unwrap()
311                .kind(),
312            ErrorKind::Internal
313        ));
314    }
315
316    #[test]
317    fn insert() {
318        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
319
320        assert!(
321            !key_store
322                .contains(key_spec().as_ref(), &key_type_bad())
323                .unwrap()
324        );
325        assert!(
326            key_store
327                .get(key_spec().as_ref(), &key_type_bad())
328                .unwrap()
329                .is_none()
330        );
331        assert!(key_store.list().unwrap().is_empty());
332
333        // verify inserting a key succeeds
334        assert!(
335            key_store
336                .insert(key().as_ref(), key_spec().as_ref())
337                .is_ok()
338        );
339
340        // further ensure correct side effects
341        assert!(
342            key_store
343                .contains(key_spec().as_ref(), &key_type())
344                .unwrap()
345        );
346        assert!(
347            key_store
348                .get(key_spec().as_ref(), &key_type())
349                .unwrap()
350                .is_some()
351        );
352        assert_eq!(key_store.list().unwrap().len(), 1);
353    }
354
355    #[test]
356    fn remove() {
357        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
358
359        // verify removing from an empty store returns None
360        assert!(
361            key_store
362                .remove(key_spec().as_ref(), &key_type())
363                .unwrap()
364                .is_none()
365        );
366
367        // verify inserting and removing results in Some(())
368        assert!(
369            key_store
370                .insert(key().as_ref(), key_spec().as_ref())
371                .is_ok()
372        );
373        assert!(
374            key_store
375                .remove(key_spec().as_ref(), &key_type())
376                .unwrap()
377                .is_some()
378        );
379
380        // verify mismatched key type on removal results in the appropriate error
381        {
382            let mut key_dictionary = key_store.key_dictionary.lock().unwrap();
383            let _ = key_dictionary.insert(
384                (key_spec().arti_path().unwrap(), key_type()),
385                key_bad().as_keystore_item().unwrap(),
386            );
387        }
388        assert!(matches!(
389            key_store
390                .remove(key_spec().as_ref(), &key_type())
391                .err()
392                .unwrap()
393                .kind(),
394            ErrorKind::Internal
395        ));
396    }
397
398    #[test]
399    fn list() {
400        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
401
402        // verify empty by default
403        assert!(key_store.list().unwrap().is_empty());
404
405        // verify size 1 after inserting a key
406        assert!(
407            key_store
408                .insert(key().as_ref(), key_spec().as_ref())
409                .is_ok()
410        );
411        assert_eq!(key_store.list().unwrap().len(), 1);
412    }
413}