# TLS

This is a guide to setting up TLS 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 ScyllaDB/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](https://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html) is an application included with Java 6+

## Prerequisites

### Generating the ScyllaDB/Cassandra Public and Private Keys

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

**NOTE:** this driver verifies the identity against subject alternative names of type `iPAddress` only; unlike the CPP driver, it does not fall back to the common name (CN). Prefer the SAN recipe below. A CN-only certificate can still be used, but only with identity verification relaxed to `CASS_SSL_VERIFY_PEER_CERT` or disabled with `CASS_SSL_VERIFY_NONE`.

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

```bash
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"
```

### Enabling `client-to-node` Encryption on ScyllaDB/Cassandra

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

```bash
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.

## Setting up the C/C++ Driver to Use TLS

A [`CassSsl`](https://cpp-rs-driver.docs.scylladb.com/stable/api/struct.CassSsl) object is required and must be configured:

```c
#include <cassandra.h>

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

  /* Configure TLS object... */

  /* To enable TLS 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 TLS without initializing the underlying library (e.g. OpenSSL)

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

```c
#include <cassandra.h>

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

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

  /* Configure TLS object... */

  /* To enable TLS 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 ScyllaDB/Cassandra Public Key

The default setting of the driver is to verify the certificate sent during the TLS handshake. For the driver to properly verify the ScyllaDB/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 ScyllaDB/Cassandra keystore generated in the previous steps. This exports a [PEM formatted](https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail) certificate which is required by the C/C++ driver.

```bash
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:

```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_n(ssl, cert, cert_size);
  if (rc != CASS_OK) {
    fprintf(stderr, "Error loading TLS 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.

```c
CassSsl* ssl = cass_ssl_new();

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

/* ... */

cass_ssl_free(ssl);
```

#### ScyllaDB/Cassandra identity verification

If a unique certificate has been generated for each ScyllaDB/Cassandra node with
the IP address in the SAN field, the driver verifies that the node it connected
to is the one the certificate was issued for.

**NOTE:** This is disabled by default. This is part of `CASS_SSL_VERIFY_PEER_IDENTITY`.
The flags form a bitmask, so it can be requested explicitly on its own or combined with `CASS_SSL_VERIFY_PEER_CERT`:

```c
CassSsl* ssl = cass_ssl_new();

// Verify the certificate chain and the peer's identity (IP address).
cass_ssl_set_verify_flags(ssl, CASS_SSL_VERIFY_PEER_CERT | CASS_SSL_VERIFY_PEER_IDENTITY);
```

**NOTE:** the identity is matched against the certificate’s subject alternative
names of type `iPAddress` only. Unlike the C/C++ driver, this driver does not
fall back to the subject common name (CN), so a certificate that identifies a
node only by CN is rejected.

To validate the certificate chain without checking who the peer claims to be —
useful with a single certificate shared by all nodes — ask for
`CASS_SSL_VERIFY_PEER_CERT` alone:

```c
// Verify the certificate chain only; the peer's identity is not checked.
cass_ssl_set_verify_flags(ssl, CASS_SSL_VERIFY_PEER_CERT);
```

Verifying the identity against a domain name rather than an IP address
(`CASS_SSL_VERIFY_PEER_IDENTITY_DNS`) is **not supported**; it is accepted, but
treated as `CASS_SSL_VERIFY_PEER_IDENTITY`.

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

Client-side certificates allow ScyllaDB/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 ScyllaDB/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.

```bash
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:

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

To extract and convert the private key:

```bash
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().

```c
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 ScyllaDB/Cassandra

The driver’s public key or the CA chain used to sign the driver’s certificate will need to be added to ScyllaDB/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 ScyllaDB/Cassandra’s truststore.

```bash
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`:

```yaml
require_client_auth: true
```
