#[cfg(feature = "tls")]
use crate::error::{Result, SecurityError};
#[cfg(feature = "tls")]
use rustls::{ClientConfig, ServerConfig};
#[cfg(feature = "tls")]
use std::io::BufReader;
#[cfg(feature = "tls")]
use std::sync::Arc;
pub struct TlsConfigBuilder {
pub server_name: Option<String>,
pub ca_cert: Option<Vec<u8>>,
pub client_cert: Option<Vec<u8>>,
pub client_key: Option<Vec<u8>>,
pub server_cert: Option<Vec<u8>>,
pub server_key: Option<Vec<u8>>,
pub verify_peer: bool,
}
impl Default for TlsConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl TlsConfigBuilder {
pub fn new() -> Self {
Self {
server_name: None,
ca_cert: None,
client_cert: None,
client_key: None,
server_cert: None,
server_key: None,
verify_peer: true,
}
}
pub fn server_name(mut self, name: String) -> Self {
self.server_name = Some(name);
self
}
pub fn ca_cert(mut self, cert: Vec<u8>) -> Self {
self.ca_cert = Some(cert);
self
}
pub fn client_cert_and_key(mut self, cert: Vec<u8>, key: Vec<u8>) -> Self {
self.client_cert = Some(cert);
self.client_key = Some(key);
self
}
pub fn server_cert_and_key(mut self, cert: Vec<u8>, key: Vec<u8>) -> Self {
self.server_cert = Some(cert);
self.server_key = Some(key);
self
}
pub fn verify_peer(mut self, verify: bool) -> Self {
self.verify_peer = verify;
self
}
#[cfg(feature = "tls")]
pub fn build_client(self) -> Result<Arc<ClientConfig>> {
use oxitls_adapter_rustls_rustcrypto::RustcryptoClientConfigBuilder;
use oxitls_webpki_roots::webpki_root_certs;
use rustls::RootCertStore;
let root_store: RootCertStore = if let Some(ca_pem) = self.ca_cert {
let mut store = RootCertStore::empty();
let mut reader = BufReader::new(ca_pem.as_slice());
let ca_certs: Vec<_> = rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<_, _>>()
.map_err(|e| SecurityError::certificate(format!("Failed to parse CA cert: {e}")))?;
for cert in ca_certs {
store.add(cert).map_err(|e| {
SecurityError::certificate(format!("Failed to add CA cert: {e}"))
})?;
}
store
} else {
webpki_root_certs()
};
let config = if let (Some(cert_pem), Some(key_pem)) = (self.client_cert, self.client_key) {
let certs: Vec<_> = {
let mut reader = BufReader::new(cert_pem.as_slice());
rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<_, _>>()
.map_err(|e| {
SecurityError::certificate(format!("Failed to parse client cert: {e}"))
})?
};
let private_key = {
let mut reader = BufReader::new(key_pem.as_slice());
rustls_pemfile::private_key(&mut reader)
.map_err(|e| {
SecurityError::certificate(format!("Failed to parse private key: {e}"))
})?
.ok_or_else(|| SecurityError::certificate("No private key found"))?
};
let provider = oxitls_adapter_rustls_rustcrypto::pure_provider();
ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| SecurityError::tls(format!("Protocol version error: {e}")))?
.with_root_certificates(root_store)
.with_client_auth_cert(certs, private_key)
.map_err(|e| {
SecurityError::certificate(format!("Failed to set client auth: {e}"))
})?
} else {
RustcryptoClientConfigBuilder::new()
.with_roots(root_store)
.build()
.map_err(|e| SecurityError::tls(e.to_string()))?
};
Ok(Arc::new(config))
}
#[cfg(feature = "tls")]
pub fn build_server(self) -> Result<Arc<ServerConfig>> {
use oxitls_adapter_rustls_rustcrypto::RustcryptoServerConfigBuilder;
use rustls::RootCertStore;
let cert_pem = self
.server_cert
.ok_or_else(|| SecurityError::certificate("Server certificate required"))?;
let key_pem = self
.server_key
.ok_or_else(|| SecurityError::certificate("Server private key required"))?;
let certs: Vec<_> = {
let mut reader = BufReader::new(cert_pem.as_slice());
rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<_, _>>()
.map_err(|e| {
SecurityError::certificate(format!("Failed to parse server cert: {e}"))
})?
};
let private_key = {
let mut reader = BufReader::new(key_pem.as_slice());
rustls_pemfile::private_key(&mut reader)
.map_err(|e| {
SecurityError::certificate(format!("Failed to parse private key: {e}"))
})?
.ok_or_else(|| SecurityError::certificate("No private key found"))?
};
let mut builder =
RustcryptoServerConfigBuilder::new().with_cert_and_key(certs, private_key);
if self.verify_peer {
let ca_pem = self.ca_cert.ok_or_else(|| {
SecurityError::certificate("CA certificate required for client verification")
})?;
let mut root_store = RootCertStore::empty();
let mut reader = BufReader::new(ca_pem.as_slice());
let ca_certs: Vec<_> = rustls_pemfile::certs(&mut reader)
.collect::<std::result::Result<_, _>>()
.map_err(|e| SecurityError::certificate(format!("Failed to parse CA cert: {e}")))?;
for cert in ca_certs {
root_store.add(cert).map_err(|e| {
SecurityError::certificate(format!("Failed to add CA cert: {e}"))
})?;
}
builder = builder.with_client_auth(true, root_store);
}
let config = builder
.build()
.map_err(|e| SecurityError::tls(e.to_string()))?;
Ok(Arc::new(config))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsVersion {
Tls12,
Tls13,
}
#[derive(Debug, Clone)]
pub struct CertificateValidation {
pub valid: bool,
pub errors: Vec<String>,
pub subject: Option<String>,
pub issuer: Option<String>,
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
}
impl CertificateValidation {
pub fn valid() -> Self {
Self {
valid: true,
errors: Vec::new(),
subject: None,
issuer: None,
expires_at: None,
}
}
pub fn invalid(errors: Vec<String>) -> Self {
Self {
valid: false,
errors,
subject: None,
issuer: None,
expires_at: None,
}
}
pub fn add_error(&mut self, error: String) {
self.valid = false;
self.errors.push(error);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tls_config_builder() {
let builder = TlsConfigBuilder::new()
.server_name("example.com".to_string())
.verify_peer(true);
assert_eq!(builder.server_name, Some("example.com".to_string()));
assert!(builder.verify_peer);
}
#[test]
fn test_certificate_validation() {
let valid = CertificateValidation::valid();
assert!(valid.valid);
assert!(valid.errors.is_empty());
let invalid = CertificateValidation::invalid(vec!["expired".to_string()]);
assert!(!invalid.valid);
assert_eq!(invalid.errors.len(), 1);
}
}