This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 73890b694 feat(csharp/src/Drivers/Apache): Implement self signed ssl
certificate validation for Spark, Impala & Hive (#3224)
73890b694 is described below
commit 73890b694dfe94369681cfb40c3e22cf5f6ee0d9
Author: Sudhir Reddy Emmadi <[email protected]>
AuthorDate: Tue Aug 5 23:11:13 2025 +0530
feat(csharp/src/Drivers/Apache): Implement self signed ssl certificate
validation for Spark, Impala & Hive (#3224)
Co-authored-by: Sudhir Emmadi <[email protected]>
---
.../Apache/Hive2/HiveServer2StandardConnection.cs | 3 +-
.../src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs | 96 ++++++++++++----------
.../Apache/Impala/ImpalaStandardConnection.cs | 6 +-
.../Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs | 4 -
4 files changed, 60 insertions(+), 49 deletions(-)
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
index 66f175a14..4f89a3904 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Net;
+using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -115,7 +116,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
? new X509Certificate2(TlsOptions.TrustedCertificatePath!)
: null;
- var certValidator =
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions);
+ RemoteCertificateValidationCallback certValidator = (sender,
cert, chain, errors) => HiveServer2TlsImpl.ValidateCertificate(cert, errors,
TlsOptions);
if (IPAddress.TryParse(hostName!, out var ipAddress))
{
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
index 345974d77..9a9311d7a 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
@@ -65,11 +65,8 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
tlsProperties.DisableServerCertificateValidation = false;
tlsProperties.AllowHostnameMismatch =
properties.TryGetValue(HttpTlsOptions.AllowHostnameMismatch, out string?
allowHostnameMismatch) && bool.TryParse(allowHostnameMismatch, out bool
allowHostnameMismatchBool) && allowHostnameMismatchBool;
tlsProperties.AllowSelfSigned =
properties.TryGetValue(HttpTlsOptions.AllowSelfSigned, out string?
allowSelfSigned) && bool.TryParse(allowSelfSigned, out bool
allowSelfSignedBool) && allowSelfSignedBool;
- if (tlsProperties.AllowSelfSigned)
- {
- if
(!properties.TryGetValue(HttpTlsOptions.TrustedCertificatePath, out string?
trustedCertificatePath)) return tlsProperties;
- tlsProperties.TrustedCertificatePath = trustedCertificatePath
!= "" && File.Exists(trustedCertificatePath) ? trustedCertificatePath : throw
new FileNotFoundException("Trusted certificate path is invalid or file does not
exist.");
- }
+ if (!properties.TryGetValue(HttpTlsOptions.TrustedCertificatePath,
out string? trustedCertificatePath)) return tlsProperties;
+ tlsProperties.TrustedCertificatePath = trustedCertificatePath !=
"" && File.Exists(trustedCertificatePath) ? trustedCertificatePath : throw new
FileNotFoundException("Trusted certificate path is invalid or file does not
exist.");
return tlsProperties;
}
@@ -78,33 +75,39 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
HttpClientHandler httpClientHandler = new();
if (tlsProperties.IsTlsEnabled)
{
- httpClientHandler.ServerCertificateCustomValidationCallback =
(request, certificate, chain, policyErrors) =>
- {
- if (policyErrors == SslPolicyErrors.None ||
tlsProperties.DisableServerCertificateValidation) return true;
- if
(string.IsNullOrEmpty(tlsProperties.TrustedCertificatePath))
- {
- return
-
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) ||
tlsProperties.AllowSelfSigned)
- &&
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) ||
tlsProperties.AllowHostnameMismatch);
- }
- if (certificate == null)
- return false;
- X509Certificate2 customCertificate = new
X509Certificate2(tlsProperties.TrustedCertificatePath);
- X509Chain chain2 = new X509Chain();
- chain2.ChainPolicy.ExtraStore.Add(customCertificate);
-
- // "tell the X509Chain class that I do trust this root
certs and it should check just the certs in the chain and nothing else"
- chain2.ChainPolicy.VerificationFlags =
X509VerificationFlags.AllowUnknownCertificateAuthority;
-
- // Build the chain and verify
- return chain2.Build(certificate);
- };
+ httpClientHandler.ServerCertificateCustomValidationCallback =
(request, cert, chain, errors) => ValidateCertificate(cert, errors,
tlsProperties);
}
proxyConfigurator.ConfigureProxy(httpClientHandler);
httpClientHandler.AutomaticDecompression =
DecompressionMethods.GZip | DecompressionMethods.Deflate;
return httpClientHandler;
}
+ static private bool IsSelfSigned(X509Certificate2 cert)
+ {
+ return cert.Subject == cert.Issuer && IsSignedBy(cert, cert);
+ }
+
+ static private bool IsSignedBy(X509Certificate2 cert, X509Certificate2
issuer)
+ {
+ try
+ {
+ using (var chain = new X509Chain())
+ {
+ chain.ChainPolicy.ExtraStore.Add(issuer);
+ chain.ChainPolicy.VerificationFlags =
X509VerificationFlags.AllowUnknownCertificateAuthority;
+ chain.ChainPolicy.RevocationMode =
X509RevocationMode.Online;
+
+ return chain.Build(cert)
+ && chain.ChainElements.Count == 1
+ && chain.ChainElements[0].Certificate.Thumbprint ==
issuer.Thumbprint;
+ }
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
static internal TlsProperties
GetStandardTlsOptions(IReadOnlyDictionary<string, string> properties)
{
TlsProperties tlsProperties = new();
@@ -130,28 +133,37 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
tlsProperties.DisableServerCertificateValidation = false;
tlsProperties.AllowHostnameMismatch =
properties.TryGetValue(StandardTlsOptions.AllowHostnameMismatch, out string?
allowHostnameMismatch) && bool.TryParse(allowHostnameMismatch, out bool
allowHostnameMismatchBool) && allowHostnameMismatchBool;
tlsProperties.AllowSelfSigned =
properties.TryGetValue(StandardTlsOptions.AllowSelfSigned, out string?
allowSelfSigned) && bool.TryParse(allowSelfSigned, out bool
allowSelfSignedBool) && allowSelfSignedBool;
- if (tlsProperties.AllowSelfSigned)
- {
- if
(!properties.TryGetValue(StandardTlsOptions.TrustedCertificatePath, out string?
trustedCertificatePath)) return tlsProperties;
- tlsProperties.TrustedCertificatePath = trustedCertificatePath
!= "" && File.Exists(trustedCertificatePath) ? trustedCertificatePath : throw
new FileNotFoundException("Trusted certificate path is invalid or file does not
exist.");
- }
+ if
(!properties.TryGetValue(StandardTlsOptions.TrustedCertificatePath, out string?
trustedCertificatePath)) return tlsProperties;
+ tlsProperties.TrustedCertificatePath = trustedCertificatePath !=
"" && File.Exists(trustedCertificatePath) ? trustedCertificatePath : throw new
FileNotFoundException("Trusted certificate path is invalid or file does not
exist.");
return tlsProperties;
}
- static internal RemoteCertificateValidationCallback
GetCertificateValidator(TlsProperties tlsProperties)
+ static internal bool ValidateCertificate(X509Certificate? cert,
SslPolicyErrors policyErrors, TlsProperties tlsProperties)
{
- return (object sender, X509Certificate? certificate, X509Chain?
chain, SslPolicyErrors policyErrors) =>
- {
- if (policyErrors == SslPolicyErrors.None ||
tlsProperties.DisableServerCertificateValidation) return true;
- if (string.IsNullOrEmpty(tlsProperties.TrustedCertificatePath))
- {
- return
-
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) ||
tlsProperties.AllowSelfSigned)
- &&
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) ||
tlsProperties.AllowHostnameMismatch);
- }
+ if (policyErrors == SslPolicyErrors.None ||
tlsProperties.DisableServerCertificateValidation)
+ return true;
+ if (cert == null || !(cert is X509Certificate2 cert2))
return false;
- };
+
+ bool isNameMismatchError =
policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) &&
!tlsProperties.AllowHostnameMismatch;
+
+ if (isNameMismatchError) return false;
+
+ if (string.IsNullOrEmpty(tlsProperties.TrustedCertificatePath))
+ {
+ return
!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) ||
(tlsProperties.AllowSelfSigned && IsSelfSigned(cert2));
+ }
+
+ X509Certificate2 trustedRoot = new
X509Certificate2(tlsProperties.TrustedCertificatePath);
+ X509Chain customChain = new();
+ customChain.ChainPolicy.ExtraStore.Add(trustedRoot);
+ // "tell the X509Chain class that I do trust this root certs and
it should check just the certs in the chain and nothing else"
+ customChain.ChainPolicy.VerificationFlags =
X509VerificationFlags.AllowUnknownCertificateAuthority;
+ customChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
+
+ bool chainValid = customChain.Build(cert2);
+ return chainValid || (tlsProperties.AllowSelfSigned &&
IsSelfSigned(cert2));
}
}
}
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
index 6cbef924a..a87a7973b 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Net;
+using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -115,13 +116,14 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
TTransport transport;
if (TlsOptions.IsTlsEnabled)
{
+ RemoteCertificateValidationCallback certValidator = (sender,
cert, chain, errors) => HiveServer2TlsImpl.ValidateCertificate(cert, errors,
TlsOptions);
if (IPAddress.TryParse(hostName!, out var address))
{
- transport = new TTlsSocketTransport(address!,
int.Parse(port!), config: new(), 0,
!string.IsNullOrEmpty(TlsOptions.TrustedCertificatePath) ? new
X509Certificate2(TlsOptions.TrustedCertificatePath!) : null, certValidator:
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions));
+ transport = new TTlsSocketTransport(address!,
int.Parse(port!), config: new(), 0, null, certValidator: certValidator);
}
else
{
- transport = new TTlsSocketTransport(hostName!,
int.Parse(port!), config: new(), 0,
!string.IsNullOrEmpty(TlsOptions.TrustedCertificatePath) ? new
X509Certificate2(TlsOptions.TrustedCertificatePath!) : null, certValidator:
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions));
+ transport = new TTlsSocketTransport(hostName!,
int.Parse(port!), config: new(), 0, null, certValidator: certValidator);
}
}
else
diff --git a/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
index f707b992f..7ae6a207f 100644
--- a/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
+++ b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
@@ -64,8 +64,6 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
yield return new object?[] { new Dictionary<string, string> { {
HttpTlsOptions.IsTlsEnabled, "True" } }, new TlsProperties { IsTlsEnabled =
true, DisableServerCertificateValidation = false, AllowSelfSigned = false,
AllowHostnameMismatch = false } };
yield return new object?[] { new Dictionary<string, string> { {
HttpTlsOptions.IsTlsEnabled, "tRUe" }, { HttpTlsOptions.AllowSelfSigned, "true"
} }, new TlsProperties { IsTlsEnabled = true,
DisableServerCertificateValidation = false, AllowSelfSigned = true,
AllowHostnameMismatch = false } };
yield return new object?[] { new Dictionary<string, string> { {
HttpTlsOptions.IsTlsEnabled, "TruE" }, { HttpTlsOptions.AllowSelfSigned, "True"
}, { HttpTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties {
IsTlsEnabled = true, DisableServerCertificateValidation = false,
AllowSelfSigned = true, AllowHostnameMismatch = true } };
- // certificate path is ignored if self signed is not allowed
- yield return new object?[] { new Dictionary<string, string> { {
HttpTlsOptions.IsTlsEnabled, "True" }, { HttpTlsOptions.AllowSelfSigned,
"False" }, { HttpTlsOptions.AllowHostnameMismatch, "True" }, {
HttpTlsOptions.TrustedCertificatePath, "" } }, new TlsProperties { IsTlsEnabled
= true, DisableServerCertificateValidation = false, AllowSelfSigned = false,
AllowHostnameMismatch = true } };
// invalid certificate path
yield return new object?[] { new Dictionary<string, string> { {
HttpTlsOptions.IsTlsEnabled, "True" }, { HttpTlsOptions.AllowSelfSigned, "True"
}, { HttpTlsOptions.AllowHostnameMismatch, "True" }, {
HttpTlsOptions.TrustedCertificatePath, "" } }, null,
typeof(FileNotFoundException) };
}
@@ -86,8 +84,6 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
yield return new object?[] { new Dictionary<string, string> { {
StandardTlsOptions.IsTlsEnabled, "True" } }, new TlsProperties { IsTlsEnabled =
true, DisableServerCertificateValidation = false, AllowSelfSigned = false,
AllowHostnameMismatch = false } };
yield return new object?[] { new Dictionary<string, string> { {
StandardTlsOptions.IsTlsEnabled, "tRUe" }, {
StandardTlsOptions.AllowSelfSigned, "true" } }, new TlsProperties {
IsTlsEnabled = true, DisableServerCertificateValidation = false,
AllowSelfSigned = true, AllowHostnameMismatch = false } };
yield return new object?[] { new Dictionary<string, string> { {
StandardTlsOptions.IsTlsEnabled, "TruE" }, {
StandardTlsOptions.AllowSelfSigned, "True" }, {
StandardTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties {
IsTlsEnabled = true, DisableServerCertificateValidation = false,
AllowSelfSigned = true, AllowHostnameMismatch = true } };
- // certificate path is ignored if self signed is not allowed
- yield return new object?[] { new Dictionary<string, string> { {
StandardTlsOptions.IsTlsEnabled, "True" }, {
StandardTlsOptions.AllowSelfSigned, "False" }, {
StandardTlsOptions.AllowHostnameMismatch, "True" }, {
StandardTlsOptions.TrustedCertificatePath, "" } }, new TlsProperties {
IsTlsEnabled = true, DisableServerCertificateValidation = false,
AllowSelfSigned = false, AllowHostnameMismatch = true } };
// invalid certificate path
yield return new object?[] { new Dictionary<string, string> { {
StandardTlsOptions.IsTlsEnabled, "True" }, {
StandardTlsOptions.AllowSelfSigned, "True" }, {
StandardTlsOptions.AllowHostnameMismatch, "True" }, {
StandardTlsOptions.TrustedCertificatePath, "" } }, null,
typeof(FileNotFoundException) };
}