#![cfg(feature = "native-tls")]
use async_native_tls::TlsAcceptor;
use futures_util::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use std::{
io,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use tds_driver::{Client, Config};
use tokio::net::{TcpListener, TcpStream};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
const CA: &str = "docker/certs/customCA.crt";
fn packet(ty: u8, payload: &[u8]) -> Vec<u8> {
let len = (payload.len() + 8) as u16;
let mut p = vec![ty, 1, (len >> 8) as u8, len as u8, 0, 0, 1, 0];
p.extend_from_slice(payload);
p
}
fn prelogin_response(encryption: u8) -> Vec<u8> {
let mut p = vec![
0x00, 0, 26, 0, 6, 0x01, 0, 32, 0, 1, 0x02, 0, 33, 0, 1, 0x03, 0, 34, 0, 0, 0x04, 0, 34, 0,
1, 0xff,
];
p.extend_from_slice(&[16, 0, 0x07, 0xd0, 0, 0, encryption, 0, 0]);
p
}
fn login_response() -> Vec<u8> {
let name: Vec<u8> = "Fake".encode_utf16().flat_map(u16::to_le_bytes).collect();
let mut body = vec![1u8];
body.extend_from_slice(&0x7400_0004u32.to_be_bytes());
body.push(4);
body.extend_from_slice(&name);
body.extend_from_slice(&[16, 0, 0x07, 0xd0]);
let mut t = vec![0xAD];
t.extend_from_slice(&(body.len() as u16).to_le_bytes());
t.extend(body);
t.extend_from_slice(&[0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
t
}
async fn read_message<R: AsyncRead + Unpin>(r: &mut R) -> io::Result<(u8, Vec<u8>)> {
let mut out = Vec::new();
loop {
let mut h = [0u8; 8];
r.read_exact(&mut h).await?;
let len = u16::from_be_bytes([h[2], h[3]]) as usize;
let mut b = vec![0u8; len - 8];
r.read_exact(&mut b).await?;
out.extend(b);
if h[1] & 1 == 1 {
return Ok((h[0], out));
}
}
}
struct ServerPrelogin<S> {
s: S,
wrapping: bool,
hdr: [u8; 8],
hpos: usize,
remaining: usize,
out: Vec<u8>,
}
impl<S: AsyncWrite + Unpin> ServerPrelogin<S> {
fn drain(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
while !self.out.is_empty() {
let n = futures_util::ready!(Pin::new(&mut self.s).poll_write(cx, &self.out))?;
self.out.drain(..n);
}
Poll::Ready(Ok(()))
}
}
impl<S: AsyncRead + Unpin> AsyncRead for ServerPrelogin<S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
let this = &mut *self;
if !this.wrapping {
return Pin::new(&mut this.s).poll_read(cx, buf);
}
if this.remaining == 0 {
while this.hpos < 8 {
let n = futures_util::ready!(
Pin::new(&mut this.s).poll_read(cx, &mut this.hdr[this.hpos..])
)?;
if n == 0 {
return Poll::Ready(Ok(0));
}
this.hpos += n;
}
this.remaining = u16::from_be_bytes([this.hdr[2], this.hdr[3]]) as usize - 8;
this.hpos = 0;
}
let max = buf.len().min(this.remaining);
let n = futures_util::ready!(Pin::new(&mut this.s).poll_read(cx, &mut buf[..max]))?;
this.remaining -= n;
Poll::Ready(Ok(n))
}
}
impl<S: AsyncWrite + Unpin> AsyncWrite for ServerPrelogin<S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let this = &mut *self;
futures_util::ready!(this.drain(cx))?;
if !this.wrapping {
return Pin::new(&mut this.s).poll_write(cx, buf);
}
this.out.extend(packet(0x12, buf));
let _ = this.drain(cx)?;
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
futures_util::ready!(self.drain(cx))?;
Pin::new(&mut self.s).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.s).poll_close(cx)
}
}
async fn fake_server(strict: bool) -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
tokio::spawn(async move {
let (tcp, _) = listener.accept().await.unwrap();
let mut raw = tcp.compat();
let pfx = std::fs::read("tests/certs/server.pfx").unwrap();
let acceptor = TlsAcceptor::new(futures_util::io::Cursor::new(pfx), "tiberius")
.await
.unwrap();
let mut tls = if strict {
let Ok(tls) = acceptor
.accept(ServerPrelogin {
s: raw,
wrapping: false,
hdr: [0; 8],
hpos: 0,
remaining: 0,
out: Vec::new(),
})
.await
else {
return;
};
tls
} else {
read_message(&mut raw).await.unwrap();
raw.write_all(&packet(4, &prelogin_response(1)))
.await
.unwrap();
let wrapped = ServerPrelogin {
s: raw,
wrapping: true,
hdr: [0; 8],
hpos: 0,
remaining: 0,
out: Vec::new(),
};
let Ok(mut tls) = acceptor.accept(wrapped).await else {
return;
};
tls.get_mut().wrapping = false;
tls
};
if strict {
let Ok(_) = read_message(&mut tls).await else {
return;
};
tls.write_all(&packet(4, &prelogin_response(0)))
.await
.unwrap();
tls.flush().await.unwrap();
}
let Ok((0x10, _)) = read_message(&mut tls).await else {
return;
};
tls.write_all(&packet(4, &login_response())).await.unwrap();
tls.flush().await.unwrap();
let _ = tls.read(&mut [0u8; 1]).await;
});
port
}
async fn connect(port: u16, connection_string: &str) -> tds_driver::Result<()> {
let cs = format!("server=tcp:localhost,{port};user=sa;password=x;{connection_string}");
let config = Config::from_ado_string(&cs)?;
let tcp = TcpStream::connect(("127.0.0.1", port)).await?;
tokio::time::timeout(
Duration::from_secs(10),
Client::connect(config, tcp.compat_write()),
)
.await
.expect("connect timed out")
.map(drop)
}
#[tokio::test]
async fn tds7_tls_handshake_against_a_tls13_capable_server() -> tds_driver::Result<()> {
let port = fake_server(false).await;
connect(port, &format!("encrypt=true;TrustServerCertificateCA={CA}")).await
}
#[cfg(feature = "tds80")]
#[tokio::test]
async fn strict_ignores_trust_server_certificate() {
let port = fake_server(true).await;
let res = connect(port, "encrypt=strict;TrustServerCertificate=true").await;
assert!(
matches!(res, Err(tds_driver::error::Error::Tls(_))),
"{res:?}"
);
}
#[cfg(feature = "tds80")]
#[tokio::test]
async fn strict_with_trust_server_certificate_honours_the_ca() -> tds_driver::Result<()> {
let port = fake_server(true).await;
connect(
port,
&format!("encrypt=strict;TrustServerCertificate=true;TrustServerCertificateCA={CA}"),
)
.await
}
#[cfg(feature = "tds80")]
#[tokio::test]
async fn strict_ignores_trust_cert_set_through_the_api() {
let port = fake_server(true).await;
let mut config = Config::new();
config.host("localhost");
config.port(port);
config.authentication(tds_driver::AuthMethod::sql_server("sa", "x"));
config.trust_cert();
config.encryption(tds_driver::EncryptionLevel::Strict);
let tcp = TcpStream::connect(("127.0.0.1", port)).await.unwrap();
let res = Client::connect(config, tcp.compat_write()).await.map(drop);
assert!(
matches!(res, Err(tds_driver::error::Error::Tls(_))),
"{res:?}"
);
}