diff --git a/docs/README.md b/docs/README.md
index 1fb237a..189dff2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -238,6 +238,106 @@ This example assumes rows with three fields, symbol, price and size.
The `c` class throws exceptions of C\# class Exceptions both for typical socket read/write reasons and in higher level cases;
that is, errors at the q level rather than the Socket level.
+## SSL/TLS Connections
+
+SSL/TLS connections can be customized by use of the `KdbTlsOptions` class. A helper class `KdbTls` exists for commonly used settings, which can produce a `KdbTlsOptions`. For full flexibility `KdbTlsOptions` can be used directly.
+To gain an understanding on the use of SSL/TLS, its useful to consult the following resources:
+
+* [`TLS/SSL best practices`](https://learn.microsoft.com/en-us/dotnet/core/extensions/sslstream-best-practices)
+* [`SslStream class`](https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=net-8.0)
+* [`SslStream.authenticateasclient`](https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslstream.beginauthenticateasclient?view=net-8.0#system-net-security-sslstream-beginauthenticateasclient(system-string-system-security-cryptography-x509certificates-x509certificatecollection-system-security-authentication-sslprotocols-system-boolean-system-asynccallback-system-object)))
+* [`Using SSL/TLS with KDB+`](https://code.kx.com/q/kb/ssl/)
+
+### SSL/TLS Examples
+
+#### Connecting with host/ip verification
+
+Connects and checks host/ip on the server cert (i.e. the server certicate has been issues to the host/ip you are connecting to)
+
+```
+var conn = new c(host, port, usernamePassword, 65536, true);
+```
+
+#### Connecting without hostname verification
+
+To connect without hostname verification but reject other validation failures (such as untrusted private-CA certs).
+
+```c#
+var conn = new c(host, port, usernamePassword, 65536, KdbTls.IgnoreHostnameMismatch(host));
+```
+
+Connection errors for untrusted server certs recieved by the client can be prevented by installing the server cert to the client OS trust store as a trusted root.
+
+For example, on Ubuntu/Debian
+
+```bash
+sudo apt-get install -y ca-certificates
+sudo cp ca-cert.pem /usr/local/share/ca-certificates/foobar-ca.crt
+sudo update-ca-certificates
+```
+
+Please consult your OS for details on adding trusted certs.
+
+#### Connecting without hostname verification and cert chain errors
+
+Tolerate hostname mismatch and chain errors example:
+
+```c#
+var conn = new c(host, port, usernamePassword, 65536, new KdbTlsOptions {
+ Enabled = true,
+ TargetHost = targetHost,
+ RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
+ {
+ if (certificate == null)
+ {
+ return false;
+ }
+
+ var cert2 = certificate as X509Certificate2 ?? new X509Certificate2(certificate);
+ var actualThumbprint = cert2.Thumbprint?.Replace(" ", "");
+ var expected = expectedThumbprint?.Replace(" ", "");
+
+ if (!string.Equals(actualThumbprint, expected, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ var allowedErrors = SslPolicyErrors.None;
+
+ if (ignoreHostnameMismatch)
+ {
+ allowedErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
+ }
+
+ allowedErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
+
+ return (errors & ~allowedErrors) == SslPolicyErrors.None;
+ })
+```
+
+#### Client side key and cert
+
+If you wish to provide a client side private key and cert to the server, the following demonstrates this:
+
+```c#
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+
+var certPem = File.ReadAllText("client-cert.pem");
+var keyPem = File.ReadAllText("client-private-key.pem");
+
+var clientCert = X509Certificate2.CreateFromPem(certPem, keyPem);
+
+var tls = new KdbTlsOptions
+{
+ Enabled = true,
+ TargetHost = host
+};
+
+tls.ClientCertificates.Add(clientCert);
+
+var conn = new c(host, port, usernamePassword, 65536, tls);
+```
## Examples
diff --git a/kx/KdbTls.cs b/kx/KdbTls.cs
new file mode 100644
index 0000000..e36acef
--- /dev/null
+++ b/kx/KdbTls.cs
@@ -0,0 +1,75 @@
+using System.Net.Security;
+
+namespace kx
+{
+ ///
+ /// Provides helper methods for creating common TLS configurations
+ /// for KDB+ client connections.
+ ///
+ public static class KdbTls
+ {
+ ///
+ /// Creates a TLS configuration that uses the default platform
+ /// certificate validation behaviour.
+ ///
+ ///
+ /// The target host name used during TLS authentication.
+ ///
+ ///
+ /// A instance configured for standard TLS validation.
+ ///
+ public static KdbTlsOptions Default(string targetHost) =>
+ new KdbTlsOptions
+ {
+ Enabled = true,
+ TargetHost = targetHost
+ };
+
+ ///
+ /// Creates a TLS configuration that ignores certificate host name mismatch
+ /// errors while still rejecting other certificate validation failures
+ /// such as untrusted private-CA cert or self-signed certs.
+ ///
+ ///
+ /// The target host name used during TLS authentication.
+ ///
+ ///
+ /// A instance configured to ignore
+ /// .
+ ///
+ public static KdbTlsOptions IgnoreHostnameMismatch(string targetHost) =>
+ new KdbTlsOptions
+ {
+ Enabled = true,
+ TargetHost = targetHost,
+ RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
+ {
+ var filteredErrors = errors & ~SslPolicyErrors.RemoteCertificateNameMismatch;
+ return filteredErrors == SslPolicyErrors.None;
+ }
+ };
+
+ ///
+ /// Creates a TLS configuration that accepts any server certificate.
+ ///
+ ///
+ /// The target host name used during TLS authentication.
+ ///
+ ///
+ /// A instance configured to accept all server certificates.
+ ///
+ ///
+ /// This disables certificate validation and should only be used in controlled
+ /// environments where the security implications are understood.
+ ///
+ public static KdbTlsOptions Insecure(string targetHost) =>
+#pragma warning disable CA5359
+ new KdbTlsOptions
+ {
+ Enabled = true,
+ TargetHost = targetHost,
+ RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true
+ };
+#pragma warning restore CA5359
+ }
+}
\ No newline at end of file
diff --git a/kx/KdbTlsOptions.cs b/kx/KdbTlsOptions.cs
new file mode 100644
index 0000000..40d6cb3
--- /dev/null
+++ b/kx/KdbTlsOptions.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+
+namespace kx
+{
+ ///
+ /// Defines TLS/SSL options for a KDB+ client connection.
+ ///
+ public sealed class KdbTlsOptions
+ {
+ ///
+ /// Gets a disabled TLS configuration.
+ ///
+ public static KdbTlsOptions Disabled { get; } = new KdbTlsOptions
+ {
+ Enabled = false
+ };
+
+ ///
+ /// Gets or sets a value indicating whether TLS is enabled for the connection.
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// Gets or sets the target host name used during TLS authentication.
+ ///
+ ///
+ /// This value is typically used for server certificate name validation.
+ ///
+ public string TargetHost { get; set; }
+
+ ///
+ /// Gets the client certificates to present to the server during TLS authentication.
+ ///
+ public X509CertificateCollection ClientCertificates { get; } = new X509CertificateCollection();
+
+ ///
+ /// Gets or sets the allowed SSL/TLS protocol versions.
+ ///
+ public SslProtocols? EnabledSslProtocols { get; set; }
+
+ ///
+ /// Gets or sets the certificate revocation checking mode to use during TLS authentication.
+ ///
+ public X509RevocationMode? CertificateRevocationCheckMode { get; set; }
+
+ ///
+ /// Gets or sets the callback used to validate the remote server certificate.
+ ///
+ public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
+
+ ///
+ /// Gets or sets the callback used to select a local client certificate.
+ ///
+ public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/kx/c.cs b/kx/c.cs
index e50ec3e..f9fa017 100644
--- a/kx/c.cs
+++ b/kx/c.cs
@@ -3,6 +3,8 @@
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
@@ -129,17 +131,14 @@ public class c : IDisposable
/// was null.
/// must be between and
/// Unable to connect to KDB+ process, access denied or process unavailable.
- public c(string host,
- int port)
- : this(host, port, Environment.UserName)
- {
- }
+ public c(string host, int port) : this(host, port, Environment.UserName) { }
- private c(string host,
+ private c(
+ string host,
int port,
string userPassword,
int maxBufferSize,
- bool useTLS,
+ KdbTlsOptions tlsOptions,
bool uds)
{
if (host == null)
@@ -173,10 +172,26 @@ private c(string host,
IPAddress.IsLoopback((_socket.RemoteEndPoint as IPEndPoint).Address);
}
_clientStream = new NetworkStream(_socket);
- if (useTLS)
- {
- _clientStream = new SslStream(_clientStream, false);
- ((SslStream)_clientStream).AuthenticateAsClient(host);
+ if (tlsOptions != null && tlsOptions.Enabled)
+ {
+ var sslStream = new SslStream(
+ _clientStream,
+ leaveInnerStreamOpen: false,
+ tlsOptions.RemoteCertificateValidationCallback,
+ tlsOptions.LocalCertificateSelectionCallback);
+ var targetHost = tlsOptions.TargetHost ?? host;
+ var clientCertificates = tlsOptions.ClientCertificates;
+ var enabledSslProtocols = tlsOptions.EnabledSslProtocols ?? SslProtocols.None;
+ var checkCertificateRevocation =
+ tlsOptions.CertificateRevocationCheckMode.HasValue &&
+ tlsOptions.CertificateRevocationCheckMode.Value != X509RevocationMode.NoCheck;
+
+ sslStream.AuthenticateAsClient(
+ targetHost,
+ clientCertificates,
+ enabledSslProtocols,
+ checkCertificateRevocation);
+ _clientStream = sslStream;
}
_writeBuffer = new byte[2 + userPassword.Length];
_writePosition = 0;
@@ -204,12 +219,38 @@ private c(string host,
/// or was null.
/// must be between and
/// Unable to connect to KDB+ process, access denied or process unavailable.
- public c(string host,
+ public c(
+ string host,
int port,
string userPassword,
int maxBufferSize = DefaultMaxBufferSize,
bool useTLS = false)
- : this(host, port, userPassword, maxBufferSize, useTLS, false)
+ : this(host, port, userPassword, maxBufferSize, useTLS ? KdbTls.Default(host) : KdbTlsOptions.Disabled, false)
+ {
+ }
+
+ /// The hostname or IP address of the KDB+ server.
+ /// The TCP port of the KDB+ server.
+ /// The username/password string used for authentication.
+ /// The maximum buffer size used for IPC messages.
+ /// The TLS options to use for the connection.
+ ///
+ /// or was null.
+ ///
+ ///
+ /// must be between and
+ /// .
+ ///
+ ///
+ /// Unable to connect to the KDB+ process, access denied, or process unavailable.
+ ///
+ public c(
+ string host,
+ int port,
+ string userPassword,
+ int maxBufferSize,
+ KdbTlsOptions tlsOptions)
+ : this(host,port,userPassword,maxBufferSize,tlsOptions ?? KdbTlsOptions.Disabled,false)
{
}
@@ -221,15 +262,40 @@ public c(string host,
/// uds file e.g. "/tmp/kx.5010" is the default with kdb+ listening on port 5010
/// The username and passsword, as "username:password" for remote authorisation.
/// The maximum buffer size, default is 65536.
- /// A boolean flag indicating whether or not TLS authentication is enabled, default is false.
/// or was null.
/// Unable to connect to KDB+ process, access denied or process unavailable.
/// The current OS does not support Unix Domain Sockets
- public c(string file,
+ public c(
+ string file,
string userPassword,
- int maxBufferSize = DefaultMaxBufferSize,
- bool useTLS = false)
- : this(file, 0, userPassword, maxBufferSize, useTLS, true)
+ int maxBufferSize = DefaultMaxBufferSize)
+ : this(file, 0, userPassword, maxBufferSize, KdbTlsOptions.Disabled, true)
+ {
+ }
+
+ ///
+ /// Initialises a new instance of using a Unix Domain Socket connection,
+ /// a username/password for authentication, a maximum buffer size, and explicit TLS options.
+ ///
+ /// The Unix Domain Socket file path.
+ /// The username/password string used for authentication.
+ /// The maximum buffer size used for IPC messages.
+ /// The TLS options to use for the connection.
+ ///
+ /// or was null.
+ ///
+ ///
+ /// Unable to connect to the KDB+ process, access denied, or process unavailable.
+ ///
+ ///
+ /// The current operating system does not support Unix Domain Sockets.
+ ///
+ public c(
+ string file,
+ string userPassword,
+ int maxBufferSize,
+ KdbTlsOptions tlsOptions)
+ : this(file, 0, userPassword, maxBufferSize, tlsOptions ?? KdbTlsOptions.Disabled, true)
{
}