use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct ServerTls {
pub cert_path: PathBuf,
pub key_path: PathBuf,
}
#[derive(Clone, Debug, Default)]
pub struct ClientTls {
pub server_name: Option<String>,
pub ca_path: Option<PathBuf>,
}
#[cfg(feature = "tls")]
mod imp {
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
use tokio_rustls::rustls::{self, ClientConfig, RootCertStore, ServerConfig};
use tokio_rustls::{TlsAcceptor, TlsConnector};
use super::{ClientTls, ServerTls};
fn provider() -> Arc<rustls::crypto::CryptoProvider> {
Arc::new(rustls::crypto::ring::default_provider())
}
fn load_certs(path: &std::path::Path) -> Result<Vec<CertificateDer<'static>>, String> {
let mut reader = BufReader::new(
File::open(path).map_err(|e| format!("open cert {}: {e}", path.display()))?,
);
rustls_pemfile::certs(&mut reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("read certs {}: {e}", path.display()))
}
fn load_key(path: &std::path::Path) -> Result<PrivateKeyDer<'static>, String> {
let mut reader = BufReader::new(
File::open(path).map_err(|e| format!("open key {}: {e}", path.display()))?,
);
rustls_pemfile::private_key(&mut reader)
.map_err(|e| format!("read key {}: {e}", path.display()))?
.ok_or_else(|| format!("no private key in {}", path.display()))
}
pub fn build_acceptor(cfg: &ServerTls) -> Result<TlsAcceptor, String> {
let certs = load_certs(&cfg.cert_path)?;
let key = load_key(&cfg.key_path)?;
let config = ServerConfig::builder_with_provider(provider())
.with_safe_default_protocol_versions()
.map_err(|e| format!("rustls protocol versions: {e}"))?
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| format!("server cert/key: {e}"))?;
Ok(TlsAcceptor::from(Arc::new(config)))
}
pub fn build_connector(cfg: &ClientTls) -> Result<TlsConnector, String> {
let mut roots = RootCertStore::empty();
match &cfg.ca_path {
Some(path) => {
for cert in load_certs(path)? {
roots
.add(cert)
.map_err(|e| format!("add CA from {}: {e}", path.display()))?;
}
}
None => {
let loaded = rustls_native_certs::load_native_certs();
if roots.is_empty() && loaded.certs.is_empty() {
return Err(format!(
"no native root certificates available ({} load error(s))",
loaded.errors.len()
));
}
for cert in loaded.certs {
let _ = roots.add(cert);
}
}
}
let config = ClientConfig::builder_with_provider(provider())
.with_safe_default_protocol_versions()
.map_err(|e| format!("rustls protocol versions: {e}"))?
.with_root_certificates(roots)
.with_no_client_auth();
Ok(TlsConnector::from(Arc::new(config)))
}
pub fn server_name(cfg: &ClientTls, host: &str) -> Result<ServerName<'static>, String> {
let name = cfg.server_name.clone().unwrap_or_else(|| host.to_owned());
ServerName::try_from(name).map_err(|e| format!("invalid TLS server name: {e}"))
}
}
#[cfg(feature = "tls")]
pub use imp::{build_acceptor, build_connector, server_name};