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

nordisk/holger

server/lib/tests/pairing_renewal.rs

//! ★ **Every paired link renews before it ends, an ended one is refused by name, and a
//! peer that lost its key re-pairs** (lane E2, 2026-10-19) — the box's half:
//!
//! 1. the replication door's identity is renewed IN PLACE at two thirds of its life
//!    (`twin_tls::renew_door`, the `box.renew.door` jera job): a handshake before the
//!    renewal sees the old certificate, one after it the new, and the door never stops
//!    answering;
//! 2. a replication credential that has ENDED is refused by name (`holger-twin-18`)
//!    before a byte moves, and the health words say `expired` and raise `cert_alert`;
//! 3. a downstream that lost its key RE-PAIRS with its new certificate: the pin moves,
//!    the old key is no longer known, the replaced record is kept; a re-pair of a peer
//!    that was never paired is refused by name (`holger-peer-5`).
//!
//! The clock is injected (`now`) wherever a life is judged, so nothing waits 180 days.

use std::sync::Arc;

use holger_server_lib::{cert_health, peers, twin_tls};

const DAY: i64 = 86_400;

fn issuer(dir: &std::path::Path) -> nordisk_twin::pki::Issuer {
    nordisk_twin::pki::Issuer::load_or_mint(&dir.join("issuer"), "holger replication issuer")
        .unwrap()
}

/// One handshake with the door at `at` as the issuer's twin: the certificate the door showed.
async fn shown(at: std::net::SocketAddr, ca: &str, twin_cert: &str, twin_key: &str) -> Vec<u8> {
    let cfg = nordisk_twin::tls::client_config(ca, twin_cert, twin_key).unwrap();
    let tcp = tokio::net::TcpStream::connect(at).await.unwrap();
    let tls = tokio_rustls::TlsConnector::from(cfg)
        .connect(
            nordisk_twin::tls::server_name(twin_tls::DOOR_NAME).unwrap(),
            tcp,
        )
        .await
        .expect("the door answers");
    tls.get_ref().1.peer_certificates().unwrap()[0]
        .as_ref()
        .to_vec()
}

fn der(pem: &str) -> Vec<u8> {
    nordisk_twin::pki::pem_to_der(pem).unwrap()
}

/// ★ **The door's identity renews in place and the door keeps answering.**
#[tokio::test]
async fn the_replication_door_renews_its_identity_before_it_ends_and_keeps_serving() {
    let dir = tempfile::tempdir().unwrap();
    let issuer = issuer(dir.path());
    let hosts = vec!["127.0.0.1".to_string()];
    let (cert, key) = issuer
        .server_identity(
            twin_tls::DOOR_NAME,
            &hosts,
            nordisk_twin::pki::DEFAULT_LEAF_DAYS,
        )
        .unwrap();
    let identity = Arc::new(twin_tls::DoorIdentity::new(&cert, &key).unwrap());
    let mut cfg =
        (*nordisk_twin::tls::server_config(&cert, &key, &issuer.cert_pem).unwrap()).clone();
    cfg.cert_resolver = identity.clone();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let at = listener.local_addr().unwrap();
    let mut stream = nordisk_twin::net::incoming(listener, Arc::new(cfg), Arc::new(|_, _, _| {}));
    tokio::spawn(async move {
        use tokio_stream::StreamExt as _;
        while let Some(s) = stream.next().await {
            drop(s);
        }
    });
    let (twin_key, csr) = nordisk_twin::pki::mint_request(twin_tls::TWIN_PRINCIPAL).unwrap();
    let twin = issuer
        .issue_from_csr(
            twin_tls::TWIN_PRINCIPAL,
            &csr,
            nordisk_twin::pki::DEFAULT_LEAF_DAYS,
        )
        .unwrap();

    assert_eq!(
        shown(at, &issuer.cert_pem, &twin.cert_pem, &twin_key).await,
        der(&cert),
        "the identity it started with"
    );
    let now = cert_health::now_unix();
    assert!(
        !twin_tls::renew_door(&identity, &issuer, &hosts, now).unwrap(),
        "fresh: not due"
    );
    assert_eq!(identity.cert_pem(), cert, "nothing changed while not due");
    // Day 244 of 365: past two thirds.
    assert!(
        twin_tls::renew_door(&identity, &issuer, &hosts, now + 244 * DAY).unwrap(),
        "due: renewed"
    );
    let renewed = identity.cert_pem();
    assert_ne!(renewed, cert, "a new identity");
    assert_eq!(
        shown(at, &issuer.cert_pem, &twin.cert_pem, &twin_key).await,
        der(&renewed),
        "the next handshake shows the renewed identity, on the same open door"
    );
    let (_, old_end) = nordisk_twin::pki::validity(&cert).unwrap();
    let (_, new_end) = nordisk_twin::pki::validity(&renewed).unwrap();
    assert!(new_end >= old_end, "a later end");
}

