Skip to content

Commit 4fa3bf2

Browse files
authored
Merge pull request #1 from cmaher/feat/mysql-rsa-optional
feat(mysql): make rsa dependency optional (RUSTSEC-2023-0071)
2 parents 05c8dc1 + d9e79c3 commit 4fa3bf2

File tree

4 files changed

+44
-26
lines changed

4 files changed

+44
-26
lines changed

.github/workflows/examples.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
services:
5151
mysql:
5252
image: mysql:latest
53+
command: --default-authentication-plugin=mysql_native_password
5354
env:
5455
MYSQL_ROOT_PASSWORD: password
5556
ports:

sqlx-mysql/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ json = ["sqlx-core/json", "serde"]
1414
any = ["sqlx-core/any"]
1515
offline = ["sqlx-core/offline", "serde/derive", "bitflags/serde"]
1616
migrate = ["sqlx-core/migrate"]
17+
rsa = ["dep:rsa"]
1718

1819
# Type Integration features
1920
bigdecimal = ["dep:bigdecimal", "sqlx-core/bigdecimal"]
@@ -38,7 +39,7 @@ hkdf = "0.12.0"
3839
hmac = { version = "0.12.0", default-features = false }
3940
md-5 = { version = "0.10.0", default-features = false }
4041
rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] }
41-
rsa = "0.9"
42+
rsa = { version = "0.9", optional = true }
4243
sha1 = { version = "0.10.1", default-features = false }
4344
sha2 = { version = "0.10.0", default-features = false }
4445

sqlx-mysql/src/connection/auth.rs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use bytes::buf::Chain;
22
use bytes::Bytes;
33
use digest::{Digest, OutputSizeUser};
44
use generic_array::GenericArray;
5+
#[cfg(feature = "rsa")]
56
use rand::thread_rng;
7+
#[cfg(feature = "rsa")]
68
use rsa::{pkcs8::DecodePublicKey, Oaep, RsaPublicKey};
79
use sha1::Sha1;
810
use sha2::Sha256;
@@ -131,9 +133,9 @@ fn scramble_sha256(
131133

132134
async fn encrypt_rsa<'s>(
133135
stream: &'s mut MySqlStream,
134-
public_key_request_id: u8,
136+
_public_key_request_id: u8,
135137
password: &'s str,
136-
nonce: &'s Chain<Bytes, Bytes>,
138+
_nonce: &'s Chain<Bytes, Bytes>,
137139
) -> Result<Vec<u8>, Error> {
138140
// https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/
139141

@@ -142,29 +144,41 @@ async fn encrypt_rsa<'s>(
142144
return Ok(to_asciz(password));
143145
}
144146

145-
// client sends a public key request
146-
stream.write_packet(&[public_key_request_id][..])?;
147-
stream.flush().await?;
148-
149-
// server sends a public key response
150-
let packet = stream.recv_packet().await?;
151-
let rsa_pub_key = &packet[1..];
152-
153-
// xor the password with the given nonce
154-
let mut pass = to_asciz(password);
155-
156-
let (a, b) = (nonce.first_ref(), nonce.last_ref());
157-
let mut nonce = Vec::with_capacity(a.len() + b.len());
158-
nonce.extend_from_slice(a);
159-
nonce.extend_from_slice(b);
160-
161-
xor_eq(&mut pass, &nonce);
162-
163-
// client sends an RSA encrypted password
164-
let pkey = parse_rsa_pub_key(rsa_pub_key)?;
165-
let padding = Oaep::new::<sha1::Sha1>();
166-
pkey.encrypt(&mut thread_rng(), padding, &pass[..])
167-
.map_err(Error::protocol)
147+
// Non-TLS RSA password encryption requires the `rsa` feature.
148+
// It is opt-in because RUSTSEC-2023-0071 (Marvin Attack timing side-channel)
149+
// has no patched release; disable it when all connections use TLS.
150+
#[cfg(not(feature = "rsa"))]
151+
return Err(err_protocol!(
152+
"sha256_password / caching_sha2_password over non-TLS requires the `rsa` feature \
153+
(disabled by default due to RUSTSEC-2023-0071)"
154+
));
155+
156+
#[cfg(feature = "rsa")]
157+
{
158+
// client sends a public key request
159+
stream.write_packet(&[_public_key_request_id][..])?;
160+
stream.flush().await?;
161+
162+
// server sends a public key response
163+
let packet = stream.recv_packet().await?;
164+
let rsa_pub_key = &packet[1..];
165+
166+
// xor the password with the given nonce
167+
let mut pass = to_asciz(password);
168+
169+
let (a, b) = (_nonce.first_ref(), _nonce.last_ref());
170+
let mut nonce = Vec::with_capacity(a.len() + b.len());
171+
nonce.extend_from_slice(a);
172+
nonce.extend_from_slice(b);
173+
174+
xor_eq(&mut pass, &nonce);
175+
176+
// client sends an RSA encrypted password
177+
let pkey = parse_rsa_pub_key(rsa_pub_key)?;
178+
let padding = Oaep::new::<sha1::Sha1>();
179+
pkey.encrypt(&mut thread_rng(), padding, &pass[..])
180+
.map_err(Error::protocol)
181+
}
168182
}
169183

170184
// XOR(x, y)
@@ -186,6 +200,7 @@ fn to_asciz(s: &str) -> Vec<u8> {
186200
}
187201

188202
// https://docs.rs/rsa/0.3.0/rsa/struct.RSAPublicKey.html?search=#example-1
203+
#[cfg(feature = "rsa")]
189204
fn parse_rsa_pub_key(key: &[u8]) -> Result<RsaPublicKey, Error> {
190205
let pem = std::str::from_utf8(key).map_err(Error::protocol)?;
191206

tests/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ services:
1414
MYSQL_ROOT_HOST: '%'
1515
MYSQL_ROOT_PASSWORD: password
1616
MYSQL_DATABASE: sqlx
17+
command: --default-authentication-plugin=mysql_native_password
1718

1819
mysql_8_client_ssl:
1920
build:

0 commit comments

Comments
 (0)