fix(sqlserver): use legacy encryption for fallback

This commit is contained in:
t8y2
2026-07-07 22:51:04 +08:00
parent 5b25299417
commit 8364f468a0
9 changed files with 54 additions and 30 deletions
@@ -1023,8 +1023,10 @@ function isSqlServerLegacyUnencryptedMode(params: string | undefined): boolean {
if (!normalized) return false;
const parsed = new URLSearchParams(normalized);
for (const [key, value] of parsed.entries()) {
if (key.trim().toLowerCase() === "sqlserverencryption") {
return ["disabled", "disable", "false", "0", "off"].includes(value.trim().toLowerCase());
const normalizedKey = key.trim().toLowerCase();
if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") {
// Accept JDBC-style `encrypt=false` from imported SQL Server URLs as the same opt-in.
if (["disabled", "disable", "false", "0", "off", "no"].includes(value.trim().toLowerCase())) return true;
}
}
return false;
@@ -1032,7 +1034,13 @@ function isSqlServerLegacyUnencryptedMode(params: string | undefined): boolean {
function setSqlServerLegacyUnencryptedMode(params: string | undefined, enabled: boolean): string {
const normalized = (params || "").trim().replace(/^\?/, "").replace(/;/g, "&");
return setUrlParam(normalized, "sqlserverEncryption", enabled ? "disabled" : "");
const parsed = new URLSearchParams(normalized);
for (const key of Array.from(parsed.keys())) {
const normalizedKey = key.trim().toLowerCase();
if (normalizedKey === "sqlserverencryption" || normalizedKey === "encrypt") parsed.delete(key);
}
if (enabled) parsed.set("sqlserverEncryption", "disabled");
return parsed.toString();
}
function isSqlServerTlsHandshakeFailure(message: string): boolean {
+2 -2
View File
@@ -305,9 +305,9 @@ export default {
testing: "Testing...",
copyTestResult: "Copy test result",
sqlServerLegacyUnencryptedMode: "Legacy compatibility mode",
sqlServerLegacyUnencryptedModeEnable: "Use SQL Server legacy TLS 1.0 unencrypted connection",
sqlServerLegacyUnencryptedModeEnable: "Use SQL Server legacy unencrypted connection",
sqlServerLegacyUnencryptedModeEnabled: "SQL Server legacy compatibility mode is enabled.",
sqlServerLegacyUnencryptedModeHint: "Applies to unencrypted transport or login-only encryption. It will still fail if the server requires TLS 1.0 encryption.\nUse it only on trusted networks, VPNs, or SSH tunnels",
sqlServerLegacyUnencryptedModeHint: "Applies to unencrypted transport or login-only encryption. It will still fail if the server requires encrypted transport that the embedded driver cannot negotiate.\nUse it only on trusted networks, VPNs, or SSH tunnels",
saveAndConnect: "Save & Connect",
save: "Save",
editTitle: "Edit Connection",
+2 -2
View File
@@ -307,9 +307,9 @@ export default withEnglishFallback({
testing: "Probando...",
copyTestResult: "Copiar resultado de prueba",
sqlServerLegacyUnencryptedMode: "Modo de compatibilidad heredado",
sqlServerLegacyUnencryptedModeEnable: "Usar conexión SQL Server heredada TLS 1.0 sin cifrar",
sqlServerLegacyUnencryptedModeEnable: "Usar conexión SQL Server heredada sin cifrar",
sqlServerLegacyUnencryptedModeEnabled: "El modo de compatibilidad heredado de SQL Server está activado.",
sqlServerLegacyUnencryptedModeHint: "Aplica a transporte sin cifrar o cifrado solo en el login. Seguirá fallando si el servidor requiere cifrado TLS 1.0.\nÚsalo solo en redes de confianza, VPN o túneles SSH",
sqlServerLegacyUnencryptedModeHint: "Aplica a transporte sin cifrar o cifrado solo en el login. Seguirá fallando si el servidor requiere transporte cifrado que el controlador integrado no puede negociar.\nÚsalo solo en redes de confianza, VPN o túneles SSH",
saveAndConnect: "Guardar y conectar",
save: "Guardar",
editTitle: "Editar conexión",
+2 -2
View File
@@ -306,9 +306,9 @@ export default withEnglishFallback({
testing: "Verifica in corso...",
copyTestResult: "Copia risultato test",
sqlServerLegacyUnencryptedMode: "Modalità compatibilità legacy",
sqlServerLegacyUnencryptedModeEnable: "Usa connessione non crittografata legacy TLS 1.0 SQL Server",
sqlServerLegacyUnencryptedModeEnable: "Usa connessione non crittografata legacy SQL Server",
sqlServerLegacyUnencryptedModeEnabled: "La modalità compatibilità legacy di SQL Server è abilitata.",
sqlServerLegacyUnencryptedModeHint: "Si applica al trasporto non crittografato o alla crittografia solo per il login. Fallirà comunque se il server richiede la crittografia TLS 1.0.\nUsala solo su reti attendibili, VPN o tunnel SSH",
sqlServerLegacyUnencryptedModeHint: "Si applica al trasporto non crittografato o alla crittografia solo per il login. Fallirà comunque se il server richiede un trasporto crittografato che il driver integrato non può negoziare.\nUsala solo su reti attendibili, VPN o tunnel SSH",
saveAndConnect: "Salva & Connetti",
save: "Salva",
editTitle: "Modifica Connessione",
+2 -2
View File
@@ -300,9 +300,9 @@ export default withEnglishFallback({
testing: "テスト中...",
copyTestResult: "テスト結果をコピー",
sqlServerLegacyUnencryptedMode: "レガシー互換モード",
sqlServerLegacyUnencryptedModeEnable: "SQL Server レガシー TLS 1.0 非暗号化接続を使用",
sqlServerLegacyUnencryptedModeEnable: "SQL Server レガシー非暗号化接続を使用",
sqlServerLegacyUnencryptedModeEnabled: "SQL Server レガシー互換モードが有効です。",
sqlServerLegacyUnencryptedModeHint: "非暗号化またはログイン時のみ暗号化する場合に適用されます。サーバーが TLS 1.0 暗号化を要求する場合は失敗します。\n信頼できるネットワーク、VPN、SSH トンネルでのみ使用してください",
sqlServerLegacyUnencryptedModeHint: "非暗号化またはログイン時のみ暗号化する場合に適用されます。サーバーが内蔵ドライバーでネゴシエートできない暗号化通信を要求する場合は失敗します。\n信頼できるネットワーク、VPN、SSH トンネルでのみ使用してください",
saveAndConnect: "保存して接続",
save: "保存",
editTitle: "接続を編集",
+2 -2
View File
@@ -307,9 +307,9 @@ export default withEnglishFallback({
testing: "Testando...",
copyTestResult: "Copiar resultado do teste",
sqlServerLegacyUnencryptedMode: "Modo de compatibilidade legado",
sqlServerLegacyUnencryptedModeEnable: "Usar conexão SQL Server legada TLS 1.0 não criptografada",
sqlServerLegacyUnencryptedModeEnable: "Usar conexão SQL Server legada não criptografada",
sqlServerLegacyUnencryptedModeEnabled: "O modo de compatibilidade legado do SQL Server está ativado.",
sqlServerLegacyUnencryptedModeHint: "Aplica-se a transporte não criptografado ou criptografia apenas no login. Ainda falhará se o servidor exigir criptografia TLS 1.0.\nUse apenas em redes confiáveis, VPNs ou túneis SSH",
sqlServerLegacyUnencryptedModeHint: "Aplica-se a transporte não criptografado ou criptografia apenas no login. Ainda falhará se o servidor exigir transporte criptografado que o driver integrado não consegue negociar.\nUse apenas em redes confiáveis, VPNs ou túneis SSH",
saveAndConnect: "Salvar e Conectar",
save: "Salvar",
editTitle: "Editar Conexão",
+2 -2
View File
@@ -307,9 +307,9 @@ export default withEnglishFallback({
testing: "测试中...",
copyTestResult: "复制测试结果",
sqlServerLegacyUnencryptedMode: "旧版兼容模式",
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 旧版TLS 1.0非加密连接",
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 旧版非加密连接",
sqlServerLegacyUnencryptedModeEnabled: "已启用 SQL Server 旧版兼容模式。",
sqlServerLegacyUnencryptedModeHint: "适用于非加密或仅登录阶段加密的场景。若服务端强制 TLS 1.0 加密,此模式仍会失败\n请仅在可信内网、VPN 或 SSH 隧道中使用",
sqlServerLegacyUnencryptedModeHint: "适用于非加密或仅登录阶段加密的场景。若服务端强制使用内置驱动无法协商的加密传输,此模式仍会失败\n请仅在可信内网、VPN 或 SSH 隧道中使用",
saveAndConnect: "保存并连接",
save: "保存",
editTitle: "编辑连接",
+2 -2
View File
@@ -307,9 +307,9 @@ export default withEnglishFallback({
testing: "測試中……",
copyTestResult: "複製測試結果",
sqlServerLegacyUnencryptedMode: "舊版相容模式",
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 舊版 TLS 1.0 非加密連線",
sqlServerLegacyUnencryptedModeEnable: "使用 SQL Server 舊版非加密連線",
sqlServerLegacyUnencryptedModeEnabled: "已啟用 SQL Server 舊版相容模式。",
sqlServerLegacyUnencryptedModeHint: "適用於非加密或僅登入階段加密的場景。若伺服器強制 TLS 1.0 加密,此模式仍會失敗\n請僅在可信內網、VPN 或 SSH 隧道中使用",
sqlServerLegacyUnencryptedModeHint: "適用於非加密或僅登入階段加密的場景。若伺服器強制使用內建驅動無法協商的加密傳輸,此模式仍會失敗\n請僅在可信內網、VPN 或 SSH 隧道中使用",
saveAndConnect: "儲存並連線",
save: "儲存",
editTitle: "編輯連線",
+29 -13
View File
@@ -17,6 +17,8 @@ use tokio_util::sync::CancellationToken;
pub type SqlServerClient = Client<Compat<TcpStream>>;
pub const SQLSERVER_DRIVER_PANIC_ERROR_PREFIX: &str = "SQL Server driver panic:";
const SIMPLE_QUERY_MODULE_KEYWORDS: &[&str] = &["FUNCTION", "PROC", "PROCEDURE", "TRIGGER", "VIEW"];
// Match JDBC/tiberius `encrypt=false`: encrypt only login, then drop back to raw TDS.
// `NotSupported` sends ENCRYPT_NOT_SUP and is rejected by some legacy SQL Server setups.
const SQLSERVER_LEGACY_ENCRYPTION_LEVEL: tiberius::EncryptionLevel = tiberius::EncryptionLevel::Off;
#[derive(Debug, PartialEq, Eq)]
@@ -55,23 +57,24 @@ pub async fn connect(
match try_connect(host, port, user, pass, database, tiberius::EncryptionLevel::Required, timeout).await {
Ok(client) => Ok(client),
Err(encrypted_error) => {
try_connect(host, port, user, pass, database, tiberius::EncryptionLevel::NotSupported, timeout)
.await
.map_err(|plain_error| {
try_connect(host, port, user, pass, database, SQLSERVER_LEGACY_ENCRYPTION_LEVEL, timeout).await.map_err(
|plain_error| {
if is_sqlserver_tls_handshake_error(&encrypted_error) {
format!(
"{encrypted_error}\n\nThis may be caused by an old SQL Server TLS configuration. \
If you are connecting to SQL Server 2008/2008 R2 or another legacy instance, \
"{encrypted_error}\n\nThis may be caused by an old SQL Server TLS/encryption configuration. \
If you are connecting to SQL Server 2008/2008 R2/2012 or another legacy instance, \
try SQL Server legacy unencrypted mode. It behaves like encrypt=false and only helps \
when the server allows unencrypted transport or login-only encryption. It will still fail \
if the server requires TLS 1.0 encryption. Only use this mode on trusted networks, VPNs, \
if the server requires encrypted transport that the embedded driver cannot negotiate. \
Only use this mode on trusted networks, VPNs, \
or SSH tunnels.\n\n\
Automatic unencrypted fallback also failed: {plain_error}"
)
Automatic legacy unencrypted fallback also failed: {plain_error}"
)
} else {
plain_error
}
})
},
)
}
}
}
@@ -82,8 +85,10 @@ fn sqlserver_legacy_encryption_disabled(url_params: Option<&str>) -> bool {
};
params.trim_start_matches('?').split(['&', ';']).filter_map(|pair| pair.split_once('=')).any(|(key, value)| {
key.trim().eq_ignore_ascii_case("sqlserverEncryption")
&& matches!(value.trim().to_ascii_lowercase().as_str(), "disabled" | "disable" | "false" | "0" | "off")
let key = key.trim();
let value = value.trim().to_ascii_lowercase();
let disabled = matches!(value.as_str(), "disabled" | "disable" | "false" | "0" | "off" | "no");
(key.eq_ignore_ascii_case("sqlserverEncryption") || key.eq_ignore_ascii_case("encrypt")) && disabled
})
}
@@ -1860,12 +1865,14 @@ mod tests {
}
#[test]
fn sqlserver_legacy_encryption_flag_is_opt_in() {
fn sqlserver_legacy_encryption_flag_accepts_dbx_and_jdbc_params() {
assert!(!super::sqlserver_legacy_encryption_disabled(None));
assert!(!super::sqlserver_legacy_encryption_disabled(Some("encrypt=false")));
assert!(!super::sqlserver_legacy_encryption_disabled(Some("encrypt=true")));
assert!(super::sqlserver_legacy_encryption_disabled(Some("sqlserverEncryption=disabled")));
assert!(super::sqlserver_legacy_encryption_disabled(Some("applicationName=dbx;sqlserverEncryption=off")));
assert!(super::sqlserver_legacy_encryption_disabled(Some("?sqlserverEncryption=false&applicationName=dbx")));
assert!(super::sqlserver_legacy_encryption_disabled(Some("applicationName=dbx;encrypt=false")));
assert!(super::sqlserver_legacy_encryption_disabled(Some("?Encrypt=0&applicationName=dbx")));
}
#[test]
@@ -1873,6 +1880,15 @@ mod tests {
assert_eq!(super::SQLSERVER_LEGACY_ENCRYPTION_LEVEL, tiberius::EncryptionLevel::Off);
}
#[test]
fn sqlserver_automatic_fallback_uses_legacy_encryption_mode() {
let source = include_str!("sqlserver.rs");
let connect = source.split("pub async fn connect").nth(1).unwrap();
let connect = connect.split("fn sqlserver_legacy_encryption_disabled").next().unwrap();
assert!(connect.contains("database, SQLSERVER_LEGACY_ENCRYPTION_LEVEL, timeout"));
assert!(!connect.contains("EncryptionLevel::NotSupported, timeout"));
}
#[test]
fn sqlserver_tls_handshake_error_detection_matches_legacy_hint_cases() {
assert!(super::is_sqlserver_tls_handshake_error(