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    hooks: Arc<dyn PkiEnvironmentHooks>,
101    /// The database in which X509 Credentials are stored.
102    database: Database,
103    /// The PkiEnvironmentProvider is the provider used by the MlsCryptoProvider which has to implement
104    /// openmls_traits::OpenMlsCryptoProvideropenMls. It therefore has to be shared with the MlsCryptoProvider but
105    /// we consider this struct to be the place where it actually belongs to.
106    mls_pki_env_provider: PkiEnvironmentProvider,
107}
108
109impl PkiEnvironment {
110    /// Create a new PKI Environment
111    pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Database) -> Result<PkiEnvironment> {
112        let mls_pki_env_provider = restore_pki_env(&database)
113            .await?
114            .map(PkiEnvironmentProvider::from)
115            .unwrap_or_default();
116        Ok(Self {
117            hooks,
118            database,
119            mls_pki_env_provider,
120        })
121    }
122
123    /// Returns true if the inner pki environment has been restored from the database.
124    pub async fn provider_is_setup(&self) -> bool {
125        self.mls_pki_env_provider.is_env_setup().await
126    }
127
128    pub fn mls_pki_env_provider(&self) -> PkiEnvironmentProvider {
129        self.mls_pki_env_provider.clone()
130    }
131
132    pub async fn update_pki_environment_provider(&self) -> Result<()> {
133        if let Some(rjt_pki_environment) = restore_pki_env(&self.database).await? {
134            self.mls_pki_env_provider.update_env(Some(rjt_pki_environment)).await;
135        }
136        Ok(())
137    }
138
139    pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
140        self.hooks.clone()
141    }
142
143    pub fn database(&self) -> &Database {
144        &self.database
145    }
146}
147
148#[derive(Debug, Clone, Default)]
149pub struct PkiEnvironmentProvider(Arc<RwLock<Option<RjtPkiEnvironment>>>);
150
151impl From<RjtPkiEnvironment> for PkiEnvironmentProvider {
152    fn from(value: RjtPkiEnvironment) -> Self {
153        Self(Arc::new(Some(value).into()))
154    }
155}
156
157impl PkiEnvironmentProvider {
158    pub async fn refresh_time_of_interest(&self) {
159        if let Some(pki) = self.0.write().await.as_mut() {
160            let _ = pki.refresh_time_of_interest();
161        }
162    }
163
164    pub async fn borrow(&self) -> RwLockReadGuard<'_, Option<RjtPkiEnvironment>> {
165        self.0.read().await
166    }
167
168    pub async fn is_env_setup(&self) -> bool {
169        self.0.read().await.is_some()
170    }
171
172    pub async fn update_env(&self, env: Option<RjtPkiEnvironment>) {
173        let mut guard = self.0.write().await;
174        *guard = env;
175    }
176}
177
178#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
179#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
180impl openmls_traits::authentication_service::AuthenticationServiceDelegate for PkiEnvironmentProvider {
181    async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
182        match credential {
183            // We assume that Basic credentials are always valid
184            CredentialRef::Basic { identity: _ } => CredentialAuthenticationStatus::Valid,
185
186            CredentialRef::X509 { certificates } => {
187                self.refresh_time_of_interest().await;
188
189                let binding = self.0.read().await;
190                let Some(pki_env) = binding.as_ref() else {
191                    // This implies that we have a Basic client without a PKI environment setup. Hence they cannot
192                    // validate X509 credentials they see. So we consider it as always valid as we
193                    // have no way to assert the validity
194                    return CredentialAuthenticationStatus::Valid;
195                };
196
197                use x509_cert::der::Decode as _;
198                let Some(cert) = certificates
199                    .first()
200                    .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
201                else {
202                    return CredentialAuthenticationStatus::Invalid;
203                };
204
205                if let Err(validation_error) = pki_env.validate_cert_and_revocation(&cert) {
206                    use crate::x509_check::{
207                        RustyX509CheckError,
208                        reexports::certval::{Error as CertvalError, PathValidationStatus},
209                    };
210
211                    if let RustyX509CheckError::CertValError(CertvalError::PathValidation(
212                        certificate_validation_error,
213                    )) = validation_error
214                    {
215                        match certificate_validation_error {
216                            PathValidationStatus::Valid
217                            | PathValidationStatus::RevocationStatusNotAvailable
218                            | PathValidationStatus::RevocationStatusNotDetermined => {}
219                            PathValidationStatus::CertificateRevoked
220                            | PathValidationStatus::CertificateRevokedEndEntity
221                            | PathValidationStatus::CertificateRevokedIntermediateCa => {
222                                // ? Revoked credentials are A-OK. They still degrade conversations though.
223                                // return CredentialAuthenticationStatus::Revoked;
224                            }
225                            PathValidationStatus::InvalidNotAfterDate => {
226                                // ? Expired credentials are A-OK. They still degrade conversations though.
227                                // return CredentialAuthenticationStatus::Expired;
228                            }
229                            _ => return CredentialAuthenticationStatus::Invalid,
230                        }
231                    } else {
232                        return CredentialAuthenticationStatus::Unknown;
233                    }
234                }
235
236                CredentialAuthenticationStatus::Valid
237            }
238        }
239    }
240}