wire_e2e_identity/acme/
certificate.rs

1use rusty_jwt_tools::prelude::*;
2use x509_cert::{Certificate, anchor::TrustAnchorChoice};
3
4use crate::acme::{
5    error::CertificateError,
6    identifier::CanonicalIdentifier,
7    prelude::*,
8    x509_check::revocation::{PkiEnvironment, PkiEnvironmentParams},
9};
10
11impl RustyAcme {
12    /// For fetching the generated certificate
13    /// see [RFC 8555 Section 7.4.2](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2)
14    pub fn certificate_req(
15        finalize: AcmeFinalize,
16        account: AcmeAccount,
17        alg: JwsAlgorithm,
18        kp: &Pem,
19        previous_nonce: String,
20    ) -> RustyAcmeResult<AcmeJws> {
21        // Extract the account URL from previous response which created a new account
22        let acct_url = account.acct_url()?;
23
24        // No payload required for getting a certificate
25        let payload = None::<serde_json::Value>;
26        let req = AcmeJws::new(alg, previous_nonce, &finalize.certificate, Some(&acct_url), payload, kp)?;
27        Ok(req)
28    }
29
30    /// see [RFC 8555 Section 7.4.2](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2)
31    pub fn certificate_response(
32        response: String,
33        order: AcmeOrder,
34        hash_alg: HashAlgorithm,
35        env: Option<&PkiEnvironment>,
36    ) -> RustyAcmeResult<Vec<Vec<u8>>> {
37        order.verify()?;
38        let pems: Vec<pem::Pem> = pem::parse_many(response)?;
39        let intermediates = Self::extract_intermediates(&pems)?;
40        let env = env.and_then(|env| {
41            let trust_anchors = env.get_trust_anchors().unwrap_or_default();
42            let trust_roots: Vec<TrustAnchorChoice> = trust_anchors.iter().map(|f| f.decoded_ta.clone()).collect();
43            PkiEnvironment::init(PkiEnvironmentParams {
44                trust_roots: trust_roots.as_slice(),
45                intermediates: intermediates.as_slice(),
46                crls: &[],
47                time_of_interest: None,
48            })
49            .ok()
50        });
51
52        pems.into_iter()
53            .enumerate()
54            .try_fold(vec![], move |mut acc, (i, cert_pem)| -> RustyAcmeResult<Vec<Vec<u8>>> {
55                // see https://datatracker.ietf.org/doc/html/rfc8555#section-11.4
56                if cert_pem.tag() != "CERTIFICATE" {
57                    return Err(RustyAcmeError::SmallstepImplementationError(
58                        "Something other than x509 certificates was returned by the ACME server",
59                    ));
60                }
61                use x509_cert::der::Decode as _;
62                let cert = x509_cert::Certificate::from_der(cert_pem.contents())?;
63
64                PkiEnvironment::extract_ski_aki_from_cert(&cert)?;
65
66                // only verify that leaf has the right identity fields
67                if i == 0 {
68                    Self::verify_leaf_certificate(cert, &order.try_get_coalesce_identifier()?, hash_alg, env.as_ref())?;
69                }
70                acc.push(cert_pem.contents().to_vec());
71                Ok(acc)
72            })
73    }
74
75    fn extract_intermediates(pems: &[pem::Pem]) -> RustyAcmeResult<Vec<Certificate>> {
76        use x509_cert::der::Decode as _;
77        pems.iter()
78            .skip(1)
79            .try_fold(vec![], |mut acc, pem| -> RustyAcmeResult<Vec<Certificate>> {
80                let cert = x509_cert::Certificate::from_der(pem.contents())?;
81                acc.push(cert);
82                Ok(acc)
83            })
84    }
85
86    /// Ensure that the generated certificate matches our expectations (i.e. that the acme server is configured the
87    /// right way) We verify that the fields in the certificate match the ones in the ACME order
88    fn verify_leaf_certificate(
89        cert: Certificate,
90        identifier: &CanonicalIdentifier,
91        hash_alg: HashAlgorithm,
92        env: Option<&PkiEnvironment>,
93    ) -> RustyAcmeResult<()> {
94        if let Some(env) = env {
95            env.validate_cert(&cert)?;
96        }
97
98        // TODO: verify that cert is signed by enrollment.sign_kp
99        let cert_identity = cert.extract_identity(env, hash_alg)?;
100
101        let invalid_client_id =
102            ClientId::try_from_qualified(&cert_identity.client_id)? != ClientId::try_from_uri(&identifier.client_id)?;
103        if invalid_client_id {
104            return Err(CertificateError::ClientIdMismatch)?;
105        }
106
107        let invalid_display_name = cert_identity.display_name != identifier.display_name;
108        if invalid_display_name {
109            return Err(CertificateError::DisplayNameMismatch)?;
110        }
111
112        let invalid_handle = cert_identity.handle != identifier.handle;
113        if invalid_handle {
114            return Err(CertificateError::HandleMismatch)?;
115        }
116
117        let invalid_domain = cert_identity.domain != identifier.domain;
118        if invalid_domain {
119            return Err(CertificateError::DomainMismatch)?;
120        }
121        Ok(())
122    }
123}