Skip to content

Commit b073baf

Browse files
committed
Implement *Login packet encryption* in mssql
1 parent 18c3d08 commit b073baf

File tree

14 files changed

+127
-62
lines changed

14 files changed

+127
-62
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 0.6.38
9+
10+
- Implement *Login packet encryption* in mssql:
11+
- SQL Server has three levels of encryption support, which are now all supported by this library:
12+
- No encryption, where all data including the password is sent in plaintext. Used only when either client or server declare missing encryption capabilities. You can enable this mode in this library by setting `encrypt=not_supported` in the connection string.
13+
- Encryption is supported on both sides, but disabled on either side. You can enable this mode in this library by setting `encrypt=off` in the connection string. In this mode, the login phase will be encrypted, but data packets will be sent in plaintext.
14+
- Encryption is supported and enabled on both sides. You can enable this mode in this library by setting `encrypt=strict` in the connection string. In this mode, both the login phase and data packets will be encrypted.
15+
- Much improved logging in the mssql driver login phase
16+
817
## 0.6.37
918

1019
- Fix encoding of `DateTime<FixedOffset>` in SQLite. It used to be encoded as an RFC3339 string (with a 'T' between date and time),

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ members = [
2020

2121
[package]
2222
name = "sqlx-oldapi"
23-
version = "0.6.37"
23+
version = "0.6.38"
2424
license = "MIT OR Apache-2.0"
2525
readme = "README.md"
2626
repository = "https://github.com/lovasoa/sqlx"
@@ -125,8 +125,8 @@ bstr = ["sqlx-core/bstr"]
125125
git2 = ["sqlx-core/git2"]
126126

127127
[dependencies]
128-
sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.37", path = "sqlx-core", default-features = false }
129-
sqlx-macros = { package = "sqlx-macros-oldapi", version = "0.6.37", path = "sqlx-macros", default-features = false, optional = true }
128+
sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.38", path = "sqlx-core", default-features = false }
129+
sqlx-macros = { package = "sqlx-macros-oldapi", version = "0.6.38", path = "sqlx-macros", default-features = false, optional = true }
130130

131131
[dev-dependencies]
132132
anyhow = "1.0.52"

examples/postgres/axum-social-with-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ edition = "2021"
88
[dependencies]
99
# Primary crates
1010
axum = { version = "0.5.13", features = ["macros"] }
11-
sqlx = { package = "sqlx-oldapi", version = "0.6.37", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] }
11+
sqlx = { package = "sqlx-oldapi", version = "0.6.38", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] }
1212
tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] }
1313

1414
# Important secondary crates

sqlx-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlx-cli"
3-
version = "0.6.37"
3+
version = "0.6.38"
44
description = "Command-line utility for SQLx, the Rust SQL toolkit."
55
edition = "2021"
66
readme = "README.md"

sqlx-core/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlx-core-oldapi"
3-
version = "0.6.37"
3+
version = "0.6.38"
44
repository = "https://github.com/lovasoa/sqlx"
55
description = "Core of SQLx, the rust SQL toolkit. Not intended to be used directly."
66
license = "MIT OR Apache-2.0"
@@ -101,7 +101,7 @@ offline = ["serde", "either/serde"]
101101
paste = "1.0.6"
102102
ahash = "0.8.3"
103103
atoi = "2.0.0"
104-
sqlx-rt = { path = "../sqlx-rt", version = "0.6.37", package = "sqlx-rt-oldapi" }
104+
sqlx-rt = { path = "../sqlx-rt", version = "0.6.38", package = "sqlx-rt-oldapi" }
105105
base64 = { version = "0.22", default-features = false, optional = true, features = ["std"] }
106106
bigdecimal_ = { version = "0.4.1", optional = true, package = "bigdecimal" }
107107
rust_decimal = { version = "1.19.0", optional = true }

