Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/Skidbladnir

crates/installer/nordisk-twin/src/pki.rs

//! **The primary is its own replication issuer.**
//!
//! One CA per box, minted on first use and kept: the key `0600` beside its
//! certificate under a directory the product names (a box with a sealed secret
//! store may keep the key there instead — gunnar does; the files here are the
//! floor, not a preference). Written key FIRST: a crash between the two leaves a
//! key with no certificate, which the next [`Issuer::load_or_mint`] adopts —
//! never a certificate whose key is gone.
//!
//! It signs exactly two shapes, and the two can never stand in for each other:
//!
//! * [`Issuer::issue_from_csr`] — a CLIENT leaf: the subject is the principal
//!   the caller names (never what the request claimed), SANs dropped, `is_ca`
//!   off, EKU clientAuth and nothing else. The request's own signature is the
//!   proof of possession; only its public key is taken from it.
//! * [`Issuer::server_identity`] — the replication door's SERVER leaf: the
//!   fixed door name, `localhost`, `127.0.0.1` and the bound addresses as SANs,
//!   EKU serverAuth only, key minted here and never stored (re-minted at every
//!   start).

use std::path::Path;

use rcgen::{
    BasicConstraints, CertificateParams, CertificateSigningRequestParams, DistinguishedName, DnType, DnValue, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType, SerialNumber,
};

use crate::{read_opt, write_atomic, Result, TwinError};

/// The issuer's private key, `0600`.
pub const ISSUER_KEY: &str = "issuer.key";
/// The issuer's certificate — what a twin pins.
pub const ISSUER_CERT: &str = "issuer.crt";
/// A twin's client leaf lives a year; renewal is the same certify under the same
/// issuer, with no shell on either box.
pub const DEFAULT_LEAF_DAYS: u32 = 365;
/// The issuer itself: ten years. Rotating it re-pairs every twin by design.
pub const ISSUER_DAYS: u32 = 3650;

fn pki(what: &str) -> impl Fn(rcgen::Error) -> TwinError + '_ {
    move |e| TwinError::Pki(format!("{what}: {e}"))
}

/// A signed leaf.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Leaf {
    pub cert_pem: String,
    /// `notAfter`, Unix seconds.
    pub not_after: i64,
}

/// **The live issuer.** `Debug` prints the certificate's subject and never the key.
#[derive(Clone)]
pub struct Issuer {
    pub cert_pem: String,
    key_pem: String,
}

impl std::fmt::Debug for Issuer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Issuer").field("subject", &common_name_of_cert(&self.cert_pem).ok().flatten()).field("key", &"<redacted>").finish()
    }
}

impl Issuer {
    /// The issuer under `dir`, if one was minted there. A key and certificate
    /// that are not each other's are an error, never a silent re-mint: a new
    /// issuer un-pairs every twin.
    pub fn load(dir: &Path) -> Result<Option<Issuer>> {
        let (Some(key_pem), Some(cert_pem)) = (read_opt(dir, ISSUER_KEY)?, read_opt(dir, ISSUER_CERT)?) else { return Ok(None) };
        if spki_of_key(&key_pem)? != spki_of_cert(&cert_pem)? {
            return Err(TwinError::Pki(format!("{}: the issuer certificate and key are not each other's; refusing to guess which is the issuer", dir.display())));
        }
        Ok(Some(Issuer { cert_pem, key_pem }))
    }

