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