use std::sync::Arc;
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use hyper_util::client::legacy::connect::HttpConnector;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::{ClientConfig, RootCertStore, crypto::aws_lc_rs as aws_lc_rs_crypto};
use crate::config::{ClientAuthentication, Protocol, TlsConfig};
use crate::error::HttpClientError;
pub(crate) fn with_protocol_alpn(config: &ClientConfig, protocol: Protocol) -> ClientConfig {
let mut out = config.clone();
out.alpn_protocols = match protocol {
Protocol::Http1 => vec![b"http/1.1".to_vec()],
Protocol::Http2 => vec![b"h2".to_vec()],
Protocol::Alpn => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
};
out
}
pub(crate) fn build_tls_config(
protocol: Protocol,
tls: &TlsConfig,
) -> Result<Arc<ClientConfig>, HttpClientError> {
let provider = Arc::new(aws_lc_rs_crypto::default_provider());
let builder =
ClientConfig::builder_with_provider(provider).with_safe_default_protocol_versions()?;
let server_auth = if tls.danger_accept_invalid_certs {
builder
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipCertVerification))
} else {
let root_store = build_root_cert_store(tls)?;
builder.with_root_certificates(root_store)
};
let base = match &tls.client_authentication {
None => server_auth.with_no_client_auth(),
Some(auth) => {
let (chain, key) = parse_client_identity(auth)?;
server_auth
.with_client_auth_cert(chain, key)
.map_err(|source| HttpClientError::TlsClientAuth { source })?
}
};
let mut config = base;
config.alpn_protocols = match protocol {
Protocol::Http1 => vec![b"http/1.1".to_vec()],
Protocol::Http2 => vec![b"h2".to_vec()],
Protocol::Alpn => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
};
Ok(Arc::new(config))
}
fn build_root_cert_store(tls: &TlsConfig) -> Result<RootCertStore, HttpClientError> {
let mut root_store = RootCertStore::empty();
if tls.use_native_certificate_store {
for cert in rustls_native_certs::load_native_certs().certs {
root_store.add(cert)?;
}
}
if let Some(pem) = &tls.certificate_authorities {
let certs = parse_certificate_authorities(pem.unredact().as_bytes())?;
for cert in certs {
root_store.add(cert)?;
}
}
if root_store.is_empty() {
return Err(HttpClientError::TlsNoTrustAnchors);
}
Ok(root_store)
}
fn parse_pem_chain<F>(
pem: &[u8],
invalid_err: F,
empty_err: HttpClientError,
) -> Result<Vec<CertificateDer<'static>>, HttpClientError>
where
F: FnOnce(std::io::Error) -> HttpClientError,
{
let mut reader = std::io::BufReader::new(pem);
let certs: Vec<_> = rustls_pemfile::certs(&mut reader)
.collect::<Result<_, _>>()
.map_err(invalid_err)?;
if certs.is_empty() {
return Err(empty_err);
}
Ok(certs)
}
fn parse_certificate_authorities(
pem: &[u8],
) -> Result<Vec<CertificateDer<'static>>, HttpClientError> {
parse_pem_chain(
pem,
|source| HttpClientError::TlsCertificateAuthoritiesInvalid { source },
HttpClientError::TlsCertificateAuthoritiesEmpty,
)
}
fn parse_client_certificate_chain(
pem: &[u8],
) -> Result<Vec<CertificateDer<'static>>, HttpClientError> {
parse_pem_chain(
pem,
|source| HttpClientError::TlsClientCertificateChainInvalid { source },
HttpClientError::TlsClientCertificateChainEmpty,
)
}
fn parse_client_identity(
auth: &ClientAuthentication,
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), HttpClientError> {
let chain = parse_client_certificate_chain(auth.certificate_chain.unredact().as_bytes())?;
let key = parse_client_key(auth.key.unredact().as_bytes())?;
Ok((chain, key))
}
const ENCRYPTED_KEY_HEADER: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
fn parse_client_key(pem: &[u8]) -> Result<PrivateKeyDer<'static>, HttpClientError> {
if std::str::from_utf8(pem).is_ok_and(|s| s.contains(ENCRYPTED_KEY_HEADER)) {
return Err(HttpClientError::TlsClientKeyEncrypted);
}
let mut reader = std::io::BufReader::new(pem);
rustls_pemfile::private_key(&mut reader)
.map_err(|source| HttpClientError::TlsClientKeyInvalid { source })?
.ok_or(HttpClientError::TlsClientKeyMissing)
}
pub(crate) fn build_https_connector(
http: HttpConnector,
tls_config: Arc<ClientConfig>,
protocol: Protocol,
) -> HttpsConnector<HttpConnector> {
let mut base = (*tls_config).clone();
base.alpn_protocols.clear();
let builder = HttpsConnectorBuilder::new()
.with_tls_config(base)
.https_or_http();
match protocol {
Protocol::Http1 => builder.enable_http1().wrap_connector(http),
Protocol::Http2 => builder.enable_http2().wrap_connector(http),
Protocol::Alpn => builder.enable_all_versions().wrap_connector(http),
}
}
#[derive(Debug)]
struct SkipCertVerification;
impl rustls::client::danger::ServerCertVerifier for SkipCertVerification {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dupe_value: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dupe_value: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
use rustls::SignatureScheme;
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}