    /// **The issuer under `dir`, minted on first use** with `subject` as its CN.
    pub fn load_or_mint(dir: &Path, subject: &str) -> Result<Issuer> {
        if let Some(i) = Issuer::load(dir)? {
            return Ok(i);
        }
        // Adopt an orphaned key (a crash between the two writes), else mint.
        let key_pem = match read_opt(dir, ISSUER_KEY)? {
            Some(k) => k,
            None => {
                let k = KeyPair::generate().map_err(pki("the issuer key"))?.serialize_pem();
                write_atomic(dir, ISSUER_KEY, k.as_bytes(), true)?;
                k
            }
        };
        let key = KeyPair::from_pem(&key_pem).map_err(pki("the issuer key"))?;
        let mut p = CertificateParams::default();
        p.distinguished_name = dn(subject);
        p.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
        p.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign, KeyUsagePurpose::DigitalSignature];
        p.serial_number = Some(serial()?);
        let (nb, na) = window(ISSUER_DAYS)?;
        p.not_before = nb;
        p.not_after = na;
        let cert_pem = p.self_signed(&key).map_err(pki("self-signing the issuer"))?.pem();
        write_atomic(dir, ISSUER_CERT, cert_pem.as_bytes(), false)?;
        Ok(Issuer { cert_pem, key_pem })
    }

    /// The signing handle: the issuer re-expressed from its own PEM with its own
    /// key — same subject, same key identifier, so every leaf chains to the
    /// certificate on disk.
    fn signer(&self) -> Result<rcgen::Issuer<'static, KeyPair>> {
        let key = KeyPair::from_pem(&self.key_pem).map_err(pki("the issuer key"))?;
        rcgen::Issuer::from_ca_cert_pem(&self.cert_pem, key).map_err(pki("the issuer certificate"))
    }

    /// **A client leaf from a request.** Parsing VERIFIES the request's
    /// self-signature (proof of possession). The request's claimed CN must be
    /// `principal` ([`TwinError::CsrClaim`]); everything else it asked for is
    /// overwritten.
    pub fn issue_from_csr(&self, principal: &str, csr_pem: &str, days: u32) -> Result<Leaf> {
        if principal.is_empty() {
            return Err(TwinError::Pki("a client certificate needs a principal; the door takes the identity from its CN".into()));
        }
        let mut req = CertificateSigningRequestParams::from_pem(csr_pem).map_err(|e| {
            TwinError::Pki(format!("this is not a usable certificate signing request: {e}. A request that does not verify against its own public key proves nothing about who holds the private half"))
        })?;
        let claimed = common_name(&req.params.distinguished_name).unwrap_or_default();
        if claimed != principal {
            return Err(TwinError::CsrClaim { claimed, principal: principal.to_string() });
        }
        let p = &mut req.params;
        p.distinguished_name = dn(principal);
        p.subject_alt_names.clear();
        p.is_ca = IsCa::ExplicitNoCa;
        p.key_usages = vec![KeyUsagePurpose::DigitalSignature];
        p.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
        p.name_constraints = None;
        p.custom_extensions.clear();
        p.crl_distribution_points.clear();
        p.use_authority_key_identifier_extension = true;
        p.serial_number = Some(serial()?);
        let (nb, na) = window(days)?;
        p.not_before = nb;
        p.not_after = na;
        let ic = self.signer()?;
        let cert = req.signed_by(&ic).map_err(pki("signing the request"))?;
        Ok(Leaf { cert_pem: cert.pem(), not_after: na.unix_timestamp() })
    }

    /// **A client leaf from a request that SUPERSEDES the principal's other
    /// holders** (Gunnar 273960cb and `AuthTable::supersede_certificates`, twin
    /// reborn 2026-09-30): one box per principal. [`Issuer::issue_from_csr`],
    /// then the key the request carried is pinned as `principal`'s ONE holder
    /// under `pins` ([`holder_pin`]); a door built with
    /// [`crate::tls::server_config_current`] refuses every other key's leaf for
    /// that CN from its next handshake, by name (`superseded`), however many days
    /// it has left. The SAME key again is a renewal: the pin does not move, and
    /// the older leaf keeps working until the new one is installed.
    pub fn issue_superseding(&self, pins: &Path, principal: &str, csr_pem: &str, days: u32) -> Result<Leaf> {
        let leaf = self.issue_from_csr(principal, csr_pem, days)?;
        let spki = spki_of_cert(&leaf.cert_pem)?;
        if holder_pin(pins, principal)?.as_deref() != Some(spki.as_slice()) {
            write_atomic(pins, &pin_name(principal)?, hex(&spki).as_bytes(), false)?;
        }
        Ok(leaf)
    }

    /// **The door's server identity**: `server_name` first, then `localhost`,
    /// `127.0.0.1` and `bound` (each an IP or a DNS name). Returns
    /// `(cert_pem, key_pem)`; the key is minted here and never written.
    pub fn server_identity(&self, server_name: &str, bound: &[String], days: u32) -> Result<(String, String)> {
        let mut names: Vec<String> = vec![server_name.to_string(), "localhost".into(), "127.0.0.1".into()];
        for b in bound {
            if !names.contains(b) {
                names.push(b.clone());
            }
        }
        let key = KeyPair::generate().map_err(pki("the door key"))?;
        let mut p = CertificateParams::default();
        p.distinguished_name = dn(server_name);
        p.subject_alt_names = names
            .iter()
            .map(|n| match n.parse::<std::net::IpAddr>() {
                Ok(ip) => Ok(SanType::IpAddress(ip)),
                Err(_) => n.clone().try_into().map(SanType::DnsName).map_err(pki("a door name")),
            })
            .collect::<Result<_>>()?;
        p.is_ca = IsCa::ExplicitNoCa;
        p.key_usages = vec![KeyUsagePurpose::DigitalSignature];
        p.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
        p.use_authority_key_identifier_extension = true;
        p.serial_number = Some(serial()?);
        let (nb, na) = window(days)?;
        p.not_before = nb;
        p.not_after = na;
        let ic = self.signer()?;
        let cert = p.signed_by(&key, &ic).map_err(pki("signing the door's certificate"))?;
        Ok((cert.pem(), key.serialize_pem()))
    }
}

