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 152d25cbd feat(csharp/src/Drivers/Apache): Custom ssl server 
certificate validation for Spark, Impala & Hive (#2610)
152d25cbd is described below

commit 152d25cbdc67730998534284c0b111b988a0e0e7
Author: Sudhir Reddy Emmadi <[email protected]>
AuthorDate: Wed Mar 26 03:18:52 2025 +0530

    feat(csharp/src/Drivers/Apache): Custom ssl server certificate validation 
for Spark, Impala & Hive (#2610)
    
    Co-authored-by: Sudhir Emmadi <[email protected]>
---
 .../Drivers/Apache/Hive2/HiveServer2Connection.cs  |   7 +-
 .../Apache/Hive2/HiveServer2HttpConnection.cs      |  26 +-----
 .../Drivers/Apache/Hive2/HiveServer2Parameters.cs  |  10 +-
 .../src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs | 101 +++++++++++++++++++++
 .../Drivers/Apache/Hive2/HiveServer2TlsOption.cs   |  53 -----------
 csharp/src/Drivers/Apache/Hive2/README.md          |   6 +-
 .../Drivers/Apache/Impala/ImpalaHttpConnection.cs  |  27 +-----
 .../Apache/Impala/ImpalaStandardConnection.cs      |   1 -
 csharp/src/Drivers/Apache/Spark/README.md          |   6 +-
 .../Drivers/Apache/Spark/SparkHttpConnection.cs    |  27 +-----
 csharp/src/Drivers/Apache/Spark/SparkParameters.cs |   1 -
 .../Apache/Hive2/HiveServer2ParametersTest.cs      |  31 -------
 .../Apache/Hive2/HiveServer2TestEnvironment.cs     |   4 -
 .../Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs |  59 ++++++++++++
 .../Drivers/Apache/Spark/SparkTestEnvironment.cs   |   4 -
 15 files changed, 191 insertions(+), 172 deletions(-)

diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
index e182adb4f..ab3efea31 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
@@ -342,7 +342,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
 
         protected internal DataTypeConversion DataTypeConversion { get; set; } 
= DataTypeConversion.None;
 
-        protected internal HiveServer2TlsOption TlsOptions { get; set; } = 
HiveServer2TlsOption.Empty;
+        protected internal TlsProperties TlsOptions { get; set; } = new 
TlsProperties();
 
         protected internal int ConnectTimeoutMilliseconds { get; set; } = 
ConnectTimeoutMillisecondsDefault;
 
@@ -731,7 +731,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             return fileVersionInfo.ProductVersion ?? 
GetProductVersionDefault();
         }
 
-        protected static Uri GetBaseAddress(string? uri, string? hostName, 
string? path, string? port, string hostOptionName)
+        protected static Uri GetBaseAddress(string? uri, string? hostName, 
string? path, string? port, string hostOptionName, bool isTlsEnabled)
         {
             // Uri property takes precedent.
             if (!string.IsNullOrWhiteSpace(uri))
@@ -755,8 +755,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
 
             bool isPortSet = !string.IsNullOrEmpty(port);
             bool isValidPortNumber = int.TryParse(port, out int portNumber) && 
portNumber > 0;
-            bool isDefaultHttpsPort = !isPortSet || (isValidPortNumber && 
portNumber == 443);
-            string uriScheme = isDefaultHttpsPort ? Uri.UriSchemeHttps : 
Uri.UriSchemeHttp;
+            string uriScheme = isTlsEnabled ? Uri.UriSchemeHttps : 
Uri.UriSchemeHttp;
             int uriPort;
             if (!isPortSet)
                 uriPort = -1;
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
index 19f799322..187e5712c 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
@@ -121,7 +121,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             Properties.TryGetValue(HiveServer2Parameters.Path, out string? 
path);
             _ = new HttpClient()
             {
-                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
HiveServer2Parameters.HostName)
+                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
HiveServer2Parameters.HostName, TlsOptions.IsTlsEnabled)
             };
         }
 
