axum-server
axum-server copied to clipboard
Support SNI-based TLS configurations
As things are right now, only TLS configurations with a single certificate are properly supported. Setting up SNI-based certificate selection is possible but quite a pain, as it requires delving into Rustls. Ideally, there would API analogous to from_pem_chain_file()
, for example:
pub async fn sni_from_pem_chain_files(
mapping: Vec<(String, (impl AsRef<Path>, impl AsRef<Path>))>
) -> Result<Self>
For reference, an approximation of how I currently have to implement this functionality (requires adding rustls
and rustls-pemfile
as direct dependencies):
use axum_server::tls_rustls::RustlsConfig;
use rustls::{
server::{ResolvesServerCertUsingSni, ServerConfig},
sign::{any_supported_type, CertifiedKey},
Certificate, PrivateKey
};
use std::{
io::{Error, Result},
path::Path,
sync::Arc,
};
async fn read_cert(path: impl AsRef<Path>) -> Result<Vec<Certificate>> {
let data = tokio::fs::read(path.as_ref()).await?;
let certs = rustls_pemfile::certs(&mut data.as_ref()).collect::<Result<Vec<_>>>()?;
Ok(certs.into_iter().map(|c| Certificate(c.to_vec())).collect())
}
async fn read_key(path: impl AsRef<Path>) -> Result<PrivateKey> {
let data = tokio::fs::read(path.as_ref()).await?;
let key =
rustls_pemfile::private_key(&mut data.as_ref())?.ok_or(Error::other("no private key in file"))?;
Ok(PrivateKey(key.secret_der().to_vec()))
}
async fn load_cert(cert_path: impl AsRef<Path>, key_path: impl AsRef<Path>) -> CertifiedKey {
CertifiedKey::new(
read_cert(cert_path).await.expect("failed reading cert file"),
any_supported_type(&read_key(key_path).await.expect("failed reading key file"))
.expect("failed converting private key"),
)
}
async fn add_cert(
resolver: &mut ResolvesServerCertUsingSni,
domain: &str,
cert_path: impl AsRef<Path>,
key_path: impl AsRef<Path>
) {
resolver
.add(domain, load_cert(cert_path, key_path).await)
.expect("failed adding cert");
}
let mut resolver = ResolvesServerCertUsingSni::new();
resolver.add("example.com", load_cert("example.com.pem", "example.com.key").await);
resolver.add("www.example.com", load_cert("example.com.pem", "example.com.key").await);
resolver.add("example.net", load_cert("example.net.pem", "example.net.key").await);
resolver.add("www.example.net", load_cert("example.net.pem", "example.net.key").await);
let mut config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth();
.with_cert_resolver(Arc::new(resolver));
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
RustlsConfig::from_config(Arc::new(config))
This is going to get simpler with Rustls 1.22 but still way too much boilerplate for what should be a trivial task.