wire_e2e_identity/pki_env/
mod.rs

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