Skip to content

Commit 58b9a75

Browse files
authored
client ssl certs (#7)
* WIP: add support for client ssl certs See sqlpage/SQLPage#299 * add support for mysql ssl client certificates * fmt * fix pg doctest * fix mysql doctest * document the new ssl client cert options * improve error messages on invalid ssl key and add tests * add tests * switch to checkout v4 * use rust-cache@v2 everywhere
1 parent a9c4f4a commit 58b9a75

File tree

23 files changed

+484
-74
lines changed

23 files changed

+484
-74
lines changed

.github/workflows/publish.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ jobs:
1212
name: Publish
1313
runs-on: ubuntu-22.04
1414
steps:
15-
- uses: actions/checkout@v3
16-
- uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f
15+
- uses: actions/checkout@v4
16+
- uses: Swatinem/rust-cache@v2
1717
- run: |
1818
cargo publish ${ARGS} --package sqlx-rt-oldapi
1919
cargo publish ${ARGS} --package sqlx-core-oldapi

.github/workflows/sqlx.yml

+26-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
name: Format
1212
runs-on: ubuntu-22.04
1313
steps:
14-
- uses: actions/checkout@v3
14+
- uses: actions/checkout@v4
1515
- run: cargo fmt --all -- --check
1616

1717
check:
@@ -22,8 +22,8 @@ jobs:
2222
runtime: [async-std, tokio, actix]
2323
tls: [native-tls, rustls]
2424
steps:
25-
- uses: actions/checkout@v3
26-
- uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f
25+
- uses: actions/checkout@v4
26+
- uses: Swatinem/rust-cache@v2
2727
- run:
2828
cargo check
2929
--manifest-path sqlx-core/Cargo.toml
@@ -51,7 +51,7 @@ jobs:
5151
rustls
5252
]
5353
steps:
54-
- uses: actions/checkout@v3
54+
- uses: actions/checkout@v4
5555
- uses: Swatinem/rust-cache@v2
5656
- run:
5757
cargo test
@@ -79,7 +79,7 @@ jobs:
7979
# bin: target/debug/cargo-sqlx
8080

8181
steps:
82-
- uses: actions/checkout@v3
82+
- uses: actions/checkout@v4
8383
- uses: Swatinem/rust-cache@v2
8484
- run:
8585
cargo build
@@ -101,7 +101,7 @@ jobs:
101101
tls: [native-tls, rustls]
102102
needs: check
103103
steps:
104-
- uses: actions/checkout@v3
104+
- uses: actions/checkout@v4
105105
- run: mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so
106106
- uses: Swatinem/rust-cache@v2
107107
- run:
@@ -125,7 +125,7 @@ jobs:
125125
tls: [native-tls, rustls]
126126
needs: check
127127
steps:
128-
- uses: actions/checkout@v3
128+
- uses: actions/checkout@v4
129129

130130
- uses: actions-rs/toolchain@v1
131131
with:
@@ -170,11 +170,26 @@ jobs:
170170
--no-default-features
171171
--features any,postgres,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
172172
env:
173-
DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt
173+
DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=./tests/certs/ca.crt
174174
# FIXME: needed to disable `ltree` tests in Postgres 9.6
175175
# but `PgLTree` should just fall back to text format
176176
RUSTFLAGS: --cfg postgres_${{ matrix.postgres }}
177177

178+
postgres_ssl_client_cert:
179+
name: Postgres with SSL client cert
180+
runs-on: ubuntu-22.04
181+
needs: check
182+
steps:
183+
- uses: actions/checkout@v4
184+
- uses: Swatinem/rust-cache@v2
185+
with:
186+
key: linux-postgres-ssl-client-cert
187+
- run: docker compose up --wait postgres_16
188+
working-directory: tests
189+
- run: cargo test --no-default-features --features any,postgres,macros,all-types,runtime-actix-rustls
190+
env:
191+
DATABASE_URL: postgres://postgres@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=./tests/certs/ca.crt&sslcert=./tests/certs/client.crt&sslkey=./tests/keys/client.key
192+
178193
mysql:
179194
name: MySQL
180195
runs-on: ubuntu-22.04
@@ -185,7 +200,7 @@ jobs:
185200
tls: [native-tls, rustls]
186201
needs: check
187202
steps:
188-
- uses: actions/checkout@v3
203+
- uses: actions/checkout@v4
189204

