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