/// An ENDED certificate, self-made: `channel` judges its window before anything else.
fn ended_cert() -> (String, String) {
    let mut p = rcgen::CertificateParams::new(vec![twin_tls::TWIN_PRINCIPAL.to_string()]).unwrap();
    (p.not_before, p.not_after) = (
        rcgen::date_time_ymd(2020, 1, 1),
        rcgen::date_time_ymd(2021, 1, 1),
    );
    let key = rcgen::KeyPair::generate().unwrap();
    (p.self_signed(&key).unwrap().pem(), key.serialize_pem())
}

/// ★ **An ended link that nobody renewed is refused by name**, and the health words say so.
#[tokio::test]
async fn an_ended_replication_certificate_is_refused_by_name_before_a_byte_moves() {
    // A door that would answer: the refusal must come before any dial.
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    listener.set_nonblocking(true).unwrap();
    let port = listener.local_addr().unwrap().port();
    let (cert_pem, key_pem) = ended_cert();
    let cred = nordisk_twin::credential::Credential {
        cert_pem: cert_pem.clone(),
        key_pem,
        ca_pem: cert_pem.clone(),
        port,
        hosts: vec!["127.0.0.1".into()],
    };
    let refused = twin_tls::channel(&cred).await.map(|_| ()).unwrap_err();
    assert!(
        refused.contains("holger-twin-18") && refused.contains("was not renewed"),
        "{refused}"
    );
    assert!(
        matches!(listener.accept(), Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock),
        "not one connection reached the door"
    );

    // The pure rule, at an injected clock: a live leaf is dialled until its end.
    let dir = tempfile::tempdir().unwrap();
    let issuer = issuer(dir.path());
    let (_, csr) = nordisk_twin::pki::mint_request(twin_tls::TWIN_PRINCIPAL).unwrap();
    let leaf = issuer
        .issue_from_csr(twin_tls::TWIN_PRINCIPAL, &csr, 30)
        .unwrap();
    let now = cert_health::now_unix();
    assert_eq!(
        twin_tls::expired(&leaf.cert_pem, now),
        None,
        "a live certificate is dialled"
    );
    assert!(
        twin_tls::expired(&leaf.cert_pem, now + 31 * DAY)
            .is_some_and(|w| w.contains("holger-twin-18")),
        "at day 31 of 30 it is refused"
    );

    // The health words: the twin's credential, as the box keeps it, graded at the clock.
    let twin_dir = twin_tls::Purpose::Twin.dir(dir.path());
    std::fs::create_dir_all(&twin_dir).unwrap();
    std::fs::write(
        twin_dir.join(nordisk_twin::credential::CERT),
        &leaf.cert_pem,
    )
    .unwrap();
    let words = cert_health::words(Some(dir.path()), now + 31 * DAY);
    assert_eq!(
        words.get("twin_cert").map(String::as_str),
        Some("expired"),
        "{words:?}"
    );
    assert!(
        words
            .get("cert_alert")
            .is_some_and(|a| a.contains("twin expired")),
        "{words:?}"
    );
}

fn cert_pem(cn: &str) -> Vec<u8> {
    rcgen::generate_simple_self_signed(vec![cn.to_string()])
        .unwrap()
        .cert
        .pem()
        .into_bytes()
}