@@ -129,8 +129,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         {
             Properties.TryGetValue(HiveServer2Parameters.DataTypeConv, out 
string? dataTypeConv);
             DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
-            Properties.TryGetValue(HiveServer2Parameters.TLSOptions, out 
string? tlsOptions);
-            TlsOptions = TlsOptionsParser.Parse(tlsOptions);
             
Properties.TryGetValue(HiveServer2Parameters.ConnectTimeoutMilliseconds, out 
string? connectTimeoutMs);
             if (connectTimeoutMs != null)
             {
@@ -138,6 +136,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
                     ? connectTimeoutMsValue
                     : throw new 
ArgumentOutOfRangeException(HiveServer2Parameters.ConnectTimeoutMilliseconds, 
connectTimeoutMs, $"must be a value of 0 (infinite) or between 1 .. 
{int.MaxValue}. default is 30000 milliseconds.");
             }
+            TlsOptions = HiveServer2TlsImpl.GetHttpTlsOptions(Properties);
         }
 
         public override AdbcStatement CreateStatement()
@@ -166,10 +165,10 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             Properties.TryGetValue(AdbcOptions.Password, out string? password);
             Properties.TryGetValue(AdbcOptions.Uri, out string? uri);
 
-            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
HiveServer2Parameters.HostName);
+            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
HiveServer2Parameters.HostName, TlsOptions.IsTlsEnabled);
             AuthenticationHeaderValue? authenticationHeaderValue = 
GetAuthenticationHeaderValue(authTypeValue, username, password);
 
-            HttpClientHandler httpClientHandler = NewHttpClientHandler();
+            HttpClientHandler httpClientHandler = 
HiveServer2TlsImpl.NewHttpClientHandler(TlsOptions);
             httpClientHandler.AutomaticDecompression = 
DecompressionMethods.GZip | DecompressionMethods.Deflate;
             HttpClient httpClient = new(httpClientHandler);
             httpClient.BaseAddress = baseAddress;
@@ -190,23 +189,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
             return transport;
         }
 
-        private HttpClientHandler NewHttpClientHandler()
-        {
-            HttpClientHandler httpClientHandler = new();
-            if (TlsOptions != HiveServer2TlsOption.Empty)
-            {
-                httpClientHandler.ServerCertificateCustomValidationCallback = 
(request, certificate, chain, policyErrors) =>
-                {
-                    if 
(policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) && 
!TlsOptions.HasFlag(HiveServer2TlsOption.AllowSelfSigned)) return false;
-                    if 
(policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) && 
!TlsOptions.HasFlag(HiveServer2TlsOption.AllowHostnameMismatch)) return false;
-
-                    return true;
-                };
-            }
-
-            return httpClientHandler;
-        }
-
         private static AuthenticationHeaderValue? 
GetAuthenticationHeaderValue(HiveServer2AuthType authType, string? username, 
string? password)
         {
             if (!string.IsNullOrEmpty(username) && 
!string.IsNullOrEmpty(password) && (authType == HiveServer2AuthType.Empty || 
authType == HiveServer2AuthType.Basic))
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
index b9d7f2c2a..f1bb90f11 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
@@ -25,7 +25,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         public const string AuthType = "adbc.hive.auth_type";
         public const string TransportType = "adbc.hive.transport_type";
         public const string DataTypeConv = "adbc.hive.data_type_conv";
-        public const string TLSOptions = "adbc.hive.tls_options";
         public const string ConnectTimeoutMilliseconds = 
"adbc.hive.connect_timeout_ms";
     }
 
@@ -47,9 +46,12 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
         public const string Scalar = "scalar";
     }
 
