Skip to main content

mssql_tls/
lib.rs

1//! # mssql-tls
2//!
3//! TLS negotiation layer for SQL Server connections.
4//!
5//! This crate handles the complexity of TLS negotiation for both TDS 7.x
6//! (pre-login encryption negotiation) and TDS 8.0 (strict TLS-first mode).
7//!
8//! ## TDS Version Differences
9//!
10//! ### TDS 7.x (SQL Server 2019 and earlier)
11//! ```text
12//! TCP Connect → PreLogin (cleartext) → TLS Handshake → Login7 (encrypted)
13//! ```
14//!
15//! ### TDS 8.0 (SQL Server 2022+ strict mode)
16//! ```text
17//! TCP Connect → TLS Handshake → PreLogin (encrypted) → Login7 (encrypted)
18//! ```
19//!
20//! ## Features
21//!
22//! - TLS 1.2 and TLS 1.3 support via rustls
23//! - Server certificate validation
24//! - Hostname verification
25//! - Custom certificate authority support
26//! - Client certificate authentication (TDS 8.0)
27//!
28//! ## Security
29//!
30//! By default, this crate validates server certificates using the Mozilla
31//! root certificate store. The `TrustServerCertificate` option disables
32//! validation but logs a warning - this should only be used for development.
33//!
34//! ```rust,ignore
35//! use mssql_tls::{TlsConfig, TlsConnector, default_tls_config};
36//!
37//! // Secure default configuration
38//! let config = default_tls_config()?;
39//!
40//! // Or use the builder pattern
41//! let tls_config = TlsConfig::new()
42//!     .strict_mode(true)  // TDS 8.0
43//!     .min_protocol_version(TlsVersion::Tls13);
44//! ```
45
46#![warn(missing_docs)]
47#![deny(unsafe_code)]
48
49pub mod config;
50pub mod connector;
51pub mod error;
52pub mod prelogin_wrapper;
53
54pub use config::{ClientAuth, TlsConfig, TlsVersion};
55pub use connector::{TlsConnector, default_tls_config};
56pub use error::TlsError;
57pub use prelogin_wrapper::TlsPreloginWrapper;
58
59// Re-export tokio-rustls stream type for convenience
60pub use tokio_rustls::client::TlsStream;
61
62// Re-export rustls PKI types so users can construct TLS configs without adding
63// a direct dependency on the `rustls` crate. Changing these re-exports is a
64// semver-breaking change (this crate is coupled to rustls 0.23.x).
65pub use rustls::pki_types::{CertificateDer, PrivateKeyDer};
66
67/// TDS TLS negotiation mode.
68///
69/// This determines when TLS handshake occurs relative to TDS protocol messages.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71#[non_exhaustive]
72pub enum TlsNegotiationMode {
73    /// TDS 7.x style: TLS handshake occurs after PreLogin exchange.
74    ///
75    /// ```text
76    /// TCP Connect → PreLogin (cleartext) → TLS Handshake → Login7 (encrypted)
77    /// ```
78    ///
79    /// This is the default for SQL Server 2019 and earlier, and for
80    /// SQL Server 2022+ when `Encrypt=true` (not strict).
81    PostPreLogin,
82
83    /// TDS 8.0 strict mode: TLS handshake occurs immediately after TCP connect.
84    ///
85    /// ```text
86    /// TCP Connect → TLS Handshake → PreLogin (encrypted) → Login7 (encrypted)
87    /// ```
88    ///
89    /// This is required for SQL Server 2022+ when `Encrypt=strict` is set.
90    /// All TDS traffic is encrypted, including the PreLogin packet.
91    Strict,
92}
93
94impl TlsNegotiationMode {
95    /// Check if this mode requires TLS before any TDS traffic.
96    #[must_use]
97    pub fn is_tls_first(&self) -> bool {
98        matches!(self, Self::Strict)
99    }
100
101    /// Check if PreLogin is sent in cleartext.
102    #[must_use]
103    pub fn prelogin_encrypted(&self) -> bool {
104        matches!(self, Self::Strict)
105    }
106
107    /// Get the mode from encryption settings.
108    ///
109    /// # Arguments
110    ///
111    /// * `encrypt_strict` - Whether `Encrypt=strict` is set (TDS 8.0)
112    #[must_use]
113    pub fn from_encrypt_mode(encrypt_strict: bool) -> Self {
114        if encrypt_strict {
115            Self::Strict
116        } else {
117            Self::PostPreLogin
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_negotiation_mode_strict() {
128        let mode = TlsNegotiationMode::Strict;
129        assert!(mode.is_tls_first());
130        assert!(mode.prelogin_encrypted());
131    }
132
133    #[test]
134    fn test_negotiation_mode_post_prelogin() {
135        let mode = TlsNegotiationMode::PostPreLogin;
136        assert!(!mode.is_tls_first());
137        assert!(!mode.prelogin_encrypted());
138    }
139
140    #[test]
141    fn test_from_encrypt_mode() {
142        assert_eq!(
143            TlsNegotiationMode::from_encrypt_mode(true),
144            TlsNegotiationMode::Strict
145        );
146        assert_eq!(
147            TlsNegotiationMode::from_encrypt_mode(false),
148            TlsNegotiationMode::PostPreLogin
149        );
150    }
151}