use std::path::PathBuf;
use product_os_security::certificates::{
default_storage_dir, CaProfile, Certificates, CertificatesFormat, ManagedCa, ManagedCaConfig,
};
use product_os_utilities::ProductOSError;
use rcgen::KeyPair;
use crate::config::NetworkProxyCertificateAuthority;
use crate::error::ProxyError;
pub struct LoadedCa {
pub certificates: Certificates,
pub cert_pem: Vec<u8>,
pub cert_path: Option<PathBuf>,
pub key_path: Option<PathBuf>,
}
impl LoadedCa {
pub fn to_managed_config(&self) -> Option<ManagedCaConfig> {
let cert_path = self.cert_path.as_ref()?;
let key_path = self.key_path.as_ref()?;
Some(ManagedCaConfig {
storage_dir: cert_path
.parent()
.unwrap_or_else(|| std::path::Path::new("."))
.to_path_buf(),
cert_file: cert_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("ca.pem")
.to_string(),
key_file: key_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("ca.key")
.to_string(),
profile: CaProfile::development(),
})
}
pub fn from_files(cert_file: String, key_file: String) -> Self {
let certificates =
Certificates::new_ca_from_file(cert_file.clone(), key_file.clone(), None);
let cert_pem = certificates
.certificates
.first()
.cloned()
.unwrap_or_default();
Self {
certificates,
cert_pem,
cert_path: Some(PathBuf::from(cert_file)),
key_path: Some(PathBuf::from(key_file)),
}
}
pub fn from_managed(
managed: &crate::config::NetworkProxyCertificateAuthorityManaged,
) -> Result<Self, ProductOSError> {
let storage_dir = managed
.storage_path
.as_ref()
.map(|p| expand_tilde(p))
.transpose()
.map_err(|e| ProductOSError::from(ProxyError::CertLoadFailed(e)))?
.unwrap_or_else(|| {
default_storage_dir().unwrap_or_else(|_| PathBuf::from(".product-os/proxy"))
});
let mut profile = CaProfile::development();
if let Some(cn) = &managed.common_name {
profile.common_name = cn.clone();
}
let config = ManagedCaConfig {
storage_dir,
cert_file: "ca.pem".to_string(),
key_file: "ca.key".to_string(),
profile,
};
let managed_ca = ManagedCa::load_or_create(&config)
.map_err(|e| ProductOSError::from(ProxyError::CertLoadFailed(e.to_string())))?;
Ok(Self {
certificates: managed_ca.certificates().clone(),
cert_pem: managed_ca.cert_pem().to_vec(),
cert_path: Some(managed_ca.cert_path().to_path_buf()),
key_path: Some(managed_ca.key_path().to_path_buf()),
})
}
}
pub fn load_certificate_authority(
ca_config: &NetworkProxyCertificateAuthority,
) -> Result<LoadedCa, ProductOSError> {
let loaded = if let Some(files) = &ca_config.files {
LoadedCa::from_files(files.cert_file.clone(), files.key_file.clone())
} else if let Some(managed) = &ca_config.managed {
if managed.enabled {
LoadedCa::from_managed(managed)?
} else {
return Err(ProductOSError::from(ProxyError::NoCertsProvided));
}
} else {
return Err(ProductOSError::from(ProxyError::NoCertsProvided));
};
Ok(LoadedCa {
certificates: normalize_pkcs8(loaded.certificates),
..loaded
})
}
fn normalize_pkcs8(mut certificates: Certificates) -> Certificates {
if matches!(
certificates.format,
CertificatesFormat::Pkcs8 | CertificatesFormat::Der
) {
return certificates;
}
let Some(cert_bytes) = certificates.certificates.first() else {
return certificates;
};
let Ok(cert_pem) = std::str::from_utf8(cert_bytes) else {
return certificates;
};
let Ok(key_pem) = std::str::from_utf8(&certificates.private) else {
return certificates;
};
if let (Ok(params), Ok(key_pair)) = (
rcgen::CertificateParams::from_ca_cert_pem(cert_pem),
KeyPair::from_pem(key_pem),
) {
if let Ok(cert) = params.self_signed(&key_pair) {
certificates.certificates[0] = cert.der().to_vec();
certificates.private = key_pair.serialize_der();
certificates.format = CertificatesFormat::Pkcs8;
}
}
certificates
}
fn expand_tilde(path: &str) -> Result<PathBuf, String> {
if let Some(rest) = path.strip_prefix("~/") {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.map_err(|_| "could not expand ~ in path".to_string())?;
return Ok(PathBuf::from(home).join(rest));
}
if path == "~" {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.map_err(|_| "could not expand ~".to_string())?;
return Ok(PathBuf::from(home));
}
Ok(PathBuf::from(path))
}