190205
- uses: actions-rs/toolchain@v1
191206
with:
@@ -236,7 +251,7 @@ jobs:
236251
tls: [native-tls, rustls]
237252
needs: check
238253
steps:
239-
- uses: actions/checkout@v3
254+
- uses: actions/checkout@v4
240255

241256
- uses: actions-rs/toolchain@v1
242257
with:
@@ -276,7 +291,7 @@ jobs:
276291
tls: [native-tls, rustls]
277292
needs: check
278293
steps:
279-
- uses: actions/checkout@v3
294+
- uses: actions/checkout@v4
280295

281296
- uses: actions-rs/toolchain@v1
282297
with:

sqlx-core/src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ impl Error {
133133
pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
134134
Error::Configuration(err.into())
135135
}
136+
137+
#[allow(dead_code)]
138+
#[inline]
139+
pub(crate) fn tls<T: Into<BoxDynError>>(err: T) -> Self {
140+
Error::Tls(err.into())
141+
}
136142
}
137143

138144
pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {

sqlx-core/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! Not intended to be used directly.
33
#![recursion_limit = "512"]
44
#![warn(future_incompatible, rust_2018_idioms)]
5-
#![allow(clippy::needless_doctest_main, clippy::type_complexity)]
5+
#![allow(clippy::needless_doctest_main, clippy::type_complexity, dead_code)]
66
// See `clippy.toml` at the workspace root
77
#![deny(clippy::disallowed_methods)]
88
//

sqlx-core/src/mysql/connection/tls.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::mysql::connection::MySqlStream;
33
use crate::mysql::protocol::connect::SslRequest;
44
use crate::mysql::protocol::Capabilities;
55
use crate::mysql::{MySqlConnectOptions, MySqlSslMode};
6+
use crate::net::TlsConfig;
67

78
pub(super) async fn maybe_upgrade(
89
stream: &mut MySqlStream,
@@ -45,16 +46,17 @@ async fn upgrade(stream: &mut MySqlStream, options: &MySqlConnectOptions) -> Res
4546
options.ssl_mode,
4647
MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity
4748
);
48-
let accept_invalid_host_names = !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity);
49-
50-
stream
51-
.upgrade(
52-
&options.host,
53-
accept_invalid_certs,
54-
accept_invalid_host_names,
55-
options.ssl_ca.as_ref(),
56-
)
57-
.await?;
49+
let accept_invalid_hostnames = !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity);
50+
51+
let tls_config = TlsConfig {
52+
accept_invalid_certs,
53+
hostname: &options.host,
54+
accept_invalid_hostnames,
55+
root_cert_path: options.ssl_ca.as_ref(),
56+
client_cert_path: options.ssl_client_cert.as_ref(),
57+
client_key_path: options.ssl_client_key.as_ref(),
58+
};
59+
stream.upgrade(tls_config).await?;
5860

5961
Ok(true)
6062
}

sqlx-core/src/mysql/options/mod.rs

