Skip to main content

rustlavel_db/sqlserver/
auth.rs

1//! SQL Server authentication, and the TLS that has to happen first.
2//!
3//! # What this module does *not* do
4//!
5//! Only SQL Server authentication — a username and a password held by the
6//! server — is implemented. Windows integrated authentication (NTLM, Kerberos,
7//! SSPI) and Microsoft Entra federated authentication are **not**: they need a
8//! full GSS-API negotiation and a Windows credential cache, and a half-built
9//! one that silently falls back to something weaker would be worse than none.
10//! A server that demands SSPI is told so by name rather than being retried.
11//!
12//! # The part that surprises everyone
13//!
14//! TDS negotiates TLS *inside* its own pre-login exchange. The sequence is:
15//!
16//! 1. The client sends a PRELOGIN packet naming the encryption it wants.
17//! 2. The server answers with a PRELOGIN packet naming what it will do.
18//! 3. If either side asked for encryption, a **complete TLS handshake** now
19//!    runs — but every handshake record is wrapped in a TDS packet of type
20//!    PRELOGIN, header and all. TLS is being tunnelled through a protocol that
21//!    has not started yet.
22//! 4. The moment the handshake finishes, the wrapping stops. From the next byte
23//!    on, the connection is ordinary TLS carrying ordinary TDS packets.
24//!
25//! [`TdsHandshakeStream`] is what makes step 3 and step 4 the same object: it
26//! frames while `wrapping` is set and passes bytes straight through afterwards,
27//! so `tokio-rustls` can drive a normal handshake over it without knowing that
28//! anything unusual is happening underneath.
29//!
30//! ## Why TLS 1.2 and not 1.3
31//!
32//! Under TLS 1.3 a server considers the handshake finished as soon as it has
33//! sent its own Finished, and immediately sends session tickets — which would
34//! arrive unwrapped while this side is still reading wrapped packets. TLS 1.2
35//! has no post-handshake traffic and both sides stop wrapping at the same
36//! byte, so the wrapped handshake is pinned to TLS 1.2. TDS 8.0, which starts
37//! TLS before TDS rather than inside it, is what lifts that restriction; this
38//! driver speaks TDS 7.4.
39
40use super::protocol::{HEADER_LEN, PacketHeader, packet, split_message};
41use rustlavel_core::{Error, Result};
42use std::io;
43use std::pin::Pin;
44use std::sync::Arc;
45use std::task::{Context, Poll, ready};
46use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf};
47use tokio::net::TcpStream;
48
49/// How much of the session is encrypted.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum Encryption {
52    /// ENCRYPT_NOT_SUP: nothing is encrypted, the password included. Only for a
53    /// server that genuinely has no certificate.
54    Disabled,
55    /// ENCRYPT_OFF: TLS wraps the login packet and is then torn down, so the
56    /// credentials are protected but the queries are not.
57    LoginOnly,
58    /// ENCRYPT_ON: TLS for the whole session. The default, because the cost is
59    /// a handshake and the alternative is queries in the clear.
60    #[default]
61    Required,
62}
63
64impl Encryption {
65    /// The byte this choice puts in the PRELOGIN ENCRYPTION option.
66    pub fn as_byte(self) -> u8 {
67        match self {
68            Encryption::Disabled => super::protocol::encryption::NOT_SUPPORTED,
69            Encryption::LoginOnly => super::protocol::encryption::OFF,
70            Encryption::Required => super::protocol::encryption::ON,
71        }
72    }
73}
74
75/// What the two sides settled on, once both have spoken.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum Negotiated {
78    /// No TLS at all.
79    None,
80    /// TLS for the login packet, then back to the clear.
81    LoginOnly,
82    /// TLS for everything.
83    Session,
84}
85
86/// Work out what happens next from the two ENCRYPTION bytes.
87///
88/// The table is small but every cell matters: the one case that must fail is a
89/// server insisting on encryption a client refuses, because continuing would
90/// put the password on the wire in something very close to plaintext.
91pub fn negotiate(requested: Encryption, server: u8) -> Result<Negotiated> {
92    use super::protocol::encryption as level;
93
94    Ok(match (requested, server) {
95        (Encryption::Disabled, level::NOT_SUPPORTED) => Negotiated::None,
96        (Encryption::Disabled, level::REQUIRED) | (Encryption::Disabled, level::ON) => {
97            return Err(Error::msg(
98                "the server requires an encrypted connection, but this connection asked for none. \
99                 Use the default encryption setting.",
100            ));
101        }
102        (Encryption::Disabled, _) => Negotiated::None,
103
104        // The server cannot encrypt. Login-only was a preference, so continue;
105        // full encryption was a requirement, so do not.
106        (Encryption::LoginOnly, level::NOT_SUPPORTED) => Negotiated::None,
107        (Encryption::Required, level::NOT_SUPPORTED) => {
108            return Err(Error::msg(
109                "this connection requires encryption, but the server reports it cannot encrypt. \
110                 Give SQL Server a certificate, or connect with encryption set to login-only.",
111            ));
112        }
113
114        // A server that says ON or REQUIRED encrypts everything, whatever the
115        // client merely preferred.
116        (_, level::ON) | (_, level::REQUIRED) => Negotiated::Session,
117        (Encryption::Required, _) => Negotiated::Session,
118        (Encryption::LoginOnly, _) => Negotiated::LoginOnly,
119    })
120}
121
122/// Encode a password for LOGIN7.
123///
124/// This is obfuscation, not a hash: MS-TDS specifies that each byte of the
125/// UTF-16LE password has its nibbles swapped and is then XORed with 0xA5. It
126/// hides nothing from anyone watching the wire, which is exactly why the login
127/// packet is sent through TLS.
128pub fn obfuscate_password(password: &str) -> Vec<u8> {
129    password
130        .encode_utf16()
131        .flat_map(u16::to_le_bytes)
132        // `rotate_left(4)` on a byte is exactly the documented nibble swap.
133        .map(|byte| byte.rotate_left(4) ^ 0xA5)
134        .collect()
135}
136
137/// Undo [`obfuscate_password`]. Only the tests need it — but a scheme whose
138/// inverse is never written is a scheme nobody has checked.
139pub fn deobfuscate_password(bytes: &[u8]) -> String {
140    let plain: Vec<u8> = bytes
141        .iter()
142        .map(|byte| {
143            let plain = byte ^ 0xA5;
144            plain.rotate_left(4)
145        })
146        .collect();
147
148    // `as_chunks` rather than `chunks_exact(2)`: each pair arrives as a fixed
149    // `[u8; 2]`, so `from_le_bytes` needs no bounds check. A trailing odd byte
150    // is dropped either way — half a UTF-16 code unit is not a character.
151    let (pairs, _odd_trailing_byte) = plain.as_chunks::<2>();
152    let units: Vec<u16> = pairs.iter().copied().map(u16::from_le_bytes).collect();
153
154    String::from_utf16_lossy(&units)
155}
156
157/// A socket that frames the TLS handshake into TDS packets, and stops once the
158/// handshake is done.
159///
160/// Written as a plain `AsyncRead`/`AsyncWrite` so `tokio-rustls` can drive an
161/// ordinary handshake across it. Writes are gathered and framed at the flush
162/// that ends each handshake flight, which keeps one TLS flight to one TDS
163/// message — the shape SQL Server expects.
164pub struct TdsHandshakeStream {
165    socket: TcpStream,
166    /// Cleared by [`TdsHandshakeStream::stop_wrapping`] once TLS is up.
167    wrapping: bool,
168    packet_size: usize,
169    /// TLS bytes written but not yet framed.
170    outgoing: Vec<u8>,
171    /// Framed bytes on their way to the socket, and how far they have got.
172    pending: Vec<u8>,
173    pending_at: usize,
174    /// Bytes read from the socket that are not yet a whole packet.
175    incoming: Vec<u8>,
176    /// Unwrapped payload waiting to be handed to the TLS engine.
177    ready: Vec<u8>,
178    ready_at: usize,
179}
180
181impl TdsHandshakeStream {
182    pub fn new(socket: TcpStream, packet_size: usize) -> Self {
183        TdsHandshakeStream {
184            socket,
185            wrapping: true,
186            packet_size,
187            outgoing: Vec::new(),
188            pending: Vec::new(),
189            pending_at: 0,
190            incoming: Vec::new(),
191            ready: Vec::new(),
192            ready_at: 0,
193        }
194    }
195
196    /// Stop framing. Called the instant the TLS handshake completes, which is
197    /// the exact byte at which the server stops framing too.
198    pub fn stop_wrapping(&mut self) {
199        self.wrapping = false;
200    }
201
202    /// Take the socket back, for a login-only session that returns to the clear.
203    ///
204    /// Fails if anything was read ahead of the handshake, because those bytes
205    /// belong to the TLS session and dropping them would corrupt the stream.
206    pub fn into_socket(self) -> Result<TcpStream> {
207        if self.ready_at < self.ready.len() || !self.incoming.is_empty() {
208            return Err(Error::Protocol(
209                "the server sent data before encryption was torn down".into(),
210            ));
211        }
212        Ok(self.socket)
213    }
214
215    /// Pull one complete packet's payload out of `incoming`, if there is one.
216    fn take_packet(&mut self) -> Result<bool> {
217        if self.incoming.len() < HEADER_LEN {
218            return Ok(false);
219        }
220        let header = PacketHeader::parse(&self.incoming)?;
221        let total = header.length as usize;
222        if total < HEADER_LEN {
223            return Err(Error::Protocol("packet length is impossibly small".into()));
224        }
225        if self.incoming.len() < total {
226            return Ok(false);
227        }
228
229        self.ready = self.incoming[HEADER_LEN..total].to_vec();
230        self.ready_at = 0;
231        self.incoming.drain(..total);
232        Ok(true)
233    }
234}
235
236fn protocol_io(error: Error) -> io::Error {
237    io::Error::new(io::ErrorKind::InvalidData, error.to_string())
238}
239
240impl AsyncRead for TdsHandshakeStream {
241    fn poll_read(
242        mut self: Pin<&mut Self>,
243        cx: &mut Context<'_>,
244        buf: &mut ReadBuf<'_>,
245    ) -> Poll<io::Result<()>> {
246        loop {
247            if self.ready_at < self.ready.len() {
248                let take = buf.remaining().min(self.ready.len() - self.ready_at);
249                let at = self.ready_at;
250                buf.put_slice(&self.ready[at..at + take]);
251                self.ready_at += take;
252                return Poll::Ready(Ok(()));
253            }
254
255            if !self.wrapping {
256                // Anything already unwrapped has been delivered; from here the
257                // socket is the TLS session's own.
258                if !self.incoming.is_empty() {
259                    self.ready = std::mem::take(&mut self.incoming);
260                    self.ready_at = 0;
261                    continue;
262                }
263                return Pin::new(&mut self.socket).poll_read(cx, buf);
264            }
265
266            if self.take_packet().map_err(protocol_io)? {
267                continue;
268            }
269
270            let mut chunk = [0u8; 8192];
271            let mut incoming = ReadBuf::new(&mut chunk);
272            ready!(Pin::new(&mut self.socket).poll_read(cx, &mut incoming))?;
273
274            let filled = incoming.filled().len();
275            if filled == 0 {
276                // End of file: let the TLS engine report the truncated
277                // handshake, which says far more than an I/O error would.
278                return Poll::Ready(Ok(()));
279            }
280            let bytes = incoming.filled().to_vec();
281            self.incoming.extend_from_slice(&bytes);
282        }
283    }
284}
285
286impl AsyncWrite for TdsHandshakeStream {
287    fn poll_write(
288        mut self: Pin<&mut Self>,
289        cx: &mut Context<'_>,
290        buf: &[u8],
291    ) -> Poll<io::Result<usize>> {
292        if !self.wrapping {
293            return Pin::new(&mut self.socket).poll_write(cx, buf);
294        }
295        // Gathered rather than sent: the framing happens at the flush that ends
296        // the flight, so one flight becomes one TDS message.
297        self.outgoing.extend_from_slice(buf);
298        Poll::Ready(Ok(buf.len()))
299    }
300
301    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
302        if self.wrapping && !self.outgoing.is_empty() {
303            let flight = std::mem::take(&mut self.outgoing);
304            let packet_size = self.packet_size;
305            for packet in split_message(packet::PRE_LOGIN, &flight, packet_size) {
306                self.pending.extend_from_slice(&packet);
307            }
308        }
309
310        while self.pending_at < self.pending.len() {
311            let this = &mut *self;
312            let written =
313                ready!(Pin::new(&mut this.socket).poll_write(cx, &this.pending[this.pending_at..]))?;
314            if written == 0 {
315                return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
316            }
317            self.pending_at += written;
318        }
319        self.pending.clear();
320        self.pending_at = 0;
321
322        Pin::new(&mut self.socket).poll_flush(cx)
323    }
324
325    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
326        Pin::new(&mut self.socket).poll_shutdown(cx)
327    }
328}
329
330/// A TDS connection, before or after encryption.
331///
332/// An enum rather than a boxed trait object for the same reason the HTTP client
333/// uses one: there are exactly two cases, they are known at compile time, and a
334/// virtual call per read on a database connection buys nothing.
335pub enum TdsStream {
336    Plain(TcpStream),
337    Tls(Box<tokio_rustls::client::TlsStream<TdsHandshakeStream>>),
338    /// No transport, only ever seen for the instant it takes to swap one kind
339    /// for another mid-handshake. A real placeholder rather than a dummy socket
340    /// so that a bug here reports itself instead of hanging on a dead file
341    /// descriptor.
342    Closed,
343}
344
345impl TdsStream {
346    pub async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
347        match self {
348            TdsStream::Plain(stream) => stream.write_all(bytes).await,
349            TdsStream::Tls(stream) => stream.write_all(bytes).await,
350            TdsStream::Closed => Err(closed()),
351        }
352    }
353
354    pub async fn flush(&mut self) -> io::Result<()> {
355        match self {
356            TdsStream::Plain(stream) => stream.flush().await,
357            TdsStream::Tls(stream) => stream.flush().await,
358            TdsStream::Closed => Err(closed()),
359        }
360    }
361
362    pub async fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
363        use tokio::io::AsyncReadExt;
364        match self {
365            TdsStream::Plain(stream) => stream.read(buffer).await,
366            TdsStream::Tls(stream) => stream.read(buffer).await,
367            TdsStream::Closed => Err(closed()),
368        }
369    }
370
371    pub async fn shutdown(&mut self) -> io::Result<()> {
372        match self {
373            TdsStream::Plain(stream) => stream.shutdown().await,
374            TdsStream::Tls(stream) => stream.shutdown().await,
375            TdsStream::Closed => Ok(()),
376        }
377    }
378
379    /// Take the transport, leaving [`TdsStream::Closed`] behind.
380    pub fn take(&mut self) -> TdsStream {
381        std::mem::replace(self, TdsStream::Closed)
382    }
383
384    /// Tear the TLS session down and go back to the bare socket, which is what
385    /// ENCRYPT_OFF asks for once the login packet has been sent.
386    pub fn into_plain(self) -> Result<TdsStream> {
387        match self {
388            TdsStream::Tls(stream) => {
389                let (wrapper, _session) = stream.into_inner();
390                Ok(TdsStream::Plain(wrapper.into_socket()?))
391            }
392            other => Ok(other),
393        }
394    }
395}
396
397fn closed() -> io::Error {
398    io::Error::new(io::ErrorKind::NotConnected, "the TDS connection has no transport")
399}
400
401/// How the certificate the server presents is checked.
402#[derive(Debug, Clone, Copy)]
403pub struct TlsOptions {
404    /// Accept whatever certificate the server presents.
405    ///
406    /// On by default, and that is a deliberate, documented compromise: SQL
407    /// Server generates a self-signed certificate at startup unless an
408    /// administrator installs one, so verification would fail against every
409    /// stock installation. Encryption still protects against passive
410    /// eavesdropping; it does not protect against an active attacker until this
411    /// is turned off. Every SQL Server driver makes the same trade under the
412    /// name `TrustServerCertificate`.
413    pub trust_server_certificate: bool,
414}
415
416impl Default for TlsOptions {
417    fn default() -> Self {
418        TlsOptions { trust_server_certificate: true }
419    }
420}
421
422/// Run the TLS handshake through the pre-login tunnel.
423pub async fn start_tls(
424    socket: TcpStream,
425    host: &str,
426    options: TlsOptions,
427    packet_size: usize,
428) -> Result<tokio_rustls::client::TlsStream<TdsHandshakeStream>> {
429    let connector = tokio_rustls::TlsConnector::from(client_config(options));
430
431    // A bare IP address is a valid TLS server name; `try_from` picks the right
432    // representation for it, which matters because a developer's connection
433    // string is nearly always an IP.
434    let name = rustls::pki_types::ServerName::try_from(host.to_string())
435        .map_err(|_| Error::msg(format!("`{host}` is not a valid TLS server name")))?;
436
437    let wrapper = TdsHandshakeStream::new(socket, packet_size);
438    let mut stream = connector.connect(name, wrapper).await.map_err(|e| {
439        Error::msg(format!(
440            "the TLS handshake inside SQL Server's pre-login exchange failed: {e}"
441        ))
442    })?;
443
444    // The handshake is over, so both sides stop framing at exactly this byte.
445    stream.get_mut().0.stop_wrapping();
446    Ok(stream)
447}
448
449/// Build the TLS configuration, once per process.
450///
451/// The provider is named rather than left to rustls to infer: a build holding
452/// two candidates makes rustls refuse to guess, and naming it here makes the
453/// choice a property of this driver rather than of whatever else happens to be
454/// in the dependency graph.
455///
456/// It is `aws_lc_rs` to match the rest of the framework, not for the
457/// post-quantum key exchange that motivated the switch elsewhere: this
458/// connection is pinned to TLS 1.2 (see [`builder`]), and the X25519MLKEM768
459/// hybrid exists only in TLS 1.3. A SQL Server connection is therefore *not*
460/// protected against a recorded-now, decrypted-later attack, and cannot be
461/// until TDS and TLS 1.3 can be made to agree.
462fn client_config(options: TlsOptions) -> Arc<rustls::ClientConfig> {
463    use std::sync::OnceLock;
464    static TRUSTING: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
465    static VERIFYING: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
466
467    if options.trust_server_certificate {
468        Arc::clone(TRUSTING.get_or_init(|| {
469            let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
470            let verifier = Arc::new(TrustAnyCertificate(Arc::clone(&provider)));
471            Arc::new(
472                builder(provider)
473                    .dangerous()
474                    .with_custom_certificate_verifier(verifier)
475                    .with_no_client_auth(),
476            )
477        }))
478    } else {
479        Arc::clone(VERIFYING.get_or_init(|| {
480            // Trust anchors from webpki-roots rather than the OS store, so
481            // behaviour is identical on a laptop and in a scratch container.
482            let roots = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() };
483            Arc::new(
484                builder(Arc::new(rustls::crypto::aws_lc_rs::default_provider()))
485                    .with_root_certificates(roots)
486                    .with_no_client_auth(),
487            )
488        }))
489    }
490}
491
492/// A config builder pinned to TLS 1.2 — see the module documentation. A wrapped
493/// handshake and TLS 1.3's post-handshake tickets cannot both be right.
494fn builder(
495    provider: Arc<rustls::crypto::CryptoProvider>,
496) -> rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier> {
497    rustls::ClientConfig::builder_with_provider(provider)
498        .with_protocol_versions(&[&rustls::version::TLS12])
499        .expect("TLS 1.2 is enabled by this crate's rustls features")
500}
501
502/// A verifier that accepts any certificate, for [`TlsOptions::trust_server_certificate`].
503///
504/// It accepts the handshake signatures unchecked as well as the certificate,
505/// and that is not laziness. SQL Server's auto-generated certificate is an
506/// X.509 **version 1** certificate, which rustls refuses even to parse
507/// (`UnsupportedCertVersion`) — so the signature check cannot run against a
508/// stock installation at all, because it never gets as far as a public key.
509///
510/// What this mode buys is confidentiality against someone watching the wire,
511/// which is what keeps the login packet's barely-obfuscated password safe. It
512/// buys nothing against an active attacker who can sit in the middle. Turn
513/// [`TlsOptions::trust_server_certificate`] off — after installing a real
514/// certificate on the server — and the ordinary webpki verifier does the whole
515/// job, chain and signatures both.
516#[derive(Debug)]
517struct TrustAnyCertificate(Arc<rustls::crypto::CryptoProvider>);
518
519impl rustls::client::danger::ServerCertVerifier for TrustAnyCertificate {
520    fn verify_server_cert(
521        &self,
522        _end_entity: &rustls::pki_types::CertificateDer<'_>,
523        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
524        _server_name: &rustls::pki_types::ServerName<'_>,
525        _ocsp_response: &[u8],
526        _now: rustls::pki_types::UnixTime,
527    ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
528        Ok(rustls::client::danger::ServerCertVerified::assertion())
529    }
530
531    fn verify_tls12_signature(
532        &self,
533        _message: &[u8],
534        _cert: &rustls::pki_types::CertificateDer<'_>,
535        _dss: &rustls::DigitallySignedStruct,
536    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
537        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
538    }
539
540    fn verify_tls13_signature(
541        &self,
542        _message: &[u8],
543        _cert: &rustls::pki_types::CertificateDer<'_>,
544        _dss: &rustls::DigitallySignedStruct,
545    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
546        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
547    }
548
549    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
550        self.0.signature_verification_algorithms.supported_schemes()
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557    use super::super::protocol::encryption as level;
558
559    #[test]
560    fn the_password_scheme_swaps_nibbles_then_xors_with_a5() {
561        // MS-TDS 2.2.6.4, worked through one character: 'a' is U+0061, so the
562        // UTF-16LE bytes are 0x61 0x00. Swapping nibbles gives 0x16 0x00, and
563        // XOR 0xA5 gives 0xB3 0xA5.
564        assert_eq!(obfuscate_password("a"), vec![0xB3, 0xA5]);
565        assert_eq!(obfuscate_password("abc"), vec![0xB3, 0xA5, 0x83, 0xA5, 0x93, 0xA5]);
566
567        // A high code point still goes through as two UTF-16 code units.
568        assert_eq!(obfuscate_password("é").len(), 2);
569        assert_eq!(obfuscate_password(""), Vec::<u8>::new());
570    }
571
572    #[test]
573    fn obfuscation_is_reversible_which_is_the_whole_point_of_calling_it_that() {
574        for password in ["", "a", "Rustlavel!2026", "pässwörd", "日本語"] {
575            assert_eq!(deobfuscate_password(&obfuscate_password(password)), password);
576        }
577    }
578
579    #[test]
580    fn encryption_choices_map_onto_the_prelogin_option_bytes() {
581        assert_eq!(Encryption::Disabled.as_byte(), level::NOT_SUPPORTED);
582        assert_eq!(Encryption::LoginOnly.as_byte(), level::OFF);
583        assert_eq!(Encryption::Required.as_byte(), level::ON);
584        assert_eq!(Encryption::default(), Encryption::Required);
585    }
586
587    #[test]
588    fn a_server_that_offers_only_login_encryption_gets_login_encryption() {
589        assert_eq!(negotiate(Encryption::LoginOnly, level::OFF).unwrap(), Negotiated::LoginOnly);
590    }
591
592    #[test]
593    fn a_server_that_wants_full_encryption_gets_it_whatever_the_client_preferred() {
594        for server in [level::ON, level::REQUIRED] {
595            assert_eq!(negotiate(Encryption::LoginOnly, server).unwrap(), Negotiated::Session);
596            assert_eq!(negotiate(Encryption::Required, server).unwrap(), Negotiated::Session);
597        }
598    }
599
600    #[test]
601    fn a_server_that_cannot_encrypt_fails_a_connection_that_requires_it() {
602        let error = negotiate(Encryption::Required, level::NOT_SUPPORTED).unwrap_err().to_string();
603        assert!(error.contains("cannot encrypt"), "{error}");
604
605        // Login-only was a preference, so it continues in the clear instead.
606        assert_eq!(negotiate(Encryption::LoginOnly, level::NOT_SUPPORTED).unwrap(), Negotiated::None);
607    }
608
609    #[test]
610    fn refusing_encryption_a_server_requires_is_an_error_not_a_downgrade() {
611        // Silently continuing here would put the password on the wire behind
612        // nothing but a nibble swap.
613        let error = negotiate(Encryption::Disabled, level::REQUIRED).unwrap_err().to_string();
614        assert!(error.contains("requires an encrypted connection"), "{error}");
615
616        assert_eq!(negotiate(Encryption::Disabled, level::NOT_SUPPORTED).unwrap(), Negotiated::None);
617    }
618
619    #[tokio::test]
620    async fn the_handshake_wrapper_frames_a_flight_and_then_gets_out_of_the_way() {
621        use tokio::io::AsyncReadExt;
622
623        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
624        let address = listener.local_addr().unwrap();
625
626        let server = tokio::spawn(async move {
627            let (mut socket, _) = listener.accept().await.unwrap();
628            let mut received = Vec::new();
629            socket.read_buf(&mut received).await.unwrap();
630            // Give the second, unwrapped write time to arrive too.
631            let mut more = Vec::new();
632            let _ = tokio::time::timeout(
633                std::time::Duration::from_millis(200),
634                socket.read_buf(&mut more),
635            )
636            .await;
637            received.extend_from_slice(&more);
638            received
639        });
640
641        let mut wrapper =
642            TdsHandshakeStream::new(TcpStream::connect(address).await.unwrap(), 4096);
643        wrapper.write_all(b"handshake").await.unwrap();
644        wrapper.flush().await.unwrap();
645        wrapper.stop_wrapping();
646        wrapper.write_all(b"raw").await.unwrap();
647        wrapper.flush().await.unwrap();
648
649        let received = server.await.unwrap();
650
651        // The first write arrived inside a PRELOGIN packet.
652        let header = PacketHeader::parse(&received).unwrap();
653        assert_eq!(header.kind, packet::PRE_LOGIN);
654        assert!(header.is_end_of_message());
655        assert_eq!(&received[HEADER_LEN..header.length as usize], b"handshake");
656
657        // The second went out untouched, because the handshake was over.
658        assert_eq!(&received[header.length as usize..], b"raw");
659    }
660}