#[derive(Debug, Clone, Default)]
pub struct TlsConfig {
pub ca_cert_pem: Option<Vec<u8>>,
pub danger_accept_invalid_certs: bool,
}
impl TlsConfig {
#[must_use]
pub fn with_public_roots() -> Self {
Self::default()
}
#[must_use]
pub fn with_ca_pem(pem: impl Into<Vec<u8>>) -> Self {
Self {
ca_cert_pem: Some(pem.into()),
danger_accept_invalid_certs: false,
}
}
#[must_use]
pub fn danger_insecure() -> Self {
Self {
ca_cert_pem: None,
danger_accept_invalid_certs: true,
}
}
}
#[cfg(feature = "tls")]
pub(crate) use imp::{client_config, server_name};
#[cfg(feature = "tls")]
mod imp {
use std::sync::Arc;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::crypto::{ring, CryptoProvider};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme};
use super::TlsConfig;
use crate::error::{Error, Result};
pub(crate) fn client_config(tls: &TlsConfig) -> Result<Arc<ClientConfig>> {
let provider = Arc::new(ring::default_provider());
let builder = ClientConfig::builder_with_provider(Arc::clone(&provider))
.with_safe_default_protocol_versions()
.map_err(|e| Error::Config(format!("rustls config: {e}")))?;
let config = if tls.danger_accept_invalid_certs {
builder
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerify(provider)))
.with_no_client_auth()
} else {
let mut roots = RootCertStore::empty();
if let Some(pem) = &tls.ca_cert_pem {
let mut cursor = std::io::Cursor::new(pem);
for cert in rustls_pemfile::certs(&mut cursor) {
let cert = cert.map_err(|e| Error::Config(format!("invalid CA PEM: {e}")))?;
roots
.add(cert)
.map_err(|e| Error::Config(format!("add CA: {e}")))?;
}
} else {
roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
}
builder.with_root_certificates(roots).with_no_client_auth()
};
Ok(Arc::new(config))
}
pub(crate) fn server_name(host: &str) -> Result<ServerName<'static>> {
ServerName::try_from(host.to_owned())
.map_err(|_| Error::Config(format!("invalid TLS server name {host:?}")))
}
#[derive(Debug)]
struct NoVerify(Arc<CryptoProvider>);
impl ServerCertVerifier for NoVerify {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp: &[u8],
_now: UnixTime,
) -> std::result::Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> std::result::Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
#[cfg(test)]
mod tests {
use super::super::TlsConfig;
use super::{client_config, server_name};
#[test]
fn builds_public_root_config() {
assert!(client_config(&TlsConfig::with_public_roots()).is_ok());
}
#[test]
fn builds_insecure_config() {
assert!(client_config(&TlsConfig::danger_insecure()).is_ok());
}
#[test]
fn rejects_garbage_ca_pem() {
let bad = TlsConfig::with_ca_pem(
b"-----BEGIN CERTIFICATE-----\nnot-base64!!!\n-----END CERTIFICATE-----\n".to_vec(),
);
assert!(client_config(&bad).is_err());
}
#[test]
fn resolves_server_names() {
assert!(server_name("db.example.com").is_ok());
assert!(server_name("127.0.0.1").is_ok());
}
}
}