core_crypto/mls/session/
key_package.rs

1use core_crypto_keystore::{entities::StoredKeypackage, traits::FetchFromDatabase};
2
3use super::Result;
4use crate::{Keypackage, KeypackageRef, KeystoreError, Session, mls::key_package::KeypackageExt};
5
6fn from_stored(stored_keypackage: &StoredKeypackage) -> Result<Keypackage> {
7    core_crypto_keystore::deser::<Keypackage>(&stored_keypackage.keypackage)
8        .map_err(KeystoreError::wrap("deserializing keypackage"))
9        .map_err(Into::into)
10}
11
12impl<D> Session<D>
13where
14    D: FetchFromDatabase,
15{
16    /// Get all [`Keypackage`]s in the database.
17    pub(crate) async fn get_keypackages(&self) -> Result<Vec<Keypackage>> {
18        let stored_keypackages: Vec<StoredKeypackage> = self
19            .database
20            .load_all()
21            .await
22            .map_err(KeystoreError::wrap("finding all keypackages"))?;
23
24        let keypackages = stored_keypackages
25            .iter()
26            .map(from_stored)
27            // if any ref from loading all fails to load now, skip it
28            // strictly we could panic, but this is safer--maybe someone removed it concurrently
29            .filter_map(|kp| kp.ok())
30            .collect();
31
32        Ok(keypackages)
33    }
34
35    /// Get all [`KeypackageRef`]s in the database.
36    pub async fn get_keypackage_refs(&self) -> Result<Vec<KeypackageRef>> {
37        self.get_keypackages()
38            .await?
39            .iter()
40            .map(|keypackage| keypackage.make_ref().map_err(Into::into))
41            .collect()
42    }
43
44    /// Load one [`Keypackage`] from its [`KeypackageRef`]
45    pub(crate) async fn load_keypackage(&self, kp_ref: &KeypackageRef) -> Result<Option<Keypackage>> {
46        self.database
47            .get_borrowed::<StoredKeypackage>(kp_ref.hash_ref())
48            .await
49            .map_err(KeystoreError::wrap("loading keypackage from database"))?
50            .map(|stored_keypackage| from_stored(&stored_keypackage))
51            .transpose()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use std::time::Duration;
58
59    use openmls::prelude::{KeyPackageIn, ProtocolVersion};
60    use openmls_traits::types::VerifiableCiphersuite;
61
62    use crate::{MlsConversationConfiguration, mls::key_package::KeypackageExt as _, test_utils::*};
63
64    #[apply(all_cred_cipher)]
65    async fn can_assess_keypackage_expiration(case: TestContext) {
66        let [session] = case.sessions().await;
67
68        // 90-day standard expiration
69        let kp_std_exp = session.new_keypackage(&case).await;
70        assert!(kp_std_exp.is_valid());
71
72        // 1-second expiration
73        let kp_1s_exp = session
74            .new_keypackage_with_lifetime(&case, Some(Duration::from_secs(1)))
75            .await;
76
77        // Sleep 2 seconds to make sure we make the kp expire
78        smol::Timer::after(std::time::Duration::from_secs(2)).await;
79        assert!(!kp_1s_exp.is_valid());
80    }
81
82    #[apply(all_cred_cipher)]
83    async fn new_keypackage_has_correct_extensions(case: TestContext) {
84        let [cc] = case.sessions().await;
85        Box::pin(async move {
86            let kp = cc.new_keypackage(&case).await;
87
88            // make sure it's valid
89            let _ = KeyPackageIn::from(kp.clone())
90                .standalone_validate(
91                    &cc.transaction.mls_provider().await.unwrap(),
92                    ProtocolVersion::Mls10,
93                    true,
94                )
95                .await
96                .unwrap();
97
98            // see https://www.rfc-editor.org/rfc/rfc9420.html#section-10-10
99            assert!(kp.extensions().is_empty());
100
101            assert_eq!(kp.leaf_node().capabilities().versions(), &[ProtocolVersion::Mls10]);
102            assert_eq!(
103                kp.leaf_node().capabilities().ciphersuites().to_vec(),
104                MlsConversationConfiguration::DEFAULT_SUPPORTED_CIPHERSUITES
105                    .iter()
106                    .map(|c| VerifiableCiphersuite::from(*c))
107                    .collect::<Vec<_>>()
108            );
109            assert!(kp.leaf_node().capabilities().proposals().is_empty());
110            assert!(kp.leaf_node().capabilities().extensions().is_empty());
111            assert_eq!(
112                kp.leaf_node().capabilities().credentials(),
113                MlsConversationConfiguration::DEFAULT_SUPPORTED_CREDENTIALS
114            );
115        })
116        .await
117    }
118
119    #[apply(all_cred_cipher)]
120    async fn can_store_and_load_key_packages(case: TestContext) {
121        let [cc] = case.sessions().await;
122
123        // generate a keypackage; automatically saves it
124        let kp = cc.new_keypackage(&case).await;
125
126        let all_keypackages = cc.session.read().await.get_keypackages().await.unwrap();
127        assert_eq!(all_keypackages[0], kp);
128
129        let kp_ref = kp.make_ref().unwrap();
130        let by_ref = cc.session.read().await.load_keypackage(&kp_ref).await.unwrap().unwrap();
131        assert_eq!(kp, by_ref);
132    }
133}