Skip to main content

p2panda_encryption/
key_registry.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Manager for public key material of other members.
4//!
5//! Peers should actively look for fresh key bundles in the network, check for invalid or expired
6//! ones and automatically choose the latest for groups.
7use std::collections::HashMap;
8use std::convert::Infallible;
9use std::fmt::Debug;
10use std::marker::PhantomData;
11
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15use crate::crypto::x25519::PublicKey;
16use crate::key_bundle::{KeyBundleError, LongTermKeyBundle, OneTimeKeyBundle, latest_key_bundle};
17use crate::traits::{IdentityHandle, IdentityRegistry, KeyBundle, PreKeyRegistry};
18
19/// Key registry to maintain public key material of other members we've collected.
20#[derive(Clone, Debug)]
21pub struct KeyRegistry<ID> {
22    _marker: PhantomData<ID>,
23}
24
25/// Serializable state of key registry (for persistence).
26#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub struct KeyRegistryState<ID>
28where
29    ID: IdentityHandle,
30{
31    identities: HashMap<ID, PublicKey>,
32    onetime_bundles: HashMap<ID, Vec<OneTimeKeyBundle>>,
33    longterm_bundles: HashMap<ID, Vec<LongTermKeyBundle>>,
34}
35
36impl<ID> KeyRegistry<ID>
37where
38    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
39{
40    /// Returns newly initialised key-registry state.
41    pub fn init() -> KeyRegistryState<ID> {
42        KeyRegistryState {
43            identities: HashMap::new(),
44            onetime_bundles: HashMap::new(),
45            longterm_bundles: HashMap::new(),
46        }
47    }
48
49    /// Remove all expired key bundles from registry.
50    pub fn remove_expired(mut y: KeyRegistryState<ID>) -> KeyRegistryState<ID> {
51        y.longterm_bundles =
52            y.longterm_bundles
53                .into_iter()
54                .fold(HashMap::new(), |mut acc, (id, bundles)| {
55                    let bundles = bundles
56                        .into_iter()
57                        .filter(|bundle| bundle.verify().is_ok())
58                        .collect::<Vec<LongTermKeyBundle>>();
59                    acc.insert(id, bundles);
60                    acc
61                });
62
63        y.onetime_bundles =
64            y.onetime_bundles
65                .into_iter()
66                .fold(HashMap::new(), |mut acc, (id, bundles)| {
67                    let bundles = bundles
68                        .into_iter()
69                        .filter(|bundle| bundle.verify().is_ok())
70                        .collect::<Vec<OneTimeKeyBundle>>();
71                    acc.insert(id, bundles);
72                    acc
73                });
74        y
75    }
76
77    /// Adds long-term pre-key bundle to the registry.
78    ///
79    /// This throws an error if an expired or invalid bundle was added.
80    pub fn add_longterm_bundle(
81        mut y: KeyRegistryState<ID>,
82        id: ID,
83        key_bundle: LongTermKeyBundle,
84    ) -> Result<KeyRegistryState<ID>, KeyRegistryError> {
85        key_bundle.verify()?;
86        let existing = y.identities.insert(id, *key_bundle.identity_key());
87        if let Some(existing) = existing {
88            // Sanity check.
89            assert_eq!(&existing, key_bundle.identity_key());
90        }
91        y.longterm_bundles
92            .entry(id)
93            .and_modify(|bundles| bundles.push(key_bundle.clone()))
94            .or_insert(vec![key_bundle]);
95        Ok(y)
96    }
97
98    #[cfg(test)]
99    #[allow(non_snake_case)]
100    fn add_longterm_bundle_UNVERIFIED(
101        mut y: KeyRegistryState<ID>,
102        id: ID,
103        key_bundle: LongTermKeyBundle,
104    ) -> KeyRegistryState<ID> {
105        y.longterm_bundles
106            .entry(id)
107            .and_modify(|bundles| bundles.push(key_bundle.clone()))
108            .or_insert(vec![key_bundle]);
109        y
110    }
111
112    /// Adds one-time pre-key bundle to the registry.
113    ///
114    /// This throws an error if an expired or invalid bundle was added.
115    pub fn add_onetime_bundle(
116        mut y: KeyRegistryState<ID>,
117        id: ID,
118        key_bundle: OneTimeKeyBundle,
119    ) -> Result<KeyRegistryState<ID>, KeyRegistryError> {
120        key_bundle.verify()?;
121        let existing = y.identities.insert(id, *key_bundle.identity_key());
122        if let Some(existing) = existing {
123            // Sanity check.
124            assert_eq!(&existing, key_bundle.identity_key());
125        }
126        y.onetime_bundles
127            .entry(id)
128            .and_modify(|bundles| bundles.push(key_bundle.clone()))
129            .or_insert(vec![key_bundle]);
130        Ok(y)
131    }
132}
133
134impl<ID> PreKeyRegistry<ID, OneTimeKeyBundle> for KeyRegistry<ID>
135where
136    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
137{
138    type State = KeyRegistryState<ID>;
139
140    type Error = Infallible;
141
142    fn key_bundle(
143        mut y: Self::State,
144        id: &ID,
145    ) -> Result<(Self::State, Option<OneTimeKeyBundle>), Self::Error> {
146        let bundle = y
147            .onetime_bundles
148            .get_mut(id)
149            .and_then(|bundles| bundles.pop());
150        Ok((y, bundle))
151    }
152}
153
154impl<ID> PreKeyRegistry<ID, LongTermKeyBundle> for KeyRegistry<ID>
155where
156    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
157{
158    type State = KeyRegistryState<ID>;
159
160    type Error = KeyRegistryError;
161
162    fn key_bundle(
163        y: Self::State,
164        id: &ID,
165    ) -> Result<(Self::State, Option<LongTermKeyBundle>), Self::Error> {
166        let Some(bundles) = y.longterm_bundles.get(id) else {
167            return Ok((y, None));
168        };
169
170        let valid_bundle = latest_key_bundle(bundles).cloned();
171
172        // Even though key bundles are available we couldn't find any non-expired ones.
173        if !bundles.is_empty() && valid_bundle.is_none() {
174            return Err(KeyRegistryError::KeyBundlesExpired);
175        }
176
177        Ok((y, valid_bundle))
178    }
179}
180
181impl<ID> IdentityRegistry<ID, KeyRegistryState<ID>> for KeyRegistry<ID>
182where
183    ID: IdentityHandle + Serialize + for<'a> Deserialize<'a>,
184{
185    type Error = Infallible;
186
187    fn identity_key(y: &KeyRegistryState<ID>, id: &ID) -> Result<Option<PublicKey>, Self::Error> {
188        let key = y.identities.get(id).cloned();
189        Ok(key)
190    }
191}
192
193#[derive(Debug, Error)]
194pub enum KeyRegistryError {
195    #[error(transparent)]
196    KeyBundle(#[from] KeyBundleError),
197
198    #[error("no key bundles found")]
199    KeyBundlesNotFound,
200
201    #[error("all available key bundles of this member expired")]
202    KeyBundlesExpired,
203}
204
205#[cfg(test)]
206mod tests {
207    use std::time::{SystemTime, UNIX_EPOCH};
208
209    use crate::Rng;
210    use crate::crypto::x25519::SecretKey;
211    use crate::key_bundle::{Lifetime, LongTermKeyBundle, PreKey};
212    use crate::traits::PreKeyRegistry;
213
214    use super::KeyRegistry;
215
216    #[test]
217    fn latest_key_bundle() {
218        let rng = Rng::from_seed([1; 32]);
219
220        let now = SystemTime::now()
221            .duration_since(UNIX_EPOCH)
222            .expect("SystemTime before UNIX EPOCH!")
223            .as_secs();
224
225        let member_id = 0;
226        let identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
227
228        // Generate first bundle.
229        let bundle_1 = {
230            let prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
231            let prekey = PreKey::new(
232                prekey_secret.verifying_key().unwrap(),
233                Lifetime::from_range(now - 60, now + 60),
234            );
235            let prekey_signature = prekey.sign(&identity_secret, &rng).unwrap();
236
237            LongTermKeyBundle::new(
238                identity_secret.verifying_key().unwrap(),
239                prekey,
240                prekey_signature,
241            )
242        };
243
244        // Generate second bundle (which expires earlier).
245        let bundle_2 = {
246            let prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
247            let prekey = PreKey::new(
248                prekey_secret.verifying_key().unwrap(),
249                Lifetime::from_range(now - 60, now + 30),
250            );
251            let prekey_signature = prekey.sign(&identity_secret, &rng).unwrap();
252
253            LongTermKeyBundle::new(
254                identity_secret.verifying_key().unwrap(),
255                prekey,
256                prekey_signature,
257            )
258        };
259
260        // Initialize key registry and register both bundles there.
261        let pki = {
262            let y = KeyRegistry::init();
263            let y = KeyRegistry::add_longterm_bundle(y, member_id, bundle_1.clone()).unwrap();
264            let y = KeyRegistry::add_longterm_bundle(y, member_id, bundle_2).unwrap();
265            y
266        };
267
268        // Registry returns bundle which has the "furthest" expiry date.
269        assert_eq!(
270            KeyRegistry::key_bundle(pki, &member_id).unwrap().1,
271            Some(bundle_1)
272        );
273    }
274
275    #[test]
276    fn invalid_bundles() {
277        let rng = Rng::from_seed([1; 32]);
278
279        let now = SystemTime::now()
280            .duration_since(UNIX_EPOCH)
281            .expect("SystemTime before UNIX EPOCH!")
282            .as_secs();
283
284        let member_id = 0;
285        let identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
286
287        let invalid_bundle = {
288            let prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
289            let prekey = PreKey::new(
290                prekey_secret.verifying_key().unwrap(),
291                Lifetime::from_range(now - 60, now - 30),
292            );
293            let prekey_signature = prekey.sign(&identity_secret, &rng).unwrap();
294
295            LongTermKeyBundle::new(
296                identity_secret.verifying_key().unwrap(),
297                prekey,
298                prekey_signature,
299            )
300        };
301
302        let pki = KeyRegistry::init();
303
304        // Registry should throw an error when trying to add an expired bundle.
305        assert!(
306            KeyRegistry::add_longterm_bundle(pki.clone(), member_id, invalid_bundle.clone())
307                .is_err()
308        );
309
310        let pki =
311            KeyRegistry::add_longterm_bundle_UNVERIFIED(pki, member_id, invalid_bundle.clone());
312
313        // Registry should throw an error when we only have expired bundles of that member.
314        assert_eq!(pki.longterm_bundles.get(&member_id).unwrap().len(), 1);
315        assert!(
316            <KeyRegistry<usize> as PreKeyRegistry<usize, LongTermKeyBundle>>::key_bundle(
317                pki.clone(),
318                &member_id
319            )
320            .is_err()
321        );
322    }
323
324    #[test]
325    fn garbage_collection() {
326        let rng = Rng::from_seed([1; 32]);
327
328        let now = SystemTime::now()
329            .duration_since(UNIX_EPOCH)
330            .expect("SystemTime before UNIX EPOCH!")
331            .as_secs();
332
333        let member_id = 0;
334        let identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
335
336        let invalid_bundle = {
337            let prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
338            let prekey = PreKey::new(
339                prekey_secret.verifying_key().unwrap(),
340                Lifetime::from_range(now - 60, now - 30),
341            );
342            let prekey_signature = prekey.sign(&identity_secret, &rng).unwrap();
343
344            LongTermKeyBundle::new(
345                identity_secret.verifying_key().unwrap(),
346                prekey,
347                prekey_signature,
348            )
349        };
350
351        let valid_bundle = {
352            let prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
353            let prekey = PreKey::new(
354                prekey_secret.verifying_key().unwrap(),
355                Lifetime::from_range(now - 60, now + 60),
356            );
357            let prekey_signature = prekey.sign(&identity_secret, &rng).unwrap();
358
359            LongTermKeyBundle::new(
360                identity_secret.verifying_key().unwrap(),
361                prekey,
362                prekey_signature,
363            )
364        };
365
366        let pki = {
367            let y = KeyRegistry::init();
368            let y =
369                KeyRegistry::add_longterm_bundle_UNVERIFIED(y, member_id, invalid_bundle.clone());
370            let y = KeyRegistry::add_longterm_bundle_UNVERIFIED(y, member_id, valid_bundle.clone());
371            y
372        };
373
374        assert_eq!(pki.longterm_bundles.get(&member_id).unwrap().len(), 2);
375
376        // Remove invalid bundles.
377        let pki = KeyRegistry::remove_expired(pki);
378        assert_eq!(pki.longterm_bundles.get(&member_id).unwrap().len(), 1);
379
380        // Registry returns correct and valid bundle.
381        assert_eq!(
382            KeyRegistry::key_bundle(pki, &member_id).unwrap().1,
383            Some(valid_bundle)
384        );
385    }
386}