crates/installer/nordisk-twin/src/tls.rs
//! **TLS 1.3 only, on `ring`, both ends.**
//!
//! The provider is `ring::default_provider()` with every suite that is not TLS
//! 1.3 removed, and the protocol list is TLS 1.3 alone — asked of THIS config,
//! not installed process-wide, so a product whose other doors still speak 1.2
//! (a public HTTPS door behind an old proxy) loses nothing here and gains
//! nothing there. Built by filtering the provider's own list, so it cannot go
//! stale the day rustls adds or retires a suite.
//!
//! * [`server_config`] — the replication door: its server leaf, and a client
//! certificate chained to the issuer REQUIRED (no `allow_unauthenticated`: a
//! door that lets a certless client in and hopes the application refuses is
//! ordinary TLS wearing mTLS's name).
//! * [`client_config`] — the twin: the PINNED CA as the only root (no platform
//! roots, ever), its own certificate presented.
use std::sync::Arc;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, UnixTime};
use crate::{Result, TwinError};
/// `ring`, TLS 1.3 suites only.
pub fn tls13_provider() -> Arc<rustls::crypto::CryptoProvider> {
let mut p = rustls::crypto::ring::default_provider();
p.cipher_suites.retain(|s| s.version().version == rustls::ProtocolVersion::TLSv1_3);
Arc::new(p)
}
fn certs(pem: &str) -> Result<Vec<CertificateDer<'static>>> {
let v: Vec<_> = CertificateDer::pem_slice_iter(pem.as_bytes()).collect::<std::result::Result<_, _>>().map_err(|e| TwinError::Pki(format!("a PEM certificate: {e}")))?;
if v.is_empty() {
return Err(TwinError::Pki("no PEM certificate where one was expected".into()));
}
Ok(v)
}
fn key(pem: &str) -> Result<PrivateKeyDer<'static>> {
PrivateKeyDer::from_pem_slice(pem.as_bytes()).map_err(|e| TwinError::Pki(format!("a PEM private key: {e}")))
}
fn roots(ca_pem: &str) -> Result<rustls::RootCertStore> {
let mut r = rustls::RootCertStore::empty();
for c in certs(ca_pem)? {
r.add(c).map_err(|e| TwinError::Pki(format!("the CA as a root: {e}")))?;
}
Ok(r)
}
fn verifier(ca_pem: &str) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> {
rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots(ca_pem)?), tls13_provider())
.build()
.map_err(|e| TwinError::Pki(format!("the client verifier: {e}")))
}
/// **The replication door's TLS**: `cert_pem`/`key_pem` its server leaf, and a
/// client certificate chained to `client_ca_pem` REQUIRED on every connection.
/// ALPN `h2` — gRPC is all this door speaks.
pub fn server_config(cert_pem: &str, key_pem: &str, client_ca_pem: &str) -> Result<Arc<rustls::ServerConfig>> {
let mut cfg = rustls::ServerConfig::builder_with_provider(tls13_provider())
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| TwinError::Pki(format!("TLS 1.3: {e}")))?
.with_client_cert_verifier(verifier(client_ca_pem)?)
.with_single_cert(certs(cert_pem)?, key(key_pem)?)
.map_err(|e| TwinError::Pki(format!("the door's certificate: {e}")))?;
cfg.alpn_protocols = vec![b"h2".to_vec()];
Ok(Arc::new(cfg))
}
/// **[`server_config`], and each client leaf must be its CN's CURRENT holder**
/// (Gunnar 273960cb / `with_registered_client_certificate_required`: a nuked
/// twin's certificate, superseded when its replacement was certified, is refused
/// at the door). The pins under `pins` are what
/// [`crate::pki::Issuer::issue_superseding`] writes; they are read at every
/// handshake, so a supersede holds from the next connection with no restart. A
/// superseded leaf is refused as `CertificateError::Revoked`
/// ([`refusal_name`]: `superseded`). A pin that cannot be read refuses (closed).
pub fn server_config_current(cert_pem: &str, key_pem: &str, client_ca_pem: &str, pins: &std::path::Path) -> Result<Arc<rustls::ServerConfig>> {
let v: Arc<dyn rustls::server::danger::ClientCertVerifier> = Arc::new(CurrentHolder { inner: verifier(client_ca_pem)?, pins: pins.to_path_buf() });
let mut cfg = rustls::ServerConfig::builder_with_provider(tls13_provider())
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| TwinError::Pki(format!("TLS 1.3: {e}")))?
.with_client_cert_verifier(v)
.with_single_cert(certs(cert_pem)?, key(key_pem)?)
.map_err(|e| TwinError::Pki(format!("the door's certificate: {e}")))?;
cfg.alpn_protocols = vec![b"h2".to_vec()];
Ok(Arc::new(cfg))
}
/// The chain to the issuer first (the webpki verifier), then the holder pin.
#[derive(Debug)]
struct CurrentHolder {
inner: Arc<dyn rustls::server::danger::ClientCertVerifier>,
pins: std::path::PathBuf,
}
impl rustls::server::danger::ClientCertVerifier for CurrentHolder {
fn offer_client_auth(&self) -> bool {
self.inner.offer_client_auth()
}
fn client_auth_mandatory(&self) -> bool {
self.inner.client_auth_mandatory()
}
fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
self.inner.root_hint_subjects()
}
fn verify_client_cert(&self, end_entity: &CertificateDer<'_>, intermediates: &[CertificateDer<'_>], now: UnixTime) -> std::result::Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
let ok = self.inner.verify_client_cert(end_entity, intermediates, now)?;
match crate::pki::is_current_holder(&self.pins, end_entity.as_ref()) {
Ok(Ok(())) => Ok(ok),
Ok(Err(_)) => Err(rustls::Error::InvalidCertificate(rustls::CertificateError::Revoked)),
Err(e) => Err(rustls::Error::General(format!("the holder pins: {e}"))),
}
}
fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
self.inner.verify_tls12_signature(message, cert, dss)
}
fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
self.inner.verify_tls13_signature(message, cert, dss)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.inner.supported_verify_schemes()
}
}
/// **Would the door built by [`server_config_current`] take `cert_pem` now?**
/// The chain for clientAuth, then the holder pin. `Err` names which.
pub fn verify_current_client(ca_pem: &str, cert_pem: &str, pins: &std::path::Path) -> Result<()> {
verify_client_chain(ca_pem, cert_pem)?;
match crate::pki::is_current_holder(pins, &crate::pki::pem_to_der(cert_pem)?)? {
Ok(()) => Ok(()),
Err(why) => Err(TwinError::NotIssuedBy(format!("superseded: {why}"))),
}
}
/// **The twin's TLS**: `ca_pem` pinned as the ONLY root, `cert_pem`/`key_pem`
/// presented. ALPN `h2`.
pub fn client_config(ca_pem: &str, cert_pem: &str, key_pem: &str) -> Result<Arc<rustls::ClientConfig>> {
let mut cfg = rustls::ClientConfig::builder_with_provider(tls13_provider())
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| TwinError::Pki(format!("TLS 1.3: {e}")))?
.with_root_certificates(roots(ca_pem)?)
.with_client_auth_cert(certs(cert_pem)?, key(key_pem)?)
.map_err(|e| TwinError::Pki(format!("the client certificate: {e}")))?;
cfg.alpn_protocols = vec![b"h2".to_vec()];
Ok(Arc::new(cfg))
}
/// **Does `cert_pem` chain to `ca_pem` for client authentication, now?** The
/// door's own verifier asked the door's own question — signature, validity
/// window and EKU — so an install that passes here is a handshake that passes
/// there. `Err` is [`TwinError::NotIssuedBy`] with rustls' reason.
pub fn verify_client_chain(ca_pem: &str, cert_pem: &str) -> Result<()> {
let v = verifier(ca_pem)?;
let chain = certs(cert_pem)?;
v.verify_client_cert(&chain[0], &chain[1..], UnixTime::now()).map(|_| ()).map_err(|e| TwinError::NotIssuedBy(e.to_string()))
}
/// The server name a twin checks the door's certificate against — the fixed
/// door name, never the address (the address it dials is whichever answered).
pub fn server_name(name: &str) -> Result<rustls::pki_types::ServerName<'static>> {
rustls::pki_types::ServerName::try_from(name.to_string()).map_err(|e| TwinError::Pki(format!("`{name}` is not a server name: {e}")))
}
/// **A refused handshake, named.** The door's accept loop hands every failed
/// handshake here; a product tags the name with its own code.
///
/// * `no-client-certificate` — the peer presented none;
/// * `superseded` — this door's issuer signed it, for a key its CN no longer
/// holds ([`server_config_current`]);
/// * `not-from-this-issuer` — it presented one this door's issuer did not sign
/// (another CA, a pairing medium's bootstrap certificate, a self-signed one);
/// * `not-tls13` — it offered nothing this door speaks;
/// * `handshake` — anything else (a scan, a reset).
pub fn refusal_name(err: &std::io::Error) -> &'static str {
let Some(inner) = err.get_ref().and_then(|e| e.downcast_ref::<rustls::Error>()) else { return "handshake" };
match inner {
rustls::Error::NoCertificatesPresented => "no-client-certificate",
rustls::Error::InvalidCertificate(rustls::CertificateError::Revoked) => "superseded",
rustls::Error::InvalidCertificate(_) => "not-from-this-issuer",
rustls::Error::PeerIncompatible(_) => "not-tls13",
_ => "handshake",
}
}