Skip to main content

core_crypto/transaction_context/credential/
check.rs

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