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::context("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).await.map_err(RecursiveError::context(
33            "loading all conversations to check if the credential to be removed is present",
34        ))?;
35        let relevant_crl_uris =
36            Self::get_crl_uris(trust_anchors.iter(), credentials.iter(), conversations.values()).await?;
37
38        self.clean_up_irrelevant_crls(&relevant_crl_uris).await?;
39
40        let crls = env
41            .fetch_crls(relevant_crl_uris.iter().map(AsRef::as_ref))
42            .await
43            .map_err(RecursiveError::e2e_identity("fetching crls"))?;
44
45        // store fresh CRLs
46        for (crl_uri, crl) in crls {
47            env.save_crl(&inner.transaction, &crl_uri, &crl)
48                .await
49                .map_err(RecursiveError::e2e_identity("saving CRL"))?;
50        }
51
52        let mut invalid_credential_refs = Vec::new();
53
54        // Check our own x509 credentials for expiration or revocation
55        // Ideally, we can load credentials by type from db as we actually only care about X509 checks.
56        // Unfortunately, this is not supported yet.
57        for credential in credentials {
58            if credential.check(&env).await.is_err() {
59                invalid_credential_refs.push(CredentialRef::from_credential(&credential));
60            }
61        }
62
63        if !invalid_credential_refs.is_empty() {
64            return Err(Error::InvalidCredentials(invalid_credential_refs));
65        }
66
67        Ok(())
68    }
69
70    /// To get CRL URLs, we want to consider all sources of relevant certificates:
71    /// - the stored credentials
72    /// - the trust anchor
73    /// - MLS groups
74    async fn get_crl_uris(
75        trust_anchors: impl Iterator<Item = &Certificate>,
76        credentials: impl Iterator<Item = &Credential>,
77        conversations: impl Iterator<Item = &Conversation>,
78    ) -> Result<CrlUris> {
79        let mls_credentials = credentials
80            .filter(|credential| credential.credential_type == CredentialType::X509)
81            .map(|credential| credential.mls_credential().mls_credential());
82
83        let mut crl_uris = extract_crl_uris_from_credentials(mls_credentials)
84            .map_err(RecursiveError::context("extracting CRL URLs from stored credentials"))?;
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::context("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 inner = self.inner().await?;
105        for db_crl in inner
106            .transaction
107            .load_all::<X509Crl>()
108            .await
109            .map_err(KeystoreError::wrap("getting all database CRLs"))?
110        {
111            if !relevant_crl_uris.contains(&db_crl.distribution_point) {
112                X509Crl::delete(inner.transaction(), &db_crl.distribution_point)
113                    .map_err(KeystoreError::wrap("removing irrelevant CRL"))?;
114            }
115        }
116        Ok(())
117    }
118}