/// **A key pair and a request for it, minted HERE** — `(key_pem, csr_pem)`.
/// P-256, what the issuer's own leaves use, so both ends negotiate one scheme.
pub fn mint_request(cn: &str) -> Result<(String, String)> {
    let key = KeyPair::generate().map_err(pki("minting a key pair"))?;
    let mut p = CertificateParams::new(Vec::<String>::new()).map_err(pki("a signing request"))?;
    p.distinguished_name = dn(cn);
    let csr = p.serialize_request(&key).map_err(pki("signing the request with its own key"))?;
    Ok((key.serialize_pem(), csr.pem().map_err(pki("armouring the request"))?))
}

/// The file under a pins directory that names `principal`'s one holder.
fn pin_name(principal: &str) -> Result<String> {
    if principal.is_empty() || !principal.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.')) || principal.starts_with('.') {
        return Err(TwinError::Pki(format!("`{principal}` is not a principal a pin can be kept for (letters, digits, - _ . only)")));
    }
    Ok(format!("holder.{principal}"))
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

fn unhex(s: &str) -> Option<Vec<u8>> {
    let s = s.trim();
    if !s.len().is_multiple_of(2) {
        return None;
    }
    (0..s.len()).step_by(2).map(|i| u8::from_str_radix(s.get(i..i + 2)?, 16).ok()).collect()
}

/// **The key `principal`'s one holder carries** (SubjectPublicKeyInfo DER), as
/// [`Issuer::issue_superseding`] pinned it under `pins`; `None` before the first
/// such issue (a leaf issued before pins existed is then taken as it is).
pub fn holder_pin(pins: &Path, principal: &str) -> Result<Option<Vec<u8>>> {
    match read_opt(pins, &pin_name(principal)?)? {
        None => Ok(None),
        Some(s) => unhex(&s).map(Some).ok_or_else(|| TwinError::Pki(format!("{}: the holder pin of `{principal}` is not hex; refusing to guess who holds it", pins.join(pin_name(principal).unwrap_or_default()).display()))),
    }
}

