Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
75 changes: 75 additions & 0 deletions kx/KdbTls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Net.Security;

namespace kx
{
/// <summary>
/// Provides helper methods for creating common TLS configurations
/// for KDB+ client connections.
/// </summary>
public static class KdbTls
{
/// <summary>
/// Creates a TLS configuration that uses the default platform
/// certificate validation behaviour.
/// </summary>
/// <param name="targetHost">
/// The target host name used during TLS authentication.
/// </param>
/// <returns>
/// A <see cref="KdbTlsOptions"/> instance configured for standard TLS validation.
/// </returns>
public static KdbTlsOptions Default(string targetHost) =>
new KdbTlsOptions
{
Enabled = true,
TargetHost = targetHost
};

/// <summary>
/// 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.
/// </summary>
/// <param name="targetHost">
/// The target host name used during TLS authentication.
/// </param>
/// <returns>
/// A <see cref="KdbTlsOptions"/> instance configured to ignore
/// <see cref="SslPolicyErrors.RemoteCertificateNameMismatch"/>.
/// </returns>
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;
}
};

/// <summary>
/// Creates a TLS configuration that accepts any server certificate.
/// </summary>
/// <param name="targetHost">
/// The target host name used during TLS authentication.
/// </param>
/// <returns>
/// A <see cref="KdbTlsOptions"/> instance configured to accept all server certificates.
/// </returns>
/// <remarks>
/// This disables certificate validation and should only be used in controlled
/// environments where the security implications are understood.
/// </remarks>
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
}
}
59 changes: 59 additions & 0 deletions kx/KdbTlsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

namespace kx
{
/// <summary>
/// Defines TLS/SSL options for a KDB+ client connection.
/// </summary>
public sealed class KdbTlsOptions
{
/// <summary>
/// Gets a disabled TLS configuration.
/// </summary>
public static KdbTlsOptions Disabled { get; } = new KdbTlsOptions
{
Enabled = false
};

/// <summary>
/// Gets or sets a value indicating whether TLS is enabled for the connection.
/// </summary>
public bool Enabled { get; set; }

/// <summary>
/// Gets or sets the target host name used during TLS authentication.
/// </summary>
/// <remarks>
/// This value is typically used for server certificate name validation.
/// </remarks>
public string TargetHost { get; set; }

/// <summary>
/// Gets the client certificates to present to the server during TLS authentication.
/// </summary>
public X509CertificateCollection ClientCertificates { get; } = new X509CertificateCollection();

/// <summary>
/// Gets or sets the allowed SSL/TLS protocol versions.
/// </summary>
public SslProtocols? EnabledSslProtocols { get; set; }

/// <summary>
/// Gets or sets the certificate revocation checking mode to use during TLS authentication.
/// </summary>
public X509RevocationMode? CertificateRevocationCheckMode { get; set; }

/// <summary>
/// Gets or sets the callback used to validate the remote server certificate.
/// </summary>
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }

/// <summary>
/// Gets or sets the callback used to select a local client certificate.
/// </summary>
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
}
}
102 changes: 84 additions & 18 deletions kx/c.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -16,7 +18,7 @@
/// This class is essentially a serializer/deserializer of .NET types
/// to/from the KDB+ IPC wire format, enabling remote method invocation in KDB+ via TCP/IP.
/// </remarks>
public class c : IDisposable

Check warning on line 21 in kx/c.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'c' only contains lower-cased ascii characters. Such names may become reserved for the language.

Check warning on line 21 in kx/c.cs

View workflow job for this annotation

GitHub Actions / build

The type name 'c' only contains lower-cased ascii characters. Such names may become reserved for the language.
{
private readonly Socket _socket;

Expand Down Expand Up @@ -129,17 +131,14 @@
/// <exception cref="ArgumentNullException"><paramref name="host" /> was null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port" /> must be between <see cref="System.Net.IPEndPoint.MinPort" /> and <see cref="System.Net.IPEndPoint.MaxPort" /></exception>
/// <exception cref="KException">Unable to connect to KDB+ process, access denied or process unavailable.</exception>
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)
Expand Down Expand Up @@ -173,10 +172,26 @@
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;
Expand Down Expand Up @@ -204,12 +219,38 @@
/// <exception cref="ArgumentNullException"><paramref name="host" /> or <paramref name="userPassword" /> was null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port" /> must be between <see cref="System.Net.IPEndPoint.MinPort" /> and <see cref="System.Net.IPEndPoint.MaxPort" /></exception>
/// <exception cref="KException">Unable to connect to KDB+ process, access denied or process unavailable.</exception>
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)
{
}

/// <param name="host">The hostname or IP address of the KDB+ server.</param>
/// <param name="port">The TCP port of the KDB+ server.</param>
/// <param name="userPassword">The username/password string used for authentication.</param>
/// <param name="maxBufferSize">The maximum buffer size used for IPC messages.</param>
/// <param name="tlsOptions">The TLS options to use for the connection.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="host"/> or <paramref name="userPassword"/> was null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> must be between <see cref="IPEndPoint.MinPort"/> and
/// <see cref="IPEndPoint.MaxPort"/>.
/// </exception>
/// <exception cref="KException">
/// Unable to connect to the KDB+ process, access denied, or process unavailable.
/// </exception>
public c(
string host,
int port,
string userPassword,
int maxBufferSize,
KdbTlsOptions tlsOptions)
: this(host,port,userPassword,maxBufferSize,tlsOptions ?? KdbTlsOptions.Disabled,false)
{
}

Expand All @@ -221,15 +262,40 @@
/// <param name="file">uds file e.g. "/tmp/kx.5010" is the default with kdb+ listening on port 5010</param>
/// <param name="userPassword">The username and passsword, as "username:password" for remote authorisation.</param>
/// <param name="maxBufferSize">The maximum buffer size, default is 65536.</param>
/// <param name="useTLS">A boolean flag indicating whether or not TLS authentication is enabled, default is false.</param>
/// <exception cref="ArgumentNullException"><paramref name="file" /> or <paramref name="userPassword" /> was null.</exception>
/// <exception cref="KException">Unable to connect to KDB+ process, access denied or process unavailable.</exception>
/// <exception cref="PlatformNotSupportedException">The current OS does not support Unix Domain Sockets</exception>
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)
{
}

/// <summary>
/// Initialises a new instance of <see cref="c"/> using a Unix Domain Socket connection,
/// a username/password for authentication, a maximum buffer size, and explicit TLS options.
/// </summary>
/// <param name="file">The Unix Domain Socket file path.</param>
/// <param name="userPassword">The username/password string used for authentication.</param>
/// <param name="maxBufferSize">The maximum buffer size used for IPC messages.</param>
/// <param name="tlsOptions">The TLS options to use for the connection.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="file"/> or <paramref name="userPassword"/> was null.
/// </exception>
/// <exception cref="KException">
/// Unable to connect to the KDB+ process, access denied, or process unavailable.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// The current operating system does not support Unix Domain Sockets.
/// </exception>
public c(
string file,
string userPassword,
int maxBufferSize,
KdbTlsOptions tlsOptions)
: this(file, 0, userPassword, maxBufferSize, tlsOptions ?? KdbTlsOptions.Disabled, true)
{
}

Expand Down
Loading