use std::sync::Arc;
use rustls::crypto::CryptoProvider;
pub(crate) fn client_config() -> Result<rustls::ClientConfig, String> {
let mut roots = rustls::RootCertStore::empty();
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let provider = select_provider(CryptoProvider::get_default(), builtin_provider)?;
client_config_with(provider, roots)
}
pub(crate) fn client_config_with(
provider: Arc<CryptoProvider>,
roots: rustls::RootCertStore,
) -> Result<rustls::ClientConfig, String> {
Ok(rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| format!("the rustls crypto provider cannot do TLS 1.2 or 1.3: {e}"))?
.with_root_certificates(roots)
.with_no_client_auth())
}
pub(crate) fn server_config(
config: &crate::config::ListenTlsConfig,
) -> Result<rustls::ServerConfig, String> {
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
if config.client_auth.is_some() && config.client_ca_file.is_none() {
return Err("client_auth needs client_ca_file to verify the certificates against".into());
}
let provider = select_provider(CryptoProvider::get_default(), builtin_provider)?;
let certs = |path: &std::path::Path| {
let certs = CertificateDer::pem_file_iter(path)
.and_then(|certs| certs.collect::<Result<Vec<_>, _>>())
.map_err(|e| format!("{}: {e}", path.display()))?;
if certs.is_empty() {
return Err(format!("{}: no PEM certificate", path.display()));
}
Ok(certs)
};
let chain = certs(&config.cert_file)?;
let key = PrivateKeyDer::from_pem_file(&config.key_file)
.map_err(|e| format!("{}: {e}", config.key_file.display()))?;
let builder = rustls::ServerConfig::builder_with_provider(provider.clone())
.with_safe_default_protocol_versions()
.map_err(|e| format!("the rustls crypto provider cannot do TLS 1.2 or 1.3: {e}"))?;
let builder = match &config.client_ca_file {
None => builder.with_no_client_auth(),
Some(path) => {
let mut roots = rustls::RootCertStore::empty();
for cert in certs(path)? {
roots
.add(cert)
.map_err(|e| format!("{}: {e}", path.display()))?;
}
let verifier =
rustls::server::WebPkiClientVerifier::builder_with_provider(roots.into(), provider);
let verifier = match config.client_auth.unwrap_or_default() {
crate::config::ClientAuth::Required => verifier,
crate::config::ClientAuth::Optional => verifier.allow_unauthenticated(),
}
.build()
.map_err(|e| format!("{}: {e}", path.display()))?;
builder.with_client_cert_verifier(verifier)
}
};
let mut server = builder
.with_single_cert(chain, key)
.map_err(|e| format!("{}: {e}", config.cert_file.display()))?;
server.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
Ok(server)
}
fn select_provider(
installed: Option<&Arc<CryptoProvider>>,
builtin: impl FnOnce() -> Option<CryptoProvider>,
) -> Result<Arc<CryptoProvider>, String> {
if let Some(installed) = installed {
return Ok(Arc::clone(installed));
}
builtin().map(Arc::new).ok_or_else(|| {
"TLS (the listener, the outbound HTTP client) needs a rustls crypto provider: enable the \
`rust_crypto` or `aws_lc_rs` feature, or install one with \
`rustls::crypto::CryptoProvider::install_default` before building the proxy"
.to_string()
})
}
#[cfg(feature = "aws_lc_rs")]
fn builtin_provider() -> Option<CryptoProvider> {
Some(rustls::crypto::aws_lc_rs::default_provider())
}
#[cfg(all(feature = "rust_crypto", not(feature = "aws_lc_rs")))]
fn builtin_provider() -> Option<CryptoProvider> {
Some(rustls_rustcrypto::provider())
}
#[cfg(not(any(feature = "rust_crypto", feature = "aws_lc_rs")))]
fn builtin_provider() -> Option<CryptoProvider> {
None
}
#[cfg(test)]
mod tests;