forked from amazon-contributing/redis-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtls.rs
145 lines (126 loc) · 4.28 KB
/
tls.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::io::{BufRead, Error, ErrorKind as IOErrorKind};
use rustls::{Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore};
use crate::{Client, ConnectionAddr, ConnectionInfo, ErrorKind, RedisError, RedisResult};
/// Structure to hold mTLS client _certificate_ and _key_ binaries in PEM format
///
#[derive(Clone)]
pub struct ClientTlsConfig {
/// client certificate byte stream in PEM format
pub client_cert: Vec<u8>,
/// client key byte stream in PEM format
pub client_key: Vec<u8>,
}
/// Structure to hold TLS certificates
/// - `client_tls`: binaries of clientkey and certificate within a `ClientTlsConfig` structure if mTLS is used
/// - `root_cert`: binary CA certificate in PEM format if CA is not in local truststore
///
#[derive(Clone)]
pub struct TlsCertificates {
/// 'ClientTlsConfig' containing client certificate and key if mTLS is to be used
pub client_tls: Option<ClientTlsConfig>,
/// root certificate byte stream in PEM format if the local truststore is *not* to be used
pub root_cert: Option<Vec<u8>>,
}
pub(crate) fn inner_build_with_tls(
mut connection_info: ConnectionInfo,
certificates: TlsCertificates,
) -> RedisResult<Client> {
let tls_params = retrieve_tls_certificates(certificates)?;
connection_info.addr = if let ConnectionAddr::TcpTls {
host,
port,
insecure,
..
} = connection_info.addr
{
ConnectionAddr::TcpTls {
host,
port,
insecure,
tls_params: Some(tls_params),
}
} else {
return Err(RedisError::from((
ErrorKind::InvalidClientConfig,
"Constructing a TLS client requires a URL with the `rediss://` scheme",
)));
};
Ok(Client { connection_info })
}
pub(crate) fn retrieve_tls_certificates(
certificates: TlsCertificates,
) -> RedisResult<TlsConnParams> {
let TlsCertificates {
client_tls,
root_cert,
} = certificates;
let client_tls_params = if let Some(ClientTlsConfig {
client_cert,
client_key,
}) = client_tls
{
let mut certs = rustls_pemfile::certs(&mut client_cert.as_slice() as &mut dyn BufRead)?;
let client_cert_chain = certs.drain(..).map(Certificate).collect();
let client_key = load_key(&mut client_key.as_slice() as &mut dyn BufRead)?;
Some(ClientTlsParams {
client_cert_chain,
client_key,
})
} else {
None
};
let root_cert_store = if let Some(root_cert) = root_cert {
let certs = rustls_pemfile::certs(&mut root_cert.as_slice() as &mut dyn BufRead)?;
let trust_anchors = certs
.iter()
.map(|cert| {
let ta = webpki::TrustAnchor::try_from_cert_der(cert).map_err(|_| {
Error::new(IOErrorKind::Other, "Unable to parse TLS trust anchors")
})?;
Ok(OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
))
})
.collect::<RedisResult<Vec<_>>>()?;
let mut root_cert_store = RootCertStore::empty();
root_cert_store.add_trust_anchors(trust_anchors.into_iter());
Some(root_cert_store)
} else {
None
};
Ok(TlsConnParams {
client_tls_params,
root_cert_store,
})
}
#[derive(Debug, Clone)]
pub struct ClientTlsParams {
pub(crate) client_cert_chain: Vec<Certificate>,
pub(crate) client_key: PrivateKey,
}
#[derive(Debug, Clone)]
pub struct TlsConnParams {
pub(crate) client_tls_params: Option<ClientTlsParams>,
pub(crate) root_cert_store: Option<RootCertStore>,
}
fn load_key(mut reader: &mut dyn BufRead) -> RedisResult<PrivateKey> {
loop {
match rustls_pemfile::read_one(&mut reader)? {
Some(rustls_pemfile::Item::ECKey(key))
| Some(rustls_pemfile::Item::RSAKey(key))
| Some(rustls_pemfile::Item::PKCS8Key(key)) => {
return Ok(PrivateKey(key));
}
None => {
break;
}
_ => {}
}
}
Err(RedisError::from(Error::new(
IOErrorKind::Other,
"Unable to extract private key from PEM file",
)))
}