1use sha2::{Digest, Sha256};
11use std::net::IpAddr;
12use time::{Duration, OffsetDateTime};
13
14pub const VALIDITY_DAYS: i64 = 13;
16
17#[derive(Clone)]
18pub struct Cert {
19 pub cert_der: Vec<u8>,
20 pub key_der: Vec<u8>,
22 pub fingerprint_sha256: [u8; 32],
23 pub not_after: OffsetDateTime,
24}
25
26impl std::fmt::Debug for Cert {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("Cert")
29 .field("fingerprint", &self.fingerprint_hex())
30 .field("not_after", &self.not_after)
31 .finish()
32 }
33}
34
35impl Cert {
36 pub fn generate(ips: &[IpAddr]) -> Result<Cert, rcgen::Error> {
38 let key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
39 let mut params = rcgen::CertificateParams::new(Vec::<String>::new())?;
40 params.subject_alt_names = ips
41 .iter()
42 .map(|ip| rcgen::SanType::IpAddress(*ip))
43 .collect();
44 let now = OffsetDateTime::now_utc();
45 params.not_before = now - Duration::minutes(1);
48 params.not_after = now + Duration::days(VALIDITY_DAYS);
49 let mut dn = rcgen::DistinguishedName::new();
50 dn.push(rcgen::DnType::CommonName, "seyd-agent");
51 params.distinguished_name = dn;
52 let not_after = params.not_after;
53 let cert = params.self_signed(&key)?;
54 let cert_der = cert.der().to_vec();
55 let fingerprint_sha256: [u8; 32] = Sha256::digest(&cert_der).into();
56 Ok(Cert {
57 cert_der,
58 key_der: key.serialize_der(),
59 fingerprint_sha256,
60 not_after,
61 })
62 }
63
64 pub fn fingerprint_hex(&self) -> String {
65 hex::encode(self.fingerprint_sha256)
66 }
67
68 pub fn days_left(&self) -> f64 {
70 (self.not_after - OffsetDateTime::now_utc()).as_seconds_f64() / 86_400.0
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn generates_short_lived_ecdsa_cert() {
80 let c = Cert::generate(&["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()]).unwrap();
81 assert_eq!(c.fingerprint_hex().len(), 64);
82 assert!(c.days_left() > 12.9 && c.days_left() <= 13.0);
83 assert!(!c.key_der.is_empty());
84 }
85}