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"
);
}