/// ★ **A peer that lost its key re-pairs; a stranger is refused by name.**
#[test]
fn a_downstream_that_lost_its_key_re_pairs_and_a_never_paired_one_is_refused_by_name() {
    let dir = tempfile::tempdir().unwrap();
    let lost = cert_pem("onprem-1");
    let first =
        peers::pair_downstream(dir.path(), "onprem-1", &lost, &["B".into()], "ops", 100).unwrap();
    // The downstream lost its key: its new certificate is not the pinned one.
    let new = cert_pem("onprem-1");
    let new_der = nordisk_twin::pki::pem_to_der(std::str::from_utf8(&new).unwrap()).unwrap();
    let lost_der = nordisk_twin::pki::pem_to_der(std::str::from_utf8(&lost).unwrap()).unwrap();
    let (down, up) = peers::load(dir.path()).unwrap();
    assert_eq!(
        peers::PeerBoard::new(down, up).downstream_by_cert(&new_der),
        None,
        "the new key is a stranger before the re-pair"
    );
    // A plain pairing again is refused (made once); the re-pair is the road.
    assert!(
        peers::pair_downstream(dir.path(), "onprem-1", &new, &["B".into()], "ops", 200)
            .unwrap_err()
            .contains("holger-peer-1")
    );
    let (now, replaced) =
        peers::repair_downstream(dir.path(), "onprem-1", &new, "ops", 300).unwrap();
    assert_eq!(replaced, first, "the replaced record is answered");
    assert_eq!(
        (now.name.as_str(), now.repos.clone(), now.paired_at),
        ("onprem-1", vec!["B".to_string()], 300),
        "the name and what it may pull are kept"
    );
    assert_ne!(now.fingerprint, first.fingerprint);
    let (down, up) = peers::load(dir.path()).unwrap();
    assert_eq!(down, vec![now.clone()], "one record, the new pin");
    let board = peers::PeerBoard::new(down, up);
    assert_eq!(
        board.downstream_by_cert(&new_der),
        Some(0),
        "the new key is the paired downstream"
    );
    assert_eq!(
        board.downstream_by_cert(&lost_der),
        None,
        "the lost key is refused"
    );
    assert!(
        dir.path()
            .join(peers::PEERS_DIR)
            .join(peers::REPLACED_DIR)
            .join("downstream-onprem-1-300.ron")
            .exists(),
        "the replaced record is kept for the audit"
    );
    // The same key again: nothing to re-pair.
    assert!(
        peers::repair_downstream(dir.path(), "onprem-1", &new, "ops", 400)
            .unwrap_err()
            .contains("holger-peer-1")
    );
    // A peer never paired: refused by name, nothing written.
    let stranger =
        peers::repair_downstream(dir.path(), "stranger", &cert_pem("stranger"), "ops", 500)
            .unwrap_err();
    assert!(stranger.contains("holger-peer-5"), "{stranger}");
    assert!(
        peers::repair_upstream(dir.path(), "nowhere", &cert_pem("x"), "ops", 500)
            .unwrap_err()
            .contains("holger-peer-5")
    );
    assert_eq!(
        peers::load(dir.path()).unwrap().0.len(),
        1,
        "the stranger left no record"
    );

    // The downstream's side: the upstream rolled its server certificate.
    let up_first = peers::pair_upstream(
        dir.path(),
        "cloud",
        "holger-rs",
        &cert_pem("cloud"),
        "ops",
        600,
    )
    .unwrap();
    let (up_now, up_old) =
        peers::repair_upstream(dir.path(), "cloud", &cert_pem("cloud"), "ops", 700).unwrap();
    assert_eq!(up_old, up_first);
    assert_eq!(
        (up_now.name.as_str(), up_now.source.as_str()),
        ("holger-rs", "cloud")
    );
    assert_eq!(
        peers::verdict(&up_now, Some(&up_first.fingerprint)),
        peers::LinkState::KeyMismatch,
        "the old upstream key no longer matches"
    );
}

/// ★ **The estate board names when the pairing key ends** (E3 left the field for E2):
/// `Topology`'s `pairing_not_after_unix` is read live off the box's own certificate, so a
/// renewal moves it without a new feed.
#[test]
fn the_board_names_the_pairing_certificates_end_read_live() {
    let ca = nordisk_pairing::mtls::InstallerCa::mint(
        "holger.test",
        std::time::Duration::from_secs(3600),
    )
    .unwrap();
    let (csr, _) = nordisk_pairing::mtls::box_request().unwrap();
    let cert = ca
        .sign_box(
            &csr,
            "main",
            std::time::Duration::from_secs(180 * DAY as u64),
        )
        .unwrap();
    let (_, end) = nordisk_twin::pki::validity(&cert).unwrap();
    let shown = std::sync::Arc::new(std::sync::Mutex::new(cert));
    let s = shown.clone();
    cert_health::set_pairing_cert(move || Some(s.lock().unwrap().clone()));
    holger_server_lib::topology::set_pairing(Some(holger_server_lib::topology::Pairing {
        estate: "holger.test".into(),
        me: "main".into(),
        ..Default::default()
    }));
    assert_eq!(
        holger_server_lib::topology::pairing()
            .unwrap()
            .not_after_unix,
        end
    );
    let renewed = ca
        .renew_box(
            &shown.lock().unwrap(),
            std::time::Duration::from_secs(300 * DAY as u64),
        )
        .unwrap();
    let (_, later) = nordisk_twin::pki::validity(&renewed).unwrap();
    *shown.lock().unwrap() = renewed;
    assert_eq!(
        holger_server_lib::topology::pairing()
            .unwrap()
            .not_after_unix,
        later,
        "the renewal moves it"
    );
}