1use mz_ore::secure::{Zeroize, Zeroizing};
13use openssl::pkcs12::Pkcs12;
14use openssl::pkey::PKey;
15use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
16use openssl::stack::Stack;
17use openssl::x509::X509;
18use postgres_openssl::MakeTlsConnector;
19use tokio_postgres::config::SslMode;
20
21macro_rules! bail_generic {
22 ($err:expr $(,)?) => {
23 return Err(TlsError::Generic(anyhow::anyhow!($err)))
24 };
25}
26
27#[derive(Debug, thiserror::Error)]
29pub enum TlsError {
30 #[error(transparent)]
32 Generic(#[from] anyhow::Error),
33 #[error(transparent)]
35 OpenSsl(#[from] openssl::error::ErrorStack),
36}
37
38pub fn make_tls(config: &tokio_postgres::Config) -> Result<MakeTlsConnector, TlsError> {
40 let mut builder = SslConnector::builder(SslMethod::tls_client())?;
41 let (verify_mode, verify_hostname) = match config.get_ssl_mode() {
47 SslMode::Disable | SslMode::Prefer => (SslVerifyMode::NONE, false),
48 SslMode::Require => match config.get_ssl_root_cert() {
49 Some(_) => (SslVerifyMode::PEER, false),
55 None => (SslVerifyMode::NONE, false),
56 },
57 SslMode::VerifyCa => (SslVerifyMode::PEER, false),
58 SslMode::VerifyFull => (SslVerifyMode::PEER, true),
59 _ => panic!("unexpected sslmode {:?}", config.get_ssl_mode()),
60 };
61
62 builder.set_verify(verify_mode);
64
65 match (config.get_ssl_cert(), config.get_ssl_key()) {
67 (Some(ssl_cert), Some(ssl_key)) => {
68 builder.set_certificate(&*X509::from_pem(ssl_cert)?)?;
69 builder.set_private_key(&*PKey::private_key_from_pem(ssl_key)?)?;
70 }
71 (None, Some(_)) => {
72 bail_generic!("must provide both sslcert and sslkey, but only provided sslkey")
73 }
74 (Some(_), None) => {
75 bail_generic!("must provide both sslcert and sslkey, but only provided sslcert")
76 }
77 _ => {}
78 }
79 if let Some(ssl_root_cert) = config.get_ssl_root_cert() {
80 for cert in X509::stack_from_pem(ssl_root_cert)? {
81 builder.cert_store_mut().add_cert(cert)?;
82 }
83 }
84
85 let mut tls_connector = MakeTlsConnector::new(builder.build());
86
87 match (verify_mode, verify_hostname) {
89 (SslVerifyMode::PEER, false) => tls_connector.set_callback(|connect, _| {
90 connect.set_verify_hostname(false);
91 Ok(())
92 }),
93 _ => {}
94 }
95
96 Ok(tls_connector)
97}
98
99pub struct Pkcs12Archive {
100 pub der: Vec<u8>,
101 pub pass: String,
102}
103
104impl Zeroize for Pkcs12Archive {
105 fn zeroize(&mut self) {
106 self.der.zeroize();
107 self.pass.zeroize();
108 }
109}
110
111impl Drop for Pkcs12Archive {
112 fn drop(&mut self) {
113 self.zeroize();
114 }
115}
116
117impl Pkcs12Archive {
118 pub fn into_parts(self) -> (Vec<u8>, String) {
119 let mut md = std::mem::ManuallyDrop::new(self);
120 let der = std::mem::take(&mut md.der);
121 let pass = std::mem::take(&mut md.pass);
122 (der, pass)
123 }
124}
125
126pub fn pkcs12der_from_pem(
128 key: &[u8],
129 cert: &[u8],
130) -> Result<Pkcs12Archive, openssl::error::ErrorStack> {
131 let mut buf = Zeroizing::new(Vec::new());
132 buf.extend(key);
133 buf.push(b'\n');
134 buf.extend(cert);
135 let pem = buf.as_slice();
136 let pkey = PKey::private_key_from_pem(pem)?;
137 let mut certs = Stack::new()?;
138
139 let mut cert_iter = X509::stack_from_pem(pem)?.into_iter();
149 let cert = match cert_iter.next() {
150 Some(cert) => cert,
151 None => X509::from_pem(pem)?,
152 };
153 for cert in cert_iter {
154 certs.push(cert)?;
155 }
156 let pass = String::new();
160 let friendly_name = "";
161 let der = Pkcs12::builder()
162 .name(friendly_name)
163 .pkey(&pkey)
164 .cert(&cert)
165 .ca(certs)
166 .build2(&pass)?
167 .to_der()?;
168 Ok(Pkcs12Archive { der, pass })
169}
170
171#[cfg(test)]
172mod tests {
173 use std::io::Write;
174 use std::net::{SocketAddr, TcpListener};
175
176 use openssl::asn1::{Asn1Integer, Asn1Time};
177 use openssl::bn::{BigNum, MsbOption};
178 use openssl::hash::MessageDigest;
179 use openssl::pkey::Private;
180 use openssl::rsa::Rsa;
181 use openssl::ssl::SslAcceptor;
182 use openssl::x509::X509NameBuilder;
183 use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
184 use tokio::io::{AsyncRead, AsyncReadExt};
185 use tokio::net::TcpStream;
186 use tokio_postgres::tls::{MakeTlsConnect, TlsConnect};
187
188 use super::*;
189
190 struct TestCa {
192 cert: X509,
193 key: PKey<Private>,
194 }
195
196 fn random_serial() -> Asn1Integer {
197 let mut bn = BigNum::new().unwrap();
198 bn.rand(64, MsbOption::MAYBE_ZERO, false).unwrap();
199 bn.to_asn1_integer().unwrap()
200 }
201
202 impl TestCa {
203 fn new() -> TestCa {
204 let key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
205 let mut name = X509NameBuilder::new().unwrap();
206 name.append_entry_by_text("CN", "mz-tls-util test CA")
207 .unwrap();
208 let name = name.build();
209 let mut builder = X509::builder().unwrap();
210 builder.set_version(2).unwrap();
211 builder.set_serial_number(&random_serial()).unwrap();
212 builder.set_subject_name(&name).unwrap();
213 builder.set_issuer_name(&name).unwrap();
214 builder.set_pubkey(&key).unwrap();
215 builder
216 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
217 .unwrap();
218 builder
219 .set_not_after(&Asn1Time::days_from_now(1).unwrap())
220 .unwrap();
221 builder
222 .append_extension(BasicConstraints::new().critical().ca().build().unwrap())
223 .unwrap();
224 builder.sign(&key, MessageDigest::sha256()).unwrap();
225 TestCa {
226 cert: builder.build(),
227 key,
228 }
229 }
230
231 fn issue(&self, cn: &str, dns_san: Option<&str>) -> (Vec<u8>, Vec<u8>) {
233 let key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
234 let mut name = X509NameBuilder::new().unwrap();
235 name.append_entry_by_text("CN", cn).unwrap();
236 let name = name.build();
237 let mut builder = X509::builder().unwrap();
238 builder.set_version(2).unwrap();
239 builder.set_serial_number(&random_serial()).unwrap();
240 builder.set_subject_name(&name).unwrap();
241 builder.set_issuer_name(self.cert.subject_name()).unwrap();
242 builder.set_pubkey(&key).unwrap();
243 builder
244 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
245 .unwrap();
246 builder
247 .set_not_after(&Asn1Time::days_from_now(1).unwrap())
248 .unwrap();
249 if let Some(dns) = dns_san {
250 let san = SubjectAlternativeName::new()
251 .dns(dns)
252 .build(&builder.x509v3_context(Some(&self.cert), None))
253 .unwrap();
254 builder.append_extension(san).unwrap();
255 }
256 builder.sign(&self.key, MessageDigest::sha256()).unwrap();
257 (
258 builder.build().to_pem().unwrap(),
259 key.private_key_to_pem_pkcs8().unwrap(),
260 )
261 }
262 }
263
264 fn run_tls_server(cert_pem: &[u8], key_pem: &[u8], client_ca_pem: Option<&[u8]>) -> SocketAddr {
270 let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls_server()).unwrap();
271 acceptor
272 .set_certificate(&X509::from_pem(cert_pem).unwrap())
273 .unwrap();
274 acceptor
275 .set_private_key(&PKey::private_key_from_pem(key_pem).unwrap())
276 .unwrap();
277 if let Some(ca_pem) = client_ca_pem {
278 for cert in X509::stack_from_pem(ca_pem).unwrap() {
279 acceptor.cert_store_mut().add_cert(cert).unwrap();
280 }
281 acceptor.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT);
282 }
283 let acceptor = acceptor.build();
284 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
285 let addr = listener.local_addr().unwrap();
286 std::thread::spawn(move || {
287 for stream in listener.incoming() {
288 if let Ok(mut tls) = acceptor.accept(stream.unwrap()) {
291 tls.write_all(b"ok").unwrap();
292 let _ = tls.shutdown();
293 }
294 }
295 });
296 addr
297 }
298
299 async fn openssl_connect(
302 config: &tokio_postgres::Config,
303 host: &str,
304 addr: SocketAddr,
305 ) -> postgres_openssl::TlsStream<TcpStream> {
306 let mut make = make_tls(config).unwrap();
307 let connect = MakeTlsConnect::<TcpStream>::make_tls_connect(&mut make, host).unwrap();
308 let tcp = TcpStream::connect(addr).await.unwrap();
309 TlsConnect::<TcpStream>::connect(connect, tcp)
310 .await
311 .unwrap()
312 }
313
314 async fn assert_server_ok<S: AsyncRead + Unpin>(mut tls: S) {
315 let mut buf = [0u8; 2];
316 tls.read_exact(&mut buf)
317 .await
318 .expect("server did not complete the handshake");
319 assert_eq!(&buf, b"ok");
320 }
321
322 #[mz_ore::test(tokio::test)]
323 #[cfg_attr(miri, ignore)] async fn verify_full_trusts_default_store() {
325 let ca = TestCa::new();
326 let (server_cert, server_key) = ca.issue("server", Some("localhost"));
327
328 let mut ca_file = tempfile::NamedTempFile::new().unwrap();
329 ca_file.write_all(&ca.cert.to_pem().unwrap()).unwrap();
330 unsafe { std::env::set_var("SSL_CERT_FILE", ca_file.path()) };
334
335 let addr = run_tls_server(&server_cert, &server_key, None);
336 let mut config = tokio_postgres::Config::new();
337 config.ssl_mode(SslMode::VerifyFull);
338
339 assert_server_ok(openssl_connect(&config, "localhost", addr).await).await;
340 }
341
342 #[mz_ore::test(tokio::test)]
343 #[cfg_attr(miri, ignore)] async fn client_certs_accepted() {
345 let ca = TestCa::new();
346 let (server_cert, server_key) = ca.issue("server", Some("localhost"));
347 let (client_cert, client_key) = ca.issue("client", None);
348 let ca_pem = ca.cert.to_pem().unwrap();
349
350 let addr = run_tls_server(&server_cert, &server_key, Some(&ca_pem));
351 let mut config = tokio_postgres::Config::new();
352 config.ssl_mode(SslMode::VerifyFull);
353 config.ssl_root_cert(&ca_pem);
354 config.ssl_cert(&client_cert);
355 config.ssl_key(&client_key);
356
357 assert_server_ok(openssl_connect(&config, "localhost", addr).await).await;
358 }
359
360 #[mz_ore::test]
361 fn pkcs12_archive_needs_drop() {
362 assert!(std::mem::needs_drop::<Pkcs12Archive>());
363 }
364
365 #[mz_ore::test]
366 fn pkcs12_archive_zeroize_clears_fields() {
367 let mut archive = Pkcs12Archive {
368 der: vec![0xDE, 0xAD, 0xBE, 0xEF],
369 pass: String::from("hunter2"),
370 };
371
372 archive.zeroize();
373
374 assert!(archive.der.is_empty(), "der was not zeroed");
375 assert!(archive.pass.is_empty(), "pass was not zeroed");
376 }
377
378 #[mz_ore::test]
379 fn pkcs12_archive_implements_zeroize() {
380 fn assert_zeroize<T: mz_ore::secure::Zeroize>() {}
381 assert_zeroize::<Pkcs12Archive>();
382 }
383}