SSL

This is a guide to setting up SSL using the C/C++ driver. This guide will use self-signed certificates, but most steps will be similar for certificates generated by a certificate authority (CA). The first step is to generate a public and private key pair for all Cassandra nodes and configure them to use the generated certificate.

Some notes on this guide:

  • Keystore and truststore might be used interchangeably. These can and often times are the same file. This guide uses the same file for both (keystore.jks) The difference is that keystores generally hold private keys, and truststores hold public keys/certificate chains.
  • Angle bracket fields (e.g. <field>) in examples need to be replaced with values specific to your environment.
  • keytool is an application included with Java 6+

SSL can be rather cumbersome to setup; if assistance is required please use the mailing list.

Generating the Cassandra Public and Private Keys

The most secure method of setting up SSL is to verify that DNS or IP address used to connect to the server matches identity information found in the SSL certificate. This helps to prevent man-in-the-middle attacks. Cassandra uses IP addresses internally so those can be used directly for verification or a domain name can be used via reverse DNS (PTR record). That means that the IP address or domain name of the Cassandra server where the certficate is installed needs to be present in either the certficate’s common name (CN) or one of its subject alternative names (SANs). It’s possible to create the certficate without either, but then it will not be possible to verify the server’s identity. Although this is not as secure, it eases the deployment of SSL by allowing the same certficate to be deployed across the entire Cassandra cluster.

To generate a public/private key pair with the IP address in the CN field use the following:

keytool -genkeypair -noprompt -keyalg RSA -validity 36500 \
  -alias node \
  -keystore keystore.jks \
  -storepass <keystore password> \
  -keypass <key password> \
  -dname "CN=<IP address or domain name goes here>, OU=Drivers and Tools, O=DataStax Inc., L=Santa Clara, ST=California, C=US"

If SAN is preferred use this command:

keytool -genkeypair -noprompt -keyalg RSA -validity 36500 \
  -alias node \
  -keystore keystore.jks \
  -storepass <keystore password> \
  -keypass <key password> \
  -ext SAN="<IP address or domain name goes here>" \
  -dname "CN=node1.datastax.com, OU=Drivers and Tools, O=DataStax Inc., L=Santa Clara, ST=California, C=US"

NOTE: If an IP address SAN is present then it overrides checking the CN.

Enabling client-to-node Encryption on Cassandra

The generated keystore from the previous step will need to be copied to all Cassandra node(s) and an update of the cassandra.yaml configuration file will need to be performed.

client_encryption_options:
  enabled: true
  keystore: <Path to keystore>/keystore.jks
  keystore_password: <keystore password> ## The password used when generating the keystore.
  truststore: <Path to keystore>/keystore.jks
  truststore_password: <keystore password>
  require_client_auth: <true or false>

NOTE: In this example keystore and truststore are identical.

The following guide has more information related to configuring SSL on Cassandra.

Setting up the C/C++ Driver to Use SSL

A CassSsl object is required and must be configured:

#include <cassandra.h>

void setup_ssl(CassCluster* cluster) {
  CassSsl* ssl = cass_ssl_new();

  /* Configure SSL object... */

  /* To enable SSL attach it to the cluster object */
  cass_cluster_set_ssl(cluster, ssl);

  /* You can detach your reference to this object once it's
   * added to the cluster object
   */
  cass_ssl_free(ssl);
}

Enable SSL without initializing the underlying library (e.g. OpenSSL)

This is useful for integrating with applications that have already initialized the underlying SSL library.

#include <cassandra.h>

void setup_ssl_no_lib_init(CassCluster* cluster) {
  /* The underlying SSL implemenation should be initialized */

  /* Enable SSL */
  CassSsl* ssl = cass_ssl_new_no_lib_init(); /* Don't reinitialize the library */

  /* Configure SSL object... */

  /* To enable SSL attach it to the cluster object */
  cass_cluster_set_ssl(cluster, ssl);

  /* You can detach your reference to this object once it's
   * added to the cluster object
   */
  cass_ssl_free(ssl);
}

Exporting and Loading the Cassandra Public Key

The default setting of the driver is to verify the certificate sent during the SSL handshake. For the driver to properly verify the Cassandra certificate the driver needs either the public key from the self-signed public key or the CA certificate chain used to sign the public key. To have this work, extract the public key from the Cassandra keystore generated in the previous steps. This exports a PEM formatted certificate which is required by the C/C++ driver.

keytool -exportcert -rfc -noprompt \
  -alias node \
  -keystore keystore.jks \
  -storepass <keystore password> \
  -file cassandra.pem

The trusted certificate can then be loaded using the following code:

int load_trusted_cert_file(const char* file, CassSsl* ssl) {
  CassError rc;
  char* cert;
  long cert_size;

  FILE *in = fopen(file, "rb");
  if (in == NULL) {
    fprintf(stderr, "Error loading certificate file '%s'\n", file);
    return 0;
  }

  fseek(in, 0, SEEK_END);
  cert_size = ftell(in);
  rewind(in);

  cert = (char*)malloc(cert_size);
  fread(cert, sizeof(char), cert_size, in);
  fclose(in);

  // Add the trusted certificate (or chain) to the driver
  rc = cass_ssl_add_trusted_cert_n(ssl, cert, cert_size);
  if (rc != CASS_OK) {
    fprintf(stderr, "Error loading SSL certificate: %s\n", cass_error_desc(rc));
    free(cert);
    return 0;
  }

  free(cert);
  return 1;
}