+36
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub use ssl_mode::MySqlSslMode;
2424
/// |---------|-------|-----------|
2525
/// | `ssl-mode` | `PREFERRED` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [`MySqlSslMode`]. |
2626
/// | `ssl-ca` | `None` | Sets the name of a file containing a list of trusted SSL Certificate Authorities. |
27+
/// | `ssl-cert` | `None` | Sets the name of a file containing a client SSL certificate to authenticate the connection to the server |
28+
/// | `ssl-key` | `None` | Sets the name of a file containing a secret SSL key for the client certificate. |
2729
/// | `statement-cache-capacity` | `100` | The maximum number of prepared statements stored in the cache. Set to `0` to disable. |
2830
/// | `socket` | `None` | Path to the unix domain socket, which will be used instead of TCP if set. |
2931
///
@@ -61,6 +63,8 @@ pub struct MySqlConnectOptions {
6163
pub(crate) database: Option<String>,
6264
pub(crate) ssl_mode: MySqlSslMode,
6365
pub(crate) ssl_ca: Option<CertificateInput>,
66+
pub(crate) ssl_client_cert: Option<CertificateInput>,
67+
pub(crate) ssl_client_key: Option<CertificateInput>,
6468
pub(crate) statement_cache_capacity: usize,
6569
pub(crate) charset: String,
6670
pub(crate) collation: Option<String>,
@@ -88,6 +92,8 @@ impl MySqlConnectOptions {
8892
collation: None,
8993
ssl_mode: MySqlSslMode::Preferred,
9094
ssl_ca: None,
95+
ssl_client_cert: None,
96+
ssl_client_key: None,
9197
statement_cache_capacity: 100,
9298
log_settings: Default::default(),
9399
pipes_as_concat: true,
@@ -186,6 +192,36 @@ impl MySqlConnectOptions {
186192
self
187193
}
188194

195+
/// Sets the name of a file containing SSL client certificate.
196+
///
197+
/// # Example
198+
///
199+
/// ```rust
200+
/// # use sqlx_core_oldapi::mysql::{MySqlSslMode, MySqlConnectOptions};
201+
/// let options = MySqlConnectOptions::new()
202+
/// .ssl_mode(MySqlSslMode::VerifyCa)
203+
/// .ssl_client_cert("path/to/client.crt");
204+
/// ```
205+
pub fn ssl_client_cert(mut self, cert: impl AsRef<Path>) -> Self {
206+
self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
207+
self
208+
}
209+
210+
/// Sets the name of a file containing SSL client key.
211+
///
212+
/// # Example
213+
///
214+
/// ```rust
215+
/// # use sqlx_core_oldapi::mysql::{MySqlSslMode, MySqlConnectOptions};
216+
/// let options = MySqlConnectOptions::new()
217+
/// .ssl_mode(MySqlSslMode::VerifyCa)
218+
/// .ssl_client_key("path/to/client.key");
219+
/// ```
220+
pub fn ssl_client_key(mut self, key: impl AsRef<Path>) -> Self {
221+
self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf()));
222+
self
223+
}
224+
189225
/// Sets the capacity of the connection's statement cache in a number of stored
190226
/// distinct statements. Caching is handled using LRU, meaning when the
191227
/// amount of queries hits the defined limit, the oldest statement will get

sqlx-core/src/mysql/options/parse.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ impl FromStr for MySqlConnectOptions {
4343

4444
for (key, value) in url.query_pairs().into_iter() {
4545
match &*key {
46-
"ssl-mode" => {
46+
"sslmode" | "ssl-mode" => {
4747
options = options.ssl_mode(value.parse().map_err(Error::config)?);
4848
}
4949

50-
"ssl-ca" => {
50+
"sslca" | "ssl-ca" => {
5151
options = options.ssl_ca(&*value);
5252
}
5353

@@ -68,6 +68,10 @@ impl FromStr for MySqlConnectOptions {
6868
options = options.socket(&*value);
6969
}
7070

71+
"sslcert" | "ssl-cert" => options = options.ssl_client_cert(&*value),
72+
73+
"sslkey" | "ssl-key" => options = options.ssl_client_key(&*value),
74+
7175
_ => {}
7276
}
7377
}

sqlx-core/src/net/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pub use socket::Socket;
66
pub use tls::CertificateInput;
77
#[allow(unused_imports)]
88
pub use tls::MaybeTlsStream;
9+
#[allow(unused_imports)]
10+
pub use tls::TlsConfig;
911

1012
#[cfg(feature = "_rt-async-std")]
1113
type PollReadBuf<'a> = [u8];

0 commit comments

Comments
 (0)