use crate::error::{NexarError, Result};
use std::sync::Arc;
pub struct ClusterCa {
cert_der: rustls::pki_types::CertificateDer<'static>,
params: Option<rcgen::CertificateParams>,
key_pair: rcgen::KeyPair,
}
impl ClusterCa {
pub fn generate() -> Result<Self> {
let mut params = rcgen::CertificateParams::new(Vec::<String>::new())
.map_err(|e| NexarError::Tls(e.to_string()))?;
params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
params.distinguished_name = rcgen::DistinguishedName::new();
params
.distinguished_name
.push(rcgen::DnType::CommonName, "nexar-cluster-ca");
let key_pair = rcgen::KeyPair::generate().map_err(|e| NexarError::Tls(e.to_string()))?;
let cert = params
.self_signed(&key_pair)
.map_err(|e| NexarError::Tls(e.to_string()))?;
let cert_der = rustls::pki_types::CertificateDer::from(cert.der().to_vec());
Ok(Self {
cert_der,
params: Some(params),
key_pair,
})
}
pub fn cert_der(&self) -> rustls::pki_types::CertificateDer<'static> {
self.cert_der.clone()
}
pub fn issue_cert(
&self,
san: &str,
) -> Result<(
rustls::pki_types::CertificateDer<'static>,
rustls::pki_types::PrivateKeyDer<'static>,
)> {
self.issue_cert_multi(&[san])
}
pub fn issue_cert_multi(
&self,
sans: &[&str],
) -> Result<(
rustls::pki_types::CertificateDer<'static>,
rustls::pki_types::PrivateKeyDer<'static>,
)> {
if sans.is_empty() {
return Err(NexarError::Tls(
"issue_cert_multi requires at least one SAN".into(),
));
}
let san_vec: Vec<String> = sans.iter().map(|s| (*s).to_string()).collect();
let leaf_params =
rcgen::CertificateParams::new(san_vec).map_err(|e| NexarError::Tls(e.to_string()))?;
let leaf_key = rcgen::KeyPair::generate().map_err(|e| NexarError::Tls(e.to_string()))?;
let leaf_cert = match &self.params {
Some(params) => {
let issuer = rcgen::Issuer::from_params(params, &self.key_pair);
leaf_params
.signed_by(&leaf_key, &issuer)
.map_err(|e| NexarError::Tls(e.to_string()))?
}
None => {
let issuer = rcgen::Issuer::from_ca_cert_der(&self.cert_der, &self.key_pair)
.map_err(|e| NexarError::Tls(format!("rebuild issuer: {e}")))?;
leaf_params
.signed_by(&leaf_key, &issuer)
.map_err(|e| NexarError::Tls(e.to_string()))?
}
};
let cert_der = rustls::pki_types::CertificateDer::from(leaf_cert.der().to_vec());
let key_der = rustls::pki_types::PrivateKeyDer::try_from(leaf_key.serialize_der())
.map_err(|e| NexarError::Tls(e.to_string()))?;
Ok((cert_der, key_der))
}
pub fn key_pair_pkcs8_der(&self) -> Vec<u8> {
self.key_pair.serialize_der()
}
pub fn from_der(
key_pair_pkcs8_der: &[u8],
cert_der: &rustls::pki_types::CertificateDer<'_>,
) -> Result<Self> {
let key_pair = rcgen::KeyPair::try_from(key_pair_pkcs8_der)
.map_err(|e| NexarError::Tls(format!("load CA key: {e}")))?;
Ok(Self {
cert_der: cert_der.clone().into_owned(),
params: None,
key_pair,
})
}
}
pub(crate) fn datacenter_transport_config() -> quinn::TransportConfig {
let mut config = quinn::TransportConfig::default();
config.receive_window(quinn::VarInt::from_u32(256 * 1024 * 1024));
config.send_window(256 * 1024 * 1024);
config.stream_receive_window(quinn::VarInt::from_u32(64 * 1024 * 1024));
config.initial_rtt(std::time::Duration::from_micros(100));
config.keep_alive_interval(Some(std::time::Duration::from_secs(5)));
config.max_idle_timeout(Some(
std::time::Duration::from_secs(30)
.try_into()
.expect("30s fits in IdleTimeout"),
));
config.max_concurrent_uni_streams(quinn::VarInt::from_u32(1024));
config
}
pub fn generate_self_signed_cert() -> Result<(
rustls::pki_types::CertificateDer<'static>,
rustls::pki_types::PrivateKeyDer<'static>,
)> {
let cert_params = rcgen::CertificateParams::new(vec!["localhost".into()])
.map_err(|e| NexarError::Tls(e.to_string()))?;
let key_pair = rcgen::KeyPair::generate().map_err(|e| NexarError::Tls(e.to_string()))?;
let cert = cert_params
.self_signed(&key_pair)
.map_err(|e| NexarError::Tls(e.to_string()))?;
let cert_der = rustls::pki_types::CertificateDer::from(cert.der().to_vec());
let key_der = rustls::pki_types::PrivateKeyDer::try_from(key_pair.serialize_der())
.map_err(|e| NexarError::Tls(e.to_string()))?;
Ok((cert_der, key_der))
}
pub fn make_server_config(
cert: rustls::pki_types::CertificateDer<'static>,
key: rustls::pki_types::PrivateKeyDer<'static>,
) -> Result<quinn::ServerConfig> {
let mut tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(e.to_string()))?;
tls_config.alpn_protocols = vec![b"nexar/1".to_vec()];
let quic_config = quinn::crypto::rustls::QuicServerConfig::try_from(Arc::new(tls_config))
.map_err(|e| NexarError::Tls(e.to_string()))?;
let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_config));
server_config.transport_config(Arc::new(datacenter_transport_config()));
Ok(server_config)
}
pub fn make_server_config_mtls(
cert: rustls::pki_types::CertificateDer<'static>,
key: rustls::pki_types::PrivateKeyDer<'static>,
ca_cert: &rustls::pki_types::CertificateDer<'static>,
) -> Result<quinn::ServerConfig> {
let mut root_store = rustls::RootCertStore::empty();
root_store
.add(ca_cert.clone())
.map_err(|e| NexarError::Tls(format!("add CA to root store: {e}")))?;
let client_verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
.build()
.map_err(|e| NexarError::Tls(format!("build client verifier: {e}")))?;
let mut tls_config = rustls::ServerConfig::builder()
.with_client_cert_verifier(client_verifier)
.with_single_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(e.to_string()))?;
tls_config.alpn_protocols = vec![b"nexar/1".to_vec()];
let quic_config = quinn::crypto::rustls::QuicServerConfig::try_from(Arc::new(tls_config))
.map_err(|e| NexarError::Tls(e.to_string()))?;
let mut server_config = quinn::ServerConfig::with_crypto(Arc::new(quic_config));
server_config.transport_config(Arc::new(datacenter_transport_config()));
Ok(server_config)
}
pub fn make_bootstrap_client_config() -> Result<quinn::ClientConfig> {
let mut tls_config = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipServerVerification))
.with_no_client_auth();
tls_config.alpn_protocols = vec![b"nexar/1".to_vec()];
let quic_config = quinn::crypto::rustls::QuicClientConfig::try_from(Arc::new(tls_config))
.map_err(|e| NexarError::Tls(e.to_string()))?;
let mut client_config = quinn::ClientConfig::new(Arc::new(quic_config));
client_config.transport_config(Arc::new(datacenter_transport_config()));
Ok(client_config)
}
pub fn make_client_config_mtls(
cert: rustls::pki_types::CertificateDer<'static>,
key: rustls::pki_types::PrivateKeyDer<'static>,
ca_cert: &rustls::pki_types::CertificateDer<'static>,
) -> Result<quinn::ClientConfig> {
let mut root_store = rustls::RootCertStore::empty();
root_store
.add(ca_cert.clone())
.map_err(|e| NexarError::Tls(format!("add CA to root store: {e}")))?;
let mut tls_config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(e.to_string()))?;
tls_config.alpn_protocols = vec![b"nexar/1".to_vec()];
let quic_config = quinn::crypto::rustls::QuicClientConfig::try_from(Arc::new(tls_config))
.map_err(|e| NexarError::Tls(e.to_string()))?;
let mut client_config = quinn::ClientConfig::new(Arc::new(quic_config));
client_config.transport_config(Arc::new(datacenter_transport_config()));
Ok(client_config)
}
#[derive(Debug)]
struct SkipServerVerification;
impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
rustls::crypto::ring::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
pub fn make_bulk_tls_server_config(
cert: rustls::pki_types::CertificateDer<'static>,
key: rustls::pki_types::PrivateKeyDer<'static>,
ca_cert: &rustls::pki_types::CertificateDer<'static>,
) -> Result<Arc<rustls::ServerConfig>> {
let mut root_store = rustls::RootCertStore::empty();
root_store
.add(ca_cert.clone())
.map_err(|e| NexarError::Tls(format!("bulk TLS: add CA to root store: {e}")))?;
let client_verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
.build()
.map_err(|e| NexarError::Tls(format!("bulk TLS: build client verifier: {e}")))?;
let mut tls_config = rustls::ServerConfig::builder()
.with_client_cert_verifier(client_verifier)
.with_single_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(format!("bulk TLS server config: {e}")))?;
tls_config.alpn_protocols = vec![b"nexar-bulk/1".to_vec()];
Ok(Arc::new(tls_config))
}
pub fn make_bulk_tls_client_config(
cert: rustls::pki_types::CertificateDer<'static>,
key: rustls::pki_types::PrivateKeyDer<'static>,
ca_cert: &rustls::pki_types::CertificateDer<'static>,
) -> Result<Arc<rustls::ClientConfig>> {
let mut root_store = rustls::RootCertStore::empty();
root_store
.add(ca_cert.clone())
.map_err(|e| NexarError::Tls(format!("bulk TLS: add CA to root store: {e}")))?;
let mut tls_config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(format!("bulk TLS client config: {e}")))?;
tls_config.alpn_protocols = vec![b"nexar-bulk/1".to_vec()];
Ok(Arc::new(tls_config))
}
pub(crate) fn make_bulk_tls_server_config_insecure() -> Result<Arc<rustls::ServerConfig>> {
let (cert, key) = generate_self_signed_cert()?;
let mut tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.map_err(|e| NexarError::Tls(format!("bulk TLS insecure server: {e}")))?;
tls_config.alpn_protocols = vec![b"nexar-bulk/1".to_vec()];
Ok(Arc::new(tls_config))
}
pub(crate) fn make_bulk_tls_client_config_insecure() -> Result<Arc<rustls::ClientConfig>> {
let mut tls_config = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipServerVerification))
.with_no_client_auth();
tls_config.alpn_protocols = vec![b"nexar-bulk/1".to_vec()];
Ok(Arc::new(tls_config))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_self_signed_cert() {
let (cert, key) = generate_self_signed_cert().unwrap();
assert!(!cert.is_empty());
let _ = key;
}
#[test]
fn test_make_server_config() {
let (cert, key) = generate_self_signed_cert().unwrap();
let config = make_server_config(cert, key).unwrap();
let _ = config;
}
#[test]
fn test_make_bootstrap_client_config() {
let config = make_bootstrap_client_config().unwrap();
let _ = config;
}
#[test]
fn test_ca_generation() {
let ca = ClusterCa::generate().unwrap();
let cert_der = ca.cert_der();
assert!(!cert_der.is_empty());
}
#[test]
fn test_issue_cert_signed_by_ca() {
let ca = ClusterCa::generate().unwrap();
let (cert, key) = ca.issue_cert("localhost").unwrap();
assert!(!cert.is_empty());
let _ = key;
}
#[test]
fn test_mtls_server_config_with_ca() {
let ca = ClusterCa::generate().unwrap();
let ca_cert = ca.cert_der();
let (cert, key) = ca.issue_cert("localhost").unwrap();
let config = make_server_config_mtls(cert, key, &ca_cert).unwrap();
let _ = config;
}
#[test]
fn test_mtls_client_config_with_ca() {
let ca = ClusterCa::generate().unwrap();
let ca_cert = ca.cert_der();
let (cert, key) = ca.issue_cert("localhost").unwrap();
let config = make_client_config_mtls(cert, key, &ca_cert).unwrap();
let _ = config;
}
#[test]
fn test_multiple_leaf_certs_from_same_ca() {
let ca = ClusterCa::generate().unwrap();
let (cert1, _key1) = ca.issue_cert("localhost").unwrap();
let (cert2, _key2) = ca.issue_cert("localhost").unwrap();
assert_ne!(cert1.as_ref(), cert2.as_ref());
}
#[test]
fn test_issue_cert_multi_rejects_empty() {
let ca = ClusterCa::generate().unwrap();
assert!(ca.issue_cert_multi(&[]).is_err());
}
#[test]
fn test_issue_cert_multi_with_multiple_sans() {
let ca = ClusterCa::generate().unwrap();
let (cert, _key) = ca.issue_cert_multi(&["nodedb", "node-3"]).unwrap();
assert!(!cert.is_empty());
}
#[test]
fn test_ca_roundtrip_via_der() {
let ca1 = ClusterCa::generate().unwrap();
let ca1_cert = ca1.cert_der();
let ca1_key = ca1.key_pair_pkcs8_der();
assert!(!ca1_key.is_empty());
let ca2 = ClusterCa::from_der(&ca1_key, &ca1_cert).unwrap();
let (leaf, _leaf_key) = ca2.issue_cert("nodedb").unwrap();
let config = make_server_config_mtls(leaf, _leaf_key, &ca1_cert).unwrap();
let _ = config;
}
}