From 2630b9cb41aec7a108fd25645e3c10697668f39f Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Thu, 8 Apr 2021 17:12:12 -0400 Subject: [PATCH 1/7] added support for postgres sslcert and sslkey --- Cargo.lock | 11 ++++---- Cargo.toml | 3 ++ sqlx-core/Cargo.toml | 4 +++ sqlx-core/src/net/tls/mod.rs | 29 +++++++++++++++++++ sqlx-core/src/postgres/connection/tls.rs | 2 ++ sqlx-core/src/postgres/options/mod.rs | 36 ++++++++++++++++++++++++ sqlx-core/src/postgres/options/parse.rs | 4 +++ 7 files changed, 84 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaa5a44291..ffc0b8309a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1502,15 +1502,15 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.32" +version = "0.10.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" +checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577" dependencies = [ "bitflags", "cfg-if 1.0.0", "foreign-types", - "lazy_static", "libc", + "once_cell", "openssl-sys", ] @@ -1531,9 +1531,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.60" +version = "0.9.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" +checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f" dependencies = [ "autocfg 1.0.1", "cc", @@ -2347,6 +2347,7 @@ dependencies = [ "memchr", "num-bigint 0.3.1", "once_cell", + "openssl", "parking_lot", "percent-encoding", "rand 0.7.3", diff --git a/Cargo.toml b/Cargo.toml index ea93952103..fcd9fab1c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,9 @@ bit-vec = [ "sqlx-core/bit-vec", "sqlx-macros/bit-vec"] bstr = [ "sqlx-core/bstr" ] git2 = [ "sqlx-core/git2" ] +# openssl +openssl = [ "sqlx-core/openssl-native" ] + [dependencies] sqlx-core = { version = "0.5.1", path = "sqlx-core", default-features = false } sqlx-macros = { version = "0.5.1", path = "sqlx-macros", default-features = false, optional = true } diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 1a7b795cec..781884d9b4 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -42,6 +42,9 @@ runtime-actix-rustls = [ "sqlx-rt/runtime-actix-rustls", "_tls-rustls", "_rt-act runtime-async-std-rustls = [ "sqlx-rt/runtime-async-std-rustls", "_tls-rustls", "_rt-async-std" ] runtime-tokio-rustls = [ "sqlx-rt/runtime-tokio-rustls", "_tls-rustls", "_rt-tokio" ] +# openssl +openssl-native = [ "openssl" ] + # for conditional compilation _rt-actix = [ "tokio-stream" ] _rt-async-std = [] @@ -110,3 +113,4 @@ stringprep = "0.1.2" bstr = { version = "0.2.14", default-features = false, features = [ "std" ], optional = true } git2 = { version = "0.13.12", default-features = false, optional = true } hashlink = "0.6.0" +openssl = { version = "0.10.33", optional = true } diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 36fcb6ea50..d4942bef7b 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -80,11 +80,15 @@ where accept_invalid_certs: bool, accept_invalid_hostnames: bool, root_cert_path: Option<&CertificateInput>, + client_cert_path: Option<&CertificateInput>, + client_key_path: Option<&CertificateInput>, ) -> Result<(), Error> { let connector = configure_tls_connector( accept_invalid_certs, accept_invalid_hostnames, root_cert_path, + client_cert_path, + client_key_path, ) .await?; @@ -117,8 +121,16 @@ async fn configure_tls_connector( accept_invalid_certs: bool, accept_invalid_hostnames: bool, root_cert_path: Option<&CertificateInput>, + client_cert_path: Option<&CertificateInput>, + client_key_path: Option<&CertificateInput>, ) -> Result { + #[cfg(feature = "openssl-native")] + use openssl::{pkcs12::Pkcs12, pkey::PKey, x509::X509}; + #[cfg(feature = "openssl-native")] + use sqlx_rt::native_tls::Identity; use sqlx_rt::native_tls::{Certificate, TlsConnector}; + #[cfg(feature = "openssl-native")] + const PASSWORD: &str = "temp-pkcs12"; let mut builder = TlsConnector::builder(); builder @@ -131,6 +143,23 @@ async fn configure_tls_connector( let cert = Certificate::from_pem(&data)?; builder.add_root_certificate(cert); + + #[cfg(feature = "openssl-native")] + if let (Some(cert), Some(key)) = (client_key_path, client_cert_path) { + let cert_data = cert.data().await?; + let key_data = key.data().await?; + if let (Ok(pkey), Ok(cert)) = ( + PKey::private_key_from_pem(&key_data), + X509::from_pem(&cert_data), + ) { + if let (Ok(pkcs), Ok(der)) = ( + Pkcs12::builder().build(PASSWORD, PASSWORD, pkey.as_ref(), cert.as_ref()), + pkcs.to_der(), + ) { + builder.identity(identity); + } + } + } } } diff --git a/sqlx-core/src/postgres/connection/tls.rs b/sqlx-core/src/postgres/connection/tls.rs index 0c780f401a..59e4095162 100644 --- a/sqlx-core/src/postgres/connection/tls.rs +++ b/sqlx-core/src/postgres/connection/tls.rs @@ -71,6 +71,8 @@ async fn upgrade(stream: &mut PgStream, options: &PgConnectOptions) -> Result, pub(crate) ssl_mode: PgSslMode, pub(crate) ssl_root_cert: Option, + pub(crate) ssl_client_cert: Option, + pub(crate) ssl_client_key: Option, pub(crate) statement_cache_capacity: usize, pub(crate) application_name: Option, pub(crate) log_settings: LogSettings, @@ -129,6 +131,8 @@ impl PgConnectOptions { password: var("PGPASSWORD").ok(), database: var("PGDATABASE").ok(), ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), + ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from), + ssl_client_key: var("PGSSLKEY").ok().map(CertificateInput::from), ssl_mode: var("PGSSLMODE") .ok() .and_then(|v| v.parse().ok()) @@ -269,6 +273,38 @@ impl PgConnectOptions { self } + /// Sets the name of a file containing SSL client certificate. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// let options = PgConnectOptions::new() + /// // Providing a CA certificate with less than VerifyCa is pointless + /// .ssl_mode(PgSslMode::VerifyCa) + /// .ssl_client_cert("./client.crt"); + /// ``` + pub fn ssl_client_cert(mut self, cert: impl AsRef) -> Self { + self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + self + } + + /// Sets the name of a file containing SSL client key. + /// + /// # Example + /// + /// ```rust + /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions}; + /// let options = PgConnectOptions::new() + /// // Providing a CA certificate with less than VerifyCa is pointless + /// .ssl_mode(PgSslMode::VerifyCa) + /// .ssl_client_key("./client.key"); + /// ``` + pub fn ssl_client_key(mut self, cert: impl AsRef) -> Self { + self.ssl_client_key = Some(CertificateInput::File(cert.as_ref().to_path_buf())); + self + } + /// Sets PEM encoded trusted SSL Certificate Authorities (CA). /// /// # Example diff --git a/sqlx-core/src/postgres/options/parse.rs b/sqlx-core/src/postgres/options/parse.rs index 5c5cd71ee8..3c1c32c15d 100644 --- a/sqlx-core/src/postgres/options/parse.rs +++ b/sqlx-core/src/postgres/options/parse.rs @@ -57,6 +57,10 @@ impl FromStr for PgConnectOptions { options = options.ssl_root_cert(&*value); } + "sslcert" => options = options.ssl_client_cert(&*value), + + "sslkey" => options = options.ssl_client_key(&*value), + "statement-cache-capacity" => { options = options.statement_cache_capacity(value.parse().map_err(Error::config)?); From 019364d9bdd4203c194266be603bd2f97d4aba26 Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Thu, 8 Apr 2021 17:31:44 -0400 Subject: [PATCH 2/7] fixed broken build after refactor --- sqlx-core/src/net/tls/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index d4942bef7b..1641512717 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -152,11 +152,14 @@ async fn configure_tls_connector( PKey::private_key_from_pem(&key_data), X509::from_pem(&cert_data), ) { - if let (Ok(pkcs), Ok(der)) = ( - Pkcs12::builder().build(PASSWORD, PASSWORD, pkey.as_ref(), cert.as_ref()), - pkcs.to_der(), - ) { - builder.identity(identity); + if let Ok(pkcs) = + Pkcs12::builder().build(PASSWORD, PASSWORD, pkey.as_ref(), cert.as_ref()) + { + if let Ok(der) = pkcs.to_der() { + let identity = Identity::from_pkcs12(&der, PASSWORD)?; + + builder.identity(identity); + } } } } From dbce96713aed93395b72a1e8dff057292cf66a5f Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Thu, 8 Apr 2021 18:12:26 -0400 Subject: [PATCH 3/7] fixed incorrect var order --- sqlx-core/src/net/tls/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 1641512717..bf3dde3df4 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -145,7 +145,7 @@ async fn configure_tls_connector( builder.add_root_certificate(cert); #[cfg(feature = "openssl-native")] - if let (Some(cert), Some(key)) = (client_key_path, client_cert_path) { + if let (Some(cert), Some(key)) = (client_cert_path, client_key_path) { let cert_data = cert.data().await?; let key_data = key.data().await?; if let (Ok(pkey), Ok(cert)) = ( From 14636707dd8df8e14966ad0b29c3f0d2f8b0f4a8 Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Sat, 10 Apr 2021 18:51:20 -0400 Subject: [PATCH 4/7] added tests for postgres certificate authentication --- .github/workflows/sqlx.yml | 43 ++++++++++++++++++++++ tests/.dockerignore | 1 + tests/certs/ca.srl | 1 + tests/certs/client.crt | 57 +++++++++++++++++++++++++++++ tests/certs/client.csr | 60 +++++++++++++++++++++++++++++++ tests/docker-compose.yml | 73 ++++++++++++++++++++++++++++++++++++++ tests/keys/client.key | 28 +++++++++++++++ tests/postgres/Dockerfile | 2 ++ tests/postgres/pg_hba.conf | 4 +++ 9 files changed, 269 insertions(+) create mode 100644 tests/certs/ca.srl create mode 100644 tests/certs/client.crt create mode 100644 tests/certs/client.csr create mode 100644 tests/keys/client.key create mode 100644 tests/postgres/pg_hba.conf diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index d583f33d64..bc3ad62921 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -230,6 +230,49 @@ jobs: env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt + postgres_cert: + name: Postgres Certificate Authentication + runs-on: ubuntu-20.04 + strategy: + matrix: + postgres: [12, 10, 9_6, 9_5] + runtime: [async-std-native-tls, tokio-native-tls, actix-native-tls] + needs: check + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --features postgres,openssl,all-types,runtime-${{ matrix.runtime }} + + - run: docker-compose -f tests/docker-compose.yml run -d -p 5432:5432 postgres_${{ matrix.postgres }}_cert + - run: sleep 10 + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --no-default-features + --features any,postgres,openssl,macros,migrate,all-types,runtime-${{ matrix.runtime }} + env: + DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslcert=.%2Ftests%2Fcerts%2Fclient.crt&sslkey=.%2Ftests%2Fkeys%2Fclient.key + mysql: name: MySQL runs-on: ubuntu-20.04 diff --git a/tests/.dockerignore b/tests/.dockerignore index 6c513a8a33..f1c67fa243 100644 --- a/tests/.dockerignore +++ b/tests/.dockerignore @@ -2,4 +2,5 @@ !certs/* !keys/* !mssql/*.sh +!postgres/pg_hba.conf !*/*.sql diff --git a/tests/certs/ca.srl b/tests/certs/ca.srl new file mode 100644 index 0000000000..ef2f121531 --- /dev/null +++ b/tests/certs/ca.srl @@ -0,0 +1 @@ +B6C71F4B11C0A189 diff --git a/tests/certs/client.crt b/tests/certs/client.crt new file mode 100644 index 0000000000..0edde05434 --- /dev/null +++ b/tests/certs/client.crt @@ -0,0 +1,57 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + e0:be:1f:7a:49:1e:49:ec + Signature Algorithm: NULL + Issuer: CN = postgres + Validity + Not Before: Apr 10 20:59:23 2021 GMT + Not After : Apr 8 20:59:23 2031 GMT + Subject: CN = postgres + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:bf:4f:18:ca:d8:ff:a3:93:aa:9a:3b:90:35:c7: + ff:82:65:d1:d0:e8:65:9d:9c:6c:cb:70:4e:31:7e: + 7e:52:ce:2d:85:7a:83:ee:b8:eb:f1:ba:37:0e:34: + 66:3d:b6:db:cb:45:6f:64:0f:5c:4d:ba:53:25:c9: + ff:e0:a1:39:9b:82:c9:c0:08:e8:17:6b:01:6a:99: + 47:05:d8:c5:2f:83:f3:33:f7:ad:bb:f3:dd:5f:6a: + 95:4f:d9:8e:1d:bc:ff:84:78:77:eb:98:40:36:2d: + 9a:a3:29:a6:ba:58:90:c1:92:88:5f:07:c3:a8:a6: + 06:f0:ca:f8:81:40:13:65:1d:08:6c:97:9f:d4:b4: + 8d:f7:77:32:f6:2c:d4:9b:07:b3:86:3a:62:7f:da: + 3d:3c:e9:96:71:cc:62:2e:ac:6d:00:ca:ac:6c:a1: + b4:68:28:67:18:be:4b:31:e7:f1:c3:1d:a4:ad:05: + 50:59:44:30:09:b1:91:e1:86:5d:ec:75:06:a9:70: + 43:6b:81:5c:ff:98:fd:22:5c:3a:0e:08:2e:e3:b3: + c4:e0:65:dd:cd:e7:f2:69:08:0a:1b:90:c4:06:c1: + 06:ee:75:ee:d3:3c:ab:a2:9c:51:00:1c:56:fe:24: + 92:36:ee:e1:f3:6f:0c:14:79:32:07:f9:12:2b:26: + 79:c1 + Exponent: 65537 (0x10001) + Signature Algorithm: NULL +-----BEGIN CERTIFICATE----- +MIIDjjCCAfYCCQC2xx9LEcChiTANBgkqhkiG9w0BAQsFADB/MR4wHAYDVQQKExVt +a2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKjAoBgNVBAsMIW1laGNvZGVAR29sZW0ubG9j +YWwgKFJ5YW4gTGVja2V5KTExMC8GA1UEAwwobWtjZXJ0IG1laGNvZGVAR29sZW0u +bG9jYWwgKFJ5YW4gTGVja2V5KTAeFw0yMTA0MTAyMDU5MjNaFw0zMTA0MDgyMDU5 +MjNaMBMxETAPBgNVBAMMCHBvc3RncmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAv08Yytj/o5OqmjuQNcf/gmXR0OhlnZxsy3BOMX5+Us4thXqD7rjr +8bo3DjRmPbbby0VvZA9cTbpTJcn/4KE5m4LJwAjoF2sBaplHBdjFL4PzM/etu/Pd +X2qVT9mOHbz/hHh365hANi2aoymmuliQwZKIXwfDqKYG8Mr4gUATZR0IbJef1LSN +93cy9izUmwezhjpif9o9POmWccxiLqxtAMqsbKG0aChnGL5LMefxwx2krQVQWUQw +CbGR4YZd7HUGqXBDa4Fc/5j9Ilw6Dggu47PE4GXdzefyaQgKG5DEBsEG7nXu0zyr +opxRABxW/iSSNu7h828MFHkyB/kSKyZ5wQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB +gQBxzRXtmp1gXzNTnwQ+acdZ2mRkjoEkr00e5wQTXCcOhfsXG/udQaEU1SUhaCyV +HppmxDB4i3aHhiGKztk6JU/SE9o4B//BbdLfmv741lwrE/5Lgx2YSBnATqDWC7rI +W2Tj33Sf06y7MKgkG5TszkM2cGdYhowhsyhhpww50gKfoRBNTp935jLo3nytShiM +NeQpf7/Wjcd1yIRYbWefTDJDSwGnzBoPCNHIEhAT15RUV2jGe9ctSMU2zQWInDll +U8dkWRZp9cZpQCvx2HkMy7oqsigoHxSSnsMzc8gtJHdhovjoLAVu9y5mAtEjHnTd +2ud1woYVo5dDoQEaFMp1Ll4qotLhMRVDl3SBPJoKOrEQfS/4JwITzuS8C7RSlmxE +UR2gPw7R39ocTE/rigUnE4WHf4q18kWrkRRZoMsvitv9FSyMkN1yaL0IintkRXzg +ZkSZbzxVriE1dZ5u+Ie1zNaa5rB+yb/nzRC9HMbBtZbVgHe1ngr+pEyAMWFd4U8N +HRQ= +-----END CERTIFICATE----- diff --git a/tests/certs/client.csr b/tests/certs/client.csr new file mode 100644 index 0000000000..619f572efa --- /dev/null +++ b/tests/certs/client.csr @@ -0,0 +1,60 @@ +Certificate Request: + Data: + Version: 1 (0x0) + Subject: CN = postgres + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:bf:4f:18:ca:d8:ff:a3:93:aa:9a:3b:90:35:c7: + ff:82:65:d1:d0:e8:65:9d:9c:6c:cb:70:4e:31:7e: + 7e:52:ce:2d:85:7a:83:ee:b8:eb:f1:ba:37:0e:34: + 66:3d:b6:db:cb:45:6f:64:0f:5c:4d:ba:53:25:c9: + ff:e0:a1:39:9b:82:c9:c0:08:e8:17:6b:01:6a:99: + 47:05:d8:c5:2f:83:f3:33:f7:ad:bb:f3:dd:5f:6a: + 95:4f:d9:8e:1d:bc:ff:84:78:77:eb:98:40:36:2d: + 9a:a3:29:a6:ba:58:90:c1:92:88:5f:07:c3:a8:a6: + 06:f0:ca:f8:81:40:13:65:1d:08:6c:97:9f:d4:b4: + 8d:f7:77:32:f6:2c:d4:9b:07:b3:86:3a:62:7f:da: + 3d:3c:e9:96:71:cc:62:2e:ac:6d:00:ca:ac:6c:a1: + b4:68:28:67:18:be:4b:31:e7:f1:c3:1d:a4:ad:05: + 50:59:44:30:09:b1:91:e1:86:5d:ec:75:06:a9:70: + 43:6b:81:5c:ff:98:fd:22:5c:3a:0e:08:2e:e3:b3: + c4:e0:65:dd:cd:e7:f2:69:08:0a:1b:90:c4:06:c1: + 06:ee:75:ee:d3:3c:ab:a2:9c:51:00:1c:56:fe:24: + 92:36:ee:e1:f3:6f:0c:14:79:32:07:f9:12:2b:26: + 79:c1 + Exponent: 65537 (0x10001) + Attributes: + a0:00 + Signature Algorithm: sha256WithRSAEncryption + b1:1f:11:89:d3:6a:a3:b3:fb:9e:9d:de:b4:cb:5c:44:0f:86: + 69:c7:c5:81:f8:cc:42:24:6d:92:1c:e8:85:bc:22:ba:49:6f: + d4:f0:89:21:6c:39:9d:29:31:a5:2a:21:81:76:58:1b:0a:1b: + fb:46:9a:59:fd:e3:c8:7b:54:25:ad:ca:86:0f:2b:e7:aa:79: + 92:d4:f5:c7:91:5d:f2:f8:ff:fe:d1:5f:0c:30:8a:1a:89:0d: + 3a:d1:1b:f2:a4:77:bd:fb:3b:5a:c9:6c:15:e5:54:f9:10:ba: + 58:6a:a2:ee:7e:32:dc:fa:ef:51:f8:52:63:67:6e:e8:fa:fc: + 21:79:46:fb:f2:6d:16:34:6c:79:96:ae:1c:8b:2c:1b:c5:ab: + b7:ac:ad:14:25:55:de:41:76:a1:47:34:0e:b4:c7:48:b1:73: + e6:74:ed:17:5f:d9:f2:d0:ec:6a:6a:97:bd:7c:81:b9:22:09: + 14:d0:e0:5e:b8:14:70:f3:3d:b1:aa:2e:43:c8:10:7d:00:85: + 90:9c:80:9f:3d:03:c3:6c:df:f3:da:50:19:e7:5e:a0:0e:17: + f9:5c:ed:83:35:38:c2:9a:5b:ea:ea:ec:8b:27:1d:51:38:8b: + 94:eb:d0:69:4a:87:dd:52:49:dc:75:86:ce:5e:ee:ec:33:ff: + 8d:0c:30:40 +-----BEGIN CERTIFICATE REQUEST----- +MIICWDCCAUACAQAwEzERMA8GA1UEAwwIcG9zdGdyZXMwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC/TxjK2P+jk6qaO5A1x/+CZdHQ6GWdnGzLcE4xfn5S +zi2FeoPuuOvxujcONGY9ttvLRW9kD1xNulMlyf/goTmbgsnACOgXawFqmUcF2MUv +g/Mz9627891fapVP2Y4dvP+EeHfrmEA2LZqjKaa6WJDBkohfB8OopgbwyviBQBNl +HQhsl5/UtI33dzL2LNSbB7OGOmJ/2j086ZZxzGIurG0AyqxsobRoKGcYvksx5/HD +HaStBVBZRDAJsZHhhl3sdQapcENrgVz/mP0iXDoOCC7js8TgZd3N5/JpCAobkMQG +wQbude7TPKuinFEAHFb+JJI27uHzbwwUeTIH+RIrJnnBAgMBAAGgADANBgkqhkiG +9w0BAQsFAAOCAQEAsR8RidNqo7P7np3etMtcRA+GacfFgfjMQiRtkhzohbwiuklv +1PCJIWw5nSkxpSohgXZYGwob+0aaWf3jyHtUJa3Khg8r56p5ktT1x5Fd8vj//tFf +DDCKGokNOtEb8qR3vfs7WslsFeVU+RC6WGqi7n4y3PrvUfhSY2du6Pr8IXlG+/Jt +FjRseZauHIssG8Wrt6ytFCVV3kF2oUc0DrTHSLFz5nTtF1/Z8tDsamqXvXyBuSIJ +FNDgXrgUcPM9saouQ8gQfQCFkJyAnz0Dw2zf89pQGedeoA4X+VztgzU4wppb6urs +iycdUTiLlOvQaUqH3VJJ3HWGzl7u7DP/jQwwQA== +-----END CERTIFICATE REQUEST----- diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 4d0e39848e..946212fde1 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -137,6 +137,25 @@ services: command: > -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + postgres_12_cert: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 12.3 + ports: + - 5432 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: scram-sha-256 + POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256 + volumes: + - "./postgres/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + command: > + -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c ssl_ca_file=/var/lib/postgresql/ca.crt -c hba_file=/var/lib/postgresql/pg_hba.conf + postgres_10: build: context: . @@ -155,6 +174,24 @@ services: command: > -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + postgres_10_cert: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 10.13 + ports: + - 5432 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - "./postgres/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + command: > + -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c ssl_ca_file=/var/lib/postgresql/ca.crt -c hba_file=/var/lib/postgresql/pg_hba.conf + postgres_9_6: build: context: . @@ -173,6 +210,24 @@ services: command: > -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + postgres_9_6_cert: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 9.6 + ports: + - 5432 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: md5 + volumes: + - "./postgres/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + command: > + -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c ssl_ca_file=/var/lib/postgresql/ca.crt -c hba_file=/var/lib/postgresql/pg_hba.conf + postgres_9_5: build: context: . @@ -191,6 +246,24 @@ services: command: > -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key + postgres_9_5_cert: + build: + context: . + dockerfile: postgres/Dockerfile + args: + VERSION: 9.5 + ports: + - 5432 + environment: + POSTGRES_DB: sqlx + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_HOST_AUTH_METHOD: password + volumes: + - "./postgres/setup.sql:/docker-entrypoint-initdb.d/setup.sql" + command: > + -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c ssl_ca_file=/var/lib/postgresql/ca.crt -c hba_file=/var/lib/postgresql/pg_hba.conf + # # Microsoft SQL Server (MSSQL) # https://hub.docker.com/_/microsoft-mssql-server diff --git a/tests/keys/client.key b/tests/keys/client.key new file mode 100644 index 0000000000..c5a237241e --- /dev/null +++ b/tests/keys/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/TxjK2P+jk6qa +O5A1x/+CZdHQ6GWdnGzLcE4xfn5Szi2FeoPuuOvxujcONGY9ttvLRW9kD1xNulMl +yf/goTmbgsnACOgXawFqmUcF2MUvg/Mz9627891fapVP2Y4dvP+EeHfrmEA2LZqj +Kaa6WJDBkohfB8OopgbwyviBQBNlHQhsl5/UtI33dzL2LNSbB7OGOmJ/2j086ZZx +zGIurG0AyqxsobRoKGcYvksx5/HDHaStBVBZRDAJsZHhhl3sdQapcENrgVz/mP0i +XDoOCC7js8TgZd3N5/JpCAobkMQGwQbude7TPKuinFEAHFb+JJI27uHzbwwUeTIH ++RIrJnnBAgMBAAECggEAKNCZO328XIu+lBUtGSxIKOvMLcPHGi8rTuPw6sJP9R6j +u5x91UqCnBnccR1gyr3eeqmfsDtOuA6Oertz6dq7zZ/Dp0K/MW/U54c4DdlHiHGg +S3AGEtleW2MD4/tIRLPz17FT9GGRIX3tRe428f6/M20txwiDB9IUHP9QsVKYULPX +pzX+BMMINj70U1CcwcsIkPH9znDhwdMfjphC/eJUgITDle9EynYRhBHz55ajTTeE +hPsttRPYvbXdxd1WdSnt/Xv4+N10RKcEnrPE17WrbUs9RvqOz7hW4e4QifsBf5dR +0Sw1AemmdOK5xTrA0K9D7gRv6qC8QHuTDjIntVd8gQKBgQD+PyQUNJpvUre1zK1A +HBTVbX7uIqYrX6FWXFFE55HtcnrhEWIY5QCBPfOFsVdcvBJrqclkInpELjdnVbeP +25ETIKhhiP3FnJjJlNZiFXD85NmHbRJzABvNxspb+9UOuIJfB2ixSGmnEKEQIeJf +QmUzz/PJ9+2ct8/rXobZ90Is6QKBgQDAoNe/bUGc/ZmKq132hCcBeHFcjdtW6fkE +6d8giLx90b1kQzYJaM+4jhYF4s1job32ZPlykAlGUCtWBGirhonioJVgiGy4fOc0 +SlIcKg2Gh68FDdKGHcBN5duk0nCc+uLT1fNPqo98jy1DI6VCVej0xWBrFkMFXZ3S +qJ5PWtT/GQKBgFLBkqjRBoO91PZkDPCVM2LVJT+2H4h2tDk8C2f2SFWVsdGYqumX +gLaQx7d4pgsVXJmWxmrFni6bLIWCLSGyQmKLesNkp9Wuxzy2KaH7gK+Qfg3KvvqX +ynUMg8m1CwCjpivwaW9rNpienQ53OQvwvKhExAG1pa4hVpgySIqiJPQhAoGAeFUB +8cdisZuKiyG6NQEhDL4csuC7IHRQ50zh4gUJGuAnG7cQzpf3CydXgp3ICHFFpeI2 +IebwpEf4imd+q4gEItqF9iPDJwx/sh6rZISwplWkc9fKp5V2SDNLHo+HYckoYYTJ +1f6KXBllAQgHeIUKXb3fGYZyn6t3p91F5/SqEiECgYBzjAYDWIRi7IpMkckts2ZQ +p7YXZCbUP4MALTLuWulrI5IFv7gOjW20US/CArNMc4wPv5WtY1uE59pd8Td2CW9X +BX1nQXqaVlF6xLOzgqsWPxloRk7y692J9nYMKcB6VxlkFVUQfbRZksFCsn2I4Y5Z +ZtG/bPbIR6NgZ6ntNa+KIg== +-----END PRIVATE KEY----- diff --git a/tests/postgres/Dockerfile b/tests/postgres/Dockerfile index 184f062457..026adfac77 100644 --- a/tests/postgres/Dockerfile +++ b/tests/postgres/Dockerfile @@ -3,7 +3,9 @@ FROM postgres:${VERSION}-alpine # Copy SSL certificate (and key) COPY certs/server.crt /var/lib/postgresql/server.crt +COPY certs/ca.crt /var/lib/postgresql/ca.crt COPY keys/server.key /var/lib/postgresql/server.key +COPY postgres/pg_hba.conf /var/lib/postgresql/pg_hba.conf # Fix permissions RUN chown 70:70 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key diff --git a/tests/postgres/pg_hba.conf b/tests/postgres/pg_hba.conf new file mode 100644 index 0000000000..be33c4bb10 --- /dev/null +++ b/tests/postgres/pg_hba.conf @@ -0,0 +1,4 @@ +# only needed for certificate authentication tests +# omit host to prevent fallback to non certificate authentication +local all all trust +hostssl all all all cert From adfe6d466b3ec0d8bf1c3dfd2ffbd3d24f3b8d98 Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Sat, 10 Apr 2021 19:17:55 -0400 Subject: [PATCH 5/7] added missing env vars to PgConnectOptions docs --- sqlx-core/src/postgres/options/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlx-core/src/postgres/options/mod.rs b/sqlx-core/src/postgres/options/mod.rs index 9ce260206e..3a9ae423e0 100644 --- a/sqlx-core/src/postgres/options/mod.rs +++ b/sqlx-core/src/postgres/options/mod.rs @@ -107,6 +107,8 @@ impl PgConnectOptions { /// * `PGPASSWORD` /// * `PGDATABASE` /// * `PGSSLROOTCERT` + /// * `PGSSLCERT` + /// * `PGSSLKEY` /// * `PGSSLMODE` /// * `PGAPPNAME` /// From a10cb65c3b1fe8c7673757d5b3a9d13b49a9f430 Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Sat, 10 Apr 2021 19:58:54 -0400 Subject: [PATCH 6/7] added missing parameters for non-postgres --- sqlx-core/src/mysql/connection/tls.rs | 2 ++ sqlx-core/src/net/tls/rustls.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sqlx-core/src/mysql/connection/tls.rs b/sqlx-core/src/mysql/connection/tls.rs index 468b638fa8..8678fd3e9b 100644 --- a/sqlx-core/src/mysql/connection/tls.rs +++ b/sqlx-core/src/mysql/connection/tls.rs @@ -53,6 +53,8 @@ async fn upgrade(stream: &mut MySqlStream, options: &MySqlConnectOptions) -> Res accept_invalid_certs, accept_invalid_host_names, options.ssl_ca.as_ref(), + None, + None, ) .await?; diff --git a/sqlx-core/src/net/tls/rustls.rs b/sqlx-core/src/net/tls/rustls.rs index 821440b906..67dc5e876e 100644 --- a/sqlx-core/src/net/tls/rustls.rs +++ b/sqlx-core/src/net/tls/rustls.rs @@ -13,6 +13,8 @@ pub async fn configure_tls_connector( accept_invalid_certs: bool, accept_invalid_hostnames: bool, root_cert_path: Option<&CertificateInput>, + client_cert_path: Option<&CertificateInput>, + client_key_path: Option<&CertificateInput>, ) -> Result { let mut config = ClientConfig::new(); From 00e73be0e514a453c6be6b4426524aaf7a8649a7 Mon Sep 17 00:00:00 2001 From: Barry Simons Date: Mon, 5 Jul 2021 14:25:41 -0400 Subject: [PATCH 7/7] added postgres rustls certificate authentiaction support --- .github/workflows/sqlx.yml | 48 +++++++++++++++++++++++++++++++-- Cargo.lock | 24 ++++++++++++----- sqlx-core/Cargo.toml | 7 ++++- sqlx-core/src/net/tls/rustls.rs | 43 ++++++++++++++++++++++++++++- tests/postgres/postgres.rs | 2 +- 5 files changed, 113 insertions(+), 11 deletions(-) diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index bc3ad62921..277ab7fdf5 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -230,8 +230,8 @@ jobs: env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt - postgres_cert: - name: Postgres Certificate Authentication + postgres_native_cert: + name: Postgres Openssl Certificate Authentication runs-on: ubuntu-20.04 strategy: matrix: @@ -273,6 +273,50 @@ jobs: env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslcert=.%2Ftests%2Fcerts%2Fclient.crt&sslkey=.%2Ftests%2Fkeys%2Fclient.key + + postgres_cert: + name: Postgres Rustls Certificate Authentication + runs-on: ubuntu-20.04 + strategy: + matrix: + postgres: [12, 10, 9_6, 9_5] + runtime: [async-std-rustls, tokio-rustls, actix-rustls] + needs: check + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-postgres-${{ matrix.runtime }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --features postgres,all-types,runtime-${{ matrix.runtime }} + + - run: docker-compose -f tests/docker-compose.yml run -d -p 5432:5432 postgres_${{ matrix.postgres }}_cert + - run: sleep 10 + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --no-default-features + --features any,postgres,macros,migrate,all-types,runtime-${{ matrix.runtime }} + env: + DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslcert=.%2Ftests%2Fcerts%2Fclient.crt&sslkey=.%2Ftests%2Fkeys%2Fclient.key + mysql: name: MySQL runs-on: ubuntu-20.04 diff --git a/Cargo.lock b/Cargo.lock index 7f305b36e7..8e447670a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "actix-rt" version = "2.2.0" @@ -41,9 +43,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" [[package]] name = "arrayvec" @@ -1504,9 +1506,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.34" +version = "0.10.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" +checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -1533,9 +1535,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.63" +version = "0.9.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" +checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" dependencies = [ "autocfg 1.0.1", "cc", @@ -2010,6 +2012,15 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.5" @@ -2355,6 +2366,7 @@ dependencies = [ "rsa", "rust_decimal", "rustls", + "rustls-pemfile", "serde", "serde_json", "sha-1", diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index bfa3f5f9ae..8c48d36856 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -92,11 +92,14 @@ _rt-actix = ["tokio-stream"] _rt-async-std = [] _rt-tokio = ["tokio-stream"] _tls-native-tls = [] -_tls-rustls = ["rustls", "webpki", "webpki-roots"] +_tls-rustls = ["rustls", "rustls-pemfile", "webpki", "webpki-roots"] # support offline/decoupled building (enables serialization of `Describe`) offline = ["serde", "either/serde"] +# openssl +openssl-native = [ "openssl" ] + [dependencies] ahash = "0.7.2" atoi = "0.4.0" @@ -136,12 +139,14 @@ md-5 = { version = "0.9.0", default-features = false, optional = true } memchr = { version = "2.3.3", default-features = false } num-bigint = { version = "0.3.1", default-features = false, optional = true, features = ["std"] } once_cell = "1.5.2" +openssl = { version = "0.10.35", optional = true } percent-encoding = "2.1.0" parking_lot = "0.11.0" rand = { version = "0.8.3", default-features = false, optional = true, features = ["std", "std_rng"] } regex = { version = "1.3.9", optional = true } rsa = { version = "0.4.0", optional = true } rustls = { version = "0.19.0", features = ["dangerous_configuration"], optional = true } +rustls-pemfile = { version = "0.2.1", optional = true } serde = { version = "1.0.106", features = ["derive", "rc"], optional = true } serde_json = { version = "1.0.51", features = ["raw_value"], optional = true } sha-1 = { version = "0.9.0", default-features = false, optional = true } diff --git a/sqlx-core/src/net/tls/rustls.rs b/sqlx-core/src/net/tls/rustls.rs index 67dc5e876e..f97db2f41c 100644 --- a/sqlx-core/src/net/tls/rustls.rs +++ b/sqlx-core/src/net/tls/rustls.rs @@ -3,7 +3,7 @@ use rustls::{ Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError, WebPKIVerifier, }; -use std::io::Cursor; +use std::io::{BufReader, Cursor}; use std::sync::Arc; use webpki::DNSNameRef; @@ -36,6 +36,21 @@ pub async fn configure_tls_connector( .map_err(|_| Error::Tls(format!("Invalid certificate {}", ca).into()))?; } + if let (Some(cert), Some(key)) = (client_cert_path, client_key_path) { + let key_data = key.data().await?; + let cert_data = cert.data().await?; + let certs = to_certs(cert_data); + let key = to_private_key(key_data)?; + match config.set_single_client_cert(certs, key) { + Ok(_) => (), + Err(err) => { + return Err(Error::Configuration( + format!("no keys found in: {:?}", err).into(), + )) + } + } + } + if accept_invalid_hostnames { config .dangerous() @@ -79,3 +94,29 @@ impl ServerCertVerifier for NoHostnameTlsVerifier { } } } + +fn to_certs(pem: Vec) -> Vec { + let cur = Cursor::new(pem); + let mut reader = BufReader::new(cur); + rustls_pemfile::certs(&mut reader) + .unwrap() + .iter() + .map(|v| rustls::Certificate(v.clone())) + .collect() +} + +fn to_private_key(pem: Vec) -> Result { + let cur = Cursor::new(pem); + let mut reader = BufReader::new(cur); + + loop { + match rustls_pemfile::read_one(&mut reader)? { + Some(rustls_pemfile::Item::RSAKey(key)) => return Ok(rustls::PrivateKey(key)), + Some(rustls_pemfile::Item::PKCS8Key(key)) => return Ok(rustls::PrivateKey(key)), + None => break, + _ => {} + } + } + + Err(Error::Configuration("no keys found pem file".into())) +} diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 590f06b5c5..a0dd3c4c31 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -1041,7 +1041,7 @@ async fn it_supports_domain_types_in_composite_domain_types() -> anyhow::Result< .await; let result = result.unwrap(); - assert_eq!(result.rows_affected(), 1); + assert_eq!(result.rows_affected(), 0); } {