use std::path::Path;
use std::sync::Arc;
use rustls::server::WebPkiClientVerifier;
use rustls::{RootCertStore, ServerConfig};
use crate::error::{Error, Result};
pub fn build_server_config(
cert_pem: &Path,
key_pem: &Path,
client_ca: Option<&Path>,
) -> Result<Arc<ServerConfig>> {
let certs = load_certs(cert_pem)?;
let key = load_private_key(key_pem)?;
let provider = Arc::new(rustls::crypto::ring::default_provider());
let builder = ServerConfig::builder_with_provider(provider)
.with_protocol_versions(&[&rustls::version::TLS13])
.map_err(|e| Error::Input(format!("rustls builder: {e}")))?;
let cfg = match client_ca {
Some(ca_path) => {
let mut roots = RootCertStore::empty();
for c in load_certs(ca_path)? {
roots
.add(c)
.map_err(|e| Error::Input(format!("adding client CA to root store: {e}")))?;
}
let verifier = WebPkiClientVerifier::builder(Arc::new(roots))
.build()
.map_err(|e| Error::Input(format!("client verifier: {e}")))?;
builder
.with_client_cert_verifier(verifier)
.with_single_cert(certs, key)
.map_err(|e| Error::Input(format!("server cert/key: {e}")))?
}
None => builder
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|e| Error::Input(format!("server cert/key: {e}")))?,
};
Ok(Arc::new(cfg))
}
fn load_certs(path: &Path) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>> {
let mut r = std::io::BufReader::new(
std::fs::File::open(path)
.map_err(|e| Error::Input(format!("opening cert PEM {}: {e}", path.display())))?,
);
let certs: std::result::Result<Vec<_>, _> = rustls_pemfile::certs(&mut r).collect();
let certs =
certs.map_err(|e| Error::Input(format!("reading cert PEM {}: {e}", path.display())))?;
if certs.is_empty() {
return Err(Error::Input(format!(
"no certificates found in {}",
path.display()
)));
}
Ok(certs)
}
fn load_private_key(path: &Path) -> Result<rustls::pki_types::PrivateKeyDer<'static>> {
let mut r = std::io::BufReader::new(
std::fs::File::open(path)
.map_err(|e| Error::Input(format!("opening key PEM {}: {e}", path.display())))?,
);
let key = rustls_pemfile::private_key(&mut r)
.map_err(|e| Error::Input(format!("reading key PEM {}: {e}", path.display())))?
.ok_or_else(|| Error::Input(format!("no private key found in {}", path.display())))?;
Ok(key)
}
#[cfg(test)]
mod tests {
use super::*;
fn test_cert_files() -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) {
let tmp = tempfile::tempdir().unwrap();
let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
let cert_pem = cert.cert.pem();
let key_pem = cert.key_pair.serialize_pem();
let cert_path = tmp.path().join("cert.pem");
let key_path = tmp.path().join("key.pem");
std::fs::write(&cert_path, cert_pem).unwrap();
std::fs::write(&key_path, key_pem).unwrap();
(tmp, cert_path, key_path)
}
#[test]
fn builds_server_config_from_self_signed() {
let (_g, cert, key) = test_cert_files();
let cfg = build_server_config(&cert, &key, None);
assert!(cfg.is_ok(), "{:?}", cfg.err());
}
#[test]
fn rejects_missing_files() {
let r = build_server_config(
std::path::Path::new("/does/not/exist/cert.pem"),
std::path::Path::new("/does/not/exist/key.pem"),
None,
);
assert!(r.is_err());
}
}