1
//! ArtiEphemeralKeystore implementation (in-memory ephemeral key storage)
2

            
3
pub(crate) mod err;
4

            
5
use std::collections::HashMap;
6
use std::result::Result as StdResult;
7
use std::sync::{Arc, Mutex};
8

            
9
use tor_error::internal;
10
use tor_key_forge::{EncodableItem, ErasedKey, KeystoreItem, KeystoreItemType};
11

            
12
use crate::keystore::ephemeral::err::ArtiEphemeralKeystoreError;
13
use crate::raw::RawEntryId;
14
use crate::{ArtiPath, Error, KeySpecifier, Keystore, KeystoreEntry, KeystoreId, Result};
15

            
16
use super::KeystoreEntryResult;
17

            
18
/// The identifier of a key stored in the `ArtiEphemeralKeystore`.
19
type 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.
32
pub 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

            
39
impl ArtiEphemeralKeystore {
40
    /// Create a new [`ArtiEphemeralKeystore`]
41
12
    pub fn new(id: String) -> Self {
42
12
        Self {
43
12
            id: KeystoreId(id),
44
12
            key_dictionary: Default::default(),
45
12
        }
46
12
    }
47
}
48

            
49
impl Keystore for ArtiEphemeralKeystore {
50
6
    fn id(&self) -> &KeystoreId {
51
6
        &self.id
52
6
    }
53

            
54
8
    fn contains(
55
8
        &self,
56
8
        key_spec: &dyn KeySpecifier,
57
8
        item_type: &KeystoreItemType,
58
8
    ) -> StdResult<bool, Error> {
59
8
        let arti_path = key_spec
60
8
            .arti_path()
61
8
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
62
8
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
63
8
        let contains_key = key_dictionary.contains_key(&(arti_path, item_type.clone()));
64
8
        Ok(contains_key)
65
8
    }
66

            
67
10
    fn get(
68
10
        &self,
69
10
        key_spec: &dyn KeySpecifier,
70
10
        item_type: &KeystoreItemType,
71
10
    ) -> StdResult<Option<ErasedKey>, Error> {
72
10
        let arti_path = key_spec
73
10
            .arti_path()
74
10
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
75
10
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
76
10
        match key_dictionary.get(&(arti_path.clone(), item_type.clone())) {
77
6
            Some(key) if key.item_type()? != *item_type => {
78
2
                // This should only happen if some external factor alters the
79
2
                // process memory or if there's a bug in our implementation of
80
2
                // Keystore::insert().
81
2
                Err(internal!(
82
2
                    "the specified KeystoreItemType does not match key type of the fetched key?!"
83
2
                )
84
2
                .into())
85
            }
86
4
            Some(key) => {
87
4
                let key: KeystoreItem = key.clone();
88
4
                let key: ErasedKey = key.into_erased()?;
89
4
                Ok(Some(key))
90
            }
91
4
            None => Ok(None),
92
        }
93
10
    }
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
10
    fn insert(&self, key: &dyn EncodableItem, key_spec: &dyn KeySpecifier) -> StdResult<(), Error> {
104
10
        let arti_path = key_spec
105
10
            .arti_path()
106
10
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
107
10
        let key_data = key.as_keystore_item()?;
108
10
        let item_type = key_data.item_type()?;
109

            
110
        // save to dictionary
111
10
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
112
10
        let _ = key_dictionary.insert((arti_path, item_type), key_data);
113
10
        Ok(())
114
10
    }
115

            
116
8
    fn remove(
117
8
        &self,
118
8
        key_spec: &dyn KeySpecifier,
119
8
        item_type: &KeystoreItemType,
120
8
    ) -> StdResult<Option<()>, Error> {
121
8
        let arti_path = key_spec
122
8
            .arti_path()
123
8
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
124
8
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
125
8
        match key_dictionary.remove(&(arti_path, item_type.clone())) {
126
6
            Some(key) if key.item_type()? != *item_type => {
127
2
                // This should only happen if some external factor alters the
128
2
                // process memory or if there's a bug in our implementation of
129
2
                // Keystore::insert().
130
2
                Err(internal!(
131
2
                    "the specified KeystoreItemType does not match key type of the removed key?!"
132
2
                )
133
2
                .into())
134
            }
135
4
            Some(_) => Ok(Some(())),
136
2
            None => Ok(None),
137
        }
138
8
    }
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
8
    fn list(&self) -> Result<Vec<KeystoreEntryResult<KeystoreEntry>>> {
150
8
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
151
8
        Ok(key_dictionary
152
8
            .keys()
153
10
            .map(|(arti_path, item_type)| {
154
4
                let raw_id = RawEntryId::Ephemeral((arti_path.clone(), item_type.clone()));
155
4
                Ok(KeystoreEntry::new(
156
4
                    arti_path.clone().into(),
157
4
                    item_type.clone(),
158
4
                    self.id(),
159
4
                    raw_id,
160
4
                ))
161
10
            })
162
8
            .collect())
163
8
    }
164
}
165

            
166
#[cfg(test)]
167
mod 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
}