use crate::error::{Result, TakError};
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::{ClientConfig, RootCertStore, SignatureScheme, DigitallySignedStruct};
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::Path;
use std::sync::Arc;
#[derive(Clone)]
pub struct TlsConfig {
pub(crate) config: Arc<ClientConfig>,
}
#[derive(Default)]
pub struct TlsConfigBuilder {
client_certs: Option<Vec<CertificateDer<'static>>>,
client_key: Option<PrivateKeyDer<'static>>,
root_store: Option<RootCertStore>,
disable_hostname_verification: bool,
accept_invalid_certs: bool,
}
#[derive(Debug)]
struct DangerousVerifier {
accept_invalid_certs: bool,
root_store: Option<Arc<RootCertStore>>,
}
impl ServerCertVerifier for DangerousVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
ocsp_response: &[u8],
now: rustls::pki_types::UnixTime,
) -> std::result::Result<ServerCertVerified, rustls::Error> {
if self.accept_invalid_certs {
Ok(ServerCertVerified::assertion())
} else if let Some(ref root_store) = self.root_store {
let verifier = rustls::client::WebPkiServerVerifier::builder(root_store.clone())
.build()
.map_err(|e| rustls::Error::General(format!("Failed to build verifier: {}", e)))?;
verifier.verify_server_cert(end_entity, intermediates, _server_name, ocsp_response, now)
} else {
Err(rustls::Error::General("Certificate verification not configured".to_string()))
}
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
if self.accept_invalid_certs {
Ok(HandshakeSignatureValid::assertion())
} else {
Err(rustls::Error::General("Signature verification not configured".to_string()))
}
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
if self.accept_invalid_certs {
Ok(HandshakeSignatureValid::assertion())
} else {
Err(rustls::Error::General("Signature verification not configured".to_string()))
}
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
]
}
}
impl TlsConfigBuilder {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "openssl-p12")]
pub fn with_p12(mut self, p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
let (client_certs, client_key, root_store) = load_p12_openssl(p12_path, password)?;
self.client_certs = Some(client_certs);
self.client_key = Some(client_key);
self.root_store = Some(root_store);
Ok(self)
}
#[cfg(not(feature = "openssl-p12"))]
pub fn with_p12(mut self, p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
let (client_certs, client_key, root_store) = load_p12_rust(p12_path, password)?;
self.client_certs = Some(client_certs);
self.client_key = Some(client_key);
self.root_store = Some(root_store);
Ok(self)
}
pub fn with_client_cert(
mut self,
ca_cert_path: impl AsRef<Path>,
client_cert_path: impl AsRef<Path>,
client_key_path: impl AsRef<Path>,
) -> Result<Self> {
let ca_certs = load_certs(ca_cert_path.as_ref())?;
let mut root_store = RootCertStore::empty();
for cert in ca_certs {
root_store
.add(cert)
.map_err(|e| TakError::Certificate(format!("Failed to add CA cert: {}", e)))?;
}
let client_certs = load_certs(client_cert_path.as_ref())?;
let client_key = load_private_key(client_key_path.as_ref())?;
self.client_certs = Some(client_certs);
self.client_key = Some(client_key);
self.root_store = Some(root_store);
Ok(self)
}
pub fn with_system_roots(mut self) -> Result<Self> {
let mut root_store = RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs();
for cert in certs.certs {
root_store.add(cert).ok();
}
if !certs.errors.is_empty() && root_store.is_empty() {
return Err(TakError::Certificate(
"Failed to load any system certificates".to_string(),
));
}
self.root_store = Some(root_store);
Ok(self)
}
pub fn with_additional_root_cert(mut self, cert_path: impl AsRef<Path>) -> Result<Self> {
let certs = load_certs(cert_path.as_ref())?;
let mut root_store = self.root_store.take().unwrap_or_else(RootCertStore::empty);
for cert in certs {
root_store
.add(cert)
.map_err(|e| TakError::Certificate(format!("Failed to add certificate: {}", e)))?;
}
self.root_store = Some(root_store);
Ok(self)
}
pub fn with_ca_cert(mut self, ca_cert_path: impl AsRef<Path>) -> Result<Self> {
let ca_certs = load_certs(ca_cert_path.as_ref())?;
let mut root_store = RootCertStore::empty();
for cert in ca_certs {
root_store
.add(cert)
.map_err(|e| TakError::Certificate(format!("Failed to add CA cert: {}", e)))?;
}
self.root_store = Some(root_store);
Ok(self)
}
pub fn danger_disable_hostname_verification(mut self, disable: bool) -> Self {
self.disable_hostname_verification = disable;
self
}
pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
self.accept_invalid_certs = accept;
self
}
pub fn build(self) -> Result<TlsConfig> {
if !self.accept_invalid_certs && !self.disable_hostname_verification {
let root_store = self.root_store
.ok_or_else(|| TakError::Certificate("No root certificates configured".to_string()))?;
let config = if let (Some(certs), Some(key)) = (self.client_certs, self.client_key) {
ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(certs, key)
.map_err(|e| TakError::Certificate(format!("Failed to configure client auth: {}", e)))?
} else {
ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth()
};
return Ok(TlsConfig {
config: Arc::new(config),
});
}
let root_store = if self.accept_invalid_certs {
None
} else {
Some(Arc::new(self.root_store
.ok_or_else(|| TakError::Certificate("No root certificates configured".to_string()))?))
};
let verifier = Arc::new(DangerousVerifier {
accept_invalid_certs: self.accept_invalid_certs,
root_store,
});
let config_builder = ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(verifier);
let config = if let (Some(certs), Some(key)) = (self.client_certs, self.client_key) {
config_builder.with_client_auth_cert(certs, key)
.map_err(|e| TakError::Certificate(format!("Failed to configure client auth: {}", e)))?
} else {
config_builder.with_no_client_auth()
};
Ok(TlsConfig {
config: Arc::new(config),
})
}
}
impl TlsConfig {
#[cfg(feature = "openssl-p12")]
pub fn new_with_p12(p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
Self::new_with_p12_openssl(p12_path, password)
}
#[cfg(not(feature = "openssl-p12"))]
pub fn new_with_p12(p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
Self::new_with_p12_rust(p12_path, password)
}
#[cfg(feature = "openssl-p12")]
fn new_with_p12_openssl(p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
let (client_certs, client_key, root_store) = load_p12_openssl(p12_path, password)?;
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(client_certs, client_key)
.map_err(|e| TakError::Certificate(format!("Failed to configure client auth: {}", e)))?;
Ok(Self {
config: Arc::new(config),
})
}
#[cfg(not(feature = "openssl-p12"))]
fn new_with_p12_rust(p12_path: impl AsRef<Path>, password: &str) -> Result<Self> {
let (client_certs, client_key, root_store) = load_p12_rust(p12_path, password)?;
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(client_certs, client_key)
.map_err(|e| TakError::Certificate(format!("Failed to configure client auth: {}", e)))?;
Ok(Self {
config: Arc::new(config),
})
}
pub fn new_with_client_cert(
ca_cert_path: impl AsRef<Path>,
client_cert_path: impl AsRef<Path>,
client_key_path: impl AsRef<Path>,
) -> Result<Self> {
let ca_certs = load_certs(ca_cert_path.as_ref())?;
let mut root_store = RootCertStore::empty();
for cert in ca_certs {
root_store
.add(cert)
.map_err(|e| TakError::Certificate(format!("Failed to add CA cert: {}", e)))?;
}
let client_certs = load_certs(client_cert_path.as_ref())?;
let client_key = load_private_key(client_key_path.as_ref())?;
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_client_auth_cert(client_certs, client_key)
.map_err(|e| TakError::Certificate(format!("Failed to configure client auth: {}", e)))?;
Ok(Self {
config: Arc::new(config),
})
}
pub fn new_with_system_roots() -> Result<Self> {
let mut root_store = RootCertStore::empty();
let certs = rustls_native_certs::load_native_certs();
for cert in certs.certs {
root_store.add(cert).ok(); }
if !certs.errors.is_empty() && root_store.is_empty() {
return Err(TakError::Certificate(
"Failed to load any system certificates".to_string(),
));
}
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
Ok(Self {
config: Arc::new(config),
})
}
pub fn new_with_ca_cert(ca_cert_path: impl AsRef<Path>) -> Result<Self> {
let ca_certs = load_certs(ca_cert_path.as_ref())?;
let mut root_store = RootCertStore::empty();
for cert in ca_certs {
root_store
.add(cert)
.map_err(|e| TakError::Certificate(format!("Failed to add CA cert: {}", e)))?;
}
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
Ok(Self {
config: Arc::new(config),
})
}
}
fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
let file = File::open(path)
.map_err(|e| TakError::Certificate(format!("Failed to open cert file: {}", e)))?;
let mut reader = BufReader::new(file);
let certs = rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| TakError::Certificate(format!("Failed to parse certificates: {}", e)))?;
if certs.is_empty() {
return Err(TakError::Certificate(
"No certificates found in file".to_string(),
));
}
Ok(certs)
}
fn load_private_key(path: &Path) -> Result<PrivateKeyDer<'static>> {
let file = File::open(path)
.map_err(|e| TakError::Certificate(format!("Failed to open key file: {}", e)))?;
let mut reader = BufReader::new(file);
let keys = rustls_pemfile::private_key(&mut reader)
.map_err(|e| TakError::Certificate(format!("Failed to parse private key: {}", e)))?
.ok_or_else(|| TakError::Certificate("No private key found in file".to_string()))?;
Ok(keys)
}
#[cfg(feature = "openssl-p12")]
fn load_p12_openssl(
p12_path: impl AsRef<Path>,
password: &str,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>, RootCertStore)> {
use openssl::pkcs12::Pkcs12;
let mut file = File::open(p12_path.as_ref())
.map_err(|e| TakError::Certificate(format!("Failed to open P12 file: {}", e)))?;
let mut p12_data = Vec::new();
file.read_to_end(&mut p12_data)
.map_err(|e| TakError::Certificate(format!("Failed to read P12 file: {}", e)))?;
let pkcs12 = Pkcs12::from_der(&p12_data)
.map_err(|e| TakError::Certificate(format!("Failed to parse P12 file: {}", e)))?;
let parsed = pkcs12
.parse2(password)
.map_err(|e| TakError::Certificate(format!("Failed to decrypt P12 file: {}", e)))?;
let client_cert = parsed.cert
.ok_or_else(|| TakError::Certificate("No client certificate found in P12 file".to_string()))?;
let client_cert_der = client_cert.to_der()
.map_err(|e| TakError::Certificate(format!("Failed to encode client certificate: {}", e)))?;
let client_certs = vec![CertificateDer::from(client_cert_der)];
let pkey = parsed.pkey
.ok_or_else(|| TakError::Certificate("No private key found in P12 file".to_string()))?;
let key_der = pkey.private_key_to_pkcs8()
.map_err(|e| TakError::Certificate(format!("Failed to encode private key as PKCS#8: {}", e)))?;
let client_key = PrivateKeyDer::Pkcs8(key_der.into());
let mut root_store = RootCertStore::empty();
if let Some(ca_stack) = parsed.ca {
for ca_cert in ca_stack {
let ca_der = ca_cert.to_der()
.map_err(|e| TakError::Certificate(format!("Failed to encode CA certificate: {}", e)))?;
root_store.add(CertificateDer::from(ca_der)).ok();
}
}
if root_store.is_empty() {
let sys_certs = rustls_native_certs::load_native_certs();
for cert in sys_certs.certs {
root_store.add(cert).ok();
}
}
Ok((client_certs, client_key, root_store))
}
#[cfg(not(feature = "openssl-p12"))]
fn load_p12_rust(
p12_path: impl AsRef<Path>,
password: &str,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>, RootCertStore)> {
let mut file = File::open(p12_path.as_ref())
.map_err(|e| TakError::Certificate(format!("Failed to open P12 file: {}", e)))?;
let mut p12_data = Vec::new();
file.read_to_end(&mut p12_data)
.map_err(|e| TakError::Certificate(format!("Failed to read P12 file: {}", e)))?;
let p12 = p12::PFX::parse(&p12_data)
.map_err(|e| TakError::Certificate(format!("Failed to parse P12 file: {}", e)))?;
let key_bags = p12
.key_bags(password)
.map_err(|e| TakError::Certificate(format!(
"Failed to decrypt P12 file: {}\n\
\n\
TAK servers often generate legacy PKCS#12 files not supported by pure Rust parsers.\n\
\n\
Option 1: Enable OpenSSL support for full P12 compatibility:\n\
[dependencies]\n\
takproto = {{ version = \"0.3\", features = [\"openssl-p12\"] }}\n\
\n\
Option 2: Extract to PEM files and use TlsConfig::new_with_client_cert():\n\
openssl pkcs12 -legacy -in {} -nokeys -out client.pem -passin pass:{}\n\
openssl pkcs12 -legacy -in {} -nocerts -nodes -out client-key.pem -passin pass:{}\n\
openssl pkcs12 -legacy -in {} -cacerts -nokeys -out ca.pem -passin pass:{}",
e,
p12_path.as_ref().display(),
password,
p12_path.as_ref().display(),
password,
p12_path.as_ref().display(),
password
)))?;
let client_key = key_bags
.into_iter()
.next()
.ok_or_else(|| TakError::Certificate("No private key found in P12 file".to_string()))?;
let client_key = PrivateKeyDer::Pkcs8(client_key.into());
let cert_bags = p12
.cert_bags(password)
.map_err(|e| TakError::Certificate(format!("Failed to extract certificates from P12 file: {}", e)))?;
if cert_bags.is_empty() {
return Err(TakError::Certificate("No certificates found in P12 file".to_string()));
}
let mut cert_iter = cert_bags.into_iter();
let client_cert = cert_iter.next()
.ok_or_else(|| TakError::Certificate("No client certificate found in P12 file".to_string()))?;
let client_certs = vec![CertificateDer::from(client_cert)];
let mut root_store = RootCertStore::empty();
for ca_cert in cert_iter {
let cert = CertificateDer::from(ca_cert);
root_store.add(cert).ok(); }
if root_store.is_empty() {
let sys_certs = rustls_native_certs::load_native_certs();
for cert in sys_certs.certs {
root_store.add(cert).ok();
}
}
Ok((client_certs, client_key, root_store))
}