-    public static class TlsOptions
+    public static class HttpTlsOptions
     {
-        public const string AllowSelfSigned = "allow_self_signed";
-        public const string AllowHostnameMismatch = "allow_hostname_mismatch";
+        public const string IsTlsEnabled = "adbc.http_options.tls.enabled";
+        public const string AllowSelfSigned = 
"adbc.http_options.tls.allow_self_signed";
+        public const string AllowHostnameMismatch = 
"adbc.http_options.tls.allow_hostname_mismatch";
+        public const string TrustedCertificatePath = 
"adbc.http_options.tls.trusted_certificate_path";
+        public const string DisableServerCertificateValidation = 
"adbc.http_options.tls.disable_server_certificate_validation";
     }
 }
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
new file mode 100644
index 000000000..b7e7c8b38
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsImpl.cs
@@ -0,0 +1,101 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*    http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Net.Http;
+
+namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
+{
+    class TlsProperties
+    {
+        public bool IsTlsEnabled { get; set; }
+        public bool DisableServerCertificateValidation { get; set; }
+        public bool AllowHostnameMismatch { get; set; }
+        public bool AllowSelfSigned { get; set; }
+        public string? TrustedCertificatePath { get; set; }
+    }
+
+    static class HiveServer2TlsImpl
+    {
+        static internal TlsProperties 
GetHttpTlsOptions(IReadOnlyDictionary<string, string> properties)
+        {
+            TlsProperties tlsProperties = new TlsProperties();
+            if (properties.TryGetValue(AdbcOptions.Uri, out string? uri) && 
!string.IsNullOrWhiteSpace(uri))
+            {
+                var uriValue = new Uri(uri);
+                tlsProperties.IsTlsEnabled = uriValue.Scheme == 
Uri.UriSchemeHttps || (properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out 
string? isSslEnabled) && bool.TryParse(isSslEnabled, out bool isSslEnabledBool) 
&& isSslEnabledBool);
+            }
+            else
+            {
+                tlsProperties.IsTlsEnabled = 
properties.TryGetValue(HttpTlsOptions.IsTlsEnabled, out string? isSslEnabled) 
&& bool.TryParse(isSslEnabled, out bool isSslEnabledBool) && isSslEnabledBool;
+            }
+            if (!tlsProperties.IsTlsEnabled)
+            {
+                return tlsProperties;
+            }
+            tlsProperties.IsTlsEnabled = true;
+            if 
(properties.TryGetValue(HttpTlsOptions.DisableServerCertificateValidation, out 
string? disableServerCertificateValidation) && 
bool.TryParse(disableServerCertificateValidation, out bool 
disableServerCertificateValidationBool) && 
disableServerCertificateValidationBool)
+            {
+                tlsProperties.DisableServerCertificateValidation = true;
+                return tlsProperties;
+            }
+            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.");
+            }
+            return tlsProperties;
+        }
+
+        static internal HttpClientHandler NewHttpClientHandler(TlsProperties 
tlsProperties)
+        {
+            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);
+                };
+            }
+            return httpClientHandler;
+        }
+    }
+}
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsOption.cs 
b/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsOption.cs
deleted file mode 100644
index 84f56a485..000000000
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2TlsOption.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-using System;
-
-namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
-{
-    [Flags]
-    internal enum HiveServer2TlsOption
-    {
-        Empty = 0,
-        AllowSelfSigned = 1,
-        AllowHostnameMismatch = 2,
-    }
-
-    internal static class TlsOptionsParser
-    {
-        internal const string SupportedList = TlsOptions.AllowSelfSigned + "," 
+ TlsOptions.AllowHostnameMismatch;
-
-        internal static HiveServer2TlsOption Parse(string? tlsOptions)
-        {
-            HiveServer2TlsOption options = HiveServer2TlsOption.Empty;
-            if (tlsOptions == null) return options;
-
-            string[] valueList = tlsOptions.Split(',');
-            foreach (string tlsOption in valueList)
-            {
-                options |= (tlsOption?.Trim().ToLowerInvariant()) switch
-                {
-                    null or "" => HiveServer2TlsOption.Empty,
-                    TlsOptions.AllowSelfSigned => 
HiveServer2TlsOption.AllowSelfSigned,
-                    TlsOptions.AllowHostnameMismatch => 
HiveServer2TlsOption.AllowHostnameMismatch,
-                    _ => throw new 
ArgumentOutOfRangeException(nameof(tlsOptions), tlsOption, "Invalid or 
unsupported TLS option"),
-                };
-            }
-            return options;
-        }
-    }
-}
diff --git a/csharp/src/Drivers/Apache/Hive2/README.md 
b/csharp/src/Drivers/Apache/Hive2/README.md
index e9c863190..5e665d3f8 100644
--- a/csharp/src/Drivers/Apache/Hive2/README.md
+++ b/csharp/src/Drivers/Apache/Hive2/README.md
@@ -35,11 +35,15 @@ but can also be passed in the call to 
`AdbcDatabase.Connect`.
 | `username`             | The user name used for basic authentication | |
 | `password`             | The password for the user name used for basic 
authentication. | |
 | `adbc.hive.data_type_conv` | Comma-separated list of data conversion 
options. Each option indicates the type of conversion to perform on data 
returned from the Hive server. <br><br>Allowed values: `none`, `scalar`. 
<br><br>Option `none` indicates there is no conversion from Hive type to native 
type (i.e., no conversion from String to Timestamp for Apache Hive over HTTP). 
Example `adbc.hive.conv_data_type=none`. <br><br>Option `scalar` will perform 
conversion (if necessary) from the Hiv [...]
-| `adbc.hive.tls_options` | Comma-separated list of TLS/SSL options. Each 
option indicates the TLS/SSL option when connecting to a Hive server. 
<br><br>Allowed values: `allow_self_signed`, `allow_hostname_mismatch`. 
<br><br>Option `allow_self_signed` allows certificate errors due to an unknown 
certificate authority, typically when using a self-signed certificate. Option 
`allow_hostname_mismatch` allow certificate errors due to a mismatch of the 
hostname. (e.g., when connecting through an [...]
 | `adbc.hive.connect_timeout_ms` | Sets the timeout (in milliseconds) to open 
a new session. Values can be 0 (infinite) or greater than zero. | `30000` |
 | `adbc.apache.statement.batch_size` | Sets the maximum number of rows to 
retrieve in a single batch request. | `50000` |
 | `adbc.apache.statement.polltime_ms` | If polling is necessary to get a 
result, this option sets the length of time (in milliseconds) to wait between 
polls. | `500` |
 | `adbc.apache.statement.query_timeout_s` | Sets the maximum time (in seconds) 
for a query to complete. Values can be 0 (infinite) or greater than zero. | 
`60` |
+| `adbc.http_options.tls.enabled` | If tls needs to enabled or not. One of 
`True`, `False` | `False` |
+| `adbc.http_options.tls.disable_server_certificate_validation` | If tls/ssl 
server certificate validation needs to enabled or not. One of `True`, `False`. 
If set to True, all certificate validation errors are ignored | `False` |
+| `adbc.http_options.tls.allow_self_signed` | If self signed tls/ssl 
certificate needs to be allowed or not. One of `True`, `False` | `False` |
+| `adbc.http_options.tls.allow_hostname_mismatch` | If hostname mismatch is 
allowed for ssl. One of `True`, `False` | `False` |
+| `adbc.http_options.tls.trusted_certificate_path` | The full path of the 
tls/ssl certificate .pem file containing custom CA certificates for verifying 
the server when connecting over TLS | `` |
 
 ## Timeout Configuration
 
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaHttpConnection.cs 
b/csharp/src/Drivers/Apache/Impala/ImpalaHttpConnection.cs
index 7de129df7..914ba9269 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaHttpConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaHttpConnection.cs
@@ -105,7 +105,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
             Properties.TryGetValue(ImpalaParameters.Path, out string? path);
             _ = new HttpClient()
             {
-                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
ImpalaParameters.HostName)
+                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
ImpalaParameters.HostName, TlsOptions.IsTlsEnabled)
             };
         }
 
