Skip to main content

pgsync_postgres/
tls.rs

1//! rustls configuration shared by ordinary and replication connections.
2
3use crate::url::TlsPolicy;
4use pgsync_core::config::SafetyConfig;
5use pgsync_core::{Error, Result};
6use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
7use std::sync::Arc;
8
9pub fn client_config(
10    policy: TlsPolicy,
11    safety: &SafetyConfig,
12    url_ca_file: Option<&str>,
13) -> Result<Arc<rustls::ClientConfig>> {
14    let provider = Arc::new(rustls::crypto::ring::default_provider());
15    let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
16        .with_safe_default_protocol_versions()
17        .map_err(|e| Error::config(format!("tls: {e}")))?;
18
19    let config = if policy.verify() {
20        let mut roots = rustls::RootCertStore::empty();
21        let native = rustls_native_certs::load_native_certs();
22        for cert in native.certs {
23            let _ = roots.add(cert);
24        }
25        for path in safety
26            .tls_ca_file
27            .iter()
28            .map(String::as_str)
29            .chain(url_ca_file)
30        {
31            let pem = std::fs::read(path)
32                .map_err(|e| Error::config(format!("cannot read CA file {path}: {e}")))?;
33            let mut added = 0;
34            for cert in rustls_pemfile::certs(&mut pem.as_slice()) {
35                let cert =
36                    cert.map_err(|e| Error::config(format!("invalid certificate in {path}: {e}")))?;
37                roots
38                    .add(cert)
39                    .map_err(|e| Error::config(format!("invalid certificate in {path}: {e}")))?;
40                added += 1;
41            }
42            if added == 0 {
43                return Err(Error::config(format!(
44                    "no certificates found in CA file {path}"
45                )));
46            }
47        }
48        if roots.is_empty() {
49            return Err(Error::config(
50                "no trusted root certificates available; set safety.tls_ca_file",
51            ));
52        }
53        builder.with_root_certificates(roots).with_no_client_auth()
54    } else {
55        builder
56            .dangerous()
57            .with_custom_certificate_verifier(Arc::new(NoVerify(provider)))
58            .with_no_client_auth()
59    };
60    Ok(Arc::new(config))
61}
62
63/// Accepts any certificate. Only reachable for loopback hosts with
64/// `safety.insecure_skip_tls_verify: true`.
65#[derive(Debug)]
66struct NoVerify(Arc<rustls::crypto::CryptoProvider>);
67
68impl rustls::client::danger::ServerCertVerifier for NoVerify {
69    fn verify_server_cert(
70        &self,
71        _end_entity: &CertificateDer<'_>,
72        _intermediates: &[CertificateDer<'_>],
73        _server_name: &ServerName<'_>,
74        _ocsp_response: &[u8],
75        _now: UnixTime,
76    ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
77        Ok(rustls::client::danger::ServerCertVerified::assertion())
78    }
79
80    fn verify_tls12_signature(
81        &self,
82        message: &[u8],
83        cert: &CertificateDer<'_>,
84        dss: &rustls::DigitallySignedStruct,
85    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
86        rustls::crypto::verify_tls12_signature(
87            message,
88            cert,
89            dss,
90            &self.0.signature_verification_algorithms,
91        )
92    }
93
94    fn verify_tls13_signature(
95        &self,
96        message: &[u8],
97        cert: &CertificateDer<'_>,
98        dss: &rustls::DigitallySignedStruct,
99    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
100        rustls::crypto::verify_tls13_signature(
101            message,
102            cert,
103            dss,
104            &self.0.signature_verification_algorithms,
105        )
106    }
107
108    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
109        self.0.signature_verification_algorithms.supported_schemes()
110    }
111}
112
113pub fn server_name(host: &str) -> Result<ServerName<'static>> {
114    ServerName::try_from(host.to_owned())
115        .map_err(|e| Error::config(format!("invalid TLS server name {host:?}: {e}")))
116}