Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .cargo/audit.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Cargo audit configuration
#
# IMPORTANT: The three RUSTSEC advisories below (RUSTSEC-2026-0098, 0099, 0104) are
# DEVELOPMENT DEPENDENCY ONLY and do NOT affect production users.
#
# Root cause: azure_identity (dev-dep only) -> reqwest 0.11 -> rustls 0.21 ->
# rustls-webpki 0.101.7 (vulnerable). This chain is NOT in production code.
#
# Production rustls stack: tokio-rustls 0.26 -> rustls 0.23 -> rustls-webpki
# 0.103.13 (secure, all CVEs fixed).
#
# These ignores are justified because:
# 1. The vulnerable rustls-webpki 0.101.7 comes ONLY via dev-dependency
# azure_identity, not the production rustls feature
# 2. The production rustls feature uses rustls 0.23 with the secure
# rustls-webpki 0.103.13
# 3. Upgrading azure_identity is out of scope -- it's an external dependency
# with its own constraints and not part of tiberius' public API surface

[advisories]
ignore = [
"RUSTSEC-2026-0098",
"RUSTSEC-2026-0099",
"RUSTSEC-2026-0104",
]
11 changes: 3 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,11 @@ version = "1.12.0"
optional = true

[dependencies.tokio-rustls]
version = "0.24.0"
optional = true
features = ["dangerous_configuration"]

[dependencies.rustls-pemfile]
version = "1"
version = "0.26"
optional = true

[dependencies.rustls-native-certs]
version = "0.6"
version = "0.8"
optional = true

[dependencies.opentls]
Expand Down Expand Up @@ -199,6 +194,6 @@ sql-browser-tokio = ["tokio", "tokio-util"]
sql-browser-smol = ["async-io", "async-net", "futures-lite"]
integrated-auth-gssapi = ["libgssapi"]
bigdecimal = ["bigdecimal_"]
rustls = ["tokio-rustls", "tokio-util", "rustls-pemfile", "rustls-native-certs"]
rustls = ["tokio-rustls", "tokio-util", "rustls-native-certs"]
native-tls = ["async-native-tls"]
vendored-openssl = ["opentls"]
13 changes: 4 additions & 9 deletions examples/aad-auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,19 @@
//! - TENANT_ID: tenant id of service principal and sql instance;
//! - SERVER: SQL server URI
use azure_identity::client_credentials_flow;
use oauth2::{ClientId, ClientSecret};
use std::{env, sync::Arc};
use tiberius::{AuthMethod, Client, Config, Query};
use tokio::net::TcpStream;
use tokio_util::compat::TokioAsyncWriteCompatExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
// following code will retrive token with AAD Service Principal Auth
let client_id =
ClientId::new(env::var("CLIENT_ID").expect("Missing CLIENT_ID environment variable."));
let client_secret = ClientSecret::new(
env::var("CLIENT_SECRET").expect("Missing CLIENT_SECRET environment variable."),
);
let client_id = env::var("CLIENT_ID").expect("Missing CLIENT_ID environment variable.");
let client_secret =
env::var("CLIENT_SECRET").expect("Missing CLIENT_SECRET environment variable.");
let tenant_id = env::var("TENANT_ID").expect("Missing TENANT_ID environment variable.");

let client = Arc::new(reqwest::Client::new());
// This will give you the final token to use in authorization.
let token = client_credentials_flow::perform(
client,
&client_id,
Expand All @@ -41,7 +36,7 @@ async fn main() -> anyhow::Result<()> {
config.host(server);
config.port(1433);
config.authentication(AuthMethod::AADToken(
token.access_token().secret().to_string(),
token.access_token().secret().to_owned(),
));
config.trust_cert();

Expand Down
43 changes: 42 additions & 1 deletion src/client/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,44 @@
feature = "vendored-openssl"
))]
use super::tls_stream::TlsStream;
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
use crate::tds::{
codec::{Decode, Encode, PacketHeader, PacketStatus, PacketType},
HEADER_BYTES,
};
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
use bytes::BytesMut;
use futures_util::io::{AsyncRead, AsyncWrite};
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
use futures_util::ready;
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
use std::cmp;
use std::{
cmp, io,
io,
pin::Pin,
task::{self, Poll},
};
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
use tracing::{event, Level};

