Skip to main content

core_crypto/transaction_context/
key_package.rs

1//! This module contains all transactional behavior related to key packages
2
3use std::time::Duration;
4
5use core_crypto_keystore::{
6    Sha256Hash,
7    entities::{StoredEncryptionKeyPair, StoredHpkePrivateKey, StoredKeyPackage},
8    traits::EntityDeleteBorrowed as _,
9};
10use openmls::prelude::{CryptoConfig, Lifetime};
11
12use super::{Error, Result, TransactionContext};
13use crate::{
14    ConversationConfiguration, CredentialRef, Keypackage, KeypackageRef, KeystoreError, RecursiveError,
15    mls::key_package::KeypackageExt as _,
16};
17
18/// Default lifetime of all generated Keypackages. Matches the limit defined in openmls
19/// Default lifetime of a generated key package.
20///
21/// Note that this sits exactly on the maximum lifetime range openmls will accept for a leaf node:
22/// `Lifetime::new` extends `not_before` a further hour into the past for clock skew, and
23/// `MAX_LEAF_NODE_LIFETIME_RANGE_SECONDS` is that margin plus this value. Raising this constant, or
24/// passing a longer lifetime to [`TransactionContext::generate_key_package`], produces key packages
25/// which `KeypackageExt::is_valid` reports as invalid from the moment they are created.
26pub const KEYPACKAGE_DEFAULT_LIFETIME: Duration = Duration::from_secs(60 * 60 * 24 * 28 * 3); // ~3 months
27
28impl TransactionContext {
29    /// Generate a [Keypackage] from the referenced credential.
30    ///
31    /// Makes no attempt to look up or prune existing keypackges.
32    ///
33    /// If `lifetime` is set, the keypackages will expire that span into the future.
34    /// If it is unset, [`KEYPACKAGE_DEFAULT_LIFETIME`]
35    /// is used.
36    ///
37    /// As a side effect, stores the keypackages and some related data in the keystore.
38    pub async fn generate_key_package(
39        &self,
40        credential_ref: &CredentialRef,
41        lifetime: Option<Duration>,
42    ) -> Result<Keypackage> {
43        let inner = self.inner()?;
44        let lifetime = Lifetime::new(lifetime.unwrap_or(KEYPACKAGE_DEFAULT_LIFETIME).as_secs());
45        let credential = credential_ref
46            .load(&inner.transaction)
47            .await
48            .map_err(RecursiveError::context("loading credential"))?;
49        let config = CryptoConfig {
50            ciphersuite: credential.cipher_suite.into(),
51            version: openmls::versions::ProtocolVersion::default(),
52        };
53
54        Keypackage::builder()
55            .leaf_node_capabilities(ConversationConfiguration::default_leaf_capabilities())
56            .key_package_lifetime(lifetime)
57            .build(
58                config,
59                &self.crypto_provider().await?,
60                &credential.signature_key_pair,
61                credential.to_mls_credential_with_key(),
62            )
63            .await
64            .map_err(Error::key_package_new())
65    }
66
67    /// Get all [`KeypackageRef`]s known to the keystore.
68    pub async fn get_key_package_refs(&self) -> Result<Vec<KeypackageRef>> {
69        let session = self.session().await?;
70        session
71            .get_keypackage_refs()
72            .await
73            .map_err(RecursiveError::context("getting all key package refs for transaction"))
74            .map_err(Into::into)
75    }
76
77    /// Remove one [`Keypackage`] from the database.
78    ///
79    /// Succeeds silently if the keypackage does not exist in the database.
80    ///
81    /// Implementation note: this must first load and deserialize the keypackage,
82    /// then remove items from three distinct tables.
83    pub async fn remove_key_package(&self, kp_ref: &KeypackageRef) -> Result<()> {
84        let Some(kp) = self
85            .session()
86            .await?
87            .load_key_package(kp_ref)
88            .await
89            .map_err(RecursiveError::context("loading key packages on session"))?
90        else {
91            return Ok(());
92        };
93
94        let inner = self.inner()?;
95        let tx = inner.transaction();
96        StoredKeyPackage::delete_borrowed(tx, kp_ref.hash_ref())
97            .map_err(KeystoreError::wrap("removing key package from keystore"))?;
98        StoredHpkePrivateKey::delete_borrowed(tx, kp.hpke_init_key().as_slice())
99            .map_err(KeystoreError::wrap("removing private key from keystore"))?;
100        StoredEncryptionKeyPair::delete_borrowed(tx, kp.leaf_node().encryption_key().as_slice())
101            .map_err(KeystoreError::wrap("removing encryption keypair from keystore"))?;
102
103        Ok(())
104    }
105
106    /// Remove all keypackages associated with this credential.
107    ///
108    /// This is fairly expensive as it must first load all keypackages, then delete those matching the credential.
109    ///
110    /// Implementation note: once it makes it as far as having a list of keypackages, does _not_ short-circuit
111    /// if removing one returns an error. In that case, only the first produced error is returned.
112    /// This helps ensure that as many keypackages for the given credential ref are removed as possible.
113    pub async fn remove_key_packages_for(&self, credential_ref: &CredentialRef) -> Result<()> {
114        let mut first_err = None;
115        macro_rules! try_retain_err {
116            ($e:expr) => {
117                match $e {
118                    Err(err) => {
119                        if first_err.is_none() {
120                            first_err = Some(Error::from(err));
121                        }
122                        continue;
123                    }
124                    Ok(val) => val,
125                }
126            };
127        }
128
129        let session = self.session().await?;
130        for keypackage in session
131            .get_key_packages()
132            .await
133            .map_err(RecursiveError::context("loading key packages"))?
134            .into_iter()
135            .filter(|keypackage| {
136                Sha256Hash::hash_from(keypackage.leaf_node().signature_key().as_slice())
137                    == credential_ref.public_key_hash()
138            })
139        {
140            let kp_ref = try_retain_err!(keypackage.make_ref());
141            try_retain_err!(self.remove_key_package(&kp_ref).await);
142        }
143
144        match first_err {
145            None => Ok(()),
146            Some(err) => Err(err),
147        }
148    }
149}