sqlx-core/src/mssql/connection/establish.rs

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,63 +18,77 @@ impl MssqlConnection {
1818
// TODO: Encryption
1919
// TODO: Send the version of SQLx over
2020

21-
log::debug!(
22-
"Sending T-SQL PRELOGIN with encryption: {:?}",
23-
options.encrypt
24-
);
25-
21+
let prelogin_packet = PreLogin {
22+
version: Version::default(),
23+
encryption: options.encrypt,
24+
instance: options.instance.clone(),
25+
..Default::default()
26+
};
27+
28+
log::debug!("Sending T-SQL PRELOGIN with encryption: {prelogin_packet:?}");
2629
stream
27-
.write_packet_and_flush(
28-
PacketType::PreLogin,
29-
PreLogin {
30-
version: Version::default(),
31-
encryption: options.encrypt,
32-
instance: options.instance.clone(),
33-
34-
..Default::default()
35-
},
36-
)
30+
.write_packet_and_flush(PacketType::PreLogin, prelogin_packet)
3731
.await?;
3832

3933
let (_, packet) = stream.recv_packet().await?;
34+
4035
let prelogin_response = PreLogin::decode(packet)?;
36+
log::debug!("Received PRELOGIN response: {:?}", prelogin_response);
37+
38+
let mut disable_encryption_after_login = false;
4139

42-
if matches!(
43-
prelogin_response.encryption,
44-
Encrypt::Required | Encrypt::On
45-
) {
46-
stream.setup_encryption().await?;
47-
} else if options.encrypt == Encrypt::Required {
48-
return Err(Error::Tls(Box::new(std::io::Error::new(
49-
std::io::ErrorKind::Other,
50-
"TLS encryption required but not supported by server",
51-
))));
40+
match (options.encrypt, prelogin_response.encryption) {
41+
(Encrypt::Required | Encrypt::On, Encrypt::Required | Encrypt::On) => {
42+
log::trace!("Mssql login phase and data packets encrypted");
43+
stream.setup_encryption().await?;
44+
}
45+
(Encrypt::Required, Encrypt::Off | Encrypt::NotSupported) => {
46+
return Err(Error::Tls(Box::new(std::io::Error::new(
47+
std::io::ErrorKind::Other,
48+
"TLS encryption required but not supported by server",
49+
))));
50+
}
51+
(Encrypt::Off, _) | (_, Encrypt::Off) => {
52+
log::info!("Mssql login phase encrypted, but data packets will be unencrypted");
53+
stream.setup_encryption().await?;
54+
disable_encryption_after_login = true;
55+
}
56+
(Encrypt::NotSupported, _) | (_, Encrypt::NotSupported) => {
57+
log::warn!("Mssql: fully unencrypted connection - will send plaintext password!");
58+
}
5259
}
5360

5461
// LOGIN7 defines the authentication rules for use between client and server
5562

63+
let login_packet = Login7 {
64+
// FIXME: use a version constant
65+
version: 0x74000004, // SQL Server 2012 - SQL Server 2019
66+
client_program_version: options.client_program_version,
67+
client_pid: options.client_pid,
68+
packet_size: options.requested_packet_size, // max allowed size of TDS packet
69+
hostname: &options.hostname,
70+
username: &options.username,
71+
password: options.password.as_deref().unwrap_or_default(),
72+
app_name: &options.app_name,
73+
server_name: &options.server_name,
74+
client_interface_name: &options.client_interface_name,
75+
language: &options.language,
76+
database: &*options.database,
77+
client_id: [0; 6],
78+
};
79+
80+
log::debug!("Sending LOGIN7 packet: {login_packet:?}");
5681
stream
57-
.write_packet_and_flush(
58-
PacketType::Tds7Login,
59-
Login7 {
60-
// FIXME: use a version constant
61-
version: 0x74000004, // SQL Server 2012 - SQL Server 2019
62-
client_program_version: options.client_program_version,
63-
client_pid: options.client_pid,
64-
packet_size: options.requested_packet_size, // max allowed size of TDS packet
65-
hostname: &options.hostname,
66-
username: &options.username,
67-
password: options.password.as_deref().unwrap_or_default(),
68-
app_name: &options.app_name,
69-
server_name: &options.server_name,
70-
client_interface_name: &options.client_interface_name,
71-
language: &options.language,
72-
database: &*options.database,
73-
client_id: [0; 6],
74-
},
75-
)
82+
.write_packet_and_flush(PacketType::Tds7Login, login_packet)
7683
.await?;
7784

85+
log::debug!("Waiting for LOGINACK or DONE");
86+
87+
if disable_encryption_after_login {
88+
log::debug!("Disabling encryption after login");
89+
stream.disable_encryption().await?;
90+
}
91+
7892
loop {
7993
// NOTE: we should receive an [Error] message if something goes wrong, otherwise,
8094
// all messages are mostly informational (ENVCHANGE, INFO, LOGINACK)
@@ -83,13 +97,17 @@ impl MssqlConnection {
8397
Message::LoginAck(_) => {
8498
// indicates that the login was successful
8599
// no action is needed, we are just going to keep waiting till we hit <Done>
100+
log::debug!("Received LoginAck");
86101
}
87102

88103
Message::Done(_) => {
104+
log::debug!("Pre-Login phase completed");
89105
break;
90106
}
91107

92-
_ => {}
108+
other_msg => {
109+
log::debug!("Ignoring unexpected pre-login message: {:?}", other_msg);
110+
}
93111
}
94112
}
95113

sqlx-core/src/mssql/connection/stream.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ impl MssqlStream {
260260
self.inner.deref_mut().handshake_complete();
261261
Ok(())
262262
}
263+
264+
pub(crate) async fn disable_encryption(&mut self) -> Result<(), Error> {
265+
self.inner.downgrade()?;
266+
Ok(())
267+
}
263268
}
264269

265270
// writes the packet out to the write buffer

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ impl FromStr for MssqlConnectOptions {
8787
match value.to_lowercase().as_str() {
8888
"strict" => options = options.encrypt(Encrypt::Required),
8989
"mandatory" | "true" | "yes" => options = options.encrypt(Encrypt::On),
90-
"optional" | "false" | "no" => options = options.encrypt(Encrypt::NotSupported),
90+
"optional" | "false" | "no" => options = options.encrypt(Encrypt::Off),
91+
"not_supported" => options = options.encrypt(Encrypt::NotSupported),
9192
_ => return Err(Error::config(MssqlInvalidOption(format!(
9293
"encrypt={} is not a valid value for encrypt. Valid values are: strict, mandatory, optional, true, false, yes, no",
9394
value

sqlx-core/src/net/tls/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,34 @@ where
139139

140140
Ok(())
141141
}
142+
143+
pub fn downgrade(&mut self) -> Result<(), Error> {
144+
let stream = match replace(self, MaybeTlsStream::Upgrading) {
145+
MaybeTlsStream::Tls(stream) => {
146+
#[cfg(feature = "_tls-rustls")]
147+
let raw = stream.into_inner().0;
148+
149+
#[cfg(all(feature = "_rt-async-std", feature = "_tls-native-tls"))]
150+
let raw = stream.into_inner();
151+
152+
#[cfg(all(not(feature = "_rt-async-std"), feature = "_tls-native-tls"))]
153+
let raw = stream.into_inner().into_inner().into_inner();
154+
155+
raw
156+
}
157+
158+
MaybeTlsStream::Raw(_) => {
159+
return Ok(());
160+
}
161+
162+
MaybeTlsStream::Upgrading => {
163+
return Err(Error::Io(io::ErrorKind::ConnectionAborted.into()));
164+
}
165+
};
166+
167+
*self = MaybeTlsStream::Raw(stream);
168+
Ok(())
169+
}
142170
}
143171

144172
#[cfg(feature = "_tls-native-tls")]

0 commit comments

Comments
 (0)