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 g.set_asn1_flag(Asn1Flag::NAMED_CURVE);
24 g
25}
26
27pub 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
37pub 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
44pub 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 let mut req_bld = X509ReqBuilder::new()?;
55
56 req_bld.set_pubkey(&pkey)?;
58
59 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 req_bld.sign(pkey, MessageDigest::sha256())?;
73
74 Ok(req_bld.build())
76}
77
78#[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 X509::from_pem(certificate.as_bytes())?;
96 PKey::private_key_from_pem(private_key.as_bytes())?;
98
99 Ok(Certificate {
100 private_key,
101 certificate,
102 })
103 }
104
105 pub fn private_key(&self) -> &str {
107 &self.private_key
108 }
109
110 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 pub fn certificate(&self) -> &str {
119 &self.certificate
120 }
121
122 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 pub fn valid_days_left(&self) -> Result<i64> {
137 if cfg!(test) {
139 return Ok(89);
140 }
141
142 let x509 = X509::from_pem(self.certificate.as_bytes())?;
144
145 let not_after = format!("{}", x509.not_after());
147 let expires = parse_date(¬_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}