Skip to content

Commit

Permalink
Implement *Login packet encryption* in mssql
Browse files Browse the repository at this point in the history
  • Loading branch information
lovasoa committed Nov 20, 2024
1 parent 18c3d08 commit b073baf
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 62 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.6.38

- Implement *Login packet encryption* in mssql:
- SQL Server has three levels of encryption support, which are now all supported by this library:
- 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.
- 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.
- 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.
- Much improved logging in the mssql driver login phase

## 0.6.37

- Fix encoding of `DateTime<FixedOffset>` in SQLite. It used to be encoded as an RFC3339 string (with a 'T' between date and time),
Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ members = [

[package]
name = "sqlx-oldapi"
version = "0.6.37"
version = "0.6.38"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/lovasoa/sqlx"
Expand Down Expand Up @@ -125,8 +125,8 @@ bstr = ["sqlx-core/bstr"]
git2 = ["sqlx-core/git2"]

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

[dev-dependencies]
anyhow = "1.0.52"
Expand Down
2 changes: 1 addition & 1 deletion examples/postgres/axum-social-with-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
# Primary crates
axum = { version = "0.5.13", features = ["macros"] }
sqlx = { package = "sqlx-oldapi", version = "0.6.37", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] }
sqlx = { package = "sqlx-oldapi", version = "0.6.38", path = "../../../", features = ["runtime-tokio-rustls", "postgres", "time", "uuid"] }
tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] }

# Important secondary crates
Expand Down
2 changes: 1 addition & 1 deletion sqlx-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlx-cli"
version = "0.6.37"
version = "0.6.38"
description = "Command-line utility for SQLx, the Rust SQL toolkit."
edition = "2021"
readme = "README.md"
Expand Down
4 changes: 2 additions & 2 deletions sqlx-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlx-core-oldapi"
version = "0.6.37"
version = "0.6.38"
repository = "https://github.com/lovasoa/sqlx"
description = "Core of SQLx, the rust SQL toolkit. Not intended to be used directly."
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -101,7 +101,7 @@ offline = ["serde", "either/serde"]
paste = "1.0.6"
ahash = "0.8.3"
atoi = "2.0.0"
sqlx-rt = { path = "../sqlx-rt", version = "0.6.37", package = "sqlx-rt-oldapi" }
sqlx-rt = { path = "../sqlx-rt", version = "0.6.38", package = "sqlx-rt-oldapi" }
base64 = { version = "0.22", default-features = false, optional = true, features = ["std"] }
bigdecimal_ = { version = "0.4.1", optional = true, package = "bigdecimal" }
rust_decimal = { version = "1.19.0", optional = true }
Expand Down
108 changes: 63 additions & 45 deletions sqlx-core/src/mssql/connection/establish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,63 +18,77 @@ impl MssqlConnection {
// TODO: Encryption
// TODO: Send the version of SQLx over

log::debug!(
"Sending T-SQL PRELOGIN with encryption: {:?}",
options.encrypt
);

let prelogin_packet = PreLogin {
version: Version::default(),
encryption: options.encrypt,
instance: options.instance.clone(),
..Default::default()
};

log::debug!("Sending T-SQL PRELOGIN with encryption: {prelogin_packet:?}");
stream
.write_packet_and_flush(
PacketType::PreLogin,
PreLogin {
version: Version::default(),
encryption: options.encrypt,
instance: options.instance.clone(),

..Default::default()
},
)
.write_packet_and_flush(PacketType::PreLogin, prelogin_packet)
.await?;

let (_, packet) = stream.recv_packet().await?;

let prelogin_response = PreLogin::decode(packet)?;
log::debug!("Received PRELOGIN response: {:?}", prelogin_response);

let mut disable_encryption_after_login = false;

if matches!(
prelogin_response.encryption,
Encrypt::Required | Encrypt::On
) {
stream.setup_encryption().await?;
} else if options.encrypt == Encrypt::Required {
return Err(Error::Tls(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"TLS encryption required but not supported by server",
))));
match (options.encrypt, prelogin_response.encryption) {
(Encrypt::Required | Encrypt::On, Encrypt::Required | Encrypt::On) => {
log::trace!("Mssql login phase and data packets encrypted");
stream.setup_encryption().await?;
}
(Encrypt::Required, Encrypt::Off | Encrypt::NotSupported) => {
return Err(Error::Tls(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"TLS encryption required but not supported by server",
))));
}
(Encrypt::Off, _) | (_, Encrypt::Off) => {
log::info!("Mssql login phase encrypted, but data packets will be unencrypted");
stream.setup_encryption().await?;
disable_encryption_after_login = true;
}
(Encrypt::NotSupported, _) | (_, Encrypt::NotSupported) => {
log::warn!("Mssql: fully unencrypted connection - will send plaintext password!");
}
}

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

let login_packet = Login7 {
// FIXME: use a version constant
version: 0x74000004, // SQL Server 2012 - SQL Server 2019
client_program_version: options.client_program_version,
client_pid: options.client_pid,
packet_size: options.requested_packet_size, // max allowed size of TDS packet
hostname: &options.hostname,
username: &options.username,
password: options.password.as_deref().unwrap_or_default(),
app_name: &options.app_name,
server_name: &options.server_name,
client_interface_name: &options.client_interface_name,
language: &options.language,
database: &*options.database,
client_id: [0; 6],
};