@@ -113,8 +113,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
         {
             Properties.TryGetValue(ImpalaParameters.DataTypeConv, out string? 
dataTypeConv);
             DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
-            Properties.TryGetValue(ImpalaParameters.TLSOptions, out string? 
tlsOptions);
-            TlsOptions = TlsOptionsParser.Parse(tlsOptions);
             
Properties.TryGetValue(ImpalaParameters.ConnectTimeoutMilliseconds, out string? 
connectTimeoutMs);
             if (connectTimeoutMs != null)
             {
@@ -122,6 +120,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
                     ? connectTimeoutMsValue
                     : throw new 
ArgumentOutOfRangeException(ImpalaParameters.ConnectTimeoutMilliseconds, 
connectTimeoutMs, $"must be a value of 0 (infinite) or between 1 .. 
{int.MaxValue}. default is 30000 milliseconds.");
             }
+            TlsOptions = HiveServer2TlsImpl.GetHttpTlsOptions(Properties);
         }
 
         internal override IArrowArrayStream NewReader<T>(T statement, Schema 
schema) => new HiveServer2Reader(statement, schema, dataTypeConversion: 
statement.Connection.DataTypeConversion);
@@ -141,10 +140,10 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
             Properties.TryGetValue(AdbcOptions.Password, out string? password);
             Properties.TryGetValue(AdbcOptions.Uri, out string? uri);
 
-            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
ImpalaParameters.HostName);
+            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
ImpalaParameters.HostName, TlsOptions.IsTlsEnabled);
             AuthenticationHeaderValue? authenticationHeaderValue = 
GetAuthenticationHeaderValue(authTypeValue, username, password);
 
-            HttpClientHandler httpClientHandler = NewHttpClientHandler();
+            HttpClientHandler httpClientHandler = 
HiveServer2TlsImpl.NewHttpClientHandler(TlsOptions);
             HttpClient httpClient = new(httpClientHandler);
             httpClient.BaseAddress = baseAddress;
             httpClient.DefaultRequestHeaders.Authorization = 
authenticationHeaderValue;
@@ -164,24 +163,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
             return transport;
         }
 
