Skip to main content

didbot_tls/
cert.rs

1//! Turning PEM text — what ACME hands back and what storage reads back — into
2//! the types rustls and the renewal clock need.
3
4use rustls::crypto::CryptoProvider;
5use rustls::pki_types::pem::{self, PemObject};
6use rustls::pki_types::{CertificateDer, PrivateKeyDer};
7use rustls::sign::CertifiedKey;
8use time::OffsetDateTime;
9
10/// Why a PEM chain and key could not become something rustls or the renewal
11/// clock can use.
12#[derive(Debug, thiserror::Error)]
13pub enum CertError {
14    /// The chain contained no certificates, or a certificate could not be
15    /// parsed as DER.
16    #[error("no usable certificate in the chain: {0}")]
17    Chain(pem::Error),
18    /// The key PEM contained no recognisable private key.
19    #[error("no private key found in the key PEM")]
20    NoKey,
21    /// The key PEM did not parse.
22    #[error("could not parse the private key: {0}")]
23    Key(pem::Error),
24    /// The key's algorithm was not one this build's crypto provider signs
25    /// with.
26    #[error("the crypto provider could not load this private key: {0}")]
27    UnsupportedKey(rustls::Error),
28    /// The private key is not the one the leaf certificate was issued for.
29    #[error("the private key does not match the certificate: {0}")]
30    KeyMismatch(rustls::Error),
31    /// The leaf certificate's `notAfter` could not be read.
32    #[error("could not read the certificate's expiry: {0}")]
33    Expiry(String),
34}
35
36/// Parses a certificate chain and private key, both PEM, into a
37/// [`CertifiedKey`] rustls can serve.
38///
39/// Refuses a key that does not match the leaf. rustls would load such a
40/// pair and then fail every handshake it is used for.
41///
42/// `cert_chain_pem` is the leaf followed by any intermediates, in the order a
43/// CA hands them back — `instant_acme::Order::poll_certificate` already
44/// returns them in that order.
45pub fn certified_key_from_pem(
46    cert_chain_pem: &str,
47    key_pem: &str,
48    provider: &CryptoProvider,
49) -> Result<CertifiedKey, CertError> {
50    let chain: Vec<CertificateDer<'static>> =
51        CertificateDer::pem_slice_iter(cert_chain_pem.as_bytes())
52            .collect::<Result<_, _>>()
53            .map_err(CertError::Chain)?;
54    if chain.is_empty() {
55        return Err(CertError::Chain(pem::Error::NoItemsFound));
56    }
57
58    let key = PrivateKeyDer::from_pem_slice(key_pem.as_bytes()).map_err(|err| match err {
59        pem::Error::NoItemsFound => CertError::NoKey,
60        other => CertError::Key(other),
61    })?;
62
63    CertifiedKey::from_der(chain, key, provider).map_err(|err| match err {
64        rustls::Error::InconsistentKeys(_) => CertError::KeyMismatch(err),
65        other => CertError::UnsupportedKey(other),
66    })
67}
68
69/// The leaf certificate's `notAfter`, read straight from the DER rather than
70/// trusted from whatever ACME or storage claims — the certificate is the one
71/// party that cannot lie about its own expiry.
72pub fn leaf_expiry(chain: &[CertificateDer<'_>]) -> Result<OffsetDateTime, CertError> {
73    let leaf = chain
74        .first()
75        .ok_or_else(|| CertError::Expiry("empty chain".to_owned()))?;
76    let (_, parsed) = x509_parser::parse_x509_certificate(leaf.as_ref())
77        .map_err(|err| CertError::Expiry(err.to_string()))?;
78    let not_after = parsed.validity().not_after;
79    OffsetDateTime::from_unix_timestamp(not_after.timestamp())
80        .map_err(|err| CertError::Expiry(err.to_string()))
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::test_support::self_signed_pem;
87
88    #[test]
89    fn round_trips_a_self_signed_pair() {
90        let (cert_pem, key_pem) = self_signed_pem("round-trip.example.invalid");
91        let provider = rustls::crypto::ring::default_provider();
92        let certified = certified_key_from_pem(&cert_pem, &key_pem, &provider).unwrap();
93        assert_eq!(certified.cert.len(), 1);
94    }
95
96    #[test]
97    fn reads_the_expiry_back_from_the_der() {
98        let (cert_pem, _key_pem) = self_signed_pem("expiry.example.invalid");
99        let chain: Vec<CertificateDer<'static>> =
100            CertificateDer::pem_slice_iter(cert_pem.as_bytes())
101                .collect::<Result<_, _>>()
102                .unwrap();
103        let expiry = leaf_expiry(&chain).unwrap();
104        assert!(expiry > OffsetDateTime::now_utc());
105    }
106
107    #[test]
108    fn refuses_an_empty_chain() {
109        let (_cert_pem, key_pem) = self_signed_pem("empty-chain.example.invalid");
110        let provider = rustls::crypto::ring::default_provider();
111        let err = certified_key_from_pem("", &key_pem, &provider).unwrap_err();
112        assert!(matches!(err, CertError::Chain(_)));
113    }
114
115    #[test]
116    fn refuses_a_key_from_another_pair() {
117        let (cert_pem, _) = self_signed_pem("leaf.example.invalid");
118        let (_, other_key_pem) = self_signed_pem("other.example.invalid");
119        let provider = rustls::crypto::ring::default_provider();
120        let err = certified_key_from_pem(&cert_pem, &other_key_pem, &provider).unwrap_err();
121        assert!(matches!(err, CertError::KeyMismatch(_)), "{err:?}");
122    }
123
124    #[test]
125    fn refuses_a_pem_with_no_key() {
126        let (cert_pem, _key_pem) = self_signed_pem("no-key.example.invalid");
127        let provider = rustls::crypto::ring::default_provider();
128        let err = certified_key_from_pem(&cert_pem, "", &provider).unwrap_err();
129        assert!(matches!(err, CertError::NoKey));
130    }
131}