/// A wrapper to handle either TLS or bare connections.
Expand Down Expand Up @@ -114,6 +140,11 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send> AsyncWrite for MaybeTlsStream<S>
///
/// What it does is it interferes on handshake for TDS packet handling,
/// and when complete, just passes the calls to the underlying connection.
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
pub(crate) struct TlsPreloginWrapper<S> {
stream: Option<S>,
pending_handshake: bool,
Expand Down Expand Up @@ -150,6 +181,11 @@ impl<S> TlsPreloginWrapper<S> {
}
}

#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
impl<S: AsyncRead + AsyncWrite + Unpin + Send> AsyncRead for TlsPreloginWrapper<S> {
fn poll_read(
mut self: Pin<&mut Self>,
Expand Down Expand Up @@ -212,6 +248,11 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send> AsyncRead for TlsPreloginWrapper<
}
}

#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
impl<S: AsyncRead + AsyncWrite + Unpin + Send> AsyncWrite for TlsPreloginWrapper<S> {
fn poll_write(
mut self: Pin<&mut Self>,
Expand Down
134 changes: 86 additions & 48 deletions src/client/tls_stream/rustls_tls_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ use std::{
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::SystemTime,
};
use tokio_rustls::{
rustls::{
client::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
WantsTransparencyPolicyOrClientCert,
danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
WantsClientCert,
},
Certificate, ClientConfig, ConfigBuilder, DigitallySignedStruct, Error as RustlsError,
RootCertStore, ServerName, WantsVerifier,
crypto::aws_lc_rs,
pki_types::{pem::PemObject, CertificateDer, ServerName, UnixTime},
version, ClientConfig, ConfigBuilder, DigitallySignedStruct, Error as RustlsError,
RootCertStore, SignatureScheme, WantsVerifier,
},
TlsConnector,
};
Expand All @@ -35,34 +36,59 @@ pub(crate) struct TlsStream<S: AsyncRead + AsyncWrite + Unpin + Send>(
Compat<tokio_rustls::client::TlsStream<Compat<S>>>,
);

#[derive(Debug)]
struct NoCertVerifier;

impl ServerCertVerifier for NoCertVerifier {
fn verify_server_cert(
&self,
_end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: SystemTime,
_now: UnixTime,
) -> Result<ServerCertVerified, RustlsError> {
Ok(ServerCertVerified::assertion())
}

fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &Certificate,
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}

fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, RustlsError> {
Ok(HandshakeSignatureValid::assertion())
}

fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}