-        private HttpClientHandler NewHttpClientHandler()
-        {
-            HttpClientHandler httpClientHandler = new();
-            if (TlsOptions != HiveServer2TlsOption.Empty)
-            {
-                httpClientHandler.ServerCertificateCustomValidationCallback = 
(request, certificate, chain, policyErrors) =>
-                {
-
-                    if 
(policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) && 
!TlsOptions.HasFlag(HiveServer2TlsOption.AllowSelfSigned)) return false;
-                    if 
(policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) && 
!TlsOptions.HasFlag(HiveServer2TlsOption.AllowHostnameMismatch)) return false;
-
-                    return true;
-                };
-            }
-
-            return httpClientHandler;
-        }
-
         private static AuthenticationHeaderValue? 
GetAuthenticationHeaderValue(ImpalaAuthType authType, string? username, string? 
password)
         {
             if (!string.IsNullOrEmpty(username) && 
!string.IsNullOrEmpty(password) && (authType == ImpalaAuthType.Empty || 
authType == ImpalaAuthType.Basic))
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs 
b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
index 43179b625..99ac368be 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
@@ -96,7 +96,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
             Properties.TryGetValue(ImpalaParameters.DataTypeConv, out string? 
dataTypeConv);
             DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
             Properties.TryGetValue(ImpalaParameters.TLSOptions, out string? 
tlsOptions);
-            TlsOptions = TlsOptionsParser.Parse(tlsOptions);
         }
 
         protected override TTransport CreateTransport()
diff --git a/csharp/src/Drivers/Apache/Spark/README.md 
b/csharp/src/Drivers/Apache/Spark/README.md
index 1510c0291..5299a21e8 100644
--- a/csharp/src/Drivers/Apache/Spark/README.md
+++ b/csharp/src/Drivers/Apache/Spark/README.md
@@ -36,11 +36,15 @@ but can also be passed in the call to 
`AdbcDatabase.Connect`.
 | `username`             | The user name used for basic authentication | |
 | `password`             | The password for the user name used for basic 
authentication. | |
 | `adbc.spark.data_type_conv` | Comma-separated list of data conversion 
options. Each option indicates the type of conversion to perform on data 
returned from the Spark server. <br><br>Allowed values: `none`, `scalar`. 
<br><br>Option `none` indicates there is no conversion from Spark type to 
native type (i.e., no conversion from String to Timestamp for Apache Spark over 
HTTP). Example `adbc.spark.conv_data_type=none`. <br><br>Option `scalar` will 
perform conversion (if necessary) from th [...]
-| `adbc.spark.tls_options` | Comma-separated list of TLS/SSL options. Each 
option indicates the TLS/SSL option when connecting to a Spark server. 
<br><br>Allowed values: `allow_self_signed`, `allow_hostname_mismatch`. 
<br><br>Option `allow_self_signed` allows certificate errors due to an unknown 
certificate authority, typically when using a self-signed certificate. Option 
`allow_hostname_mismatch` allow certificate errors due to a mismatch of the 
hostname. (e.g., when connecting through  [...]
 | `adbc.spark.connect_timeout_ms` | Sets the timeout (in milliseconds) to open 
a new session. Values can be 0 (infinite) or greater than zero. | `30000` |
 | `adbc.apache.statement.batch_size` | Sets the maximum number of rows to 
retrieve in a single batch request. | `50000` |
 | `adbc.apache.statement.polltime_ms` | If polling is necessary to get a 
result, this option sets the length of time (in milliseconds) to wait between 
polls. | `500` |
 | `adbc.apache.statement.query_timeout_s` | Sets the maximum time (in seconds) 
for a query to complete. Values can be 0 (infinite) or greater than zero. | 
`60` |
+| `adbc.http_options.tls.enabled` | If tls needs to enabled or not. One of 
`True`, `False` | `False` |
+| `adbc.http_options.tls.disable_server_certificate_validation` | If tls/ssl 
server certificate validation needs to enabled or not. One of `True`, `False`. 
If set to True, all certificate validation errors are ignored | `False` |
+| `adbc.http_options.tls.allow_self_signed` | If self signed tls/ssl 
certificate needs to be allowed or not. One of `True`, `False` | `False` |
+| `adbc.http_options.tls.allow_hostname_mismatch` | If hostname mismatch is 
allowed for ssl. One of `True`, `False` | `False` |
+| `adbc.http_options.tls.trusted_certificate_path` | The full path of the 
tls/ssl certificate .pem file containing custom CA certificates for verifying 
the server when connecting over TLS | `` |
 
 ## Timeout Configuration
 
diff --git a/csharp/src/Drivers/Apache/Spark/SparkHttpConnection.cs 
b/csharp/src/Drivers/Apache/Spark/SparkHttpConnection.cs
index a586f4b55..e28ab4632 100644
--- a/csharp/src/Drivers/Apache/Spark/SparkHttpConnection.cs
+++ b/csharp/src/Drivers/Apache/Spark/SparkHttpConnection.cs
@@ -121,7 +121,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
             Properties.TryGetValue(SparkParameters.Path, out string? path);
             _ = new HttpClient()
             {
-                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
SparkParameters.HostName)
+                BaseAddress = GetBaseAddress(uri, hostName, path, port, 
SparkParameters.HostName, TlsOptions.IsTlsEnabled)
             };
         }
 
