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}