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

            
3
pub(crate) mod err;
4

            
5
use std::collections::HashMap;
6
use std::sync::{Arc, Mutex};
7

            
8
use tor_error::internal;
9
use tor_key_forge::{EncodableKey, ErasedKey, KeyType, SshKeyData};
10

            
11
use crate::keystore::ephemeral::err::ArtiEphemeralKeystoreError;
12
use crate::Error;
13
use crate::{ArtiPath, KeyPath, KeySpecifier, Keystore, KeystoreId};
14

            
15
/// The identifier of a key stored in the `ArtiEphemeralKeystore`.
16
type KeyIdent = (ArtiPath, KeyType);
17

            
18
/// The Ephemeral Arti key store
19
///
20
/// This is a purely in-memory key store. Keys written to this store
21
/// are never written to disk, and are stored in-memory as [`SshKeyData`].
22
/// Keys saved in this Keystore do not persist between restarts!
23
///
24
/// While Arti never writes the keys for this key store to disk, the operating
25
/// system may do so for reasons outside of this library's control. Some
26
/// examples are swapping RAM to disk, generating core dumps, invoking
27
/// suspend-to-disk power management, etc. This key store does not attempt to
28
/// prevent this operating system behvaiour.
29
pub struct ArtiEphemeralKeystore {
30
    /// Identifier hard-coded to 'ephemeral'
31
    id: KeystoreId,
32
    /// Keys stored as [`SshKeyData`].
33
    key_dictionary: Arc<Mutex<HashMap<KeyIdent, SshKeyData>>>,
34
}
35

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

            
46
impl Keystore for ArtiEphemeralKeystore {
47
2
    fn id(&self) -> &KeystoreId {
48
2
        &self.id
49
2
    }
50

            
51
8
    fn contains(&self, key_spec: &dyn KeySpecifier, key_type: &KeyType) -> Result<bool, Error> {
52
8
        let arti_path = key_spec
53
8
            .arti_path()
54
8
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
55
8
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
56
8
        let contains_key = key_dictionary.contains_key(&(arti_path, key_type.clone()));
57
8
        Ok(contains_key)
58
8
    }
59

            
60
8
    fn get(
61
8
        &self,
62
8
        key_spec: &dyn KeySpecifier,
63
8
        key_type: &KeyType,
64
8
    ) -> Result<Option<ErasedKey>, Error> {
65
8
        let arti_path = key_spec
66
8
            .arti_path()
67
8
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
68
8
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
69
8
        match key_dictionary.get(&(arti_path.clone(), key_type.clone())) {
70
4
            Some(key) => {
71
4
                let key: ErasedKey = key.clone().into_erased()?;
72
4
                Ok(Some(key))
73
            }
74
4
            None => Ok(None),
75
        }
76
8
    }
77

            
78
12
    fn insert(
79
12
        &self,
80
12
        key: &dyn EncodableKey,
81
12
        key_spec: &dyn KeySpecifier,
82
12
        key_type: &KeyType,
83
12
    ) -> Result<(), Error> {
84
12
        let arti_path = key_spec
85
12
            .arti_path()
86
12
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
87
12
        let key_data = key.as_ssh_key_data()?;
88

            
89
        // TODO: add key_type validation to Keystore::get and Keystore::remove.
90
        // The presence of a key with a mismatched key_type can be either due to keystore
91
        // corruption, or API misuse. We will need a new error type and corresponding ErrorKind for
92
        // that).
93
        //
94
        // TODO: add key_type validation to ArtiNativeKeystore
95
12
        if &key_data.key_type()? != key_type {
96
            // This can never happen unless:
97
            //   * Keystore::insert is called directly with an incorrect KeyType for `key`, or
98
            //   * Keystore::insert is called via KeyMgr, but the EncodableKey implementation of
99
            //   the key is broken. EncodableKey can't be implemented by external types,
100
            //   so a broken implementation means we have an internal bug.
101
2
            return Err(internal!(
102
2
                "the specified KeyType does not match key type of the inserted key?!"
103
2
            )
104
2
            .into());
105
10
        }
106
10

            
107
10
        // save to dictionary
108
10
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
109
10
        let _ = key_dictionary.insert((arti_path, key_type.clone()), key_data);
110
10
        Ok(())
111
12
    }
112

            
113
4
    fn remove(&self, key_spec: &dyn KeySpecifier, key_type: &KeyType) -> Result<Option<()>, Error> {
114
4
        let arti_path = key_spec
115
4
            .arti_path()
116
4
            .map_err(ArtiEphemeralKeystoreError::ArtiPathUnavailableError)?;
117
4
        let mut key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
118
4
        Ok(key_dictionary
119
4
            .remove(&(arti_path, key_type.clone()))
120
5
            .map(|_| ()))
121
4
    }
122

            
123
8
    fn list(&self) -> Result<Vec<(KeyPath, KeyType)>, Error> {
124
8
        let key_dictionary = self.key_dictionary.lock().expect("lock poisoned");
125
8
        Ok(key_dictionary
126
8
            .keys()
127
10
            .map(|(arti_path, key_type)| (arti_path.clone().into(), key_type.clone()))
128
8
            .collect())
129
8
    }
130
}
131

            
132
#[cfg(test)]
133
mod tests {
134
    // @@ begin test lint list maintained by maint/add_warning @@
135
    #![allow(clippy::bool_assert_comparison)]
136
    #![allow(clippy::clone_on_copy)]
137
    #![allow(clippy::dbg_macro)]
138
    #![allow(clippy::mixed_attributes_style)]
139
    #![allow(clippy::print_stderr)]
140
    #![allow(clippy::print_stdout)]
141
    #![allow(clippy::single_char_pattern)]
142
    #![allow(clippy::unwrap_used)]
143
    #![allow(clippy::unchecked_duration_subtraction)]
144
    #![allow(clippy::useless_vec)]
145
    #![allow(clippy::needless_pass_by_value)]
146
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
147

            
148
    use tor_basic_utils::test_rng::testing_rng;
149
    use tor_llcrypto::pk::ed25519;
150

            
151
    use super::*;
152

            
153
    use crate::test_utils::TestSpecifier;
154

            
155
    // some helper methods
156

            
157
    fn key() -> ErasedKey {
158
        let mut rng = testing_rng();
159
        let keypair = ed25519::Keypair::generate(&mut rng);
160
        Box::new(keypair)
161
    }
162

            
163
    fn key_type() -> &'static KeyType {
164
        &KeyType::Ed25519Keypair
165
    }
