diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf3887cde..db08266c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` in SQLite. It used to be encoded as an RFC3339 string (with a 'T' between date and time), diff --git a/Cargo.lock b/Cargo.lock index 0d39404f55..030c45fc21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3313,7 +3313,7 @@ dependencies = [ [[package]] name = "sqlx-cli" -version = "0.6.37" +version = "0.6.38" dependencies = [ "anyhow", "async-trait", @@ -3338,7 +3338,7 @@ dependencies = [ [[package]] name = "sqlx-core-oldapi" -version = "0.6.37" +version = "0.6.38" dependencies = [ "ahash 0.8.11", "atoi", @@ -3500,7 +3500,7 @@ dependencies = [ [[package]] name = "sqlx-macros-oldapi" -version = "0.6.37" +version = "0.6.38" dependencies = [ "dotenvy", "either", @@ -3520,7 +3520,7 @@ dependencies = [ [[package]] name = "sqlx-oldapi" -version = "0.6.37" +version = "0.6.38" dependencies = [ "anyhow", "async-std", @@ -3547,7 +3547,7 @@ dependencies = [ [[package]] name = "sqlx-rt-oldapi" -version = "0.6.37" +version = "0.6.38" dependencies = [ "async-native-tls", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 9c02bff1fa..d5aaa0789f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -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" diff --git a/examples/postgres/axum-social-with-tests/Cargo.toml b/examples/postgres/axum-social-with-tests/Cargo.toml index d03d44b139..db5004113c 100644 --- a/examples/postgres/axum-social-with-tests/Cargo.toml +++ b/examples/postgres/axum-social-with-tests/Cargo.toml @@ -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 diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 15b8114e7c..52a3a77d22 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -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" diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index c1e6564123..d619aedec2 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -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" @@ -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 } diff --git a/sqlx-core/src/mssql/connection/establish.rs b/sqlx-core/src/mssql/connection/establish.rs index e3dca05ded..6e1c238274 100644 --- a/sqlx-core/src/mssql/connection/establish.rs +++ b/sqlx-core/src/mssql/connection/establish.rs @@ -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) @@ -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 + log::debug!("Received LoginAck"); } Message::Done(_) => { + log::debug!("Pre-Login phase completed"); break; } - _ => {} + other_msg => { + log::debug!("Ignoring unexpected pre-login message: {:?}", other_msg); + } } } diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs index 0a830fd4b8..2d8d5b43f6 100644 --- a/sqlx-core/src/mssql/connection/stream.rs +++ b/sqlx-core/src/mssql/connection/stream.rs @@ -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 diff --git a/sqlx-core/src/mssql/options/parse.rs b/sqlx-core/src/mssql/options/parse.rs index fe26fdf8d1..b7c73ba08b 100644 --- a/sqlx-core/src/mssql/options/parse.rs +++ b/sqlx-core/src/mssql/options/parse.rs @@ -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 diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 7473936505..2bddf1526c 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -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")] diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 2f26e5b210..5d6ea8a207 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -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" @@ -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 } diff --git a/sqlx-rt/Cargo.toml b/sqlx-rt/Cargo.toml index 63225858f9..886c4d09cf 100644 --- a/sqlx-rt/Cargo.toml +++ b/sqlx-rt/Cargo.toml @@ -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." diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 4ff9c8a525..827250deb6 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -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! diff --git a/tests/mssql/mssql.conf b/tests/mssql/mssql.conf new file mode 100644 index 0000000000..6d77b61a33 --- /dev/null +++ b/tests/mssql/mssql.conf @@ -0,0 +1,2 @@ +[network] +forceencryption = 0 \ No newline at end of file