const DAY_MS: i64 = 86_400_000;
pub const RENEW_AT_FRACTION_REMAINING: f64 = 0.25;
pub const REFUSE_DAYS: i64 = 14;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Grade {
Fine,
Renew,
Refuse,
Expired,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Expiry {
pub days_left: i64,
pub lifetime_days: i64,
pub grade: Grade,
}
pub fn grade(now_unix_ms: i64, not_before_unix_ms: i64, not_after_unix_ms: i64) -> Expiry {
let lifetime_days = (not_after_unix_ms - not_before_unix_ms).div_euclid(DAY_MS);
let days_left = (not_after_unix_ms - now_unix_ms).div_euclid(DAY_MS);
if lifetime_days <= 0 {
return Expiry {
days_left,
lifetime_days,
grade: Grade::Expired,
};
}
let renew_at = (lifetime_days as f64 * RENEW_AT_FRACTION_REMAINING).floor() as i64;
let refuse_at = REFUSE_DAYS.min((renew_at - 1).max(0));
let grade = if days_left < 0 {
Grade::Expired
} else if days_left <= refuse_at {
Grade::Refuse
} else if days_left <= renew_at {
Grade::Renew
} else {
Grade::Fine
};
Expiry {
days_left,
lifetime_days,
grade,
}
}
impl Expiry {
pub fn line(&self, who: &str) -> String {
match self.grade {
Grade::Fine => format!(
"{who} is good for {} more day(s) of its {}-day life.",
self.days_left, self.lifetime_days
),
Grade::Renew => format!(
"RE-ISSUE NOW: {who} expires in {} day(s) of its {}-day life. This box has no shell \
and its only door is the control plane this certificate opens, so a lapse is \
not a degraded service — it is a box nobody can repair. WireGuard underneath \
keeps it REACHABLE and cannot make it repairable.",
self.days_left, self.lifetime_days
),
Grade::Refuse => format!(
"REFUSED WITH {} DAY(S) STILL VALID: {who} expires in {} day(s). It is refused \
NOW, while the certificate still works and this box can still be talked to, \
because in {} day(s) it could not be. Re-enrol it — the control plane is open \
to you today and will not be. A warning here would be a line in a log read \
after the box was already dead.",
self.days_left, self.days_left, self.days_left
),
Grade::Expired => format!(
"EXPIRED: {who} lapsed {} day(s) ago. The mutual listener refuses this box at \
the handshake, before any RPC it could be repaired with. If it has no shell, \
the way back is re-imaging it from the medium and re-enrolling — WireGuard \
still carries packets to it and there is nothing at the other end that will \
take them.",
-self.days_left
),
}
}
pub fn is_refusal(&self) -> bool {
matches!(self.grade, Grade::Refuse | Grade::Expired)
}
}
pub fn grade_der(now_unix_ms: i64, der: &[u8]) -> Result<Expiry, crate::Error> {
let (_rest, cert) = x509_parser::parse_x509_certificate(der).map_err(|err| {
crate::Error::Pki(format!("this is not a usable X.509 certificate: {err}"))
})?;
let validity = cert.validity();
let ms = |t: x509_parser::time::ASN1Time| t.timestamp() * 1000;
Ok(grade(
now_unix_ms,
ms(validity.not_before),
ms(validity.not_after),
))
}
pub fn grade_pem(now_unix_ms: i64, pem: &[u8]) -> Result<Expiry, crate::Error> {
let (_rest, block) = x509_parser::pem::parse_x509_pem(pem)
.map_err(|err| crate::Error::Pki(format!("this is not a usable PEM certificate: {err}")))?;
grade_der(now_unix_ms, &block.contents)
}
#[cfg(test)]
mod tests {
use super::*;
const YEAR: i64 = 365;
fn at(day_of_life: i64, lifetime: i64) -> Expiry {
let not_before = 1_700_000_000_000;
let not_after = not_before + lifetime * DAY_MS;
grade(not_before + day_of_life * DAY_MS, not_before, not_after)
}
#[test]
fn every_grade_can_happen() {
assert_eq!(at(0, YEAR).grade, Grade::Fine);
assert_eq!(at(280, YEAR).grade, Grade::Renew);
assert_eq!(at(360, YEAR).grade, Grade::Refuse);
assert_eq!(at(400, YEAR).grade, Grade::Expired);
}
#[test]
fn the_refusal_comes_before_the_lapse() {
let e = at(YEAR - REFUSE_DAYS, YEAR);
assert_eq!(e.grade, Grade::Refuse);
assert!(
e.days_left > 0,
"refusing only once it has lapsed is refusing too late"
);
assert!(e.is_refusal());
let text = e.line("this twin's client certificate");
assert!(text.contains("STILL VALID"), "{text}");
assert!(text.contains("this twin's client certificate"), "{text}");
}
#[test]
fn a_short_lifetime_gets_a_proportionate_warning() {
assert_eq!(at(0, 60).grade, Grade::Fine);
assert_eq!(at(30, 60).grade, Grade::Fine);
assert_eq!(
at(45, 60).grade,
Grade::Renew,
"a quarter of 60 days left is the renewal edge"
);
assert_eq!(
at(46, 60).grade,
Grade::Refuse,
"14 days left is inside REFUSE_DAYS"
);
assert_eq!(at(273, YEAR).grade, Grade::Fine);
assert_eq!(at(274, YEAR).grade, Grade::Renew);
}
#[test]
fn the_refusal_never_overtakes_the_renewal() {
for lifetime in [7, 20, 30, 56, 60, 90, 365, 3650] {
let mut seen_renew = false;
let mut refused_on = None;
for day in 0..=lifetime {
match at(day, lifetime).grade {
Grade::Renew => seen_renew = true,
Grade::Refuse if refused_on.is_none() => refused_on = Some(day),
_ => {}
}
}
assert!(
refused_on.is_some(),
"a {lifetime}-day leaf must eventually be refused, or the guard is decoration"
);
assert!(
seen_renew,
"a {lifetime}-day leaf was refused on day {:?} without ever being told to renew",
refused_on
);
}
}
#[test]
fn a_zero_length_certificate_is_expired_whatever_the_clock_says() {
let t = 1_700_000_000_000;
assert_eq!(grade(t, t, t).grade, Grade::Expired);
assert_eq!(grade(t - 100 * DAY_MS, t, t).grade, Grade::Expired);
}
#[test]
fn the_expired_line_is_honest_about_wireguard() {
let text = at(400, YEAR).line("this twin's client certificate");
assert!(text.contains("EXPIRED"), "{text}");
assert!(
text.contains("re-imaging"),
"the way back must be named: {text}"
);
assert!(
text.contains("WireGuard still carries packets"),
"it must say plainly that the tunnel does not rescue this: {text}"
);
}
#[test]
fn unparseable_bytes_are_not_graded_fine() {
assert!(grade_der(1_700_000_000_000, b"not a certificate at all").is_err());
}
}