Skip to main content

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