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, into_internal};
10
use tor_key_forge::{
11
    CertData, EncodableItem, ErasedKey, KeystoreItem, KeystoreItemType, ParsedEd25519Cert,
12
};
13

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

            
18
use super::KeystoreEntryResult;
19

            
20
/// The identifier of a key stored in the `ArtiEphemeralKeystore`.
21
type 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.
34
pub 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

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

            
51
impl Keystore for ArtiEphemeralKeystore {
52
48886
    fn id(&self) -> &KeystoreId {
53
48886
        &self.id
54
48886
    }
55

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

            
69
4650
    fn get(
70
4650
        &self,
71
4650
        key_spec: &dyn KeySpecifier,
72
4650
        item_type: &KeystoreItemType,
73
4650
    ) -> StdResult<Option<ErasedKey>, Error> {
74
4650
        let arti_path = key_spec
75
4650
            .arti_path()
76
4650
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
77
4650
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
78
4650
        match key_dictionary.get(&(arti_path.clone(), item_type.clone())) {
79
1206
            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
2
                Err(internal!(
84
2
                    "the specified KeystoreItemType does not match key type of the fetched key?!"
85
2
                )
86
2
                .into())
87
            }
88
1204
            Some(key) => {
89
1204
                let key: KeystoreItem = key.clone();
90

            
91
80
                match key {
92
1124
                    KeystoreItem::Key(key) => {
93
1124
                        let key: ErasedKey = key.into_erased()?;
94
1124
                        Ok(Some(key))
95
                    }
96
80
                    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
80
                        let cert = ParsedEd25519Cert::decode(c.to_vec())
102
80
                            .map_err(into_internal!("found invalid cert in ephemeral store?!"))?;
103

            
104
80
                        Ok(Some(Box::new(cert)))
105
                    }
106
                    _ => Err(internal!("unknown item type {key:?} in the keystore").into()),
107
                }
108
            }
109
3444
            None => Ok(None),
110
        }
111
4650
    }
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
3690
    fn insert(&self, key: &dyn EncodableItem, key_spec: &dyn KeySpecifier) -> StdResult<(), Error> {
122
3690
        let arti_path = key_spec
123
3690
            .arti_path()
124
3690
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
125
3690
        let key_data = key.as_keystore_item()?;
126
3690
        let item_type = key_data.item_type()?;
127

            
128
        // save to dictionary
129
3690
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
130
3690
        let _ = key_dictionary.insert((arti_path, item_type), key_data);
131
3690
        Ok(())
132
3690
    }
133

            
134
728
    fn remove(
135
728
        &self,
136
728
        key_spec: &dyn KeySpecifier,
137
728
        item_type: &KeystoreItemType,
138
728
    ) -> StdResult<Option<()>, Error> {
139
728
        let arti_path = key_spec
140
728
            .arti_path()
141
728
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
142
728
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
143
728
        match key_dictionary.remove(&(arti_path, item_type.clone())) {
144
726
            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
2
                Err(internal!(
149
2
                    "the specified KeystoreItemType does not match key type of the removed key?!"
150
2
                )
151
2
                .into())
152
            }
153
724
            Some(_) => Ok(Some(())),
154
2
            None => Ok(None),
155
        }
156
728
    }
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
10408
    fn list(&self) -> Result<Vec<KeystoreEntryResult<KeystoreEntry>>> {
168
10408
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
169
10408
        Ok(key_dictionary
170
10408
            .keys()
171
47710
            .map(|(arti_path, item_type)| {
172
47444
                let raw_id = RawEntryId::Ephemeral((arti_path.clone(), item_type.clone()));
173
47444
                Ok(KeystoreEntry::new(
174
47444
                    arti_path.clone().into(),
175
47444
                    item_type.clone(),
176
47444
                    self.id(),
177
47444
                    raw_id,
178
47444
                ))
179
47444
            })
180
10408
            .collect())
181
10408
    }
182
}
183

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