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
652
    pub fn new(id: String) -> Self {
44
652
        Self {
45
652
            id: KeystoreId(id),
46
652
            key_dictionary: Default::default(),
47
652
        }
48
652
    }
49
}
50

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

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

            
69
4770
    fn get(
70
4770
        &self,
71
4770
        key_spec: &dyn KeySpecifier,
72
4770
        item_type: &KeystoreItemType,
73
4770
    ) -> StdResult<Option<ErasedKey>, Error> {
74
4770
        let arti_path = key_spec
75
4770
            .arti_path()
76
4770
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
77
4770
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
78
4770
        match key_dictionary.get(&(arti_path.clone(), item_type.clone())) {
79
1326
            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
1324
            Some(key) => {
89
1324
                let key: KeystoreItem = key.clone();
90

            
91
80
                match key {
92
1244
                    KeystoreItem::Key(key) => {
93
1244
                        let key: ErasedKey = key.into_erased()?;
94
1244
                        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
4770
    }
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
4090
    fn insert(&self, key: &dyn EncodableItem, key_spec: &dyn KeySpecifier) -> StdResult<(), Error> {
122
4090
        let arti_path = key_spec
123
4090
            .arti_path()
124
4090
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
125
4090
        let key_data = key.as_keystore_item()?;
126
4090
        let item_type = key_data.item_type()?;
127

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

            
134
768
    fn remove(
135
768
        &self,
136
768
        key_spec: &dyn KeySpecifier,
137
768
        item_type: &KeystoreItemType,
138
768
    ) -> StdResult<Option<()>, Error> {
139
768
        let arti_path = key_spec
140
768
            .arti_path()
141
768
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
142
768
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
143
768
        match key_dictionary.remove(&(arti_path, item_type.clone())) {
144
766
            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
764
            Some(_) => Ok(Some(())),
154
2
            None => Ok(None),
155
        }
156
768
    }
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
11608
    fn list(&self) -> Result<Vec<KeystoreEntryResult<KeystoreEntry>>> {
168
11608
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
169
11608
        Ok(key_dictionary
170
11608
            .keys()
171
49300
            .map(|(arti_path, item_type)| {
172
49004
                let raw_id = RawEntryId::Ephemeral((arti_path.clone(), item_type.clone()));
173
49004
                Ok(KeystoreEntry::new(
174
49004
                    arti_path.clone().into(),
175
49004
                    item_type.clone(),
176
49004
                    self.id(),
177
49004
                    raw_id,
178
49004
                ))
179
49004
            })
180
11608
            .collect())
181
11608
    }
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
    #![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
}