Skip to main content

core_crypto/transaction_context/credential/
check.rs

1use core_crypto_keystore::{entities::E2eiCrl, traits::FetchFromDatabase};
2use wire_e2e_identity::{pki_env::PkiEnvironment, 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::{
11            crl::{CrlUris, extract_crl_uris_from_credentials, extract_crl_uris_from_group},
12            ext::CredentialExt as _,
13        },
14    },
15    transaction_context::TransactionContext,
16};
17
18impl TransactionContext {
19    /// This function must be called at least once every 24 hours. It is recommended to do this during an idle period,
20    /// because in case x509 credentials are used, HTTP requests are done to fetch new certificate revocation lists.
21    pub async fn check_credentials(&self) -> Result<()> {
22        let database = self.database().await?;
23        let env = self.pki_environment().await?;
24
25        let credentials = Credential::get_all(&database)
26            .await
27            .map_err(RecursiveError::mls_credential("getting all credentials"))?;
28        let trust_anchors = env.get_trust_anchors().await;
29
30        let session = self.session().await?;
31        let conversations = Conversation::load_all(session)
32            .await
33            .map_err(RecursiveError::mls_conversation(
34                "loading all conversations to check if the credential to be removed is present",
35            ))?;
36        let relevant_crl_uris =
37            Self::get_crl_uris(trust_anchors.iter(), credentials.iter(), conversations.values()).await?;
38
39        self.clean_up_irrelevant_crls(&relevant_crl_uris).await?;
40
41        let crls = env
42            .fetch_crls(relevant_crl_uris.iter().map(AsRef::as_ref))
43            .await
44            .map_err(RecursiveError::e2e_identity("fetching crls"))?;
45
46        // store fresh CRLs
47        for (crl_uri, crl) in crls {
48            env.save_crl(&crl_uri, &crl)
49                .await
50                .map_err(RecursiveError::e2e_identity("saving CRL"))?;
51        }
52
53        let mut invalid_credential_refs = Vec::new();
54
55        // check our own credentials for expiration or revocation
56        for credential in credentials {
57            if self.check_credential(&env, &credential).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 check_credential(&self, pki_env: &PkiEnvironment, credential: &Credential) -> Result<()> {
104        let cert = credential
105            .mls_credential()
106            .parse_leaf_cert()
107            .map_err(RecursiveError::mls_credential("parsing leaf certificate"))?
108            .ok_or(Error::InvalidCredential)?;
109        pki_env
110            .validate_cert(&cert)
111            .await
112            .map_err(RecursiveError::e2e_identity("validating credential certificate"))?;
113        Ok(())
114    }
115
116    async fn clean_up_irrelevant_crls(&self, relevant_crl_uris: &CrlUris) -> Result<()> {
117        let database = self.database().await?;
118        for db_crl in database
119            .load_all::<E2eiCrl>()
120            .await
121            .map_err(KeystoreError::wrap("getting all database CRLs"))?
122        {
123            if !relevant_crl_uris.contains(&db_crl.distribution_point) {
124                database
125                    .remove::<E2eiCrl>(&db_crl.distribution_point)
126                    .await
127                    .map_err(KeystoreError::wrap("removing irrelevant CRL"))?;
128            }
129        }
130        Ok(())
131    }
132}