wire_e2e_identity/
pki_env.rs

1//! PKI Environment API
2
3use std::{collections::HashSet, sync::Arc};
4
5use async_lock::{RwLock, RwLockReadGuard};
6use core_crypto_keystore::{
7    connection::Database,
8    entities::{E2eiAcmeCA, E2eiCrl, E2eiIntermediateCert},
9    traits::FetchFromDatabase,
10};
11use openmls_traits::authentication_service::{CredentialAuthenticationStatus, CredentialRef};
12use x509_cert::der::Decode as _;
13
14use crate::{
15    error::E2eIdentityError,
16    pki_env_hooks::PkiEnvironmentHooks,
17    x509_check::{
18        RustyX509CheckError,
19        revocation::{PkiEnvironment as RjtPkiEnvironment, PkiEnvironmentParams},
20    },
21};
22
23pub type Result<T> = core::result::Result<T, Error>;
24
25#[derive(Debug, thiserror::Error)]
26pub enum Error {
27    #[error(transparent)]
28    IdentityError(#[from] E2eIdentityError),
29    #[error(transparent)]
30    X509Error(#[from] RustyX509CheckError),
31    #[error(transparent)]
32    UrlError(#[from] url::ParseError),
33    #[error(transparent)]
34    JsonError(#[from] serde_json::Error),
35    #[error(transparent)]
36    X509CertDerError(#[from] x509_cert::der::Error),
37    #[error(transparent)]
38    KeystoreError(#[from] core_crypto_keystore::CryptoKeystoreError),
39}
40
41/// New Certificate Revocation List distribution points.
42#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
43pub struct NewCrlDistributionPoints(Option<HashSet<String>>);
44
45impl From<NewCrlDistributionPoints> for Option<Vec<String>> {
46    fn from(mut dp: NewCrlDistributionPoints) -> Self {
47        dp.take().map(|d| d.into_iter().collect())
48    }
49}
50
51impl IntoIterator for NewCrlDistributionPoints {
52    type Item = String;
53
54    type IntoIter = std::collections::hash_set::IntoIter<String>;
55
56    fn into_iter(self) -> Self::IntoIter {
57        let items = self.0.unwrap_or_default();
58        items.into_iter()
59    }
60}
61
62async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<Option<RjtPkiEnvironment>> {
63    let mut trust_roots = vec![];
64    let Ok(Some(ta_raw)) = data_provider.get_unique::<E2eiAcmeCA>().await else {
65        return Ok(None);
66    };
67
68    trust_roots.push(
69        x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
70    );
71
72    let intermediates = data_provider
73        .load_all::<E2eiIntermediateCert>()
74        .await?
75        .into_iter()
76        .map(|inter| x509_cert::Certificate::from_der(&inter.content))
77        .collect::<core::result::Result<Vec<_>, _>>()?;
78
79    let crls = data_provider
80        .load_all::<E2eiCrl>()
81        .await?
82        .into_iter()
83        .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
84        .collect::<core::result::Result<Vec<_>, _>>()?;
85
86    let params = PkiEnvironmentParams {
87        trust_roots: &trust_roots,
88        intermediates: &intermediates,
89        crls: &crls,
90        time_of_interest: None,
91    };
92
93    Ok(Some(RjtPkiEnvironment::init(params)?))
94}
95
96/// The PKI environment which can be initialized independently from a CoreCrypto session.
97#[derive(Debug, Clone)]
98pub struct PkiEnvironment {
99    /// Implemented by the clients and used by us to make external calls during e2e flow
100    // TODO: remove this config with further implementation of RFC CC2, as soon as hooks are actually used
101    #[expect(dead_code)]
102    hooks: Arc<dyn PkiEnvironmentHooks>,
103    /// The database in which X509 Credentials are stored.
104    database: Database,
105    /// The PkiEnvironmentProvider is the provider used by the MlsCryptoProvider which has to implement
106    /// openmls_traits::OpenMlsCryptoProvideropenMls. It therefore has to be shared with the MlsCryptoProvider but
107    /// we consider this struct to be the place where it actually belongs to.
108    mls_pki_env_provider: PkiEnvironmentProvider,
109}
110
111impl PkiEnvironment {
112    /// Create a new PKI Environment
113    pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Database) -> Result<PkiEnvironment> {
114        let mls_pki_env_provider = restore_pki_env(&database)
115            .await?
116            .map(PkiEnvironmentProvider::from)
117            .unwrap_or_default();
118        Ok(Self {
119            hooks,
120            database,
121            mls_pki_env_provider,
122        })
123    }
124
125    /// Returns true if the inner pki environment has been restored from the database.
126    pub async fn provider_is_setup(&self) -> bool {
127        self.mls_pki_env_provider.is_env_setup().await
128    }
129
130    pub fn mls_pki_env_provider(&self) -> PkiEnvironmentProvider {
131        self.mls_pki_env_provider.clone()
132    }
133
134    pub async fn update_pki_environment_provider(&self) -> Result<()> {
135        if let Some(rjt_pki_environment) = restore_pki_env(&self.database).await? {
136            self.mls_pki_env_provider.update_env(Some(rjt_pki_environment)).await;
137        }
138        Ok(())
139    }
140
141    pub fn database(&self) -> &Database {
142        &self.database
143    }
144}
145
146#[derive(Debug, Clone, Default)]
147pub struct PkiEnvironmentProvider(Arc<RwLock<Option<RjtPkiEnvironment>>>);
148
149impl From<RjtPkiEnvironment> for PkiEnvironmentProvider {
150    fn from(value: RjtPkiEnvironment) -> Self {
151        Self(Arc::new(Some(value).into()))
152    }
153}
154
155impl PkiEnvironmentProvider {
156    pub async fn refresh_time_of_interest(&self) {
157        if let Some(pki) = self.0.write().await.as_mut() {
158            let _ = pki.refresh_time_of_interest();
159        }
160    }
161
162    pub async fn borrow(&self) -> RwLockReadGuard<'_, Option<RjtPkiEnvironment>> {
163        self.0.read().await
164    }
165
166    pub async fn is_env_setup(&self) -> bool {
167        self.0.read().await.is_some()
168    }
169
170    pub async fn update_env(&self, env: Option<RjtPkiEnvironment>) {
171        let mut guard = self.0.write().await;
172        *guard = env;
173    }
174}
175
176#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
177#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
178impl openmls_traits::authentication_service::AuthenticationServiceDelegate for PkiEnvironmentProvider {
179    async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
180        match credential {
181            // We assume that Basic credentials are always valid
182            CredentialRef::Basic { identity: _ } => CredentialAuthenticationStatus::Valid,
183
184            CredentialRef::X509 { certificates } => {
185                self.refresh_time_of_interest().await;
186
187                let binding = self.0.read().await;
188                let Some(pki_env) = binding.as_ref() else {
189                    // This implies that we have a Basic client without a PKI environment setup. Hence they cannot
190                    // validate X509 credentials they see. So we consider it as always valid as we
191                    // have no way to assert the validity
192                    return CredentialAuthenticationStatus::Valid;
193                };
194
195                use x509_cert::der::Decode as _;
196                let Some(cert) = certificates
197                    .first()
198                    .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
199                else {
200                    return CredentialAuthenticationStatus::Invalid;
201                };
202
203                if let Err(validation_error) = pki_env.validate_cert_and_revocation(&cert) {
204                    use crate::x509_check::{
205                        RustyX509CheckError,
206                        reexports::certval::{Error as CertvalError, PathValidationStatus},
207                    };
208
209                    if let RustyX509CheckError::CertValError(CertvalError::PathValidation(
210                        certificate_validation_error,
211                    )) = validation_error
212                    {
213                        match certificate_validation_error {
214                            PathValidationStatus::Valid
215                            | PathValidationStatus::RevocationStatusNotAvailable
216                            | PathValidationStatus::RevocationStatusNotDetermined => {}
217                            PathValidationStatus::CertificateRevoked
218                            | PathValidationStatus::CertificateRevokedEndEntity
219                            | PathValidationStatus::CertificateRevokedIntermediateCa => {
220                                // ? Revoked credentials are A-OK. They still degrade conversations though.
221                                // return CredentialAuthenticationStatus::Revoked;
222                            }
223                            PathValidationStatus::InvalidNotAfterDate => {
224                                // ? Expired credentials are A-OK. They still degrade conversations though.
225                                // return CredentialAuthenticationStatus::Expired;
226                            }
227                            _ => return CredentialAuthenticationStatus::Invalid,
228                        }
229                    } else {
230                        return CredentialAuthenticationStatus::Unknown;
231                    }
232                }
233
234                CredentialAuthenticationStatus::Valid
235            }
236        }
237    }
238}