redis/
tls.rs

1use std::io::Error;
2
3use rustls::pki_types::pem::PemObject;
4use rustls::pki_types::{CertificateDer, PrivateKeyDer};
5use rustls::RootCertStore;
6
7use crate::connection::TlsConnParams;
8use crate::{Client, ConnectionAddr, ConnectionInfo, ErrorKind, RedisError, RedisResult};
9
10/// Structure to hold mTLS client _certificate_ and _key_ binaries in PEM format
11///
12#[derive(Clone)]
13pub struct ClientTlsConfig {
14    /// client certificate byte stream in PEM format
15    pub client_cert: Vec<u8>,
16    /// client key byte stream in PEM format
17    pub client_key: Vec<u8>,
18}
19
20/// Structure to hold TLS certificates
21/// - `client_tls`: binaries of clientkey and certificate within a `ClientTlsConfig` structure if mTLS is used
22/// - `root_cert`: binary CA certificate in PEM format if CA is not in local truststore
23///
24#[derive(Clone)]
25pub struct TlsCertificates {
26    /// 'ClientTlsConfig' containing client certificate and key if mTLS is to be used
27    pub client_tls: Option<ClientTlsConfig>,
28    /// root certificate byte stream in PEM format if the local truststore is *not* to be used
29    pub root_cert: Option<Vec<u8>>,
30}
31
32pub(crate) fn inner_build_with_tls(
33    mut connection_info: ConnectionInfo,
34    certificates: &TlsCertificates,
35) -> RedisResult<Client> {
36    let tls_params = retrieve_tls_certificates(certificates)?;
37
38    connection_info.addr = if let ConnectionAddr::TcpTls {
39        host,
40        port,
41        insecure,
42        ..
43    } = connection_info.addr
44    {
45        ConnectionAddr::TcpTls {
46            host,
47            port,
48            insecure,
49            tls_params: Some(tls_params),
50        }
51    } else {
52        return Err(RedisError::from((
53            ErrorKind::InvalidClientConfig,
54            "Constructing a TLS client requires a URL with the `rediss://` scheme",
55        )));
56    };
57
58    Ok(Client { connection_info })
59}
60
61pub(crate) fn retrieve_tls_certificates(
62    certificates: &TlsCertificates,
63) -> RedisResult<TlsConnParams> {
64    let TlsCertificates {
65        client_tls,
66        root_cert,
67    } = certificates;
68
69    let client_tls_params = if let Some(ClientTlsConfig {
70        client_cert,
71        client_key,
72    }) = client_tls
73    {
74        let client_cert_chain = CertificateDer::pem_slice_iter(client_cert)
75            .collect::<Result<Vec<_>, _>>()
76            .map_err(|err| {
77                Error::other(format!(
78                    "Unable to parse client certificate chain PEM: {err}"
79                ))
80            })?;
81
82        let client_key = PrivateKeyDer::from_pem_slice(client_key).map_err(|err| {
83            Error::other(format!(
84                "Unable to extract private key from PEM file: {err}"
85            ))
86        })?;
87
88        Some(ClientTlsParams {
89            client_cert_chain,
90            client_key,
91        })
92    } else {
93        None
94    };
95
96    let root_cert_store = if let Some(root_cert) = root_cert {
97        let mut root_cert_store = RootCertStore::empty();
98        for result in CertificateDer::pem_slice_iter(root_cert) {
99            let cert = result.map_err(|err| {
100                Error::other(format!("Unable to parse root certificate PEM: {err}"))
101            })?;
102
103            if root_cert_store.add(cert).is_err() {
104                return Err(Error::other("Unable to parse TLS trust anchors").into());
105            }
106        }
107
108        Some(root_cert_store)
109    } else {
110        None
111    };
112
113    Ok(TlsConnParams {
114        client_tls_params,
115        root_cert_store,
116        #[cfg(any(feature = "tls-rustls-insecure", feature = "tls-native-tls"))]
117        danger_accept_invalid_hostnames: false,
118    })
119}
120
121#[derive(Debug)]
122pub struct ClientTlsParams {
123    pub(crate) client_cert_chain: Vec<CertificateDer<'static>>,
124    pub(crate) client_key: PrivateKeyDer<'static>,
125}
126
127/// [`PrivateKeyDer`] does not implement `Clone` so we need to implement it manually.
128impl Clone for ClientTlsParams {
129    fn clone(&self) -> Self {
130        use PrivateKeyDer::*;
131        Self {
132            client_cert_chain: self.client_cert_chain.clone(),
133            client_key: match &self.client_key {
134                Pkcs1(key) => Pkcs1(key.secret_pkcs1_der().to_vec().into()),
135                Pkcs8(key) => Pkcs8(key.secret_pkcs8_der().to_vec().into()),
136                Sec1(key) => Sec1(key.secret_sec1_der().to_vec().into()),
137                _ => unreachable!(),
138            },
139        }
140    }
141}