wire_e2e_identity/acme/
certificate.rs

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