Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SNI-based TLS configurations #109

Open
palant opened this issue Feb 16, 2024 · 0 comments
Open

Support SNI-based TLS configurations #109

palant opened this issue Feb 16, 2024 · 0 comments

Comments

@palant
Copy link

palant commented Feb 16, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant