Skip to main content

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, RecursiveError,
8    mls::{
9        conversation::Conversation,
10        credential::crl::{CrlUris, extract_crl_uris_from_credentials, extract_crl_uris_from_group},
11    },
12    transaction_context::TransactionContext,
13};
14
15impl TransactionContext {
16    /// Check all X509 credentials for expiration and revocation
17    /// This function must be called at least once every 24 hours. It is recommended to do this during an idle period,
18    /// because in case x509 credentials are used, HTTP requests are done to fetch new certificate revocation lists.
19    pub async fn check_credentials(&self) -> Result<()> {
20        let database = self.database().await?;
21        let env = self.pki_environment().await?;
22
23        let credentials = Credential::get_all(&*database)
24            .await
25            .map_err(RecursiveError::mls_credential("getting all credentials"))?;
26        let trust_anchors = env.get_trust_anchors().await;
27
28        let session = self.session().await?;
29        let conversations = Conversation::load_all(session)
30            .await
31            .map_err(RecursiveError::mls_conversation(
32                "loading all conversations to check if the credential to be removed is present",
33            ))?;
34        let relevant_crl_uris =
35            Self::get_crl_uris(trust_anchors.iter(), credentials.iter(), conversations.values()).await?;
36
37        self.clean_up_irrelevant_crls(&relevant_crl_uris).await?;
38
39        let crls = env
40            .fetch_crls(relevant_crl_uris.iter().map(AsRef::as_ref))
41            .await
42            .map_err(RecursiveError::e2e_identity("fetching crls"))?;
43
44        // store fresh CRLs
45        for (crl_uri, crl) in crls {
46            env.save_crl(&crl_uri, &crl)
47                .await
48                .map_err(RecursiveError::e2e_identity("saving CRL"))?;
49        }
50
51        let mut invalid_credential_refs = Vec::new();
52
53        // Check our own x509 credentials for expiration or revocation
54        // Ideally, we can load credentials by type from db as we actually only care about X509 checks.
55        // Unfortunately, this is not supported yet.
56        for credential in credentials {
57            if credential.check(&env).await.is_err() {
58                invalid_credential_refs.push(CredentialRef::from_credential(&credential));
59            }
60        }
61
62        if !invalid_credential_refs.is_empty() {
63            return Err(Error::InvalidCredentials(invalid_credential_refs));
64        }
65
66        Ok(())
67    }
68
69    /// To get CRL URLs, we want to consider all sources of relevant certificates:
70    /// - the stored credentials
71    /// - the trust anchor
72    /// - MLS groups
73    async fn get_crl_uris(
74        trust_anchors: impl Iterator<Item = &Certificate>,
75        credentials: impl Iterator<Item = &Credential>,
76        conversations: impl Iterator<Item = &Conversation>,
77    ) -> Result<CrlUris> {
78        let mls_credentials = credentials
79            .filter(|credential| credential.credential_type == CredentialType::X509)
80            .map(|credential| credential.mls_credential().mls_credential());
81
82        let mut crl_uris = extract_crl_uris_from_credentials(mls_credentials).map_err(
83            RecursiveError::mls_credential("extracting CRL URLs from stored credentials"),
84        )?;
85
86        for trust_anchor in trust_anchors {
87            crl_uris.extend(
88                extract_crl_uris(trust_anchor)
89                    .map_err(RecursiveError::e2e_identity("extracting CRL URL from trust anchor"))?
90                    .unwrap_or_default(),
91            );
92        }
93
94        for conversation in conversations {
95            let uris_from_group = extract_crl_uris_from_group(&*conversation.group().await)
96                .map_err(RecursiveError::mls_credential("extracting CRL URLs from MLS groups"))?;
97            crl_uris.extend(uris_from_group);
98        }
99
100        Ok(crl_uris)
101    }
102
103    async fn clean_up_irrelevant_crls(&self, relevant_crl_uris: &CrlUris) -> Result<()> {
104        let database = self.database().await?;
105        for db_crl in database
106            .load_all::<E2eiCrl>()
107            .await
108            .map_err(KeystoreError::wrap("getting all database CRLs"))?
109        {
110            if !relevant_crl_uris.contains(&db_crl.distribution_point) {
111                database
112                    .remove::<E2eiCrl>(&db_crl.distribution_point)
113                    .await
114                    .map_err(KeystoreError::wrap("removing irrelevant CRL"))?;
115            }
116        }
117        Ok(())
118    }
119}