zookeeper_client/
tls.rs

1use std::sync::Arc;
2
3use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
4use rustls::crypto::{CryptoProvider, WebPkiSupportedAlgorithms};
5use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
6use rustls::server::ParsedCertificate;
7use rustls::{ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme};
8
9use crate::client::Result;
10use crate::Error;
11
12/// Options for tls connection.
13#[derive(Debug)]
14pub struct TlsOptions {
15    identity: Option<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
16    ca_certs: RootCertStore,
17    hostname_verification: bool,
18}
19
20impl Clone for TlsOptions {
21    fn clone(&self) -> Self {
22        Self {
23            identity: self.identity.as_ref().map(|id| (id.0.clone(), id.1.clone_key())),
24            ca_certs: self.ca_certs.clone(),
25            hostname_verification: self.hostname_verification,
26        }
27    }
28}
29
30impl Default for TlsOptions {
31    /// Tls options with well-known ca roots.
32    fn default() -> Self {
33        let mut options = Self::no_ca();
34        options.ca_certs.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
35        options
36    }
37}
38
39// Rustls tends to make disable of hostname verification verbose since it exposes man-in-the-middle
40// attacks. Though, there are still attempts to disable hostname verification in rustls, but no got
41// merged until now.
42// * Allow disabling Hostname Verification: https://github.com/rustls/rustls/issues/578
43// * Dangerous verifiers API proposal: https://github.com/rustls/rustls/pull/1197
44#[derive(Debug)]
45struct TlsServerCertVerifier {
46    roots: RootCertStore,
47    supported: WebPkiSupportedAlgorithms,
48    hostname_verification: bool,
49}
50
51impl TlsServerCertVerifier {
52    fn new(roots: RootCertStore, hostname_verification: bool) -> Self {
53        Self {
54            roots,
55            supported: CryptoProvider::get_default().unwrap().signature_verification_algorithms,
56            hostname_verification,
57        }
58    }
59}
60
61impl ServerCertVerifier for TlsServerCertVerifier {
62    fn verify_server_cert(
63        &self,
64        end_entity: &CertificateDer<'_>,
65        intermediates: &[CertificateDer<'_>],
66        server_name: &ServerName<'_>,
67        _ocsp_response: &[u8],
68        now: UnixTime,
69    ) -> Result<ServerCertVerified, TlsError> {
70        let cert = ParsedCertificate::try_from(end_entity)?;
71        rustls::client::verify_server_cert_signed_by_trust_anchor(
72            &cert,
73            &self.roots,
74            intermediates,
75            now,
76            self.supported.all,
77        )?;
78
79        if self.hostname_verification {
80            rustls::client::verify_server_name(&cert, server_name)?;
81        }
82        Ok(ServerCertVerified::assertion())
83    }
84
85    fn verify_tls12_signature(
86        &self,
87        message: &[u8],
88        cert: &CertificateDer<'_>,
89        dss: &DigitallySignedStruct,
90    ) -> Result<HandshakeSignatureValid, TlsError> {
91        rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported)
92    }
93
94    fn verify_tls13_signature(
95        &self,
96        message: &[u8],
97        cert: &CertificateDer<'_>,
98        dss: &DigitallySignedStruct,
99    ) -> Result<HandshakeSignatureValid, TlsError> {
100        rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported)
101    }
102
103    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
104        self.supported.supported_schemes()
105    }
106}
107
108impl TlsOptions {
109    /// Tls options with no ca certificates. Use [TlsOptions::default] if well-known ca roots is
110    /// desirable.
111    pub fn no_ca() -> Self {
112        Self { ca_certs: RootCertStore::empty(), identity: None, hostname_verification: true }
113    }
114
115    /// Disables hostname verification in tls handshake.
116    ///
117    /// # Safety
118    /// This exposes risk to man-in-the-middle attacks.
119    pub unsafe fn with_no_hostname_verification(mut self) -> Self {
120        self.hostname_verification = false;
121        self
122    }
123
124    /// Adds new ca certificates.
125    pub fn with_pem_ca_certs(mut self, certs: &str) -> Result<Self> {
126        for r in rustls_pemfile::certs(&mut certs.as_bytes()) {
127            let cert = match r {
128                Ok(cert) => cert,
129                Err(err) => return Err(Error::with_other("fail to read cert", err)),
130            };
131            if let Err(err) = self.ca_certs.add(cert) {
132                return Err(Error::with_other("fail to add cert", err));
133            }
134        }
135        Ok(self)
136    }
137
138    /// Specifies client identity for server to authenticate.
139    pub fn with_pem_identity(mut self, cert: &str, key: &str) -> Result<Self> {
140        let r: std::result::Result<Vec<_>, _> = rustls_pemfile::certs(&mut cert.as_bytes()).collect();
141        let certs = match r {
142            Err(err) => return Err(Error::with_other("fail to read cert", err)),
143            Ok(certs) => certs,
144        };
145        let key = match rustls_pemfile::private_key(&mut key.as_bytes()) {
146            Err(err) => return Err(Error::with_other("fail to read client private key", err)),
147            Ok(None) => return Err(Error::BadArguments(&"no client private key")),
148            Ok(Some(key)) => key,
149        };
150        self.identity = Some((certs, key));
151        Ok(self)
152    }
153
154    fn take_roots(&mut self) -> RootCertStore {
155        std::mem::replace(&mut self.ca_certs, RootCertStore::empty())
156    }
157
158    pub(crate) fn into_config(mut self) -> Result<ClientConfig> {
159        // This has to be called before server cert verifier to install default crypto provider.
160        let builder = ClientConfig::builder();
161        let verifier = TlsServerCertVerifier::new(self.take_roots(), self.hostname_verification);
162        let builder = builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier));
163        if let Some((client_cert, client_key)) = self.identity.take() {
164            match builder.with_client_auth_cert(client_cert, client_key) {
165                Ok(config) => Ok(config),
166                Err(err) => Err(Error::with_other("invalid client private key", err)),
167            }
168        } else {
169            Ok(builder.with_no_client_auth())
170        }
171    }
172}