166

            
167
    fn key_type_bad() -> &'static KeyType {
168
        &KeyType::X25519StaticKeypair
169
    }
170

            
171
    fn key_spec() -> Box<dyn KeySpecifier> {
172
        Box::<TestSpecifier>::default()
173
    }
174

            
175
    // tests!
176

            
177
    #[test]
178
    fn id() {
179
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
180

            
181
        assert_eq!(&KeystoreId("test-ephemeral".to_string()), key_store.id());
182
    }
183

            
184
    #[test]
185
    fn contains() {
186
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
187

            
188
        // verify no key in store
189
        assert!(!key_store.contains(key_spec().as_ref(), key_type()).unwrap());
190

            
191
        // insert key and verify in store
192
        assert!(key_store
193
            .insert(key().as_ref(), key_spec().as_ref(), key_type())
194
            .is_ok());
195
        assert!(key_store.contains(key_spec().as_ref(), key_type()).unwrap());
196
    }
197

            
198
    #[test]
199
    fn get() {
200
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
201

            
202
        // verify no result to get
203
        assert!(key_store
204
            .get(key_spec().as_ref(), key_type())
205
            .unwrap()
206
            .is_none());
207

            
208
        // insert and verify get is a result
209
        assert!(key_store
210
            .insert(key().as_ref(), key_spec().as_ref(), key_type())
211
            .is_ok());
212

            
213
        let key = key_store
214
            .get(key_spec().as_ref(), key_type())
215
            .unwrap()
216
            .unwrap();
217

            
218
        // Ensure the returned key is of the right type
219
        assert!(key.downcast::<ed25519::Keypair>().is_ok());
220
    }
221

            
222
    #[test]
223
    fn insert() {
224
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
225

            
226
        // verify inserting a key with the wrong key type fails
227
        assert!(key_store
228
            .insert(key().as_ref(), key_spec().as_ref(), key_type_bad())
229
            .is_err());
230
        // further ensure there are no side effects
231
        assert!(!key_store
232
            .contains(key_spec().as_ref(), key_type_bad())
233
            .unwrap());
234
        assert!(key_store
235
            .get(key_spec().as_ref(), key_type_bad())
236
            .unwrap()
237
            .is_none());
238
        assert!(key_store.list().unwrap().is_empty());
239

            
240
        // verify inserting a good key succeeds
241
        assert!(key_store
242
            .insert(key().as_ref(), key_spec().as_ref(), key_type())
243
            .is_ok());
244

            
245
        // further ensure correct side effects
246
        assert!(key_store.contains(key_spec().as_ref(), key_type()).unwrap());
247
        assert!(key_store
248
            .get(key_spec().as_ref(), key_type())
249
            .unwrap()
250
            .is_some());
251
        assert_eq!(key_store.list().unwrap().len(), 1);
252
    }
253

            
254
    #[test]
255
    fn remove() {
256
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
257

            
258
        // verify removing from an empty store returns None
259
        assert!(key_store
260
            .remove(key_spec().as_ref(), key_type())
261
            .unwrap()
262
            .is_none());
263

            
264
        // verify inserting and removing results in Some(())
265
        assert!(key_store
266
            .insert(key().as_ref(), key_spec().as_ref(), key_type())
267
            .is_ok());
268
        assert!(key_store
269
            .remove(key_spec().as_ref(), key_type())
270
            .unwrap()
271
            .is_some());
272
    }
273

            
274
    #[test]
275
    fn list() {
276
        let key_store = ArtiEphemeralKeystore::new("test-ephemeral".to_string());
277

            
278
        // verify empty by default
279
        assert!(key_store.list().unwrap().is_empty());
280

            
281
        // verify size 1 after inserting a key
282
        assert!(key_store
283
            .insert(key().as_ref(), key_spec().as_ref(), key_type())
284
            .is_ok());
285
        assert_eq!(key_store.list().unwrap().len(), 1);
286
    }
287
}