acme_micro/
cert.rs

1use crate::error::Result;
2use lazy_static::lazy_static;
3use openssl::{
4    ec::{Asn1Flag, EcGroup, EcKey},
5    hash::MessageDigest,
6    nid::Nid,
7    pkey::{self, PKey},
8    rsa::Rsa,
9    stack::Stack,
10    x509::{extension::SubjectAlternativeName, X509Req, X509ReqBuilder, X509},
11};
12
13use crate::error::*;
14
15lazy_static! {
16    pub(crate) static ref EC_GROUP_P256: EcGroup = ec_group(Nid::X9_62_PRIME256V1);
17    pub(crate) static ref EC_GROUP_P384: EcGroup = ec_group(Nid::SECP384R1);
18}
19
20fn ec_group(nid: Nid) -> EcGroup {
21    let mut g = EcGroup::from_curve_name(nid).expect("EcGroup");
22    // this is required for openssl 1.0.x (but not 1.1.x)
23    g.set_asn1_flag(Asn1Flag::NAMED_CURVE);
24    g
25}
26
27/// Make an RSA private key (from which we can derive a public key).
28///
29/// This library does not check the number of bits used to create the key pair.
30/// For Let's Encrypt, the bits must be between 2048 and 4096.
31pub fn create_rsa_key(bits: u32) -> Result<PKey<pkey::Private>> {
32    let pri_key_rsa = Rsa::generate(bits)?;
33    let pkey = PKey::from_rsa(pri_key_rsa)?;
34    Ok(pkey)
35}
36
37/// Make a P-256 private key (from which we can derive a public key).
38pub fn create_p256_key() -> Result<PKey<pkey::Private>> {
39    let pri_key_ec = EcKey::generate(&*EC_GROUP_P256)?;
40    let pkey = PKey::from_ec_key(pri_key_ec)?;
41    Ok(pkey)
42}
43
44/// Make a P-384 private key pair (from which we can derive a public key).
45pub fn create_p384_key() -> Result<PKey<pkey::Private>> {
46    let pri_key_ec = EcKey::generate(&*EC_GROUP_P384)?;
47    let pkey = PKey::from_ec_key(pri_key_ec)?;
48    Ok(pkey)
49}
50
51pub(crate) fn create_csr(pkey: &PKey<pkey::Private>, domains: &[&str]) -> Result<X509Req> {
52    //
53    // the csr builder
54    let mut req_bld = X509ReqBuilder::new()?;
55
56    // set private/public key in builder
57    req_bld.set_pubkey(&pkey)?;
58
59    // set all domains as alt names
60    let mut stack = Stack::new()?;
61    let ctx = req_bld.x509v3_context(None);
62    let mut an = SubjectAlternativeName::new();
63    for d in domains {
64        an.dns(d);
65    }
66
67    let ext = an.build(&ctx).context("SubjectAlternativeName::build")?;
68    stack.push(ext).expect("Stack::push");
69    req_bld.add_extensions(&stack)?;
70
71    // sign it
72    req_bld.sign(pkey, MessageDigest::sha256())?;
73
74    // the csr
75    Ok(req_bld.build())
76}
77
78/// Encapsulated certificate and private key.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Certificate {
81    private_key: String,
82    certificate: String,
83}
84
85impl Certificate {
86    pub(crate) fn new(private_key: String, certificate: String) -> Self {
87        Certificate {
88            private_key,
89            certificate,
90        }
91    }
92
93    pub fn parse(private_key: String, certificate: String) -> Result<Self> {
94        // validate certificate
95        X509::from_pem(certificate.as_bytes())?;
96        // validate private key
97        PKey::private_key_from_pem(private_key.as_bytes())?;
98
99        Ok(Certificate {
100            private_key,
101            certificate,
102        })
103    }
104
105    /// The PEM encoded private key.
106    pub fn private_key(&self) -> &str {
107        &self.private_key
108    }
109
110    /// The private key as DER.
111    pub fn private_key_der(&self) -> Result<Vec<u8>> {
112        let pkey = PKey::private_key_from_pem(self.private_key.as_bytes())?;
113        let der = pkey.private_key_to_der()?;
114        Ok(der)
115    }
116
117    /// The PEM encoded issued certificate.
118    pub fn certificate(&self) -> &str {
119        &self.certificate
120    }
121
122    /// The issued certificate as DER.
123    pub fn certificate_der(&self) -> Result<Vec<u8>> {
124        let x509 = X509::from_pem(self.certificate.as_bytes())?;
125        let der = x509.to_der()?;
126        Ok(der)
127    }
128
129    /// Inspect the certificate to count the number of (whole) valid days left.
130    ///
131    /// It's up to the ACME API provider to decide how long an issued certificate is valid.
132    /// Let's Encrypt sets the validity to 90 days. This function reports 89 days for newly
133    /// issued cert, since it counts _whole_ days.
134    ///
135    /// It is possible to get negative days for an expired certificate.
136    pub fn valid_days_left(&self) -> Result<i64> {
137        // the cert used in the tests is not valid to load as x509
138        if cfg!(test) {
139            return Ok(89);
140        }
141
142        // load as x509
143        let x509 = X509::from_pem(self.certificate.as_bytes())?;
144
145        // convert asn1 time to Tm
146        let not_after = format!("{}", x509.not_after());
147        // Display trait produces this format, which is kinda dumb.
148        // Apr 19 08:48:46 2019 GMT
149        let expires = parse_date(&not_after)?;
150        let dur = expires - time::OffsetDateTime::now_utc();
151
152        Ok(dur.whole_days())
153    }
154}
155
156fn parse_date(s: &str) -> Result<time::OffsetDateTime> {
157    debug!("Parse date/time: {}", s);
158    use time_fmt::parse::TimeZoneSpecifier;
159    use time_tz::{Offset, TimeZone};
160    let (datetime, zonespecifier) =
161        time_fmt::parse::parse_date_time_maybe_with_zone("%h %e %H:%M:%S %Y %Z", s)
162            .context("parsing of date failed")?;
163    let offset_datetime = match zonespecifier {
164        Some(TimeZoneSpecifier::Offset(offset)) => datetime.assume_offset(offset),
165        None => datetime.assume_utc(),
166        Some(TimeZoneSpecifier::Name(name)) => {
167            let zone = time_tz::timezones::get_by_name(name)
168                .ok_or(anyhow::anyhow!("unknown timezone specified"))?;
169            datetime.assume_offset(zone.get_offset_primary().to_utc())
170        }
171    };
172    Ok(offset_datetime)
173}
174
175#[cfg(test)]
176mod test {
177    use super::*;
178
179    #[test]
180    fn test_parse_date() {
181        let x = parse_date("May  3 07:40:15 2019 GMT")
182            .context("input date parsing failed")
183            .unwrap();
184        assert_eq!(
185            time_fmt::format::format_offset_date_time("%F %T", x)
186                .context("date formatting failed")
187                .unwrap(),
188            "2019-05-03 07:40:15"
189        );
190    }
191}