use crate::mitm::certificate_authority::{CertificateAuthority, CACHE_TTL};
use moka::future::Cache;
use product_os_http::uri::Authority;
use product_os_security::certificates::CaProfile;
use product_os_utilities::ProductOSError;
use rand::thread_rng;
use rand::Rng;
use rcgen::{Certificate, CertificateParams, KeyPair};
use std::sync::Arc;
use tokio_rustls::rustls::{
pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer},
ServerConfig,
};
use tracing::debug;
pub struct RcgenAuthority {
key_pair: Arc<KeyPair>,
ca_cert: Arc<Certificate>,
cache: Cache<Authority, Arc<ServerConfig>>,
}
impl RcgenAuthority {
pub fn new(key_pair: KeyPair, ca_cert: Certificate, cache_size: u64) -> Self {
Self {
key_pair: Arc::new(key_pair),
ca_cert: Arc::new(ca_cert),
cache: Cache::builder()
.max_capacity(cache_size)
.time_to_live(std::time::Duration::from_secs(CACHE_TTL))
.build(),
}
}
fn gen_cert_sync(
ca_key_pair: &KeyPair,
ca_cert: &Certificate,
authority: &Authority,
) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>), rcgen::Error> {
let mut params = CaProfile::server_leaf_params(authority.host())?;
params.serial_number = Some(thread_rng().gen::<u64>().into());
let leaf_key = KeyPair::generate()?;
let cert = params.signed_by(&leaf_key, ca_cert, ca_key_pair)?;
let leaf_private = PrivateKeyDer::from(PrivatePkcs8KeyDer::from(leaf_key.serialize_der()));
Ok((cert.into(), leaf_private))
}
#[allow(dead_code)]
pub(crate) fn gen_cert(
&self,
authority: &Authority,
) -> Result<CertificateDer<'static>, rcgen::Error> {
Ok(Self::gen_cert_sync(self.key_pair.as_ref(), self.ca_cert.as_ref(), authority)?.0)
}
}
impl CertificateAuthority for RcgenAuthority {
async fn gen_server_config(
&self,
authority: &Authority,
) -> Result<Arc<ServerConfig>, ProductOSError> {
if let Some(server_cfg) = self.cache.get(authority).await {
debug!("Using cached server config");
return Ok(server_cfg);
}
debug!("Generating server config");
let key_pair = Arc::clone(&self.key_pair);
let ca_cert = Arc::clone(&self.ca_cert);
let authority_clone = authority.clone();
let (cert, private_key) = match tokio::task::spawn_blocking(move || {
Self::gen_cert_sync(&key_pair, &ca_cert, &authority_clone)
})
.await
{
Ok(Ok(pair)) => pair,
Ok(Err(e)) => {
tracing::error!(
"Failed to generate certificate for {}: {:?}; falling back to generic cert",
authority,
e
);
let key_pair = Arc::clone(&self.key_pair);
let ca_cert = Arc::clone(&self.ca_cert);
let fallback_cert = tokio::task::spawn_blocking(move || -> Result<
(CertificateDer<'static>, PrivateKeyDer<'static>),
rcgen::Error,
> {
let mut fallback_params = CertificateParams::default();
fallback_params.serial_number = Some(thread_rng().gen::<u64>().into());
let leaf_key = KeyPair::generate()?;
match fallback_params.signed_by(&leaf_key, ca_cert.as_ref(), key_pair.as_ref()) {
Ok(c) => Ok((
c.into(),
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(leaf_key.serialize_der())),
)),
Err(fallback_err) => {
tracing::error!("Fallback certificate generation also failed: {:?}", fallback_err);
let cert = CertificateParams::default().self_signed(&leaf_key)?;
Ok((
cert.into(),
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(leaf_key.serialize_der())),
))
}
}
})
.await;
match fallback_cert {
Ok(Ok(pair)) => pair,
Ok(Err(_)) => {
tracing::error!("Emergency self-signed cert generation failed");
return Err(ProductOSError::GenericError(
"Cannot generate any certificate; CA key pair may be invalid".into(),
));
}
Err(e) => {
tracing::error!("spawn_blocking failed for fallback cert: {:?}", e);
return Err(ProductOSError::GenericError(format!(
"Certificate generation task failed: {e:?}"
)));
}
}
}
Err(e) => {
tracing::error!("spawn_blocking failed for cert generation: {:?}", e);
return Err(ProductOSError::GenericError(format!(
"Certificate generation task failed: {e:?}"
)));
}
};
let certs = vec![cert];
let mut server_cfg = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, private_key)
.map_err(|e| {
ProductOSError::GenericError(format!("Failed to build ServerConfig: {e:?}"))
})?;
server_cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let server_cfg = Arc::new(server_cfg);
self.cache
.insert(authority.clone(), Arc::clone(&server_cfg))
.await;
Ok(server_cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_rustls::rustls;
fn create_test_authority() -> RcgenAuthority {
let _ = rustls::crypto::ring::default_provider().install_default();
let key_pair = rcgen::KeyPair::generate().expect("generate test CA key pair");
let mut cert_params = rcgen::CertificateParams::default();
cert_params
.distinguished_name
.push(rcgen::DnType::CommonName, "test-ca");
cert_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
let ca_cert = cert_params
.self_signed(&key_pair)
.expect("self-sign test CA cert");
RcgenAuthority::new(key_pair, ca_cert, 100)
}
#[test]
fn test_rcgen_authority_creation() {
let _authority = create_test_authority();
}
#[test]
fn test_rcgen_authority_gen_cert() {
let authority = create_test_authority();
let host_authority: Authority = "example.com".parse().unwrap();
let cert_der = authority
.gen_cert(&host_authority)
.expect("Failed to generate cert");
assert!(
!cert_der.is_empty(),
"Generated certificate should not be empty"
);
}
#[test]
fn test_rcgen_authority_gen_cert_ip_address() {
let authority = create_test_authority();
let ip_authority: Authority = "127.0.0.1".parse().unwrap();
let cert_der = authority
.gen_cert(&ip_authority)
.expect("Failed to generate cert for IP");
assert!(
!cert_der.is_empty(),
"Generated certificate for IP should not be empty"
);
}
#[tokio::test]
async fn test_rcgen_authority_gen_server_config() {
let authority = create_test_authority();
let host_authority: Authority = "example.com".parse().unwrap();
let config = authority
.gen_server_config(&host_authority)
.await
.expect("gen_server_config should succeed");
assert_eq!(config.alpn_protocols.len(), 2);
assert_eq!(config.alpn_protocols[0], b"h2");
assert_eq!(config.alpn_protocols[1], b"http/1.1");
}
#[tokio::test]
async fn test_rcgen_authority_cache_returns_same_config() {
let authority = create_test_authority();
let host_authority: Authority = "cached.example.com".parse().unwrap();
let config1 = authority
.gen_server_config(&host_authority)
.await
.expect("gen_server_config should succeed");
let config2 = authority
.gen_server_config(&host_authority)
.await
.expect("gen_server_config should succeed");
assert!(
Arc::ptr_eq(&config1, &config2),
"Second call should return cached config"
);
}
#[tokio::test]
async fn test_rcgen_authority_different_hosts_different_configs() {
let authority = create_test_authority();
let host1: Authority = "host1.example.com".parse().unwrap();
let host2: Authority = "host2.example.com".parse().unwrap();
let config1 = authority
.gen_server_config(&host1)
.await
.expect("gen_server_config should succeed");
let config2 = authority
.gen_server_config(&host2)
.await
.expect("gen_server_config should succeed");
assert!(
!Arc::ptr_eq(&config1, &config2),
"Different hosts should have different configs"
);
}
}