It is possible to load multiple self-signed certificates or CA certificate chains. This will be required in cases when self-signed certificates with unique IP addresses are being used. It is possible to disable the certificate verification process, but it is not recommended.

CassSsl* ssl = cass_ssl_new();

// Disable certifcate verifcation
cass_ssl_set_verify_flags(ssl, CASS_SSL_VERIFY_NONE);

/* ... */

cass_ssl_free(ssl);

Enabling Cassandra identity verification

If a unique certificate has been generated for each Cassandra node with the IP address or domain name in the CN or SAN fields, you also need to enable identity verification.

NOTE: This is disabled by default.

// Add identity verification flag: CASS_SSL_VERIFY_PEER_IDENTITY (IP address)
cass_ssl_set_verify_flags(ssl, CASS_SSL_VERIFY_PEER_CERT | CASS_SSL_VERIFY_PEER_IDENTITY);

Important: Previous versions of this section suggested using reverse DNS lookup as a way to validate the peer’s certificate i.e. using CASS_SSL_VERIFY_PEER_IDENTITY_DNS with cass_cluster_set_use_hostname_resolution(cluster, cass_true), but this is susceptible to man-in-the-middle (MITM) attacks and is no longer recommended.

/* DO NOT USE THE FOLLOWING. IT IS SUSCEPTIBLE TO MITM ATTACKS: */

CassSsl* ssl = cass_ssl_new();

cass_ssl_set_verify_flags(ssl, CASS_SSL_VERIFY_PEER_CERT | CASS_SSL_VERIFY_PEER_IDENTITY_DNS);

/* ... */

CassCluster* cluster = cass_cluster_new();

/*
This can allow an attacker to use their own certificate to fool the driver into thinking it's
connected to the intended endpoint, but is instead connected to the attacker's endpoint.
*/
cass_cluster_set_use_hostname_resolution(cluster, cass_true);

/* ... */

cass_cluster_free(cluster);

Using Cassandra and the C/C++ driver with client-side certificates

Client-side certificates allow Cassandra to authenticate the client using public key cryptography and chains of trust. This is same process as above but in reverse. The client has a public and private key and the Cassandra node has a copy of the private key or the CA chain used to generate the pair.

Generating and loading the client-side certificate

A new public/private key pair needs to be generated for client authentication.

keytool -genkeypair -noprompt -keyalg RSA -validity 36500 \
  -alias driver \
  -keystore keystore-driver.jks \
  -storepass <keystore password> \
  -keypass <key password>

The public and private key then need to be extracted and converted to the PEM format.

To extract the public:

keytool -exportcert -rfc -noprompt \
  -alias driver \
  -keystore keystore-driver.jks \
  -storepass <keystore password> \
  -file driver.pem

To extract and convert the private key:

keytool -importkeystore -noprompt -srcalias certificatekey -deststoretype PKCS12 \
  -srcalias driver \
  -srckeystore keystore-driver.jks \
  -srcstorepass <keystore password> \
  -storepass <key password> \
  -destkeystore keystore-driver.p12

openssl pkcs12 -nomacver -nocerts \
  -in keystore-driver.p12 \
  -password pass:<key password> \
  -passout pass:<key password> \
  -out driver-private.pem

Now PEM formatted public and private key can be loaded. These files can be loaded using the same code from above in load_trusted_cert_file().

CassError rc = CASS_OK;
CassSsl* ssl = cass_ssl_new();

char* cert = NULL;
size_t cert_size = 0;

// Load PEM-formatted certificate data and size into cert and cert_size...

rc = cass_ssl_set_cert_n(ssl, cert, cert_size);
if (rc != CASS_OK) {
  // Handle error
}

char* key = NULL;
size_t key_size = 0;

// A password is required when the private key is encrypted. If the private key
// is NOT password protected use NULL.
const char* key_password = "<key password>";

// Load PEM-formatted private key data and size into key and key_size...

rc = cass_ssl_set_private_key_n(ssl, key, key_size, key_password, strlen(key_password));
if (rc != CASS_OK) {
  // Handle error
}

cass_ssl_free(ssl);

Setting up client authentication with Cassandra

The driver’s public key or the CA chain used to sign the driver’s certificate will need to be added to Cassandra’s truststore. If using self-signed certificate then the public key will need to be extracted from the driver’s keystore generated in the previous steps.

Extract the public key from the driver’s keystore and add it to Cassandra’s truststore.

keytool -exportcert -noprompt \
  -alias driver \
  -keystore keystore-driver.jks \
  -storepass cassandra \
  -file cassandra-driver.crt

keytool -import -noprompt \
  -alias truststore \
  -keystore keystore.jks \
  -storepass cassandra \
  -file cassandra-driver.crt

You also need to enable client authentication in cassandra.yaml:

require_client_auth: true