use rcgen::{BasicConstraints, CertificateParams, CertificateSigningRequestParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, SanType};
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration;
pub const BOOTSTRAP_CN: &str = "bootstrap";
pub const INSTALLER_CN: &str = "installer";
pub const VM_SERVER_NAME: &str = "bootstrap.installer";
fn vm_sans() -> Vec<SanType> {
match VM_SERVER_NAME.to_string().try_into() {
Ok(n) => vec![SanType::DnsName(n)],
Err(_) => Vec::new(),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MtlsError(pub String);
impl std::fmt::Display for MtlsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "REFUSED [pairing-mtls] {}", self.0)
}
}
impl std::error::Error for MtlsError {}
fn e(what: &str) -> impl Fn(rcgen::Error) -> MtlsError + '_ {
move |err| MtlsError(format!("{what}: {err}"))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Issued {
pub cert_pem: String,
pub key_pem: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstallerCa {
pub cert_pem: String,
pub key_pem: String,
}
fn window(valid_for: Duration) -> (time::OffsetDateTime, time::OffsetDateTime) {
window_back(valid_for, time::Duration::minutes(5))
}
fn window_back(valid_for: Duration, back: time::Duration) -> (time::OffsetDateTime, time::OffsetDateTime) {
let now = time::OffsetDateTime::now_utc();
(now - back, now + time::Duration::seconds(valid_for.as_secs() as i64))
}
pub const DOOR_BACKDATE: Duration = Duration::from_secs(24 * 3600);
pub fn cert_left(pem: &str, now_unix: i64) -> Result<Duration, MtlsError> {
let (_, p) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|err| MtlsError(format!("not a PEM certificate: {err}")))?;
let cert = p.parse_x509().map_err(|err| MtlsError(format!("not an X.509 certificate: {err}")))?;
let end = cert.validity().not_after.timestamp();
Ok(Duration::from_secs(end.saturating_sub(now_unix).max(0) as u64))
}
pub fn now_unix() -> i64 {
time::OffsetDateTime::now_utc().unix_timestamp()
}
impl InstallerCa {
pub fn mint(estate: &str, valid_for: Duration) -> Result<InstallerCa, MtlsError> {
let key = KeyPair::generate().map_err(e("the CA key"))?;
let mut p = ca_params(&format!("nordisk-pairing installer CA — {estate}"));
(p.not_before, p.not_after) = window(valid_for);
let cert = p.self_signed(&key).map_err(e("the CA certificate"))?;
Ok(InstallerCa { cert_pem: cert.pem(), key_pem: key.serialize_pem() })
}
fn issuer(&self) -> Result<rcgen::Issuer<'static, KeyPair>, MtlsError> {
let key = KeyPair::from_pem(&self.key_pem).map_err(e("the CA key"))?;
rcgen::Issuer::from_ca_cert_pem(&self.cert_pem, key).map_err(e("the CA certificate"))
}
fn leaf(&self, cn: &str, sans: Vec<SanType>, purpose: Vec<ExtendedKeyUsagePurpose>, valid_for: Duration) -> Result<Issued, MtlsError> {
self.leaf_back(cn, sans, purpose, window(valid_for))
}
fn leaf_back(&self, cn: &str, sans: Vec<SanType>, purpose: Vec<ExtendedKeyUsagePurpose>, validity: (time::OffsetDateTime, time::OffsetDateTime)) -> Result<Issued, MtlsError> {
let ca = self.issuer()?;
let key = KeyPair::generate().map_err(e("a leaf key"))?;
let mut p = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, cn);
p.distinguished_name = dn;
p.subject_alt_names = sans;
p.extended_key_usages = purpose;
(p.not_before, p.not_after) = validity;
let cert = p.signed_by(&key, &ca).map_err(e("a leaf certificate"))?;
Ok(Issued { cert_pem: cert.pem(), key_pem: key.serialize_pem() })
}
pub fn issue_door(&self, utility: Ipv4Addr, valid_for: Duration) -> Result<Issued, MtlsError> {
let back = time::Duration::seconds(DOOR_BACKDATE.as_secs() as i64);
self.leaf_back("nordisk-pairing door", vec![SanType::IpAddress(IpAddr::V4(utility))], vec![ExtendedKeyUsagePurpose::ServerAuth], window_back(valid_for, back))
}
pub fn issue_bootstrap(&self, valid_for: Duration) -> Result<Issued, MtlsError> {
self.leaf(BOOTSTRAP_CN, vm_sans(), vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth], valid_for)
}
pub fn issue_installer(&self, valid_for: Duration) -> Result<Issued, MtlsError> {
let back = time::Duration::seconds(DOOR_BACKDATE.as_secs() as i64);
self.leaf_back(INSTALLER_CN, Vec::new(), vec![ExtendedKeyUsagePurpose::ClientAuth], window_back(valid_for, back))
}
pub fn sign_box(&self, csr_pem: &str, name: &str, valid_for: Duration) -> Result<String, MtlsError> {
if name == BOOTSTRAP_CN || name == INSTALLER_CN {
return Err(MtlsError(format!("`{name}` is not a box name")));
}
let ca = self.issuer()?;
let mut csr = CertificateSigningRequestParams::from_pem(csr_pem).map_err(e("the box's signing request"))?;
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, name);
csr.params.distinguished_name = dn;
csr.params.subject_alt_names = vm_sans();
csr.params.is_ca = IsCa::NoCa;
csr.params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth];
(csr.params.not_before, csr.params.not_after) = window(valid_for);
Ok(csr.signed_by(&ca).map_err(e("the box's certificate"))?.pem())
}
}
impl InstallerCa {
pub fn renew_box(&self, current: &str, valid_for: Duration) -> Result<String, MtlsError> {
let (spki, cn) = key_and_name(current)?;
if cn == BOOTSTRAP_CN || cn == INSTALLER_CN {
return Err(MtlsError(format!("`{cn}` is not a box name")));
}
let ca = self.issuer()?;
let public = rcgen::SubjectPublicKeyInfo::from_der(&spki).map_err(e("the box's public key"))?;
let mut p = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, cn);
p.distinguished_name = dn;
p.subject_alt_names = vm_sans();
p.is_ca = IsCa::NoCa;
p.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth];
(p.not_before, p.not_after) = window(valid_for);
Ok(p.signed_by(&public, &ca).map_err(e("the renewed certificate"))?.pem())
}
}
impl InstallerCa {
pub fn renew_ca(&self, valid_for: Duration) -> Result<InstallerCa, MtlsError> {
let key = KeyPair::from_pem(&self.key_pem).map_err(e("the CA key"))?;
let (_, cn) = key_and_name(&self.cert_pem)?;
let mut p = ca_params(&cn);
(p.not_before, p.not_after) = window(valid_for);
let cert = p.self_signed(&key).map_err(e("the renewed CA certificate"))?;
Ok(InstallerCa { cert_pem: cert.pem(), key_pem: self.key_pem.clone() })
}
}
pub fn bootstrap_refusal(boot: &Issued) -> Option<String> {
let (spki, cn) = match key_and_name(&boot.cert_pem) {
Ok(v) => v,
Err(err) => return Some(format!("REFUSED [rebootstrap-unreadable] the bootstrap certificate: {err}")),
};
if cn != BOOTSTRAP_CN {
return Some(format!("REFUSED [rebootstrap-not-bootstrap] the certificate names `{cn}`, not a bootstrap certificate (`{BOOTSTRAP_CN}`)"));
}
match KeyPair::from_pem(&boot.key_pem) {
Ok(k) if rcgen::PublicKeyData::subject_public_key_info(&k) == spki => None,
Ok(_) => Some("REFUSED [rebootstrap-other-key] the key handed is not the bootstrap certificate's".into()),
Err(err) => Some(format!("REFUSED [rebootstrap-unreadable] the bootstrap key: {err}")),
}
}
fn key_and_name(pem: &str) -> Result<(Vec<u8>, String), MtlsError> {
let (_, p) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|err| MtlsError(format!("not a PEM certificate: {err}")))?;
let cert = p.parse_x509().map_err(|err| MtlsError(format!("not an X.509 certificate: {err}")))?;
let cn = cert.subject().iter_common_name().next().and_then(|a| a.as_str().ok()).ok_or_else(|| MtlsError("the certificate names no CN".into()))?;
Ok((cert.public_key().raw.to_vec(), cn.to_string()))
}
fn ca_params(cn: &str) -> CertificateParams {
let mut p = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, cn);
p.distinguished_name = dn;
p.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
p.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature];
p
}
fn life(pem: &str) -> Result<(i64, i64), MtlsError> {
let (_, p) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|err| MtlsError(format!("not a PEM certificate: {err}")))?;
let cert = p.parse_x509().map_err(|err| MtlsError(format!("not an X.509 certificate: {err}")))?;
Ok((cert.validity().not_before.timestamp(), cert.validity().not_after.timestamp()))
}
pub const RENEW_AT: (i64, i64) = (2, 3);
pub fn renewal_due(pem: &str, now_unix: i64) -> Result<bool, MtlsError> {
let (from, to) = life(pem)?;
let at = from + (to - from).max(0) * RENEW_AT.0 / RENEW_AT.1;
Ok(now_unix >= at)
}
pub fn renewal_refusal(current: &str, new: &str) -> Option<String> {
let (old_key, old_cn) = match key_and_name(current) {
Ok(v) => v,
Err(e) => return Some(format!("REFUSED [renew-unreadable] this VM's own certificate: {e}")),
};
let (new_key, new_cn) = match key_and_name(new) {
Ok(v) => v,
Err(e) => return Some(format!("REFUSED [renew-unreadable] the renewed certificate: {e}")),
};
if new_key != old_key {
return Some("REFUSED [renew-other-key] the certificate is for another key, not this VM's own".into());
}
if new_cn != old_cn {
return Some(format!("REFUSED [renew-other-name] the certificate names `{new_cn}`, this VM is `{old_cn}`"));
}
match (life(current), life(new)) {
(Ok((_, old_end)), Ok((_, new_end))) if new_end >= old_end => None,
_ => Some("REFUSED [renew-not-later] the certificate ends before the one this VM holds".into()),
}
}
pub fn cert_cn(pem: &str) -> Result<String, MtlsError> {
let (_, p) = x509_parser::pem::parse_x509_pem(pem.trim().as_bytes()).map_err(|err| MtlsError(format!("not a PEM certificate: {err}")))?;
let cert = p.parse_x509().map_err(|err| MtlsError(format!("not an X.509 certificate: {err}")))?;
let cn = cert.subject().iter_common_name().next().and_then(|a| a.as_str().ok()).ok_or_else(|| MtlsError("the certificate names no CN".into()))?;
Ok(cn.to_string())
}
impl InstallerCa {
pub fn sign_attached(&self, csr_pem: &str, cn: &str, host: &str, valid_for: Duration) -> Result<String, MtlsError> {
let ca = self.issuer()?;
let mut csr = CertificateSigningRequestParams::from_pem(csr_pem).map_err(e("the estate's signing request"))?;
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, cn);
csr.params.distinguished_name = dn;
csr.params.subject_alt_names = match host.parse::<IpAddr>() {
Ok(ip) => vec![SanType::IpAddress(ip)],
Err(_) => vec![SanType::DnsName(host.try_into().map_err(e("the estate's host name"))?)],
};
csr.params.is_ca = IsCa::NoCa;
csr.params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth];
(csr.params.not_before, csr.params.not_after) = window(valid_for);
Ok(csr.signed_by(&ca).map_err(e("the estate's certificate"))?.pem())
}
}
pub fn box_request() -> Result<(String, String), MtlsError> {
let key = KeyPair::generate().map_err(e("the box key"))?;
request_for(key)
}
pub fn box_request_for(key_pem: &str) -> Result<(String, String), MtlsError> {
request_for(KeyPair::from_pem(key_pem).map_err(e("the box key"))?)
}
fn request_for(key: KeyPair) -> Result<(String, String), MtlsError> {
let mut p = CertificateParams::default();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, "box");
p.distinguished_name = dn;
let csr = p.serialize_request(&key).map_err(e("the signing request"))?;
Ok((csr.pem().map_err(e("the signing request as PEM"))?, key.serialize_pem()))
}
fn certs(pem: &str) -> Result<Vec<CertificateDer<'static>>, MtlsError> {
let v: Vec<_> = CertificateDer::pem_slice_iter(pem.as_bytes()).collect::<Result<_, _>>().map_err(|err| MtlsError(format!("a certificate PEM: {err:?}")))?;
if v.is_empty() {
return Err(MtlsError("a certificate PEM with no certificate in it".into()));
}
Ok(v)
}
fn key(pem: &str) -> Result<PrivateKeyDer<'static>, MtlsError> {
PrivateKeyDer::from_pem_slice(pem.as_bytes()).map_err(|err| MtlsError(format!("a private key PEM: {err:?}")))
}
fn provider() -> Arc<rustls::crypto::CryptoProvider> {
Arc::new(crate::pqtls::provider())
}
fn roots(ca_pem: &str) -> Result<rustls::RootCertStore, MtlsError> {
let mut r = rustls::RootCertStore::empty();
for c in certs(ca_pem)? {
r.add(c).map_err(|err| MtlsError(format!("the installer CA as a root: {err}")))?;
}
Ok(r)
}
pub fn server_config(ca_pem: &str, door: &Issued) -> Result<Arc<rustls::ServerConfig>, MtlsError> {
let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots(ca_pem)?), provider())
.build()
.map_err(|err| MtlsError(format!("the client verifier: {err}")))?;
let cfg = rustls::ServerConfig::builder_with_provider(provider())
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|err| MtlsError(format!("TLS 1.3: {err}")))?
.with_client_cert_verifier(verifier)
.with_single_cert(certs(&door.cert_pem)?, key(&door.key_pem)?)
.map_err(|err| MtlsError(format!("the door's certificate: {err}")))?;
Ok(Arc::new(cfg))
}
pub fn client_config(ca_pem: &str, client: &Issued) -> Result<Arc<rustls::ClientConfig>, MtlsError> {
let cfg = rustls::ClientConfig::builder_with_provider(provider())
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|err| MtlsError(format!("TLS 1.3: {err}")))?
.with_root_certificates(roots(ca_pem)?)
.with_client_auth_cert(certs(&client.cert_pem)?, key(&client.key_pem)?)
.map_err(|err| MtlsError(format!("the client certificate: {err}")))?;
Ok(Arc::new(cfg))
}
pub fn door_name(utility: Ipv4Addr) -> ServerName<'static> {
ServerName::IpAddress(IpAddr::V4(utility).into())
}
pub fn peer_cn(conn: &rustls::ServerConnection) -> Option<String> {
cn_of(conn.peer_certificates()?.first()?)
}
pub fn vm_name() -> ServerName<'static> {
ServerName::try_from(VM_SERVER_NAME).expect("VM_SERVER_NAME is a valid DNS name")
}
pub fn server_cn(conn: &rustls::ClientConnection) -> Option<String> {
cn_of(conn.peer_certificates()?.first()?)
}
fn cn_of(der: &CertificateDer<'_>) -> Option<String> {
let (_, cert) = x509_parser::parse_x509_certificate(der.as_ref()).ok()?;
let cn = cert.subject().iter_common_name().next()?.as_str().ok()?.to_string();
Some(cn)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
const H: Duration = Duration::from_secs(3600);
#[test]
fn a_box_joins_with_the_bootstrap_cert_then_speaks_as_itself_and_strangers_are_refused_both_ways() {
let ca = InstallerCa::mint("fleet.test", H).unwrap();
let door = ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
let boot = ca.issue_bootstrap(H).unwrap();
let (csr, box_key) = box_request().unwrap();
let mine = Issued { cert_pem: ca.sign_box(&csr, "main", H).unwrap(), key_pem: box_key };
let other = InstallerCa::mint("another", H).unwrap();
let stranger = other.issue_bootstrap(H).unwrap();
let serve = |client: Issued, pin: String| -> Result<String, String> {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = l.local_addr().unwrap();
let scfg = server_config(&ca.cert_pem, &door).unwrap();
let t = std::thread::spawn(move || {
let (s, _) = l.accept().unwrap();
let conn = rustls::ServerConnection::new(scfg).unwrap();
let mut tls = rustls::StreamOwned::new(conn, s);
let mut b = [0u8; 5];
match tls.read_exact(&mut b) {
Ok(()) => {
let cn = peer_cn(&tls.conn).unwrap_or_default();
let _ = tls.write_all(cn.as_bytes());
tls.conn.send_close_notify();
let _ = tls.flush();
Ok(cn)
}
Err(err) => Err(err.to_string()),
}
});
let ccfg = client_config(&pin, &client).map_err(|e| e.to_string())?;
let s = std::net::TcpStream::connect(addr).unwrap();
let conn = rustls::ClientConnection::new(ccfg, door_name(Ipv4Addr::LOCALHOST)).unwrap();
let mut tls = rustls::StreamOwned::new(conn, s);
let sent = tls.write_all(b"hello").and_then(|_| tls.flush());
let mut back = String::new();
let read = tls.read_to_string(&mut back);
let server = t.join().unwrap();
match (sent, read, server) {
(Ok(()), Ok(_), Ok(cn)) if back == cn => Ok(cn),
(s, r, sv) => Err(format!("sent {s:?} read {r:?} server {sv:?}")),
}
};
assert_eq!(serve(boot.clone(), ca.cert_pem.clone()), Ok(BOOTSTRAP_CN.to_string()));
assert_eq!(serve(mine, ca.cert_pem.clone()), Ok("main".to_string()), "the box speaks as the name the door gave it");
assert!(serve(stranger, ca.cert_pem.clone()).is_err(), "a certificate from another CA is refused by the door");
assert!(serve(boot, other.cert_pem.clone()).is_err(), "a box that pins the installer CA refuses a door it did not issue");
}
#[test]
fn the_door_and_a_box_negotiate_x25519mlkem768() {
let ca = InstallerCa::mint("fleet.test", H).unwrap();
let door = ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
let boot = ca.issue_bootstrap(H).unwrap();
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = l.local_addr().unwrap();
let scfg = server_config(&ca.cert_pem, &door).unwrap();
let t = std::thread::spawn(move || {
let (s, _) = l.accept().unwrap();
let mut tls = rustls::StreamOwned::new(rustls::ServerConnection::new(scfg).unwrap(), s);
let mut b = [0u8; 5];
tls.read_exact(&mut b).unwrap();
tls.write_all(b"done!").unwrap();
tls.flush().unwrap();
tls.conn.negotiated_key_exchange_group().map(|g| g.name())
});
let ccfg = client_config(&ca.cert_pem, &boot).unwrap();
let conn = rustls::ClientConnection::new(ccfg, door_name(Ipv4Addr::LOCALHOST)).unwrap();
let mut tls = rustls::StreamOwned::new(conn, std::net::TcpStream::connect(addr).unwrap());
tls.write_all(b"hello").unwrap();
tls.flush().unwrap();
let mut b = [0u8; 5];
tls.read_exact(&mut b).unwrap();
let client = tls.conn.negotiated_key_exchange_group().map(|g| g.name());
assert_eq!((t.join().unwrap(), client), (Some(rustls::NamedGroup::X25519MLKEM768), Some(rustls::NamedGroup::X25519MLKEM768)));
}
#[test]
fn the_door_certificate_is_back_dated_a_day_and_what_a_certificate_has_left_is_read_off_it() {
let ca = InstallerCa::mint("fleet.test", 90 * 24 * H).unwrap();
let now = now_unix();
let nb = |pem: &str| {
let der = certs(pem).unwrap().remove(0);
let (_, c) = x509_parser::parse_x509_certificate(der.as_ref()).unwrap();
c.validity().not_before.timestamp()
};
let door = ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
let back = now - nb(&door.cert_pem);
assert!((24 * 3600 - 60..=24 * 3600 + 60).contains(&back), "the door is back-dated a day, got {back} s");
let boot = ca.issue_bootstrap(H).unwrap();
assert!(now - nb(&boot.cert_pem) <= 5 * 60 + 60, "the bootstrap certificate keeps five minutes");
let left = cert_left(&ca.cert_pem, now).unwrap();
assert!(left > 89 * 24 * H && left <= 90 * 24 * H, "{left:?}");
assert_eq!(cert_left(&ca.cert_pem, now + 91 * 24 * 3600).unwrap(), Duration::ZERO, "ended");
assert!(cert_left("not a pem", now).is_err());
}
#[test]
fn the_box_request_is_signed_with_the_door_given_name_and_bootstrap_is_not_a_name() {
let ca = InstallerCa::mint("fleet.test", H).unwrap();
let (csr, _) = box_request().unwrap();
let pem = ca.sign_box(&csr, "twin", H).unwrap();
let der = certs(&pem).unwrap().remove(0);
let (_, cert) = x509_parser::parse_x509_certificate(der.as_ref()).unwrap();
assert_eq!(cert.subject().iter_common_name().next().unwrap().as_str().unwrap(), "twin");
assert!(ca.sign_box(&csr, BOOTSTRAP_CN, H).is_err());
assert!(ca.sign_box("not a csr", "twin", H).is_err());
}
#[test]
fn a_renewed_ca_keeps_its_key_and_name_and_the_old_pin_still_holds() {
let ca = InstallerCa::mint("fleet.test", H).unwrap();
let renewed = ca.renew_ca(180 * 24 * H).unwrap();
assert_eq!(renewed.key_pem, ca.key_pem, "the same key");
assert_ne!(renewed.cert_pem, ca.cert_pem);
assert_eq!(key_and_name(&renewed.cert_pem).unwrap(), key_and_name(&ca.cert_pem).unwrap(), "the same key and name");
assert!(cert_left(&renewed.cert_pem, 0).unwrap() > cert_left(&ca.cert_pem, 0).unwrap(), "a later end");
let door = renewed.issue_door(Ipv4Addr::LOCALHOST, H).unwrap();
let (csr, k) = box_request().unwrap();
let mine = Issued { cert_pem: renewed.sign_box(&csr, "main", H).unwrap(), key_pem: k };
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = l.local_addr().unwrap();
let scfg = server_config(&ca.cert_pem, &door).unwrap();
let t = std::thread::spawn(move || {
let (s, _) = l.accept().unwrap();
let mut tls = rustls::StreamOwned::new(rustls::ServerConnection::new(scfg).unwrap(), s);
let mut b = [0u8; 5];
tls.read_exact(&mut b).map(|_| peer_cn(&tls.conn).unwrap_or_default()).map_err(|e| e.to_string())
});
let ccfg = client_config(&ca.cert_pem, &mine).unwrap();
let s = std::net::TcpStream::connect(addr).unwrap();
let mut tls = rustls::StreamOwned::new(rustls::ClientConnection::new(ccfg, door_name(Ipv4Addr::LOCALHOST)).unwrap(), s);
tls.write_all(b"hello").and_then(|_| tls.flush()).expect("the box takes the renewed CA's door under the old pin");
assert_eq!(t.join().unwrap(), Ok("main".to_string()), "the door takes the renewed CA's box under the old pin");
}
fn handshake(pin: &str, door: Issued, mine: Issued) -> Result<String, String> {
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = l.local_addr().unwrap();
let scfg = server_config(pin, &door).map_err(|e| e.to_string())?;
let t = std::thread::spawn(move || {
let (s, _) = l.accept().map_err(|e| e.to_string())?;
let mut tls = rustls::StreamOwned::new(rustls::ServerConnection::new(scfg).unwrap(), s);
let mut b = [0u8; 5];
tls.read_exact(&mut b).map(|_| peer_cn(&tls.conn).unwrap_or_default()).map_err(|e| e.to_string())
});
let ccfg = client_config(pin, &mine).map_err(|e| e.to_string())?;
let s = std::net::TcpStream::connect(addr).unwrap();
let mut tls = rustls::StreamOwned::new(rustls::ClientConnection::new(ccfg, door_name(Ipv4Addr::LOCALHOST)).unwrap(), s);
let sent = tls.write_all(b"hello").and_then(|_| tls.flush()).map_err(|e| format!("the box: {e}"));
drop(tls);
let heard = t.join().unwrap();
sent.and(heard)
}
#[test]
fn day_200_with_the_original_ca_still_joins_and_the_ca_renews_under_the_old_pin() {
let fresh = InstallerCa::mint("fleet.test", H).unwrap();
let key = KeyPair::from_pem(&fresh.key_pem).unwrap();
let mut p = ca_params(&key_and_name(&fresh.cert_pem).unwrap().1);
let now = time::OffsetDateTime::now_utc();
(p.not_before, p.not_after) = (now - time::Duration::days(200), now - time::Duration::days(20));
let expired = InstallerCa { cert_pem: p.self_signed(&key).unwrap().pem(), key_pem: fresh.key_pem.clone() };
let now_unix = now.unix_timestamp();
assert_eq!(cert_left(&expired.cert_pem, now_unix).unwrap(), Duration::ZERO, "the CA certificate is past its end");
assert!(renewal_due(&expired.cert_pem, now_unix).unwrap(), "and due");
let issue = |ca: &InstallerCa| {
let (csr, k) = box_request().unwrap();
(ca.issue_door(Ipv4Addr::LOCALHOST, H).unwrap(), Issued { cert_pem: ca.sign_box(&csr, "main", H).unwrap(), key_pem: k })
};
let (door, mine) = issue(&expired);
assert_eq!(handshake(&expired.cert_pem, door, mine), Ok("main".to_string()), "an expired anchor still anchors");
let renewed = expired.renew_ca(180 * 24 * H).unwrap();
assert!(cert_left(&renewed.cert_pem, now_unix).unwrap() > 170 * 24 * H, "renewed past day 200: a new lifetime");
assert!(!renewal_due(&renewed.cert_pem, now_unix).unwrap());
let (door, mine) = issue(&renewed);
assert_eq!(handshake(&expired.cert_pem, door, mine), Ok("main".to_string()), "the boxes' old pin takes the renewed CA's certificates");
}
}