diff --git a/Cargo.toml b/Cargo.toml index 081e6491c..05888a538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ license = "MIT/Apache-2.0" name = "tiberius" readme = "README.md" repository = "https://github.com/prisma/tiberius" -version = "0.12.2" +version = "0.12.3" [workspace] members = ["runtimes-macro"] @@ -52,7 +52,6 @@ connection-string = "0.2" num-traits = "0.2" uuid = "1.0" -[target.'cfg(windows)'.dependencies] winauth = { version = "0.0.4", optional = true } [target.'cfg(unix)'.dependencies] @@ -135,6 +134,13 @@ version = "0.2.1" optional = true features = ["io-async-std", "vendored"] +# Used by the vendored-openssl backend to load trusted roots from the +# Windows certificate store: the vendored OpenSSL only probes Unix +# filesystem paths, so without this it trusts nothing on Windows. +[target.'cfg(windows)'.dependencies.schannel] +version = "0.1" +optional = true + [dev-dependencies.uuid] version = "1.0" features = ["v4"] @@ -201,4 +207,4 @@ integrated-auth-gssapi = ["libgssapi"] bigdecimal = ["bigdecimal_"] rustls = ["tokio-rustls", "tokio-util", "rustls-pemfile", "rustls-native-certs"] native-tls = ["async-native-tls"] -vendored-openssl = ["opentls"] +vendored-openssl = ["opentls", "schannel"] diff --git a/src/client/auth.rs b/src/client/auth.rs index 208d8d060..2003d99ba 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -26,16 +26,16 @@ impl Debug for SqlServerAuth { } #[derive(Clone, PartialEq, Eq)] -#[cfg(any(all(windows, feature = "winauth"), doc))] -#[cfg_attr(feature = "docs", doc(all(windows, feature = "winauth")))] +#[cfg(any(feature = "winauth", doc))] +#[cfg_attr(feature = "docs", doc(feature = "winauth"))] pub struct WindowsAuth { pub(crate) user: String, pub(crate) password: String, pub(crate) domain: Option, } -#[cfg(any(all(windows, feature = "winauth"), doc))] -#[cfg_attr(feature = "docs", doc(all(windows, feature = "winauth")))] +#[cfg(any(feature = "winauth", doc))] +#[cfg_attr(feature = "docs", doc(feature = "winauth"))] impl Debug for WindowsAuth { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WindowsAuth") @@ -52,8 +52,8 @@ pub enum AuthMethod { /// Authenticate directly with SQL Server. SqlServer(SqlServerAuth), /// Authenticate with Windows credentials. - #[cfg(any(all(windows, feature = "winauth"), doc))] - #[cfg_attr(feature = "docs", doc(cfg(all(windows, feature = "winauth"))))] + #[cfg(any(feature = "winauth", doc))] + #[cfg_attr(feature = "docs", doc(cfg(feature = "winauth")))] Windows(WindowsAuth), /// Authenticate as the currently logged in user. On Windows uses SSPI and /// Kerberos on Unix platforms. @@ -84,8 +84,8 @@ impl AuthMethod { } /// Construct a new Windows authentication configuration. - #[cfg(any(all(windows, feature = "winauth"), doc))] - #[cfg_attr(feature = "docs", doc(cfg(all(windows, feature = "winauth"))))] + #[cfg(any(feature = "winauth", doc))] + #[cfg_attr(feature = "docs", doc(cfg(feature = "winauth")))] pub fn windows(user: impl AsRef, password: impl ToString) -> Self { let (domain, user) = match user.as_ref().find('\\') { Some(idx) => (Some(&user.as_ref()[..idx]), &user.as_ref()[idx + 1..]), diff --git a/src/client/connection.rs b/src/client/connection.rs index c6ce1d66e..6c641c41a 100644 --- a/src/client/connection.rs +++ b/src/client/connection.rs @@ -18,7 +18,7 @@ use crate::{ }; use asynchronous_codec::Framed; use bytes::BytesMut; -#[cfg(any(windows, feature = "integrated-auth-gssapi"))] +#[cfg(any(windows, feature = "integrated-auth-gssapi", feature = "winauth"))] use codec::TokenSspi; use futures_util::io::{AsyncRead, AsyncWrite}; use futures_util::ready; @@ -38,7 +38,9 @@ use std::{cmp, fmt::Debug, io, pin::Pin, task}; use task::Poll; use tracing::{event, Level}; #[cfg(all(windows, feature = "winauth"))] -use winauth::{windows::NtlmSspiBuilder, NextBytes}; +use winauth::windows::NtlmSspiBuilder; +#[cfg(feature = "winauth")] +use winauth::NextBytes; /// A `Connection` is an abstraction between the [`Client`] and the server. It /// can be used as a `Stream` to fetch [`Packet`]s from and to `send` packets @@ -120,7 +122,7 @@ impl Connection { TokenStream::new(self).flush_done().await } - #[cfg(any(windows, feature = "integrated-auth-gssapi"))] + #[cfg(any(windows, feature = "integrated-auth-gssapi", feature = "winauth"))] /// Flush the incoming token stream until receiving `SSPI` token. async fn flush_sspi(&mut self) -> crate::Result { TokenStream::new(self).flush_sspi().await @@ -381,7 +383,7 @@ impl Connection { self.send(header, next_token).await?; } - #[cfg(all(windows, feature = "winauth"))] + #[cfg(feature = "winauth")] AuthMethod::Windows(auth) => { let spn = self.context.spn().to_string(); let builder = winauth::NtlmV2ClientBuilder::new().target_spn(spn); diff --git a/src/client/tls_stream/opentls_tls_stream.rs b/src/client/tls_stream/opentls_tls_stream.rs index 1f028669e..76c8c9df0 100644 --- a/src/client/tls_stream/opentls_tls_stream.rs +++ b/src/client/tls_stream/opentls_tls_stream.rs @@ -53,6 +53,47 @@ pub(crate) async fn create_tls_stream( } TrustConfig::Default => { event!(Level::INFO, "Using default trust configuration."); + + // The vendored OpenSSL discovers root certificates by probing + // Unix filesystem paths (openssl-probe), which finds nothing on + // Windows — its trust anchors live in registry-backed + // certificate stores. Load the ROOT store into the connector so + // certificate validation can succeed; the current-user view is + // a composite that includes the local-machine store, so certs + // installed via certlm.msc (machine) or GP-pushed (e.g. Zscaler) + // are picked up automatically. Individual certificates OpenSSL + // cannot parse are skipped, matching what rustls-native-certs does. + // + // NOTE: open_current_user("ROOT") is correct for user-session + // processes (e.g. the KeeperDB desktop app). A Windows service + // would need open_local_machine("ROOT") instead, since services + // run in session 0 and the current-user store may be empty there. + #[cfg(windows)] + match schannel::cert_store::CertStore::open_current_user("ROOT") { + Ok(store) => { + for windows_cert in store.certs() { + match Certificate::from_der(windows_cert.to_der()) { + Ok(root_cert) => { + builder = builder.add_root_certificate(root_cert); + } + Err(e) => { + event!( + Level::WARN, + "Skipping an unparseable certificate from the Windows ROOT store: {}", + e + ); + } + } + } + } + Err(e) => { + event!( + Level::WARN, + "Could not open the Windows ROOT certificate store; certificate validation will have no trusted roots: {}", + e + ); + } + } } } diff --git a/src/tds/codec/login.rs b/src/tds/codec/login.rs index 265db381e..0ecc3d2fd 100644 --- a/src/tds/codec/login.rs +++ b/src/tds/codec/login.rs @@ -187,7 +187,7 @@ impl<'a> LoginMessage<'a> { } } - #[cfg(any(all(unix, feature = "integrated-auth-gssapi"), windows))] + #[cfg(any(all(unix, feature = "integrated-auth-gssapi"), windows, feature = "winauth"))] pub fn integrated_security(&mut self, bytes: Option>) { if bytes.is_some() { self.option_flags_2.insert(OptionFlag2::IntegratedSecurity); diff --git a/src/tds/codec/token/token_sspi.rs b/src/tds/codec/token/token_sspi.rs index 954d6dd8b..ccb078bf2 100644 --- a/src/tds/codec/token/token_sspi.rs +++ b/src/tds/codec/token/token_sspi.rs @@ -12,7 +12,7 @@ impl AsRef<[u8]> for TokenSspi { } impl TokenSspi { - #[cfg(any(windows, all(unix, feature = "integrated-auth-gssapi")))] + #[cfg(any(windows, feature = "winauth", all(unix, feature = "integrated-auth-gssapi")))] pub fn new(bytes: Vec) -> Self { Self(bytes) } diff --git a/src/tds/context.rs b/src/tds/context.rs index 732bac15c..55797e649 100644 --- a/src/tds/context.rs +++ b/src/tds/context.rs @@ -62,7 +62,7 @@ impl Context { self.spn = Some(format!("MSSQLSvc/{}:{}", host.as_ref(), port)); } - #[cfg(any(windows, all(unix, feature = "integrated-auth-gssapi")))] + #[cfg(any(windows, feature = "winauth", all(unix, feature = "integrated-auth-gssapi")))] pub fn spn(&self) -> &str { self.spn.as_deref().unwrap_or("") } diff --git a/src/tds/stream/token.rs b/src/tds/stream/token.rs index 35ce0658b..87c343174 100644 --- a/src/tds/stream/token.rs +++ b/src/tds/stream/token.rs @@ -75,7 +75,7 @@ where } } - #[cfg(any(windows, feature = "integrated-auth-gssapi"))] + #[cfg(any(windows, feature = "integrated-auth-gssapi", feature = "winauth"))] pub(crate) async fn flush_sspi(self) -> crate::Result { let mut stream = self.try_unfold(); let mut last_error = None;