@@ -129,8 +129,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
         {
             Properties.TryGetValue(SparkParameters.DataTypeConv, out string? 
dataTypeConv);
             DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
-            Properties.TryGetValue(SparkParameters.TLSOptions, out string? 
tlsOptions);
-            TlsOptions = TlsOptionsParser.Parse(tlsOptions);
             Properties.TryGetValue(SparkParameters.ConnectTimeoutMilliseconds, 
out string? connectTimeoutMs);
             if (connectTimeoutMs != null)
             {
@@ -138,6 +136,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
                     ? connectTimeoutMsValue
                     : throw new 
ArgumentOutOfRangeException(SparkParameters.ConnectTimeoutMilliseconds, 
connectTimeoutMs, $"must be a value of 0 (infinite) or between 1 .. 
{int.MaxValue}. default is 30000 milliseconds.");
             }
+            TlsOptions = HiveServer2TlsImpl.GetHttpTlsOptions(Properties);
         }
 
         internal override IArrowArrayStream NewReader<T>(T statement, Schema 
schema) => new HiveServer2Reader(statement, schema, dataTypeConversion: 
statement.Connection.DataTypeConversion);
@@ -159,10 +158,10 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
             Properties.TryGetValue(AdbcOptions.Password, out string? password);
             Properties.TryGetValue(AdbcOptions.Uri, out string? uri);
 
-            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
SparkParameters.HostName);
+            Uri baseAddress = GetBaseAddress(uri, hostName, path, port, 
SparkParameters.HostName, TlsOptions.IsTlsEnabled);
             AuthenticationHeaderValue? authenticationHeaderValue = 
GetAuthenticationHeaderValue(authTypeValue, token, username, password, 
access_token);
 
-            HttpClientHandler httpClientHandler = NewHttpClientHandler();
+            HttpClientHandler httpClientHandler = 
HiveServer2TlsImpl.NewHttpClientHandler(TlsOptions);
             HttpClient httpClient = new(httpClientHandler);
             httpClient.BaseAddress = baseAddress;
             httpClient.DefaultRequestHeaders.Authorization = 
authenticationHeaderValue;
@@ -182,24 +181,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
             return transport;
         }
 
-        private HttpClientHandler NewHttpClientHandler()
-        {
-            HttpClientHandler httpClientHandler = new();
-            if (TlsOptions != HiveServer2TlsOption.Empty)
-            {
-                httpClientHandler.ServerCertificateCustomValidationCallback = 
(request, certificate, chain, policyErrors) =>
-                {
-                    if (policyErrors == SslPolicyErrors.None) return true;
-
-                    return
-                       
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) || 
TlsOptions.HasFlag(HiveServer2TlsOption.AllowSelfSigned))
-                    && 
(!policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) || 
TlsOptions.HasFlag(HiveServer2TlsOption.AllowHostnameMismatch));
-                };
-            }
-
-            return httpClientHandler;
-        }
-
         private static AuthenticationHeaderValue? 
GetAuthenticationHeaderValue(SparkAuthType authType, string? token, string? 
username, string? password, string? access_token)
         {
             if (!string.IsNullOrEmpty(token) && (authType == 
SparkAuthType.Empty || authType == SparkAuthType.Token))
diff --git a/csharp/src/Drivers/Apache/Spark/SparkParameters.cs 
b/csharp/src/Drivers/Apache/Spark/SparkParameters.cs
index d3bd9be6d..8e75ae3f5 100644
--- a/csharp/src/Drivers/Apache/Spark/SparkParameters.cs
+++ b/csharp/src/Drivers/Apache/Spark/SparkParameters.cs
@@ -32,7 +32,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
         public const string AuthType = "adbc.spark.auth_type";
         public const string Type = "adbc.spark.type";
         public const string DataTypeConv = "adbc.spark.data_type_conv";
-        public const string TLSOptions = "adbc.spark.tls_options";
         public const string ConnectTimeoutMilliseconds = 
"adbc.spark.connect_timeout_ms";
     }
 