/// **Is the leaf `der` its CN's current holder?** `Ok(())` when no pin is kept
/// for its CN or the pin is its key; `Err(why)` when another key holds the
/// principal now. What [`crate::tls::server_config_current`] asks per handshake.
pub fn is_current_holder(pins: &Path, der: &[u8]) -> Result<std::result::Result<(), String>> {
    let (_, c) = x509_parser::parse_x509_certificate(der).map_err(|e| TwinError::Pki(format!("not an X.509 certificate: {e}")))?;
    let Some(cn) = c.subject().iter_common_name().next().and_then(|a| a.as_str().ok()).map(str::to_string) else { return Ok(Ok(())) };
    if pin_name(&cn).is_err() {
        return Ok(Ok(())); // no pin can be kept for such a name
    }
    match holder_pin(pins, &cn)? {
        None => Ok(Ok(())),
        Some(pin) if pin == c.tbs_certificate.subject_pki.raw => Ok(Ok(())),
        Some(_) => Ok(Err(format!(
            "this `{cn}` leaf (serial {}) is for a key `{cn}` no longer holds: a newer box was certified as `{cn}` since, and one principal is one box",
            c.tbs_certificate.raw_serial_as_string()
        ))),
    }
}

/// The CN a request claims.
pub fn csr_claimed_cn(csr_pem: &str) -> Result<Option<String>> {
    let req = CertificateSigningRequestParams::from_pem(csr_pem).map_err(pki("the request"))?;
    Ok(common_name(&req.params.distinguished_name))
}

/// SubjectPublicKeyInfo DER of a PEM private key.
pub fn spki_of_key(key_pem: &str) -> Result<Vec<u8>> {
    Ok(rcgen::PublicKeyData::subject_public_key_info(&KeyPair::from_pem(key_pem).map_err(pki("the private key"))?))
}

/// The first certificate DER of a PEM text.
pub fn pem_to_der(pem: &str) -> Result<Vec<u8>> {
    let (_, block) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|e| TwinError::Pki(format!("not a PEM certificate: {e}")))?;
    Ok(block.contents)
}

/// SubjectPublicKeyInfo DER a PEM certificate carries.
pub fn spki_of_cert(cert_pem: &str) -> Result<Vec<u8>> {
    let der = pem_to_der(cert_pem)?;
    let (_, c) = x509_parser::parse_x509_certificate(&der).map_err(|e| TwinError::Pki(format!("not an X.509 certificate: {e}")))?;
    Ok(c.tbs_certificate.subject_pki.raw.to_vec())
}

/// `(notBefore, notAfter)` of a PEM certificate, Unix seconds.
pub fn validity(cert_pem: &str) -> Result<(i64, i64)> {
    let der = pem_to_der(cert_pem)?;
    let (_, c) = x509_parser::parse_x509_certificate(&der).map_err(|e| TwinError::Pki(format!("not an X.509 certificate: {e}")))?;
    Ok((c.validity().not_before.timestamp(), c.validity().not_after.timestamp()))
}

/// The subject CN of a PEM certificate.
pub fn common_name_of_cert(cert_pem: &str) -> Result<Option<String>> {
    common_name_of_der(&pem_to_der(cert_pem)?)
}

/// The subject CN of a DER certificate — what a door maps to a principal.
pub fn common_name_of_der(der: &[u8]) -> Result<Option<String>> {
    let (_, c) = x509_parser::parse_x509_certificate(der).map_err(|e| TwinError::Pki(format!("not an X.509 certificate: {e}")))?;
    let cn = c.subject().iter_common_name().next().and_then(|a| a.as_str().ok()).map(str::to_string);
    Ok(cn)
}

fn dn(cn: &str) -> DistinguishedName {
    let mut d = DistinguishedName::new();
    d.push(DnType::CommonName, cn);
    d
}

fn common_name(d: &DistinguishedName) -> Option<String> {
    match d.get(&DnType::CommonName)? {
        DnValue::Utf8String(s) => Some(s.clone()),
        DnValue::PrintableString(s) => Some(s.as_str().to_owned()),
        DnValue::Ia5String(s) => Some(s.as_str().to_owned()),
        _ => None,
    }
}

