|
| 1 | +use core_crypto_keystore::{entities::E2eiCrl, traits::FetchFromDatabase}; |
| 2 | +use wire_e2e_identity::x509_check::extract_crl_uris; |
| 3 | +use x509_cert::Certificate; |
| 4 | + |
| 5 | +use super::{Error, Result}; |
| 6 | +use crate::{ |
| 7 | + Credential, CredentialRef, CredentialType, KeystoreError, MlsConversation, RecursiveError, |
| 8 | + mls::credential::{ |
| 9 | + crl::{CrlUris, extract_crl_uris_from_credentials, extract_crl_uris_from_group}, |
| 10 | + ext::CredentialExt as _, |
| 11 | + }, |
| 12 | + transaction_context::TransactionContext, |
| 13 | +}; |
| 14 | + |
| 15 | +impl TransactionContext { |
| 16 | + /// This function must be called at least once every 24 hours. It is recommended to do this during an idle period, |
| 17 | + /// because in case x509 credentials are used, HTTP requests are done to fetch new certificate revocation lists. |
| 18 | + pub async fn check_credentials(&self) -> Result<()> { |
| 19 | + let database = self.database().await?; |
| 20 | + let pki_env = self.pki_environment().await?; |
| 21 | + |
| 22 | + let credentials = Credential::get_all(&database) |
| 23 | + .await |
| 24 | + .map_err(RecursiveError::mls_credential("getting all credentials"))?; |
| 25 | + let trust_anchor = pki_env |
| 26 | + .trust_anchor() |
| 27 | + .await |
| 28 | + .map_err(RecursiveError::e2e_identity("reading trust anchor cert"))?; |
| 29 | + let conversations_with_id = |
| 30 | + MlsConversation::load_all(&database) |
| 31 | + .await |
| 32 | + .map_err(RecursiveError::mls_conversation( |
| 33 | + "loading all conversations to check if the credential to be removed is present", |
| 34 | + ))?; |
| 35 | + let conversations = conversations_with_id |
| 36 | + .iter() |
| 37 | + .map(|conversation_with_id| conversation_with_id.1); |
| 38 | + let relevant_crl_uris = Self::get_crl_uris(trust_anchor, credentials.iter(), conversations).await?; |
| 39 | + |
| 40 | + self.clean_up_irrelevant_crls(&relevant_crl_uris).await?; |
| 41 | + |
| 42 | + let crls = pki_env |
| 43 | + .fetch_crls(relevant_crl_uris.into()) |
| 44 | + .await |
| 45 | + .map_err(RecursiveError::e2e_identity("fetching crls"))?; |
| 46 | + |
| 47 | + // store fresh CRLs |
| 48 | + for (crl_uri, crl) in crls { |
| 49 | + self.e2ei_register_crl(crl_uri, crl).await?; |
| 50 | + } |
| 51 | + |
| 52 | + let mut invalid_credential_refs = Vec::new(); |
| 53 | + |
| 54 | + // check our own credentials for expiration or revocation |
| 55 | + for credential in credentials { |
| 56 | + if self.check_credential(&credential).await.is_err() { |
| 57 | + invalid_credential_refs.push(CredentialRef::from_credential(&credential)); |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + if !invalid_credential_refs.is_empty() { |
| 62 | + return Err(Error::InvalidCredentials(invalid_credential_refs)); |
| 63 | + } |
| 64 | + |
| 65 | + Ok(()) |
| 66 | + } |
| 67 | + |
| 68 | + /// To get CRL URLs, we want to consider all sources of relevant certificates: |
| 69 | + /// - the stored credentials |
| 70 | + /// - the trust anchor |
| 71 | + /// - mls groups |
| 72 | + async fn get_crl_uris( |
| 73 | + trust_anchor: Certificate, |
| 74 | + credentials: impl Iterator<Item = &Credential>, |
| 75 | + conversations: impl Iterator<Item = &MlsConversation>, |
| 76 | + ) -> Result<CrlUris> { |
| 77 | + let mls_credentials = credentials |
| 78 | + .filter(|credential| credential.credential_type == CredentialType::X509) |
| 79 | + .map(|credential| credential.mls_credential().mls_credential()); |
| 80 | + |
| 81 | + let mut crl_uris = extract_crl_uris_from_credentials(mls_credentials).map_err( |
| 82 | + RecursiveError::mls_credential("extracting crl urls from stored credentials"), |
| 83 | + )?; |
| 84 | + |
| 85 | + crl_uris.extend( |
| 86 | + extract_crl_uris(&trust_anchor) |
| 87 | + .map_err(RecursiveError::e2e_identity("extracting crl uri from trust anchor"))? |
| 88 | + .unwrap_or_default(), |
| 89 | + ); |
| 90 | + |
| 91 | + for conversation in conversations { |
| 92 | + let uris_from_group = extract_crl_uris_from_group(conversation.group()) |
| 93 | + .map_err(RecursiveError::mls_credential("extracting CRL URLs from mls groups"))?; |
| 94 | + crl_uris.extend(uris_from_group); |
| 95 | + } |
| 96 | + |
| 97 | + Ok(crl_uris) |
| 98 | + } |
| 99 | + |
| 100 | + async fn check_credential(&self, credential: &Credential) -> Result<()> { |
| 101 | + let pki_env = self.pki_environment().await?; |
| 102 | + let provider = pki_env.mls_pki_env_provider(); |
| 103 | + let auth_service_arc = provider.borrow().await; |
| 104 | + let Some(pki_env) = auth_service_arc.as_ref() else { |
| 105 | + return Err(crate::transaction_context::e2e_identity::Error::PkiEnvironmentUnset.into()); |
| 106 | + }; |
| 107 | + let Some(cert) = credential |
| 108 | + .mls_credential() |
| 109 | + .parse_leaf_cert() |
| 110 | + .map_err(RecursiveError::mls_credential("parsing leaf certificate"))? |
| 111 | + else { |
| 112 | + return Err(Error::InvalidCredential); |
| 113 | + }; |
| 114 | + pki_env |
| 115 | + .validate_cert_and_revocation(&cert) |
| 116 | + .map_err(RecursiveError::e2e_identity("validating credential certificate"))?; |
| 117 | + Ok(()) |
| 118 | + } |
| 119 | + |
| 120 | + async fn clean_up_irrelevant_crls(&self, relevant_crl_uris: &CrlUris) -> Result<()> { |
| 121 | + let database = self.database().await?; |
| 122 | + for db_crl in database |
| 123 | + .load_all::<E2eiCrl>() |
| 124 | + .await |
| 125 | + .map_err(KeystoreError::wrap("getting all database CRLs"))? |
| 126 | + { |
| 127 | + if !relevant_crl_uris.contains(&db_crl.distribution_point) { |
| 128 | + database |
| 129 | + .remove::<E2eiCrl>(&db_crl.distribution_point) |
| 130 | + .await |
| 131 | + .map_err(KeystoreError::wrap("removing irrelevant CRL"))?; |
| 132 | + } |
| 133 | + } |
| 134 | + Ok(()) |
| 135 | + } |
| 136 | +} |
0 commit comments