diff --git a/csharp/test/Drivers/Apache/Hive2/HiveServer2ParametersTest.cs 
b/csharp/test/Drivers/Apache/Hive2/HiveServer2ParametersTest.cs
index 992e5ffb1..b481080bc 100644
--- a/csharp/test/Drivers/Apache/Hive2/HiveServer2ParametersTest.cs
+++ b/csharp/test/Drivers/Apache/Hive2/HiveServer2ParametersTest.cs
@@ -34,16 +34,6 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
                 Assert.Throws(exceptionType, () => 
DataTypeConversionParser.Parse(dataTypeConversion));
         }
 
-        [SkippableTheory]
-        [MemberData(nameof(GetParametersTlsOptionTestData))]
-        internal void TestParametersTlsOptionParse(string? tlsOptions, 
HiveServer2TlsOption expected, Type? exceptionType = default)
-        {
-            if (exceptionType == default)
-                Assert.Equal(expected, TlsOptionsParser.Parse(tlsOptions));
-            else
-                Assert.Throws(exceptionType, () => 
TlsOptionsParser.Parse(tlsOptions));
-        }
-
         public static IEnumerable<object?[]> 
GetParametersDataTypeConvTestData()
         {
             // Default
@@ -68,26 +58,5 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
             yield return new object?[] { $"xxx", DataTypeConversion.Empty, 
typeof(ArgumentOutOfRangeException) };
             yield return new object?[] { $"none,scalar,xxx", 
DataTypeConversion.None | DataTypeConversion.Scalar, 
typeof(ArgumentOutOfRangeException)  };
         }
-
-        public static IEnumerable<object?[]> GetParametersTlsOptionTestData()
-        {
-            // Default
-            yield return new object?[] { null, HiveServer2TlsOption.Empty };
-            yield return new object?[] { "", HiveServer2TlsOption.Empty};
-            yield return new object?[] { " ", HiveServer2TlsOption.Empty };
-            // Explicit
-            yield return new object?[] { $"{TlsOptions.AllowSelfSigned}", 
HiveServer2TlsOption.AllowSelfSigned };
-            yield return new object?[] { 
$"{TlsOptions.AllowHostnameMismatch}", 
HiveServer2TlsOption.AllowHostnameMismatch };
-            // Ignore empty
-            yield return new object?[] { $",{TlsOptions.AllowSelfSigned}", 
HiveServer2TlsOption.AllowSelfSigned };
-            yield return new object?[] { 
$",{TlsOptions.AllowHostnameMismatch},", 
HiveServer2TlsOption.AllowHostnameMismatch };
-            // Combined, embedded space, mixed-case
-            yield return new object?[] { 
$"{TlsOptions.AllowSelfSigned},{TlsOptions.AllowHostnameMismatch}", 
HiveServer2TlsOption.AllowSelfSigned | 
HiveServer2TlsOption.AllowHostnameMismatch };
-            yield return new object?[] { 
$"{TlsOptions.AllowHostnameMismatch},{TlsOptions.AllowSelfSigned}", 
HiveServer2TlsOption.AllowSelfSigned  | 
HiveServer2TlsOption.AllowHostnameMismatch };
-            yield return new object?[] { $" {TlsOptions.AllowHostnameMismatch} 
, {TlsOptions.AllowSelfSigned} ", HiveServer2TlsOption.AllowSelfSigned | 
HiveServer2TlsOption.AllowHostnameMismatch };
-            yield return new object?[] { 
$"{TlsOptions.AllowSelfSigned.ToUpperInvariant()},{TlsOptions.AllowHostnameMismatch.ToUpperInvariant()}",
 HiveServer2TlsOption.AllowSelfSigned | 
HiveServer2TlsOption.AllowHostnameMismatch };
-            // Invalid
-            yield return new object?[] { 
$"xxx,{TlsOptions.AllowSelfSigned.ToUpperInvariant()},{TlsOptions.AllowHostnameMismatch.ToUpperInvariant()}",
 HiveServer2TlsOption.Empty, typeof(ArgumentOutOfRangeException) };
-        }
     }
 }