/// How far back a certificate's `notBefore` is set: a box that mints BEFORE its
/// clock is corrected must not hand out a certificate born "in the future". An
/// estate's boxes mint their issuer and door identity at start and are stepped by
/// the estate clock only after pairing — MEASURED on holger's mock 2026-09-28 (and
/// on UpCloud by gunnar: guests boot +2 h without kvmclock): the primary's door,
/// minted two hours ahead, was refused by its corrected twin `CertificateExpired`
/// on every handshake. A day covers that step with room; the lifetime is unchanged.
pub const NOT_BEFORE_SLACK: time::Duration = time::Duration::hours(24);

/// `[now − NOT_BEFORE_SLACK, now + days)` — see [`NOT_BEFORE_SLACK`].
fn window(days: u32) -> Result<(time::OffsetDateTime, time::OffsetDateTime)> {
    if days == 0 {
        return Err(TwinError::Pki("a certificate valid for zero days is expired the moment it is signed".into()));
    }
    let now = time::OffsetDateTime::now_utc();
    Ok((now - NOT_BEFORE_SLACK, now + time::Duration::days(i64::from(days))))
}

/// 20 random bytes, top bit clear: rcgen's default serial is the key's hash, so
/// two leaves for one key would share a serial, and a serial is what a
/// revocation names.
fn serial() -> Result<SerialNumber> {
    let mut b = [0u8; 20];
    rustls::crypto::ring::default_provider().secure_random.fill(&mut b).map_err(|_| TwinError::Pki("the system CSPRNG refused a serial number".into()))?;
    b[0] &= 0x7f;
    Ok(SerialNumber::from_slice(&b))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn dir(tag: &str) -> std::path::PathBuf {
        let p = std::env::temp_dir().join(format!("nordisk-twin-pki-{tag}-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&p);
        p
    }

    #[test]
    fn the_issuer_is_minted_once_and_kept_with_its_key_private() {
        let d = dir("once");
        let a = Issuer::load_or_mint(&d, "holger-replication-issuer").unwrap();
        let b = Issuer::load_or_mint(&d, "another subject").unwrap();
        assert_eq!(a.cert_pem, b.cert_pem, "a second call adopts, never re-mints");
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt as _;
            assert_eq!(std::fs::metadata(d.join(ISSUER_KEY)).unwrap().permissions().mode() & 0o777, 0o600);
        }
        assert!(!format!("{a:?}").contains("PRIVATE"), "Debug never prints the key");
        // An orphaned key (crash between the writes) is adopted.
        let cert_before = std::fs::read_to_string(d.join(ISSUER_CERT)).unwrap();
        std::fs::remove_file(d.join(ISSUER_CERT)).unwrap();
        let c = Issuer::load_or_mint(&d, "holger-replication-issuer").unwrap();
        assert_eq!(spki_of_cert(&c.cert_pem).unwrap(), spki_of_cert(&cert_before).unwrap(), "the same key");
        let _ = std::fs::remove_dir_all(&d);
    }

    #[test]
    fn a_request_is_issued_as_what_it_claims_client_only_and_nothing_else() {
        let d = dir("csr");
        let i = Issuer::load_or_mint(&d, "issuer").unwrap();
        let (key, csr) = mint_request("holger-twin").unwrap();
        let leaf = i.issue_from_csr("holger-twin", &csr, DEFAULT_LEAF_DAYS).unwrap();
        assert_eq!(spki_of_cert(&leaf.cert_pem).unwrap(), spki_of_key(&key).unwrap(), "the request's key, nothing else");
        assert_eq!(common_name_of_cert(&leaf.cert_pem).unwrap().as_deref(), Some("holger-twin"));
        let (nb, na) = validity(&leaf.cert_pem).unwrap();
        assert!(na - nb > 364 * 86_400 && na == leaf.not_after);
        // Born a day back: a verifier two hours behind the minting box still takes it.
        let now = time::OffsetDateTime::now_utc().unix_timestamp();
        assert!(now - nb >= 24 * 3600 - 60 && now - nb <= 24 * 3600 + 60, "notBefore is now - 24 h: {}", now - nb);
        crate::tls::verify_client_chain(&i.cert_pem, &leaf.cert_pem).expect("chains to the issuer for clientAuth");
        // A request claiming another CN is refused, by name.
        let (_, admin) = mint_request("admin").unwrap();
        assert_eq!(i.issue_from_csr("holger-twin", &admin, 365).unwrap_err().kind(), "twin-csr-claim");
        // A tampered request (not its own signature) proves nothing.
        let bad = csr.replace("MI", "MJ");
        assert!(i.issue_from_csr("holger-twin", &bad, 365).is_err());
        let _ = std::fs::remove_dir_all(&d);
    }
    /// ★ Ported from Gunnar 273960cb (twin reborn, `tests/twin_reborn.rs` and
    /// `supersede_certificates`): certifying a NEW key under a principal supersedes
    /// the principal's other holders — one twin per principal — while the SAME key
    /// again (a renewal, a re-run of the courier) supersedes nothing.
    #[test]
    fn a_new_twin_key_supersedes_the_old_one_and_the_same_key_again_is_a_renewal() {
        let d = dir("supersede");
        let pins = d.join("pins");
        let i = Issuer::load_or_mint(&d, "issuer").unwrap();
        let (_, a_csr) = mint_request("holger-twin").unwrap();
        let a = i.issue_superseding(&pins, "holger-twin", &a_csr, DEFAULT_LEAF_DAYS).unwrap();
        let a_der = pem_to_der(&a.cert_pem).unwrap();
        assert_eq!(is_current_holder(&pins, &a_der).unwrap(), Ok(()), "twin A holds it");
        // The courier runs again for twin A: the same key, a new leaf, nothing superseded.
        let a2 = i.issue_superseding(&pins, "holger-twin", &a_csr, DEFAULT_LEAF_DAYS).unwrap();
        assert_ne!(a2.cert_pem, a.cert_pem);
        assert_eq!(is_current_holder(&pins, &a_der).unwrap(), Ok(()), "a renewal keeps the older leaf until the new one is installed");
        // Twin B: EMPTY, a NEW key, the same principal.
        let (_, b_csr) = mint_request("holger-twin").unwrap();
        let b = i.issue_superseding(&pins, "holger-twin", &b_csr, DEFAULT_LEAF_DAYS).unwrap();
        assert_eq!(is_current_holder(&pins, &pem_to_der(&b.cert_pem).unwrap()).unwrap(), Ok(()));
        let why = is_current_holder(&pins, &a_der).unwrap().unwrap_err();
        assert!(why.contains("holger-twin") && why.contains("no longer holds"), "{why}");
        assert!(crate::tls::verify_client_chain(&i.cert_pem, &a.cert_pem).is_ok(), "the dead twin's leaf still chains, 365 days of it");
        assert_eq!(crate::tls::verify_current_client(&i.cert_pem, &a.cert_pem, &pins).unwrap_err().kind(), "twin-not-issued-by", "and is refused as superseded");
        crate::tls::verify_current_client(&i.cert_pem, &b.cert_pem, &pins).unwrap();
        // Another principal is its own holder; an unpinned principal is taken as it is.
        let (_, r_csr) = mint_request("holger-recharge").unwrap();
        let r = i.issue_from_csr("holger-recharge", &r_csr, 30).unwrap();
        assert_eq!(is_current_holder(&pins, &pem_to_der(&r.cert_pem).unwrap()).unwrap(), Ok(()), "no pin: issued before pins, or never superseding");
        assert!(holder_pin(&pins, "../x").is_err(), "a principal is never a path");
        let _ = std::fs::remove_dir_all(&d);
    }
}