log::debug!("Sending LOGIN7 packet: {login_packet:?}");
stream
.write_packet_and_flush(
PacketType::Tds7Login,
Login7 {
// FIXME: use a version constant
version: 0x74000004, // SQL Server 2012 - SQL Server 2019
client_program_version: options.client_program_version,
client_pid: options.client_pid,
packet_size: options.requested_packet_size, // max allowed size of TDS packet
hostname: &options.hostname,
username: &options.username,
password: options.password.as_deref().unwrap_or_default(),
app_name: &options.app_name,
server_name: &options.server_name,
client_interface_name: &options.client_interface_name,
language: &options.language,
database: &*options.database,
client_id: [0; 6],
},
)
.write_packet_and_flush(PacketType::Tds7Login, login_packet)
.await?;

log::debug!("Waiting for LOGINACK or DONE");

if disable_encryption_after_login {
log::debug!("Disabling encryption after login");
stream.disable_encryption().await?;
}

loop {
// NOTE: we should receive an [Error] message if something goes wrong, otherwise,
// all messages are mostly informational (ENVCHANGE, INFO, LOGINACK)
Expand All @@ -83,13 +97,17 @@ impl MssqlConnection {
Message::LoginAck(_) => {
// indicates that the login was successful
// no action is needed, we are just going to keep waiting till we hit <Done>
log::debug!("Received LoginAck");
}

Message::Done(_) => {
log::debug!("Pre-Login phase completed");
break;
}

_ => {}
other_msg => {
log::debug!("Ignoring unexpected pre-login message: {:?}", other_msg);
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions sqlx-core/src/mssql/connection/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ impl MssqlStream {
self.inner.deref_mut().handshake_complete();
Ok(())
}

pub(crate) async fn disable_encryption(&mut self) -> Result<(), Error> {
self.inner.downgrade()?;
Ok(())
}
}

// writes the packet out to the write buffer
Expand Down
3 changes: 2 additions & 1 deletion sqlx-core/src/mssql/options/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ impl FromStr for MssqlConnectOptions {
match value.to_lowercase().as_str() {
"strict" => options = options.encrypt(Encrypt::Required),
"mandatory" | "true" | "yes" => options = options.encrypt(Encrypt::On),
"optional" | "false" | "no" => options = options.encrypt(Encrypt::NotSupported),
"optional" | "false" | "no" => options = options.encrypt(Encrypt::Off),
"not_supported" => options = options.encrypt(Encrypt::NotSupported),
_ => return Err(Error::config(MssqlInvalidOption(format!(
"encrypt={} is not a valid value for encrypt. Valid values are: strict, mandatory, optional, true, false, yes, no",
value
Expand Down
28 changes: 28 additions & 0 deletions sqlx-core/src/net/tls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,34 @@ where

Ok(())
}

pub fn downgrade(&mut self) -> Result<(), Error> {
let stream = match replace(self, MaybeTlsStream::Upgrading) {
MaybeTlsStream::Tls(stream) => {
#[cfg(feature = "_tls-rustls")]
let raw = stream.into_inner().0;

#[cfg(all(feature = "_rt-async-std", feature = "_tls-native-tls"))]
let raw = stream.into_inner();

#[cfg(all(not(feature = "_rt-async-std"), feature = "_tls-native-tls"))]
let raw = stream.into_inner().into_inner().into_inner();

raw
}

MaybeTlsStream::Raw(_) => {
return Ok(());
}

MaybeTlsStream::Upgrading => {
return Err(Error::Io(io::ErrorKind::ConnectionAborted.into()));
}
};

*self = MaybeTlsStream::Raw(stream);
Ok(())
}
}

#[cfg(feature = "_tls-native-tls")]
Expand Down
6 changes: 3 additions & 3 deletions sqlx-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlx-macros-oldapi"
version = "0.6.37"
version = "0.6.38"
repository = "https://github.com/lovasoa/sqlx"
description = "Macros for SQLx, the rust SQL toolkit. Not intended to be used directly."
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -75,8 +75,8 @@ heck = { version = "0.5" }
either = "1.6.1"
once_cell = "1.9.0"
proc-macro2 = { version = "1.0.36", default-features = false }
sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.37", default-features = false, features = ["any"], path = "../sqlx-core" }
sqlx-rt = { version = "0.6.37", default-features = false, path = "../sqlx-rt", package = "sqlx-rt-oldapi" }
sqlx-core = { package = "sqlx-core-oldapi", version = "0.6.38", default-features = false, features = ["any"], path = "../sqlx-core" }
sqlx-rt = { version = "0.6.38", default-features = false, path = "../sqlx-rt", package = "sqlx-rt-oldapi" }
serde = { version = "1.0.132", features = ["derive"], optional = true }
serde_json = { version = "1.0.73", optional = true }
sha2 = { version = "0.10.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion sqlx-rt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlx-rt-oldapi"
version = "0.6.37"
version = "0.6.38"
repository = "https://github.com/launchbadge/sqlx"
license = "MIT OR Apache-2.0"
description = "Runtime abstraction used by SQLx, the Rust SQL toolkit. Not intended to be used directly."
Expand Down
2 changes: 2 additions & 0 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ services:
VERSION: 2022-latest
ports:
- 1433
volumes:
- "./mssql/mssql.conf:/var/opt/mssql/mssql.conf"
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: Password123!
Expand Down
2 changes: 2 additions & 0 deletions tests/mssql/mssql.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[network]
forceencryption = 0

0 comments on commit b073baf

Please sign in to comment.