Skip to main content

wire_e2e_identity/pki_env/
mod.rs

1//! PKI Environment API
2
3mod crl;
4pub mod hooks;
5
6#[cfg(test)]
7mod dummy;
8
9use std::{collections::HashSet, sync::Arc};
10
11use async_lock::Mutex;
12use certval::{
13    CertSource, CertVector as _, CertificationPathSettings, Error as CertvalError, PathValidationStatus, TaSource,
14    TimeOfInterest,
15};
16use core_crypto_keystore::{
17    Database, Transaction,
18    entities::{X509Crl, X509IntermediateCert, X509TrustAnchor},
19    traits::{EntityDatabaseMutation as _, EntityDeleteBorrowed, FetchFromDatabase},
20};
21use openmls_traits::authentication_service::{CredentialAuthenticationStatus, CredentialRef};
22use x509_cert::{
23    Certificate,
24    der::{Decode as _, Encode as _},
25};
26
27use crate::{
28    pki_env::hooks::PkiEnvironmentHooks,
29    validation,
30    validation::{extract_crl_uris, now, prepare_environment, validate_cert, validate_trust_anchor_cert},
31};
32
33pub type Result<T> = core::result::Result<T, Error>;
34
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37    #[error("The trust anchor certificate couldn't be loaded from the database.")]
38    NoTrustAnchor,
39    #[error("The trust anchor certificate already exists in the database.")]
40    TrustAnchorAlreadyExists,
41    #[error("Failed to fetch CRL from '{uri}': HTTP {status}")]
42    CrlFetchUnsuccessful { uri: String, status: u16 },
43    #[error(transparent)]
44    HooksError(#[from] hooks::PkiEnvironmentHooksError),
45    #[error(transparent)]
46    Validation(#[from] validation::Error),
47    #[error(transparent)]
48    UrlError(#[from] url::ParseError),
49    #[error(transparent)]
50    JsonError(#[from] serde_json::Error),
51    #[error(transparent)]
52    X509CertDerError(#[from] x509_cert::der::Error),
53    #[error(transparent)]
54    KeystoreError(#[from] core_crypto_keystore::CryptoKeystoreError),
55    #[error("certval error: {0}")]
56    Certval(certval::Error),
57    #[error("spki error: {0}")]
58    Spki(spki::Error),
59}
60
61/// New Certificate Revocation List distribution points.
62#[derive(Debug, Clone, derive_more::From, derive_more::Into, derive_more::Deref, derive_more::DerefMut)]
63pub struct NewCrlDistributionPoints(Option<HashSet<String>>);
64
65impl From<NewCrlDistributionPoints> for Option<Vec<String>> {
66    fn from(mut dp: NewCrlDistributionPoints) -> Self {
67        dp.take().map(|d| d.into_iter().collect())
68    }
69}
70
71impl IntoIterator for NewCrlDistributionPoints {
72    type Item = String;
73
74    type IntoIter = std::collections::hash_set::IntoIter<String>;
75
76    fn into_iter(self) -> Self::IntoIter {
77        let items = self.0.unwrap_or_default();
78        items.into_iter()
79    }
80}
81
82async fn restore_pki_env(data_provider: &impl FetchFromDatabase) -> Result<certval::environment::PkiEnvironment> {
83    let mut trust_roots = vec![];
84    for ta_raw in data_provider.load_all::<X509TrustAnchor>().await? {
85        trust_roots.push(
86            x509_cert::Certificate::from_der(&ta_raw.content).map(x509_cert::anchor::TrustAnchorChoice::Certificate)?,
87        );
88    }
89
90    let intermediates = data_provider
91        .load_all::<X509IntermediateCert>()
92        .await?
93        .into_iter()
94        .map(|inter| x509_cert::Certificate::from_der(&inter.content))
95        .collect::<core::result::Result<Vec<_>, _>>()?;
96
97    let crls = data_provider
98        .load_all::<X509Crl>()
99        .await?
100        .into_iter()
101        .map(|crl| x509_cert::crl::CertificateList::from_der(&crl.content))
102        .collect::<core::result::Result<Vec<_>, _>>()?;
103
104    Ok(prepare_environment(&trust_roots, &intermediates, &crls)?)
105}
106
107/// The PKI environment which can be initialized independently from a CoreCrypto session.
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: Arc<Database>,
113    env: Mutex<certval::environment::PkiEnvironment>,
114}
115
116impl std::fmt::Debug for PkiEnvironment {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("PkiEnvironment")
119            .field("hooks", &self.hooks)
120            .field("database", &self.database)
121            .field("env", &format!("{:p}", &self.env))
122            .finish()
123    }
124}
125
126impl PkiEnvironment {
127    /// Create a new PKI Environment
128    pub async fn new(hooks: Arc<dyn PkiEnvironmentHooks>, database: Arc<Database>) -> Result<PkiEnvironment> {
129        let env = restore_pki_env(&*database).await?;
130        Ok(Self {
131            hooks,
132            database,
133            env: Mutex::new(env),
134        })
135    }
136
137    /// Return certificates that are used as trust anchors.
138    pub async fn get_trust_anchors(&self) -> Vec<Certificate> {
139        self.env
140            .lock()
141            .await
142            .get_trust_anchors()
143            .iter()
144            .map(|choice| Certificate::from_der(&choice.encoded_ta).expect("valid DER"))
145            .collect()
146    }
147
148    /// Get the hooks.
149    pub fn hooks(&self) -> Arc<dyn PkiEnvironmentHooks> {
150        self.hooks.clone()
151    }
152
153    /// Get the database.
154    pub fn database(&self) -> &Database {
155        &self.database
156    }
157
158    /// Get an Arc to the database.
159    ///
160    /// In general [`Self::database`] is lighter-weight and should be preferred.
161    pub fn database_arc(&self) -> Arc<Database> {
162        self.database.clone()
163    }
164
165    /// Adds the certificate as a trust anchor to the PKI environment.
166    ///
167    /// The certificate is saved to the database, and included in the PKI environment for
168    /// future validation.
169    pub async fn add_trust_anchor(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
170        // Validate it (expiration & signature only)
171        validate_trust_anchor_cert(&*self.env.lock().await, &cert)?;
172
173        let fingerprint = cert
174            .tbs_certificate()
175            .subject_public_key_info()
176            .fingerprint_bytes()
177            .map_err(Error::Spki)?
178            .to_vec();
179
180        // A trust anchor can only be added once
181        if tx.get::<X509TrustAnchor>(&fingerprint).await?.is_some() {
182            return Err(Error::TrustAnchorAlreadyExists);
183        }
184
185        let cert_data = X509TrustAnchor {
186            fingerprint,
187            content: cert.to_der()?,
188        };
189
190        cert_data.save(tx)?;
191
192        let mut trust_anchors = TaSource::new();
193        trust_anchors.push(certval::CertFile {
194            filename: "".to_string(),
195            bytes: cert.to_der()?,
196        });
197        trust_anchors.initialize().map_err(Error::Certval)?;
198        self.env.lock().await.add_trust_anchor_source(Box::new(trust_anchors));
199        Ok(())
200    }
201
202    /// Remove the trust anchor from the PKI environment.
203    ///
204    /// Note that any certificates relying on the removed trust anchor may no longer
205    /// validate.
206    pub async fn remove_trust_anchor(&self, tx: &Transaction, fingerprint: &[u8]) -> Result<()> {
207        X509TrustAnchor::delete_borrowed(tx, fingerprint)?;
208
209        let anchors = tx.load_all::<X509TrustAnchor>().await?;
210
211        let mut guard = self.env.lock().await;
212        guard.clear_trust_anchor_sources();
213
214        let mut source = TaSource::new();
215        for anchor in anchors {
216            let mut anchor = Arc::unwrap_or_clone(anchor);
217            source.push(certval::CertFile {
218                filename: "".to_string(),
219                bytes: std::mem::take(&mut anchor.content),
220            });
221        }
222
223        source.initialize().map_err(Error::Certval)?;
224        guard.add_trust_anchor_source(Box::new(source));
225
226        Ok(())
227    }
228
229    /// Adds the certificate to the PKI environment.
230    ///
231    /// The certificate is saved to the database, and included in the PKI environment for
232    /// future validation.
233    ///
234    /// CRL (Certificate Revocation List) distribution points are extracted from the certificate and
235    /// an attempt is made to fetch a CRL from each one.
236    pub async fn add_intermediate_cert(&self, tx: &Transaction, cert: Certificate) -> Result<()> {
237        let toi = TimeOfInterest::from_unix_secs(now()?)?;
238
239        // Save cert's DER representation to the database
240        let (ski, aki) = crate::utils::extract_ski_aki_from_cert(&cert)?;
241        let ski_aki_pair = format!("{ski}:{}", aki.unwrap_or_default());
242        let cert_der = cert.to_der()?;
243        let intermediate_cert = X509IntermediateCert {
244            content: cert_der,
245            ski_aki_pair,
246        };
247
248        intermediate_cert.save(tx)?;
249
250        // Get CRL distribution points and CRLs
251        let dps: Vec<String> = extract_crl_uris(&cert)?.iter().flatten().cloned().collect();
252        let crls = self.fetch_crls(dps.iter().map(AsRef::as_ref)).await?;
253
254        // Save all CRLs to the database
255        for (distribution_point, crl) in &crls {
256            self.save_crl(tx, distribution_point, crl).await?;
257        }
258
259        let mut cps = CertificationPathSettings::new();
260        cps.set_time_of_interest(toi);
261        let mut cert_source = CertSource::new();
262        cert_source.push(certval::CertFile {
263            filename: "".to_string(),
264            bytes: cert.to_der()?,
265        });
266
267        let mut guard = self.env.lock().await;
268        cert_source.initialize(&cps).map_err(Error::Certval)?;
269        cert_source.find_all_partial_paths(&guard, &cps);
270        guard.add_certificate_source(Box::new(cert_source));
271
272        Ok(())
273    }
274
275    /// Validate an end-entity X509 certificate.
276    ///
277    /// Performs validation of the provided certificate in the context
278    /// defined by the set of trust anchors and intermediate certificates
279    /// contained in this PKI environment. Revocation check is performed
280    /// and time of interest is set to the time of the call.
281    pub async fn validate_cert(&self, cert: &x509_cert::Certificate) -> validation::Result<()> {
282        validate_cert(&*self.env.lock().await, cert, true)
283    }
284
285    /// Validate an X509 credential.
286    ///
287    /// # Panics
288    ///
289    /// Panics if the provided credential is not of type X509.
290    pub async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
291        let CredentialRef::X509 { certificates } = credential else {
292            panic!("this function can only be called with an X509 credential");
293        };
294
295        let Some(cert) = certificates
296            .first()
297            .and_then(|cert_raw| x509_cert::Certificate::from_der(cert_raw).ok())
298        else {
299            return CredentialAuthenticationStatus::Invalid;
300        };
301
302        match validate_cert(&*self.env.lock().await, &cert, true) {
303            Err(validation::Error::CertValError(CertvalError::PathValidation(
304                PathValidationStatus::CertificateRevoked
305                | PathValidationStatus::CertificateRevokedEndEntity
306                | PathValidationStatus::CertificateRevokedIntermediateCa,
307            ))) => CredentialAuthenticationStatus::Revoked,
308            Err(validation::Error::CertValError(CertvalError::PathValidation(
309                PathValidationStatus::InvalidNotAfterDate,
310            ))) => CredentialAuthenticationStatus::Expired,
311            Err(validation::Error::CertValError(CertvalError::PathValidation(_))) => {
312                CredentialAuthenticationStatus::Invalid
313            }
314            Err(_) => CredentialAuthenticationStatus::Unknown,
315            Ok(_) => CredentialAuthenticationStatus::Valid,
316        }
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use spki::der::DecodePem as _;
323
324    use super::*;
325
326    const EXAMPLE_CERT_PEM: &str = "
327-----BEGIN CERTIFICATE-----
328MIIBkzCCAUWgAwIBAgIUHFYIFRkm33GKIOb4xLeNtkjl3TIwBQYDK2VwMDcxFTAT
329BgNVBAMMDFRlc3QgUm9vdCBDQTERMA8GA1UECgwIVGVzdCBPcmcxCzAJBgNVBAYT
330AlVTMB4XDTI2MDUyODE1MzA0NFoXDTM2MDUyNTE1MzA0NFowNzEVMBMGA1UEAwwM
331VGVzdCBSb290IENBMREwDwYDVQQKDAhUZXN0IE9yZzELMAkGA1UEBhMCVVMwKjAF
332BgMrZXADIQDa0nMgIgBZeNM2ysNUVp80zwjZNqPJt7HYK3GX7GPp9aNjMGEwHQYD
333VR0OBBYEFHA0MmaaNGOTuBvdo3zzQoKFJ3p5MB8GA1UdIwQYMBaAFHA0MmaaNGOT
334uBvdo3zzQoKFJ3p5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAUG
335AytlcANBAJffPzL50OWnmEBo9mGBQfPVzKRIfFc8EaXox1D5VF9cC1r8nRa0hUq+
336LOVS/gxNk618+PKA2bYq67MZQXCYGgk=
337-----END CERTIFICATE-----
338";
339
340    #[tokio::test]
341    async fn can_add_trust_anchor() {
342        let db = Database::open_in_memory().unwrap();
343        let tx = db.new_transaction().await.unwrap();
344        let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
345        let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
346        assert!(pki_env.add_trust_anchor(&tx, cert.clone()).await.is_ok());
347        assert!(matches!(
348            pki_env.add_trust_anchor(&tx, cert).await,
349            Err(Error::TrustAnchorAlreadyExists)
350        ));
351    }
352
353    #[tokio::test]
354    async fn can_remove_trust_anchor() {
355        let db = Database::open_in_memory().unwrap();
356        let tx = db.new_transaction().await.unwrap();
357        let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
358        let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
359        pki_env.add_trust_anchor(&tx, cert.clone()).await.unwrap();
360
361        let certs = pki_env.get_trust_anchors().await;
362        assert_eq!(certs.len(), 1);
363
364        pki_env
365            .remove_trust_anchor(
366                &tx,
367                &certs[0]
368                    .tbs_certificate()
369                    .subject_public_key_info()
370                    .fingerprint_bytes()
371                    .expect("Getting fingerprint of subject plublic key info"),
372            )
373            .await
374            .unwrap();
375        assert_eq!(pki_env.get_trust_anchors().await.len(), 0);
376    }
377
378    #[tokio::test]
379    async fn can_add_intermediate_cert() {
380        let db = Database::open_in_memory().unwrap();
381        let tx = db.new_transaction().await.unwrap();
382        let pki_env = PkiEnvironment::with_dummy_hooks(db).await.unwrap();
383        let cert = x509_cert::Certificate::from_pem(EXAMPLE_CERT_PEM).unwrap();
384        assert!(pki_env.add_intermediate_cert(&tx, cert).await.is_ok());
385    }
386}