core_crypto/mls/session/
credential.rs

1use std::sync::Arc;
2
3use openmls::prelude::{SignaturePublicKey, SignatureScheme};
4use openmls_traits::OpenMlsCryptoProvider as _;
5
6use super::{Error, Result};
7use crate::{
8    Credential, CredentialFindFilters, CredentialRef, CredentialType, LeafError, MlsConversation, RecursiveError,
9    Session, mls::session::SessionInner,
10};
11
12impl Session {
13    /// Find all credentials known by this session which match the specified conditions.
14    ///
15    /// If no filters are set, this is equivalent to [`Self::get_credentials`].
16    pub async fn find_credentials(&self, find_filters: CredentialFindFilters<'_>) -> Result<Vec<CredentialRef>> {
17        let guard = self.inner.read().await;
18        let inner = guard.as_ref().ok_or(Error::MlsNotInitialized)?;
19        Ok(inner
20            .identities
21            .find_credential(find_filters)
22            .map(|credential| CredentialRef::from_credential(&credential))
23            .collect())
24    }
25
26    /// Get all credentials known by this session.
27    pub async fn get_credentials(&self) -> Result<Vec<CredentialRef>> {
28        self.find_credentials(Default::default()).await
29    }
30
31    /// Add a credential to the identities of this session.
32    ///
33    /// As a side effect, stores the credential in the keystore.
34    pub(crate) async fn add_credential(&self, credential: Credential) -> Result<CredentialRef> {
35        let credential = self.add_credential_producing_arc(credential).await?;
36        Ok(CredentialRef::from_credential(&credential))
37    }
38
39    /// Add a credential to the identities of this session.
40    ///
41    /// As a side effect, stores the credential in the keystore.
42    ///
43    /// Returns the actual credential instance which was loaded from the DB.
44    /// This is a convenience for internal use and should _not_ be propagated across
45    /// the FFI boundary. Instead, use [`Self::add_credential`] to produce a [`CredentialRef`].
46    pub(crate) async fn add_credential_producing_arc(&self, credential: Credential) -> Result<Arc<Credential>> {
47        if *credential.client_id() != self.id().await? {
48            return Err(Error::WrongCredential);
49        }
50
51        self.add_credential_without_clientid_check(credential).await
52    }
53
54    /// Add a credential to the identities of this session without validating that its client ID matches the session client id.
55    ///
56    /// This is rarely useful and should only be used when absolutely necessary. You'll know it if you need it.
57    ///
58    /// Prefer [`Self::add_credential`].
59    pub(crate) async fn add_credential_without_clientid_check(
60        &self,
61        mut credential: Credential,
62    ) -> Result<Arc<Credential>> {
63        let credential_ref = credential
64            .save(&self.crypto_provider.keystore())
65            .await
66            .map_err(RecursiveError::mls_credential("saving credential"))?;
67
68        let guard = self.inner.upgradable_read().await;
69        let inner = guard.as_ref().ok_or(Error::MlsNotInitialized)?;
70
71        // failfast before loading the cache if we know already that this credential ref can't be added to the identity set
72        let distinct_result = inner.identities.ensure_distinct(
73            credential_ref.signature_scheme(),
74            credential_ref.r#type(),
75            credential_ref.earliest_validity(),
76        );
77        if let Err(err) = distinct_result {
78            // first clean up by removing the credential we just saved
79            // otherwise, we'll have nondeterministic results when we load
80            //
81            // TODO this depends for correctness that no two added credentials have the same keypair;
82            // if this happens for a keypair which was removed, we'll remove the (old, used) keypair
83            // and forever after be unable to mls_init on that DB due to a missing keypair for the given credential
84            // this is pointlessly difficult to check right now, but we should do a uniqueness check
85            // after WPB-20844
86            credential
87                .delete(&self.crypto_provider.keystore())
88                .await
89                .map_err(RecursiveError::mls_credential(
90                    "deleting nondistinct credential from keystore",
91                ))?;
92            return Err(err);
93        }
94
95        // only upgrade to a write guard here in order to minimize the amount of time the unique lock is held
96        let mut guard = async_lock::RwLockUpgradableReadGuard::upgrade(guard).await;
97        let inner = guard.as_mut().ok_or(Error::MlsNotInitialized)?;
98        let credential = inner.identities.push_credential(credential).await?;
99
100        Ok(credential)
101    }
102
103    /// Remove a credential from the identities of this session.
104    ///
105    /// First checks that the credential is not used in any conversation.
106    /// Removes both the credential itself and also any key packages which were generated from it.
107    pub async fn remove_credential(&self, credential_ref: &CredentialRef) -> Result<()> {
108        // setup
109        if *credential_ref.client_id() != self.id().await? {
110            return Err(Error::WrongCredential);
111        }
112
113        let database = self.crypto_provider.keystore();
114
115        let credentials = credential_ref
116            .load(&database)
117            .await
118            .map_err(RecursiveError::mls_credential_ref(
119                "loading all credentials from ref to remove from session identities",
120            ))?;
121
122        // in a perfect world, we'd pre-cache the mls credentials in a set structure of some sort for faster querying.
123        // unfortunately, `MlsCredential` is `!Hash` and `!Ord`, so both the standard sets are out.
124        // so whatever, linear scan over the credentials every time will have to do.
125
126        // ensure this credential is not in use by any conversation
127        for (conversation_id, conversation) in
128            MlsConversation::load_all(&database)
129                .await
130                .map_err(RecursiveError::mls_conversation(
131                    "loading all conversations to check if the credential to be removed is present",
132                ))?
133        {
134            let converation_credential = conversation
135                .own_mls_credential()
136                .map_err(RecursiveError::mls_conversation("geting conversation credential"))?;
137            if credentials
138                .iter()
139                .any(|credential| credential.mls_credential() == converation_credential)
140            {
141                return Err(Error::CredentialStillInUse(conversation_id));
142            }
143        }
144
145        // remove any key packages generated by this credential
146        let keypackages = self.find_all_keypackages(&self.crypto_provider.keystore()).await?;
147        let keypackages_from_this_credential = keypackages.iter().filter_map(|(_stored_key_package, key_package)| {
148            credentials
149                    .iter()
150                    .any(|credential| key_package.leaf_node().credential() == credential.mls_credential())
151                    // if computing the hash reference fails, we will just not delete the key package
152                    .then(|| key_package.hash_ref(self.crypto_provider.crypto()).ok()).flatten()
153        });
154        self.prune_keypackages(&self.crypto_provider, keypackages_from_this_credential)
155            .await?;
156
157        // remove all credentials associated with this ref
158        // only remove the actual credential after the keypackages are all gone,
159        // and keep the lock open as briefly as possible
160        {
161            let mut inner = self.inner.write().await;
162            let inner = inner.as_mut().ok_or(Error::MlsNotInitialized)?;
163            for credential in &credentials {
164                inner.identities.remove_by_mls_credential(credential.mls_credential());
165            }
166        }
167
168        // finally remove the credentials from the keystore so they won't be loaded on next mls_init
169        for credential in credentials {
170            credential
171                .delete(&database)
172                .await
173                .map_err(RecursiveError::mls_credential("deleting credential from keystore"))?;
174        }
175
176        Ok(())
177    }
178
179    /// convenience function deferring to the implementation on the inner type
180    pub(crate) async fn find_most_recent_credential(
181        &self,
182        signature_scheme: SignatureScheme,
183        credential_type: CredentialType,
184    ) -> Result<Arc<Credential>> {
185        match &*self.inner.read().await {
186            None => Err(Error::MlsNotInitialized),
187            Some(SessionInner { identities, .. }) => identities
188                .find_most_recent_credential(signature_scheme, credential_type)
189                .await
190                .ok_or(Error::CredentialNotFound(credential_type, signature_scheme)),
191        }
192    }
193
194    /// convenience function deferring to the implementation on the inner type
195    pub(crate) async fn find_credential_by_public_key(
196        &self,
197        signature_scheme: SignatureScheme,
198        credential_type: CredentialType,
199        public_key: &SignaturePublicKey,
200    ) -> Result<Arc<Credential>> {
201        match &*self.inner.read().await {
202            None => Err(Error::MlsNotInitialized),
203            Some(SessionInner { identities, .. }) => identities
204                .find_credential_by_public_key(signature_scheme, credential_type, public_key)
205                .await
206                .ok_or(Error::CredentialNotFound(credential_type, signature_scheme)),
207        }
208    }
209
210    /// Convenience function to get the most recent credential, creating it if the credential type is basic.
211    ///
212    /// If the credential type is X509, a missing credential returns `LeafError::E2eiEnrollmentNotDone`
213    pub(crate) async fn find_most_recent_or_create_basic_credential(
214        &self,
215        signature_scheme: SignatureScheme,
216        credential_type: CredentialType,
217    ) -> Result<Arc<Credential>> {
218        let credential = match self
219            .find_most_recent_credential(signature_scheme, credential_type)
220            .await
221        {
222            Ok(credential) => credential,
223            Err(Error::CredentialNotFound(..)) if credential_type == CredentialType::Basic => {
224                let client_id = self.id().await?;
225                let credential = Credential::basic(signature_scheme, client_id, &self.crypto_provider).map_err(
226                    RecursiveError::mls_credential(
227                        "creating basic credential in find_most_recent_or_create_basic_credential",
228                    ),
229                )?;
230                self.add_credential_producing_arc(credential).await?
231            }
232            Err(Error::CredentialNotFound(..)) if credential_type == CredentialType::X509 => {
233                return Err(LeafError::E2eiEnrollmentNotDone.into());
234            }
235            Err(err) => return Err(err),
236        };
237        Ok(credential)
238    }
239}