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 certficates 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 interchangably. These can and often times are the same file. This guide uses the same file for both (keystore.jks) The difference is 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 or #datastax-drivers on irc.freenode.net <http://freenode.net> for help.

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 that’s the only supported information for identity verification. That means that the IP address 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 goes here>, OU=Drivers and Tools, O=DataStax Inc., L=Santa Clara, ST=California, C=US"

If SAN is prefered 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 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);
}

Exporting and Loading the Cassandra Public Key

The default setting of the driver is to verify the certifcate sent during the SSL handshake. For the driver to properly verify the Cassandra certifcate 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 certficate 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 certficate can then be loaded using the following code: “`c 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(ssl, cass_string_init2(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 certficate chains. In the event where self-signed certfiicates with unique IP addresses are being used this will be required. It is possible to disable the certificate verification process, but it is not recommended.

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

Enabling Cassandra identity verification

If a unique certifcate has been generated for each Cassandra node with the IP address in the CN or SAN fields, identity verification will also need to be enabled.

NOTE: This is disabled by default.

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

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

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.

Genterating and loading the client-side certficate

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;

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(ssl, cass_string_init2(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(ssl, cass_string_init2(key, key_size), key_password);
if (rc != CASS_OK) {
  // Handle error
}

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 extrated 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

Client authentication in cassandra.yaml will also need to be enabled

require_client_auth: true