diff --git a/csharp/test/Drivers/Apache/Hive2/HiveServer2TestEnvironment.cs 
b/csharp/test/Drivers/Apache/Hive2/HiveServer2TestEnvironment.cs
index 79829ae40..c442a8a7e 100644
--- a/csharp/test/Drivers/Apache/Hive2/HiveServer2TestEnvironment.cs
+++ b/csharp/test/Drivers/Apache/Hive2/HiveServer2TestEnvironment.cs
@@ -90,10 +90,6 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
             {
                 parameters.Add(HiveServer2Parameters.DataTypeConv, 
testConfiguration.DataTypeConversion!);
             }
-            if (!string.IsNullOrEmpty(testConfiguration.TlsOptions))
-            {
-                parameters.Add(HiveServer2Parameters.TLSOptions, 
testConfiguration.TlsOptions!);
-            }
             if (!string.IsNullOrEmpty(testConfiguration.BatchSize))
             {
                 parameters.Add(ApacheParameters.BatchSize, 
testConfiguration.BatchSize!);
diff --git a/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs 
b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
new file mode 100644
index 000000000..70d5534e6
--- /dev/null
+++ b/csharp/test/Drivers/Apache/Hive2/HiveServer2TlsImplTest.cs
@@ -0,0 +1,59 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*    http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Apache.Arrow.Adbc.Drivers.Apache.Hive2;
+using Xunit;
+
+namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Hive2
+{
+    public class HiveServer2TlsImplTest
+    {
+        [SkippableTheory]
+        [MemberData(nameof(GetSslOptionsTestData))]
+        internal void TestValidateTlsOptions(Dictionary<string, string>? 
dataTypeConversion, TlsProperties expected, Type? exceptionType = default)
+        {
+            if (exceptionType == default)
+                Assert.Equivalent(expected, 
HiveServer2TlsImpl.GetHttpTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
+            else
+                Assert.Throws(exceptionType, () => 
HiveServer2TlsImpl.GetHttpTlsOptions(dataTypeConversion ?? new 
Dictionary<string, string>()));
+        }
+
+        public static IEnumerable<object?[]> GetSslOptionsTestData()
+        {
+            yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "False" } }, new TlsProperties { IsTlsEnabled = 
false } };
+            yield return new object?[] { new Dictionary<string, string> { { 
AdbcOptions.Uri, "https://arrow.apache.org"; } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = false } };
+            // uri takes precedence over ssl option
+            yield return new object?[] { new Dictionary<string, string> { { 
AdbcOptions.Uri, "https://arrow.apache.org"; }, { HttpTlsOptions.IsTlsEnabled, 
"False" } }, new TlsProperties { IsTlsEnabled = true, 
DisableServerCertificateValidation = false } };
+            // other ssl options are ignored if 
disableServerCertificateValidation is set to true
+            yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "True" }, { 
HttpTlsOptions.DisableServerCertificateValidation, "True" }, { 
HttpTlsOptions.AllowSelfSigned, "True" }, { 
HttpTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties { 
IsTlsEnabled = true, DisableServerCertificateValidation = true } };
+            // other ssl options are ignored if ssl is disabled
+            yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "False" }, { HttpTlsOptions.AllowSelfSigned, 
"True" }, { HttpTlsOptions.AllowHostnameMismatch, "True" } }, new TlsProperties 
{ IsTlsEnabled = false } };
+            // case insensitive boolean string parsing
+            yield return new object?[] { new Dictionary<string, string> { { 
HttpTlsOptions.IsTlsEnabled, "false" } }, new TlsProperties { IsTlsEnabled = 
false } };
+            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) };
+        }
+    }
+}
diff --git a/csharp/test/Drivers/Apache/Spark/SparkTestEnvironment.cs 
b/csharp/test/Drivers/Apache/Spark/SparkTestEnvironment.cs
index 9c9124d6d..265ac6b96 100644
--- a/csharp/test/Drivers/Apache/Spark/SparkTestEnvironment.cs
+++ b/csharp/test/Drivers/Apache/Spark/SparkTestEnvironment.cs
@@ -101,10 +101,6 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Apache.Spark
             {
                 parameters.Add(SparkParameters.DataTypeConv, 
testConfiguration.DataTypeConversion!);
             }
-            if (!string.IsNullOrEmpty(testConfiguration.TlsOptions))
-            {
-                parameters.Add(SparkParameters.TLSOptions, 
testConfiguration.TlsOptions!);
-            }
             if (!string.IsNullOrEmpty(testConfiguration.BatchSize))
             {
                 parameters.Add(ApacheParameters.BatchSize, 
testConfiguration.BatchSize!);


Reply via email to