Skip to main content

core_crypto/transaction_context/credential/
check.rs

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