core_crypto/transaction_context/credential/
check.rs

1use core_crypto_keystore::{entities::E2eiCrl, traits::FetchFromDatabase};
2use wire_e2e_identity::x509_check::extract_crl_uris;
3use x509_cert::Certificate;
4
5use super::{Error, Result};
6use crate::{
7    Credential, CredentialRef, CredentialType, KeystoreError, MlsConversation, RecursiveError,
8    mls::credential::{
9        crl::{CrlUris, extract_crl_uris_from_credentials, extract_crl_uris_from_group},
10        ext::CredentialExt as _,
11    },
12    transaction_context::TransactionContext,
13};
14
15impl TransactionContext {
16    /// This function must be called at least once every 24 hours. It is recommended to do this during an idle period,
17    /// because in case x509 credentials are used, HTTP requests are done to fetch new certificate revocation lists.
18    pub async fn check_credentials(&self) -> Result<()> {
19        let database = self.database().await?;
20        let pki_env = self.pki_environment().await?;
21
22        let credentials = Credential::get_all(&database)
23            .await
24            .map_err(RecursiveError::mls_credential("getting all credentials"))?;
25        let trust_anchor = pki_env
26            .trust_anchor()
27            .await
28            .map_err(RecursiveError::e2e_identity("reading trust anchor cert"))?;
29        let conversations_with_id =
30            MlsConversation::load_all(&database)
31                .await
32                .map_err(RecursiveError::mls_conversation(
33                    "loading all conversations to check if the credential to be removed is present",
34                ))?;
35        let conversations = conversations_with_id
36            .iter()
37            .map(|conversation_with_id| conversation_with_id.1);
38        let relevant_crl_uris = Self::get_crl_uris(trust_anchor, credentials.iter(), conversations).await?;
39
40        self.clean_up_irrelevant_crls(&relevant_crl_uris).await?;
41
42        let crls = pki_env
43            .fetch_crls(relevant_crl_uris.iter().map(AsRef::as_ref))
44            .await
45            .map_err(RecursiveError::e2e_identity("fetching crls"))?;
46
47        // store fresh CRLs
48        for (crl_uri, crl) in crls {
49            self.e2ei_register_crl(crl_uri, crl).await?;
50        }
51
52        let mut invalid_credential_refs = Vec::new();
53
54        // check our own credentials for expiration or revocation
55        for credential in credentials {
56            if self.check_credential(&credential).await.is_err() {
57                invalid_credential_refs.push(CredentialRef::from_credential(&credential));
58            }
59        }
60
61        if !invalid_credential_refs.is_empty() {
62            return Err(Error::InvalidCredentials(invalid_credential_refs));
63        }
64
65        Ok(())
66    }
67
68    /// To get CRL URLs, we want to consider all sources of relevant certificates:
69    /// - the stored credentials
70    /// - the trust anchor
71    /// - MLS groups
72    async fn get_crl_uris(
73        trust_anchor: Certificate,
74        credentials: impl Iterator<Item = &Credential>,
75        conversations: impl Iterator<Item = &MlsConversation>,
76    ) -> Result<CrlUris> {
77        let mls_credentials = credentials
78            .filter(|credential| credential.credential_type == CredentialType::X509)
79            .map(|credential| credential.mls_credential().mls_credential());
80
81        let mut crl_uris = extract_crl_uris_from_credentials(mls_credentials).map_err(
82            RecursiveError::mls_credential("extracting CRL URLs from stored credentials"),
83        )?;
84
85        crl_uris.extend(
86            extract_crl_uris(&trust_anchor)
87                .map_err(RecursiveError::e2e_identity("extracting CRL URL from trust anchor"))?
88                .unwrap_or_default(),
89        );
90
91        for conversation in conversations {
92            let uris_from_group = extract_crl_uris_from_group(conversation.group())
93                .map_err(RecursiveError::mls_credential("extracting CRL URLs from MLS groups"))?;
94            crl_uris.extend(uris_from_group);
95        }
96
97        Ok(crl_uris)
98    }
99
100    async fn check_credential(&self, credential: &Credential) -> Result<()> {
101        let pki_env = self.pki_environment().await?;
102        let provider = pki_env.mls_pki_env_provider();
103        let auth_service_arc = provider.borrow().await;
104        let Some(pki_env) = auth_service_arc.as_ref() else {
105            return Err(crate::transaction_context::e2e_identity::Error::PkiEnvironmentUnset.into());
106        };
107        let Some(cert) = credential
108            .mls_credential()
109            .parse_leaf_cert()
110            .map_err(RecursiveError::mls_credential("parsing leaf certificate"))?
111        else {
112            return Err(Error::InvalidCredential);
113        };
114        pki_env
115            .validate_cert_and_revocation(&cert)
116            .map_err(RecursiveError::e2e_identity("validating credential certificate"))?;
117        Ok(())
118    }
119
120    async fn clean_up_irrelevant_crls(&self, relevant_crl_uris: &CrlUris) -> Result<()> {
121        let database = self.database().await?;
122        for db_crl in database
123            .load_all::<E2eiCrl>()
124            .await
125            .map_err(KeystoreError::wrap("getting all database CRLs"))?
126        {
127            if !relevant_crl_uris.contains(&db_crl.distribution_point) {
128                database
129                    .remove::<E2eiCrl>(&db_crl.distribution_point)
130                    .await
131                    .map_err(KeystoreError::wrap("removing irrelevant CRL"))?;
132            }
133        }
134        Ok(())
135    }
136}