TLS/SSL

You can secure traffic between the driver and Apache Cassandra or DataStax Enterprise (DSE) with TLS/SSL. There are two aspects to that:

  • Client-to-node encryption, where the traffic is encrypted and the client verifies the identity of the nodes it connects to.
  • Optional client certificate authentication, where server nodes also verify the identity of the client.

This section describes the driver-side configuration, it assumes that you’ve already configured SSL encryption in the server. If you’re using DSE, you can checkout the server documentation that covers the basic procedures to setup SSL with DSE. If you’re using Apache Cassandra, you can checkout the documentation that covers the setup with Apache Cassandra.

You can find SSL examples on how to configure the driver for both server and client auth in the driver’s Github repository.

Driver configuration

Use Builder.WithSSL() method to enable client TLS/SSL encryption:

var cluster = Cluster.Builder()
    .AddContactPoints(...)
    .WithSSL()
    .Build();

Builder.WithSSL() and Builder.WithSSL(new SSLOptions()) enable TLS/SSL with the default configuration. The default configuration includes a RemoteCertificateValidationCallback that logs any errors returned by .NET’s SSL API.

To customize this configuration check out the several SSL options available on the SSLOptions class:

var cluster = Cluster.Builder()
    .AddContactPoints(...)
    .WithSSL(new SSLOptions().SetCertificateRevocationCheck(true))
    .Build();

The .NET SSL API will return a RemoteCertificateNameMismatch error when the HostName does not match the name on the server certificate. By default the driver performs reverse DNS resolution to obtain the HostName from the server node’s IP address. You can change this by providing a custom hostname resolver in the SSLOptions.SetHostNameResolver(...) method:

var cluster = Cluster.Builder()
    .AddContactPoints(...)
    .WithSSL(new SSLOptions().SetHostNameResolver(...))
    .Build();

Alternatively, you can provide your own RemoteCertificateValidationCallback to handle the SSL errors and perform your own validation logic (see below for an example of this). Note that you can introduce security vulnerabilities with this so you should avoid it if you can.

Enabling server authentication with a custom root certificate

If you have a custom (untrusted) root certificate, then there are two ways to provide it: using the system/user store or loading it manually in code.

Certificate store

On Windows, you can add this certificate to the Trusted Root Certification Authorities logical store. This can be done with certmgr.msc for example, which is a MMC (Microsoft Management Console) snap-in tool to manage certificates on Windows systems. On non Windows platforms, support for system/user certificate stores is limited and the location of this store depends on which SSL library is used so you might prefer loading the certificate manually in code.

Loading the certificate in code

An alternative to the certificate store is to load the certificate in code and provide a custom certificate validator using SSLOptions.SetRemoteCertValidationCallback(RemoteCertificateValidationCallback) that compares the root certificate of the chain returned by the server with your previously loaded root certificate.

Here is an example (CustomValidator here is a class that you would have to implement):

// custom validator
var certificateValidator = new CustomValidator(new X509Certificate2(@"C:\path\to\ca.crt"));

var cluster = Cluster.Builder()
    .AddContactPoints("...")
    .WithSSL(new SSLOptions().SetRemoteCertValidationCallback(
        (sender, certificate, chain, errors) => certificateValidator.Validate(sender, certificate, chain, errors)))
    .Build();

The examples on the driver’s repository have a basic custom certificate validator which can be used as a starting point.

Enabling client authentication

To enable client authentication, you need to provide the driver with the client certificate(s):

var cluster = Cluster.Builder()
    .AddContactPoints("...")
    .WithSSL(new SSLOptions()
        // set client certificate collection
        .SetCertificateCollection(new X509Certificate2Collection
        {
            // use the following constructor if the certificate is password protected
            new X509Certificate2(@"C:\path\to\client_cert.pfx", "cert_password"),

            // use the following constructor if the certificate is not password protected
            //new X509Certificate2(@"C:\path\to\client_cert.pfx")
        )
    )
    .Build();

If you prefer to use the certificate store you can use X509Store to obtain a collection of all installed certificates (optionally you can filter this collection before providing it to the driver so that it only contains the relevant certificate). On Windows, the following example works if you add the client certificate to the Personal logical store (using certmgr.msc for example as described previously).

X509Certificate2Collection collection;
using (var store = new X509Store(StoreLocation.LocalMachine))
{
    store.Open(OpenFlags.ReadOnly);
    collection = store.Certificates;
}

var cluster = Cluster.Builder()
    .AddContactPoints("...")
    .WithSSL(new SSLOptions().SetCertificateCollection(collection))
    .Build();