-
Notifications
You must be signed in to change notification settings - Fork 153
Strict encryption option #412, hostname_in_certificate option #340, t… #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2634e8c
5bff405
702d9bc
2ff4766
51f71f6
59c6483
478e770
e839e4e
30f0569
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,8 @@ pub struct Config { | |||||||||||||||||||||||||||||||
| pub(crate) trust: TrustConfig, | ||||||||||||||||||||||||||||||||
| pub(crate) auth: AuthMethod, | ||||||||||||||||||||||||||||||||
| pub(crate) readonly: bool, | ||||||||||||||||||||||||||||||||
| pub(crate) hostname_in_certificate: Option<String>, | ||||||||||||||||||||||||||||||||
| pub(crate) client_name: Option<String>, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[derive(Clone, Debug)] | ||||||||||||||||||||||||||||||||
|
|
@@ -65,6 +67,8 @@ impl Default for Config { | |||||||||||||||||||||||||||||||
| trust: TrustConfig::Default, | ||||||||||||||||||||||||||||||||
| auth: AuthMethod::None, | ||||||||||||||||||||||||||||||||
| readonly: false, | ||||||||||||||||||||||||||||||||
| hostname_in_certificate: None, | ||||||||||||||||||||||||||||||||
| client_name: None, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
@@ -149,14 +153,29 @@ impl Config { | |||||||||||||||||||||||||||||||
| /// Will panic in case `trust_cert` was called before. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// - Defaults to validating the server certificate is validated against system's certificate storage. | ||||||||||||||||||||||||||||||||
| pub fn trust_cert_ca(&mut self, path: impl ToString) { | ||||||||||||||||||||||||||||||||
| pub fn trust_cert_ca(&mut self, path: impl Into<PathBuf>) { | ||||||||||||||||||||||||||||||||
| if let TrustConfig::TrustAll = &self.trust { | ||||||||||||||||||||||||||||||||
| panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.") | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string())) | ||||||||||||||||||||||||||||||||
| self.trust = TrustConfig::CaCertificateLocation(path.into()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Sets the hostname to be used for certificate validation. | ||||||||||||||||||||||||||||||||
| /// If not set, the hostname from `host` will be used. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// - Defaults to the value of `host`. | ||||||||||||||||||||||||||||||||
| pub fn hostname_in_certificate(&mut self, hostname: impl ToString) { | ||||||||||||||||||||||||||||||||
| self.hostname_in_certificate = Some(hostname.to_string()); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Sets the client name to be sent to the server. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// - Defaults to the current workstation id (hostname). | ||||||||||||||||||||||||||||||||
| pub fn client_name(&mut self, name: impl ToString) { | ||||||||||||||||||||||||||||||||
| self.client_name = Some(name.to_string()); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Sets the authentication method. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// - Defaults to `None`. | ||||||||||||||||||||||||||||||||
|
|
@@ -190,6 +209,12 @@ impl Config { | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| pub(crate) fn get_hostname_in_certificate(&self) -> &str { | ||||||||||||||||||||||||||||||||
| self.hostname_in_certificate | ||||||||||||||||||||||||||||||||
| .as_deref() | ||||||||||||||||||||||||||||||||
| .unwrap_or_else(|| self.get_host()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Get the host address including port | ||||||||||||||||||||||||||||||||
| pub fn get_addr(&self) -> String { | ||||||||||||||||||||||||||||||||
| format!("{}:{}", self.get_host(), self.get_port()) | ||||||||||||||||||||||||||||||||
|
|
@@ -210,7 +235,7 @@ impl Config { | |||||||||||||||||||||||||||||||
| /// |`database`|`<string>`|The name of the database.| | ||||||||||||||||||||||||||||||||
| /// |`TrustServerCertificate`|`true`,`false`,`yes`,`no`|Specifies whether the driver trusts the server certificate when connecting using TLS. Cannot be used toghether with `TrustServerCertificateCA`| | ||||||||||||||||||||||||||||||||
| /// |`TrustServerCertificateCA`|`<path>`|Path to a `pem`, `crt` or `der` certificate file. Cannot be used together with `TrustServerCertificate`| | ||||||||||||||||||||||||||||||||
| /// |`encrypt`|`true`,`false`,`yes`,`no`,`DANGER_PLAINTEXT`|Specifies whether the driver uses TLS to encrypt communication.| | ||||||||||||||||||||||||||||||||
| /// |`encrypt`|`strict`,`true`,`false`,`yes`,`no`,`DANGER_PLAINTEXT`|Specifies whether the driver uses TLS to encrypt communication.| | ||||||||||||||||||||||||||||||||
| /// |`Application Name`, `ApplicationName`|`<string>`|Sets the application name for the connection.| | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// [ADO.NET connection string]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings | ||||||||||||||||||||||||||||||||
|
|
@@ -265,10 +290,18 @@ impl Config { | |||||||||||||||||||||||||||||||
| builder.trust_cert_ca(ca); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if let Some(hostname_in_cert) = s.hostname_in_certificate() { | ||||||||||||||||||||||||||||||||
| builder.hostname_in_certificate(hostname_in_cert); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| builder.encryption(s.encrypt()?); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| builder.readonly(s.readonly()); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if let Some(client_name) = s.client_name() { | ||||||||||||||||||||||||||||||||
| builder.client_name(client_name); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Ok(builder) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
@@ -346,6 +379,20 @@ pub(crate) trait ConfigString { | |||||||||||||||||||||||||||||||
| .map(|ca| ca.to_string()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| fn hostname_in_certificate(&self) -> Option<String> { | ||||||||||||||||||||||||||||||||
| self.dict() | ||||||||||||||||||||||||||||||||
| .get("hostnameincertificate") | ||||||||||||||||||||||||||||||||
| .or_else(|| self.dict().get("hostname in certificate")) | ||||||||||||||||||||||||||||||||
| .map(|ca| ca.to_string()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| fn client_name(&self) -> Option<String> { | ||||||||||||||||||||||||||||||||
| self.dict() | ||||||||||||||||||||||||||||||||
| .get("workstationid") | ||||||||||||||||||||||||||||||||
| .or_else(|| self.dict().get("workstation id")) | ||||||||||||||||||||||||||||||||
| .map(|name| name.to_string()) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| #[cfg(any( | ||||||||||||||||||||||||||||||||
| feature = "rustls", | ||||||||||||||||||||||||||||||||
| feature = "native-tls", | ||||||||||||||||||||||||||||||||
|
|
@@ -358,6 +405,9 @@ pub(crate) trait ConfigString { | |||||||||||||||||||||||||||||||
| Ok(true) => Ok(EncryptionLevel::Required), | ||||||||||||||||||||||||||||||||
| Ok(false) => Ok(EncryptionLevel::Off), | ||||||||||||||||||||||||||||||||
| Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported), | ||||||||||||||||||||||||||||||||
| Err(_) if val.eq_ignore_ascii_case("strict") && cfg!(feature = "tds80") => { | ||||||||||||||||||||||||||||||||
| Ok(EncryptionLevel::Strict) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+408
to
+410
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider improving error message when When ♻️ Proposed improvement for clearer error handling Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
- Err(_) if val.eq_ignore_ascii_case("strict") && cfg!(feature = "tds80") => {
- Ok(EncryptionLevel::Strict)
- }
+ Err(_) if val.eq_ignore_ascii_case("strict") => {
+ #[cfg(feature = "tds80")]
+ {
+ Ok(EncryptionLevel::Strict)
+ }
+ #[cfg(not(feature = "tds80"))]
+ {
+ Err(crate::Error::Conversion(
+ "encrypt=strict requires the 'tds80' feature to be enabled".into(),
+ ))
+ }
+ }
Err(e) => Err(e),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| Err(e) => Err(e), | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
| .unwrap_or(Ok(EncryptionLevel::Off)) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the breaking change in default features.
Changing the default feature set from
tds73totds80is a breaking change for downstream users. Users who relied on the previous defaults will now automatically receive TDS 8.0 behavior, including:EncryptionLevel::Strictsupport (requires TLS handshake with ALPN)Users who need the previous behavior must explicitly opt out of default features and select
tds73in theirCargo.toml:This change should be prominently documented in the CHANGELOG/release notes with migration guidance.
Note: Since
tds80 = ["tds73"], the tds80 feature is additive and preserves tds73 behavior while enabling additional functionality. However, the new code paths (Strict encryption, ALPN negotiation) may introduce behavioral differences that users should be aware of.🤖 Prompt for AI Agents