43
43
44
44
#[ cfg( feature = "runtime" ) ]
45
45
use openssl:: error:: ErrorStack ;
46
- use openssl:: hash:: MessageDigest ;
47
- use openssl:: nid:: Nid ;
48
46
#[ cfg( feature = "runtime" ) ]
49
47
use openssl:: ssl:: SslConnector ;
50
- use openssl:: ssl:: { self , ConnectConfiguration , SslRef } ;
48
+ use openssl:: ssl:: { self , ConnectConfiguration , SslFiletype , SslRef } ;
51
49
use openssl:: x509:: X509VerifyResult ;
52
- use std:: error:: Error ;
50
+ use openssl:: { hash:: MessageDigest , ssl:: SslMethod } ;
51
+ use openssl:: { nid:: Nid , ssl:: SslVerifyMode } ;
53
52
use std:: fmt:: { self , Debug } ;
54
53
use std:: future:: Future ;
55
54
use std:: io;
56
55
use std:: pin:: Pin ;
57
56
#[ cfg( feature = "runtime" ) ]
58
57
use std:: sync:: Arc ;
59
58
use std:: task:: { Context , Poll } ;
59
+ use std:: { error:: Error , path:: PathBuf } ;
60
60
use tokio:: io:: { AsyncRead , AsyncWrite , ReadBuf } ;
61
61
use tokio_openssl:: SslStream ;
62
- use tokio_postgres:: tls;
63
62
#[ cfg( feature = "runtime" ) ]
64
63
use tokio_postgres:: tls:: MakeTlsConnect ;
65
64
use tokio_postgres:: tls:: { ChannelBinding , TlsConnect } ;
65
+ use tokio_postgres:: { config:: SslMode , tls} ;
66
66
67
67
#[ cfg( test) ]
68
68
mod test;
69
69
70
+ /// TLS configuration.
71
+ #[ cfg( feature = "runtime" ) ]
72
+ pub struct TlsConfig {
73
+ /// SSL mode (`sslmode`).
74
+ pub mode : SslMode ,
75
+ /// Location of the client cert and key (`sslcert`, `sslkey`).
76
+ pub client_cert : Option < ( PathBuf , PathBuf ) > ,
77
+ /// Location of the root certificate (`sslrootcert`).
78
+ pub root_cert : Option < PathBuf > ,
79
+ }
80
+
70
81
/// A `MakeTlsConnect` implementation using the `openssl` crate.
71
82
///
72
83
/// Requires the `runtime` Cargo feature (enabled by default).
@@ -87,6 +98,59 @@ impl MakeTlsConnector {
87
98
}
88
99
}
89
100
101
+ /// Creates a new connector from the provided [`TlsConfig`].
102
+ ///
103
+ /// The returned [`MakeTlsConnector`] will be configured to mimick libpq-ssl behavior.
104
+ pub fn from_tls_config ( tls_config : TlsConfig ) -> Result < MakeTlsConnector , ErrorStack > {
105
+ let mut builder = SslConnector :: builder ( SslMethod :: tls_client ( ) ) ?;
106
+ // The mode dictates whether we verify peer certs and hostnames. By default, Postgres is
107
+ // pretty relaxed and recommends SslMode::VerifyCa or SslMode::VerifyFull for security.
108
+ //
109
+ // For more details, check out Table 33.1. SSL Mode Descriptions in
110
+ // https://postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION.
111
+ let ( verify_mode, verify_hostname) = match tls_config. mode {
112
+ SslMode :: Disable | SslMode :: Prefer => ( SslVerifyMode :: NONE , false ) ,
113
+ SslMode :: Require => match tls_config. root_cert {
114
+ // If a root CA file exists, the behavior of sslmode=require will be the same as
115
+ // that of verify-ca, meaning the server certificate is validated against the CA.
116
+ //
117
+ // For more details, check out the note about backwards compatibility in
118
+ // https://postgresql.org/docs/current/libpq-ssl.html#LIBQ-SSL-CERTIFICATES.
119
+ Some ( _) => ( SslVerifyMode :: PEER , false ) ,
120
+ None => ( SslVerifyMode :: NONE , false ) ,
121
+ } ,
122
+ SslMode :: VerifyCa => ( SslVerifyMode :: PEER , false ) ,
123
+ SslMode :: VerifyFull => ( SslVerifyMode :: PEER , true ) ,
124
+ _ => panic ! ( "unexpected sslmode {:?}" , tls_config. mode) ,
125
+ } ;
126
+
127
+ // Configure peer verification
128
+ builder. set_verify ( verify_mode) ;
129
+
130
+ // Configure certificates
131
+ if tls_config. client_cert . is_some ( ) {
132
+ let ( cert, key) = tls_config. client_cert . unwrap ( ) ;
133
+ builder. set_certificate_file ( cert, SslFiletype :: PEM ) ?;
134
+ builder. set_private_key_file ( key, SslFiletype :: PEM ) ?;
135
+ }
136
+ if tls_config. root_cert . is_some ( ) {
137
+ builder. set_ca_file ( tls_config. root_cert . unwrap ( ) ) ?;
138
+ }
139
+
140
+ let mut tls_connector = MakeTlsConnector :: new ( builder. build ( ) ) ;
141
+
142
+ // Configure hostname verification
143
+ match ( verify_mode, verify_hostname) {
144
+ ( SslVerifyMode :: PEER , false ) => tls_connector. set_callback ( |connect, _| {
145
+ connect. set_verify_hostname ( false ) ;
146
+ Ok ( ( ) )
147
+ } ) ,
148
+ _ => { }
149
+ }
150
+
151
+ Ok ( tls_connector)
152
+ }
153
+
90
154
/// Sets a callback used to apply per-connection configuration.
91
155
///
92
156
/// The the callback is provided the domain name along with the `ConnectConfiguration`.
0 commit comments