fn get_server_name(config: &Config) -> crate::Result<ServerName> {
fn get_server_name(config: &Config) -> crate::Result<ServerName<'static>> {
match (ServerName::try_from(config.get_host()), &config.trust) {
(Ok(sn), _) => Ok(sn),
(Ok(sn), _) => Ok(sn.to_owned()),
(Err(_), TrustConfig::TrustAll) => {
Ok(ServerName::try_from("placeholder.domain.com").unwrap())
}
Expand All @@ -74,36 +100,54 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send> TlsStream<S> {
pub(super) async fn new(config: &Config, stream: S) -> crate::Result<Self> {
event!(Level::INFO, "Performing a TLS handshake");

let builder = ClientConfig::builder().with_safe_defaults();
let builder = ClientConfig::builder_with_provider(Arc::new(aws_lc_rs::default_provider()))
.with_protocol_versions(&[&version::TLS12])
.map_err(|e| crate::Error::Tls(e.to_string()))?;

let client_config = match &config.trust {
TrustConfig::CaCertificateLocation(path) => {
if let Ok(buf) = fs::read(path) {
let cert = match path.extension() {
Some(ext)
if ext.to_ascii_lowercase() == "pem"
|| ext.to_ascii_lowercase() == "crt" =>
{
let pem_cert = rustls_pemfile::certs(&mut buf.as_slice())?;
if pem_cert.len() != 1 {
return Err(crate::Error::Io {
kind: IoErrorKind::InvalidInput,
message: format!("Certificate file {} contain 0 or more than 1 certs", path.to_string_lossy()),
});
}

Certificate(pem_cert.into_iter().next().unwrap())
}
Some(ext) if ext.to_ascii_lowercase() == "der" => {
Certificate(buf)
Some(ext)
if ext.eq_ignore_ascii_case("pem")
|| ext.eq_ignore_ascii_case("crt") =>
{
let pem_certs: Vec<
CertificateDer<'static>,
> = CertificateDer::pem_slice_iter(&buf)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| crate::Error::Io {
kind: IoErrorKind::InvalidData,
message: format!(
"Failed to parse PEM certificate: {e}"
),
})?;
if pem_certs.len() != 1 {
return Err(crate::Error::Io {
kind: IoErrorKind::InvalidInput,
message: format!(
"Certificate file {} must contain exactly one certificate",
path.to_string_lossy()
),
Comment thread
phillipleblanc marked this conversation as resolved.
});
}
Some(_) | None => return Err(crate::Error::Io {

pem_certs.into_iter().next().unwrap()
}
Some(ext)
if ext.eq_ignore_ascii_case("der") =>
{
CertificateDer::from(buf)
}
Some(_) | None => {
return Err(crate::Error::Io {
kind: IoErrorKind::InvalidInput,
message: "Provided CA certificate with unsupported file-extension! Supported types are pem, crt and der.".to_string(),
}),
};
})
}
};
let mut cert_store = RootCertStore::empty();
cert_store.add(&cert)?;
cert_store.add(cert)?;
builder
.with_root_certificates(cert_store)
.with_no_client_auth()
Expand All @@ -119,14 +163,10 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send> TlsStream<S> {
Level::WARN,
"Trusting the server certificate without validation."
);
let mut config = builder
.with_root_certificates(RootCertStore::empty())
.with_no_client_auth();
config
builder
.dangerous()
.set_certificate_verifier(Arc::new(NoCertVerifier {}));
// config.enable_sni = false;
config
.with_custom_certificate_verifier(Arc::new(NoCertVerifier))
.with_no_client_auth()
}
TrustConfig::Default => {
event!(Level::INFO, "Using default trust configuration.");
Expand Down Expand Up @@ -181,28 +221,26 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send> AsyncWrite for TlsStream<S> {
}

trait ConfigBuilderExt {
fn with_native_roots(self) -> ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert>;
fn with_native_roots(self) -> ConfigBuilder<ClientConfig, WantsClientCert>;
}

impl ConfigBuilderExt for ConfigBuilder<ClientConfig, WantsVerifier> {
fn with_native_roots(self) -> ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
fn with_native_roots(self) -> ConfigBuilder<ClientConfig, WantsClientCert> {
let mut roots = RootCertStore::empty();
let mut valid_count = 0;
let mut invalid_count = 0;

for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs")
{
let cert = Certificate(cert.0);
match roots.add(&cert) {
match roots.add(cert) {
Ok(_) => valid_count += 1,
Err(err) => {
tracing::event!(Level::TRACE, "invalid cert der {:?}", cert.0);
tracing::event!(Level::DEBUG, "certificate parsing failed: {:?}", err);
event!(Level::DEBUG, "certificate parsing failed: {:?}", err);
invalid_count += 1
}
}
}
tracing::event!(
event!(
Level::TRACE,
"with_native_roots processed {} valid and {} invalid certs",
valid_count,
Expand Down
Loading