You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sm...@apache.org on 2022/02/07 22:41:15 UTC
[cassandra] branch trunk updated: Add support for PEM based key material for SSL
This is an automated email from the ASF dual-hosted git repository.
smiklosovic pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 3655b26 Add support for PEM based key material for SSL
3655b26 is described below
commit 3655b26adf8d3b94095924920d05cc1a16d0f4c0
Author: Maulin Vasavada <mv...@paypal.com>
AuthorDate: Fri Oct 8 23:51:47 2021 -0700
Add support for PEM based key material for SSL
patch by Maulin Vasavada; reviewed by Jon Meredith and Stefan Miklosovic for CASSANDRA-17031
---
.gitignore | 2 +-
CHANGES.txt | 1 +
examples/ssl-factory/build.xml | 11 +-
.../KubernetesSecretsPEMSslContextFactory.java | 184 +++++++++
.../KubernetesSecretsSslContextFactory.java | 4 +-
.../test/conf/cassandra_encrypted_private_key.pem | 51 +++
...ssandra_encrypted_private_key_multiplecerts.pem | 71 ++++
.../test/conf/cassandra_trusted_certificates.pem | 22 +
.../conf/cassandra_unencrypted_private_key.pem | 50 +++
...KubernetesSecretsPEMSslContextFactoryTest.java} | 240 ++++++-----
.../KubernetesSecretsSslContextFactoryTest.java | 93 ++---
.../security/FileBasedSslContextFactory.java | 46 ++-
.../security/PEMBasedSslContextFactory.java | 372 +++++++++++++++++
.../org/apache/cassandra/security/PEMReader.java | 282 +++++++++++++
test/conf/cassandra-pem-jks-sslcontextfactory.yaml | 150 +++++++
...pem-sslcontextfactory-invalidconfiguration.yaml | 147 +++++++
test/conf/cassandra-pem-sslcontextfactory.yaml | 151 +++++++
test/conf/cassandra_ssl_test.keystore.pem | 51 +++
test/conf/cassandra_ssl_test.truststore.pem | 22 +
.../cassandra_ssl_test.unencrypted_keystore.pem | 50 +++
.../PEMBasedSslContextFactoryConfigTest.java | 79 ++++
...PEMBasedSslContextFactoryInvalidConfigTest.java | 62 +++
.../security/PEMBasedSslContextFactoryTest.java | 449 ++++++++++++++++++++
.../PEMJKSSslContextFactoryConfigTest.java | 71 ++++
.../apache/cassandra/security/PEMReaderTest.java | 454 +++++++++++++++++++++
.../apache/cassandra/security/SSLFactoryTest.java | 42 ++
26 files changed, 2990 insertions(+), 167 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3b01673..18f2204 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,7 +66,7 @@ target/
.DS_Store
Thumbs.db
-/.ant-targets-build.xml
+**/.ant-targets-build.xml
.ant_targets
# Generated files from the documentation
diff --git a/CHANGES.txt b/CHANGES.txt
index 66aae18..bf0de22 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
4.1
+ * Add support for PEM based key material for SSL (CASSANDRA-17031)
* Standardize storage configuration parameters' names. Support unit suffixes. (CASSANDRA-15234)
* Remove support for Windows (CASSANDRA-16956)
* Runtime-configurable YAML option to prohibit USE statements (CASSANDRA-17318)
diff --git a/examples/ssl-factory/build.xml b/examples/ssl-factory/build.xml
index 9f150b0..d803aeb 100644
--- a/examples/ssl-factory/build.xml
+++ b/examples/ssl-factory/build.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
@@ -51,6 +51,7 @@
</fileset>
<path refid="build.classpath"/>
<path location="${build.dir}/${final.name}.jar"/>
+ <pathelement location="${build.classes}"/>
<pathelement location="${test.build.classes}"/>
<pathelement location="${test.build.conf}"/>
</path>
@@ -73,9 +74,11 @@
</jar>
</target>
- <target name="buildTests" depends="init">
- <deltree dir="${test.build.dir}/conf"/>
- <copydir src="test/conf" dest="${test.build.dir}/conf"/>
+ <target name="buildTests" depends="build">
+ <delete dir="${test.build.dir}/conf"/>
+ <copy todir="${test.build.dir}/conf">
+ <fileset dir="test/conf" includes="**"/>
+ </copy>
<javac destdir="${test.build.classes}" debug="true" includeantruntime="false">
<src path="${test.src}" />
<src path="${test.build.dir}/conf" />
diff --git a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java
new file mode 100644
index 0000000..fb11c91
--- /dev/null
+++ b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactory.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ * <p>
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * The most obvious choice might be to just use the time in nano/milli-seconds precision but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values do not have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ * <p>
+ * Defaults:
+ * <pre>
+ * private key password = cassandra
+ * keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ * truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ * <p>
+ * Customization: In order to customize the K8s secret configuration, override appropriate values in the below Cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ * server_encryption_options:
+ * internode_encryption: none
+ * ssl_context_factory:
+ * class_name: org.apache.cassandra.security.KubernetesSecretsPEMSslContextFactory
+ * parameters:
+ * PRIVATE_KEY_ENV_VAR: PRIVATE_KEY
+ * PRIVATE_KEY_PASSWORD_ENV_VAR: PRIVATE_KEY_PASSWORD
+ * KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ * TRUSTED_CERTIFICATES_ENV_VAR: TRUSTED_CERTIFICATES
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ * <p>
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ * name: my-pod
+ * labels:
+ * app: my-app
+ * spec:
+ * containers:
+ * - name: my-app
+ * image: my-app:latest
+ * imagePullPolicy: Always
+ * env:
+ * - name: PRIVATE_KEY
+ * valueFrom:
+ * secretKeyRef:
+ * name: my-ssl-store
+ * key: private-key
+ * - name: PRIVATE_KEY_PASSWORD
+ * valueFrom:
+ * secretKeyRef:
+ * name: my-ssl-store
+ * key: private-key-password
+ * - name: TRUSTED_CERTIFICATES
+ * valueFrom:
+ * secretKeyRef:
+ * name: my-ssl-store
+ * key: trusted-certificates
+ * volumeMounts:
+ * - name: my-ssl-store
+ * mountPath: "/etc/my-ssl-store"
+ * readOnly: true
+ * volumes:
+ * - name: my-ssl-store
+ * secret:
+ * secretName: my-ssl-store
+ * items:
+ * - key: keystore-last-updatedtime
+ * path: keystore-last-updatedtime
+ * - key: truststore-last-updatedtime
+ * path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsPEMSslContextFactory extends KubernetesSecretsSslContextFactory
+{
+ public static final String DEFAULT_PRIVATE_KEY = "";
+ public static final String DEFAULT_PRIVATE_KEY_PASSWORD = "";
+ public static final String DEFAULT_TRUSTED_CERTIFICATES = "";
+
+ @VisibleForTesting
+ static final String DEFAULT_PRIVATE_KEY_ENV_VAR_NAME = "PRIVATE_KEY";
+ @VisibleForTesting
+ static final String DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME = "PRIVATE_KEY_PASSWORD";
+ @VisibleForTesting
+ static final String DEFAULT_TRUSTED_CERTIFICATES_ENV_VAR_NAME = "TRUSTED_CERTIFICATES";
+
+ private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsPEMSslContextFactory.class);
+ private String pemEncodedKey;
+ private String keyPassword;
+ private String pemEncodedCertificates;
+ private PEMBasedSslContextFactory pemBasedSslContextFactory;
+
+ public KubernetesSecretsPEMSslContextFactory()
+ {
+ pemBasedSslContextFactory = new PEMBasedSslContextFactory();
+ }
+
+ public KubernetesSecretsPEMSslContextFactory(Map<String, Object> parameters)
+ {
+ super(parameters);
+
+ pemEncodedKey = getValueFromEnv(getString(PEMConfigKey.PRIVATE_KEY_ENV_VAR, DEFAULT_PRIVATE_KEY_ENV_VAR_NAME),
+ DEFAULT_PRIVATE_KEY);
+ keyPassword = getValueFromEnv(getString(PEMConfigKey.PRIVATE_KEY_PASSWORD_ENV_VAR,
+ DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME),
+ DEFAULT_PRIVATE_KEY_PASSWORD);
+ pemEncodedCertificates = getValueFromEnv(getString(PEMConfigKey.TRUSTED_CERTIFICATE_ENV_VAR, DEFAULT_TRUSTED_CERTIFICATES_ENV_VAR_NAME),
+ DEFAULT_TRUSTED_CERTIFICATES);
+
+ parameters.put(PEMBasedSslContextFactory.ConfigKey.ENCODED_KEY.getKeyName(), pemEncodedKey);
+ parameters.put(PEMBasedSslContextFactory.ConfigKey.KEY_PASSWORD.getKeyName(), keyPassword);
+ parameters.put(PEMBasedSslContextFactory.ConfigKey.ENCODED_CERTIFICATES.getKeyName(), pemEncodedCertificates);
+
+ pemBasedSslContextFactory = new PEMBasedSslContextFactory(parameters);
+ }
+
+ @Override
+ public synchronized void initHotReloading()
+ {
+ // No-op
+ }
+
+ @Override
+ public boolean hasKeystore()
+ {
+ return pemBasedSslContextFactory.hasKeystore();
+ }
+
+ @Override
+ protected KeyManagerFactory buildKeyManagerFactory() throws SSLException
+ {
+ KeyManagerFactory kmf = pemBasedSslContextFactory.buildKeyManagerFactory();
+ checkedExpiry = pemBasedSslContextFactory.checkedExpiry;
+ return kmf;
+ }
+
+ @Override
+ protected TrustManagerFactory buildTrustManagerFactory() throws SSLException
+ {
+ return pemBasedSslContextFactory.buildTrustManagerFactory();
+ }
+
+ public interface PEMConfigKey
+ {
+ String PRIVATE_KEY_ENV_VAR = "PRIVATE_KEY_ENV_VAR";
+ String PRIVATE_KEY_PASSWORD_ENV_VAR = "PRIVATE_KEY_PASSWORD_ENV_VAR";
+ String TRUSTED_CERTIFICATE_ENV_VAR = "TRUSTED_CERTIFICATE_ENV_VAR";
+ }
+}
diff --git a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
index 699efbc..c83fb03 100644
--- a/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
+++ b/examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
@@ -186,8 +186,8 @@ public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFacto
}
/**
- * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
- * and compares the values for those variables with the current timestamps. In case the environment variables are
+ * Checks mounted paths for {@code KEYSTORE_UPDATED_TIMESTAMP_PATH} and {@code TRUSTSTORE_UPDATED_TIMESTAMP_PATH}
+ * and compares the values for those variables with the current timestamps. In case the mounted paths are
* not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
* that nothing has changed.
* @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
diff --git a/examples/ssl-factory/test/conf/cassandra_encrypted_private_key.pem b/examples/ssl-factory/test/conf/cassandra_encrypted_private_key.pem
new file mode 100644
index 0000000..ed981cc
--- /dev/null
+++ b/examples/ssl-factory/test/conf/cassandra_encrypted_private_key.pem
@@ -0,0 +1,51 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
+g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
+xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
+L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
+sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
+f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
+RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
+0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
+evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
+tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
+wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
+K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
+zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
+mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
+WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
+jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
+eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
+DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
+AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
+Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
+Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
+ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
+bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
+hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
+RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
+pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
+FujZhqbKUDbYAcqTkoQ=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/examples/ssl-factory/test/conf/cassandra_encrypted_private_key_multiplecerts.pem b/examples/ssl-factory/test/conf/cassandra_encrypted_private_key_multiplecerts.pem
new file mode 100644
index 0000000..fed57d4
--- /dev/null
+++ b/examples/ssl-factory/test/conf/cassandra_encrypted_private_key_multiplecerts.pem
@@ -0,0 +1,71 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6TAbBgkqhkiG9w0BBQMwDgQI4QuRiKYzf88CAggABIIEyPRVmPp38SIFr8H3
+wi+oc6b+HJH7SPflXO6XZe4Tignw/aSyBTsLm2dWrzojRAYMIRd1xC7yQ2ffYrvx
+uoYbtOQeAminNqvwXdRTnwu6oC0rxdBT8RQ9NK7xL2tQyD/shmOeTJG/glXxaeqS
+rT0CZ5P5GJh6xdIWLEu3lEa3NSWVFE2YacUphmxBoaWjBjsJfWTgkF665SgP+2lh
+8R2WTcHrHjD8jR4jHB03wlup0LOmOwzplUmqHB9TyuA4wF6tlJajwBcPa0PNI6ny
+e9YcdcRr7Y0IxnPQr7PhQNV5AQb9TivwX4WaZxR+BXtwMglp+mz0ohjwLS3z6pqr
+tLrFhv2qcacSl+CKukFb9umV/QBkUk/iu+jwLcNJKPC965GWdieNbO0akBQpQsUN
+mqaF9DYHogW5lRnybl8WWPIR8tXmSCbSUIgzw4lRK+o15I4vaMI0NfkwFD/2y1sn
+t3m9LnVBukkpx3g/CPKd9PbZZeWpOTrnRJQfOu9Fj2lmkpGp0peCBqLJpO0pieVl
+87EQ0ZCErtAGLGIAhWnDUqRK0MaWZ+DMQNKYn5klF4YTVBkfRc9tQbIgBaa77wvz
+gvVWBuJtTFpCt9c8jByTH1gLbchC4bhLsy1nO7moevypMmNW4rqw9x5f0EIR3zCU
+L5/buoIh91TG5JB7BaIbVHtbB/Y2siARRXJibuw3ChBjqPOfzQ66j//NCMqhfTwT
+x2wn7L1BB4xyLJgVW9803FUTzaaL3EvJjzpdvrGC7vzcB6Jd0La9cjHhWSAPOKro
+nD9XPCbgLs10vW9g1Tc8drnZklhw6f7xrIQhWFg6VlwmVpvCQhEpX48rCCo2PH9X
+uzeJA+oqFEH3zfDp0r+q6jbAl+5TkkbBBgC2GCoI1vTYSKaWjeKKHkgzGG0QQLAz
+YXWMXvWKOME4wVPkeVxJv80RqDv0JsoOrnVoaFAzAHJMWa7UZQnjkrbVz/y8ZbV4
+oLJjQjvuOnU2PoXXs6SXbzOs1xx5zbX1UUH3Wc7/CCaUec2hemQJ5m6b1XJciyrY
+GHpMKWtXky9Mo1ruTP7ZH1nk03b4PTObKSx2gQD5AZ/ASuTeahMqMb/2PJkDkpHA
+sy8b1zOn2YTbf4K6NWVNIOkiaApmKhhX0Af6Lg8Wr2ymRTXdp/Um8f+SQLADpB/F
+xOydEN622wmihKDge9DrUFqPG/bdIiRGLXLg8efNboC6/cn/i/sheO7/YlrvcUNo
+qxDa/Bb1N/DgmtgAQ1ZP+AKjk6FKkwZRF1X/VZkZ6auscDlaPetF7razPeIJUrKN
+z/x4AD2txGYKmeYztYR577hPXBw+PPKdggRhIugb6z5Tk89C2pDEwfnByA/wcGJr
+w5avxrubosPrp0QtJpZMzouOvcD52VUiZzDfu9dqI/hpinyt5rETj5E19qxBjIZt
+X3Nq5lY2ktbyqVIo8Z8w4EUU+3XHZKqDwjyYvjxCxv5lVVnqvQrH9h3kENBMrPYQ
+4XonQHpUGG7g7pA3ylmHi+nEedr0H5qKHzyFZlRdI7CLVNoAtBSdwvmtGd2cVVXU
+EaToKNbHPXXYYPX/oVAWZYwD7PHXIRJkiEZnrFARNhLypicU7yjvejUPXcVy5HMh
+XqEbrODPp4VXfbYhVg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDXjCCAkYCAhI0MA0GCSqGSIb3DQEBBAUAMHcxCzAJBgNVBAYTAlVTMRMwEQYD
+VQQIDApDYWxpZm9ybmlhMREwDwYDVQQHDAhTYW4gSm9zZTEXMBUGA1UECgwOUGVy
+c29uYWwsIEluYy4xEDAOBgNVBAsMB1Jvb3QgQ0ExFTATBgNVBAMMDG15ZG9tYWlu
+LmNvbTAeFw0yMTExMjIyMjQ5MzlaFw0yMjExMjIyMjQ5MzlaMHIxCzAJBgNVBAYT
+AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYDVQQHDAhTYW4gSm9zZTEXMBUG
+A1UECgwOUGVyc29uYWwsIEluYy4xCzAJBgNVBAsMAklUMRUwEwYDVQQDDAxteWRv
+bWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5fdA7wwD9
+9e5RcdLscvGB+hqJUEHuNC53SYKg5X4Sf0H4ExQUbsy8UaoWzWHhgGbCtTvUVavl
+72xsO74ei0EblopW7QknF0kaTO8Vi3mxhUAdtZFLG/o0NS9J16HdGDGojJwuqU9+
+sMQt1w0HCTMlriELnxaUFKP7M9b0uK5VODEKJ38QKNGXUDt66D7BVYeT/6hz2cXK
+QWDoHk/JadALSzW5ES8KIHfxCLnl2TcKxQhJ4CnL8qeGvc8N3VyTh2AXajaJW5RB
+8Oy4CVoYxcdmP1IapxCD+yNcmNt9XpUTD+6eM5gnvtbye+MSfwPz2MW+fWEDZXOv
+3VxhJyTRFNVTAgMBAAEwDQYJKoZIhvcNAQEEBQADggEBADYK/pn6QG7bvUL0Xnnw
+1KPf1nx36gfJE2V0bNk4uyNNeYufMKS8gPLzC+a3RigCEDc+hIZFE5BJexHd7DXA
+CWgHZJtdjM/Xlgoxbf1yfGV3DWeIZlNFSFZujBIpbm1Ug2BAeV31YRWODPZlUSEZ
+0jv8NEs8/oEz9bM4jwRdn09lo4D9hE6o8qDnrzmN2LBZP5dDIJ6g/M+mq/SJFnho
+qBrfUABZhbgk2+tkZ89OI2xpASCLo6X/vqua2iho6km3x+cz6EI9BbvVr6xOOdVK
+m6gs/Bi4MGTh35jdmvyXoyBUOd1w3yBBj86qbEt2ZHYqreRTxntQYx06598Q9Dsi
+xdg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDajCCAlICCQD/7mxPcMTPIDANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV
+UzETMBEGA1UECAwKQ2FsaWZvcm5pYTERMA8GA1UEBwwIU2FuIEpvc2UxFzAVBgNV
+BAoMDlBlcnNvbmFsLCBJbmMuMRAwDgYDVQQLDAdSb290IENBMRUwEwYDVQQDDAxt
+eWRvbWFpbi5jb20wHhcNMjExMTIyMjExODAwWhcNNDkwNDA5MjExODAwWjB3MQsw
+CQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTERMA8GA1UEBwwIU2FuIEpv
+c2UxFzAVBgNVBAoMDlBlcnNvbmFsLCBJbmMuMRAwDgYDVQQLDAdSb290IENBMRUw
+EwYDVQQDDAxteWRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCkIwuNGv3ckew/o2UwaDlYgXH9bh1jap4ZCb6qpjvR3tq9nCerY6XMli0Z
+Xxg0wMHDNUr/jmVYIdQjbz0DVNz/l6ZBJHzHCEgqR40pNM3NgC5sDyuNhF3WLNvj
+WgHEwYosfb/9kFRjKUPqqtJ0ccj87OP3XrE/4epCTdJdmugroAQSpXt1ZZfwwPO4
+K27DzMD9W01EmeLcUhMfrpUnKGCfL22c0sZZm/6Khk4BExC3pSILP/NREKeUEAHw
++rxhNqbUyD/e4/DutdtJ5zONA+GVVGYCpu1Iy0W78Jve4MD2/TFPcEzf5omiWpPz
+WjpOWayD43ur0SZnYJ5haUlZ+bSLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABqN
+/eb+mKEw2zklPZuzy5uhOc7ImG2LP/ha3oc2ntwkqZQ2LmVA/2ZNVfcNrRRngIfn
+Ir9Kual7djmKmIQvanpnSyXOKOlIiuN0VOrewgzlBZZwlFJL/AH1l7K9uZfBbV5h
+oFfaR9wc+vRGsg3nqO9+hEuk6xbp0jk8QCt26EVhEPlijxzbxTQYiNPNSLuf/kPW
+C9xtIKSWIDT4N6DtH7BtHGQKQdRJ2b4SSUF4joEmBe6jcrLBeDybmuFtKqlVJKUk
+tzBd9CPseqMML1c518KzxlSkXNxTCa7PWEnuN5atLZ+pGGjxtGcDKkrZ9Cgi09G8
+MzB8b4C/goypyhBNlyI=
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/examples/ssl-factory/test/conf/cassandra_trusted_certificates.pem b/examples/ssl-factory/test/conf/cassandra_trusted_certificates.pem
new file mode 100644
index 0000000..8806ce8
--- /dev/null
+++ b/examples/ssl-factory/test/conf/cassandra_trusted_certificates.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/examples/ssl-factory/test/conf/cassandra_unencrypted_private_key.pem b/examples/ssl-factory/test/conf/cassandra_unencrypted_private_key.pem
new file mode 100644
index 0000000..ce3d8e7
--- /dev/null
+++ b/examples/ssl-factory/test/conf/cassandra_unencrypted_private_key.pem
@@ -0,0 +1,50 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw
+efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv
+wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047
+DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB
+9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi
+G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM
+B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL
+uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU
+l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt
+bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y
+G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF
+FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u
+7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC
+0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W
+ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8
+N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw
+fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk
+cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh
+uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz
+9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv
+hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR
+QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9
+FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K
+37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj
+Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr
+RXKInnLInFD9JD94VqmMozo=
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java
similarity index 52%
copy from examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java
copy to examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java
index fec48fd..2d127f3 100644
--- a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java
+++ b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsPEMSslContextFactoryTest.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.lang3.StringUtils;
@@ -37,63 +38,39 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.cassandra.config.EncryptionOptions;
-
-import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR;
+import static org.apache.cassandra.security.KubernetesSecretsPEMSslContextFactory.DEFAULT_PRIVATE_KEY_ENV_VAR_NAME;
+import static org.apache.cassandra.security.KubernetesSecretsPEMSslContextFactory.DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME;
+import static org.apache.cassandra.security.KubernetesSecretsPEMSslContextFactory.PEMConfigKey.PRIVATE_KEY_PASSWORD_ENV_VAR;
+import static org.apache.cassandra.security.KubernetesSecretsPEMSslContextFactory.PEMConfigKey.TRUSTED_CERTIFICATE_ENV_VAR;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH;
-import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.TRUSTSTORE_UPDATED_TIMESTAMP_PATH;
-public class KubernetesSecretsSslContextFactoryTest
+public class KubernetesSecretsPEMSslContextFactoryTest
{
- private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactoryTest.class);
- private static final String TRUSTSTORE_PATH = EncryptionOptions.ConfigKey.TRUSTSTORE.toString();
- private static final String KEYSTORE_PATH = EncryptionOptions.ConfigKey.KEYSTORE.toString();
-
- private Map<String, Object> commonConfig = new HashMap<>();
- private final static String truststoreUpdatedTimestampFilepath = "build/test/conf/cassandra_truststore_last_updatedtime";
- private final static String keystoreUpdatedTimestampFilepath = "build/test/conf/cassandra_keystore_last_updatedtime";
+ private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsPEMSslContextFactoryTest.class);
- private static class KubernetesSecretsSslContextFactoryForTestOnly extends KubernetesSecretsSslContextFactory
- {
+ private static final String ENCRYPTED_PRIVATE_KEY_FILEPATH = "build/test/conf/cassandra_encrypted_private_key.pem";
+ private static final String ENCRYPTED_PRIVATE_KEY_WITH_MULTIPLE_CERTS_IN_CERTCHAIN_FILEPATH = "build/test/conf" +
+ "/cassandra_encrypted_private_key_multiplecerts.pem";
+ private static final String UNENCRYPTED_PRIVATE_KEY_FILEPATH = "build/test/conf/cassandra_unencrypted_private_key.pem";
+ private static final String TRUSTED_CERTIFICATES_FILEPATH = "build/test/conf/cassandra_trusted_certificates.pem";
+ private final static String TRUSTSTORE_UPDATED_TIMESTAMP_FILEPATH = "build/test/conf/cassandra_truststore_last_updatedtime";
+ private final static String KEYSTORE_UPDATED_TIMESTAMP_FILEPATH = "build/test/conf/cassandra_keystore_last_updatedtime";
- public KubernetesSecretsSslContextFactoryForTestOnly()
- {
- }
+ private static String private_key;
+ private static String unencrypted_private_key;
+ private static String trusted_certificates;
- public KubernetesSecretsSslContextFactoryForTestOnly(Map<String, Object> config)
- {
- super(config);
- }
-
- /*
- * This is overriden to first give priority to the input map configuration since we should not be setting env
- * variables from the unit tests. However, if the input map configuration doesn't have the value for the
- * given key then fallback to loading from the real environment variables.
- */
- @Override
- String getValueFromEnv(String envVarName, String defaultValue)
- {
- String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
- if (StringUtils.isEmpty(envVarValue))
- {
- logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
- return super.getValueFromEnv(envVarName, defaultValue);
- }
- else
- {
- logger.info("Configuration has env variable {} with value {}. Will use that.",
- envVarName, envVarValue);
- return envVarValue;
- }
- }
- }
+ private final Map<String, Object> commonConfig = new HashMap<>();
@BeforeClass
public static void prepare()
{
- deleteFileIfExists(truststoreUpdatedTimestampFilepath);
- deleteFileIfExists(keystoreUpdatedTimestampFilepath);
+ deleteFileIfExists(TRUSTSTORE_UPDATED_TIMESTAMP_FILEPATH);
+ deleteFileIfExists(KEYSTORE_UPDATED_TIMESTAMP_FILEPATH);
+ private_key = readFile(ENCRYPTED_PRIVATE_KEY_FILEPATH);
+ unencrypted_private_key = readFile(UNENCRYPTED_PRIVATE_KEY_FILEPATH);
+ trusted_certificates = readFile(TRUSTED_CERTIFICATES_FILEPATH);
}
private static void deleteFileIfExists(String filePath)
@@ -113,68 +90,63 @@ public class KubernetesSecretsSslContextFactoryTest
}
}
+ private static String readFile(String file)
+ {
+ try
+ {
+ return new String(Files.readAllBytes(Paths.get(file)));
+ }
+ catch (Exception e)
+ {
+ logger.error("Unable to read the file {}. Without this tests in this file would fail.", file);
+ }
+ return null;
+ }
+
@Before
public void setup()
{
- commonConfig.put(TRUSTSTORE_PATH, "build/test/conf/cassandra_ssl_test.truststore");
- commonConfig.put(TRUSTSTORE_PASSWORD_ENV_VAR, "MY_TRUSTSTORE_PASSWORD");
- commonConfig.put(TRUSTSTORE_UPDATED_TIMESTAMP_PATH, truststoreUpdatedTimestampFilepath);
+ commonConfig.put(TRUSTED_CERTIFICATE_ENV_VAR,
+ "MY_TRUSTED_CERTIFICATES");
+ commonConfig.put("MY_TRUSTED_CERTIFICATES", trusted_certificates);
+ commonConfig.put(TRUSTSTORE_UPDATED_TIMESTAMP_PATH, TRUSTSTORE_UPDATED_TIMESTAMP_FILEPATH);
/*
* In order to test with real 'env' variables comment out this line and set appropriate env variable. This is
* done to avoid having a dependency on env in the unit test.
*/
- commonConfig.put("MY_TRUSTSTORE_PASSWORD", "cassandra");
commonConfig.put("require_client_auth", Boolean.FALSE);
commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
}
private void addKeystoreOptions(Map<String, Object> config)
{
- config.put(KEYSTORE_PATH, "build/test/conf/cassandra_ssl_test.keystore");
- config.put(KEYSTORE_PASSWORD_ENV_VAR, "MY_KEYSTORE_PASSWORD");
- config.put(KEYSTORE_UPDATED_TIMESTAMP_PATH, keystoreUpdatedTimestampFilepath);
+ config.put(DEFAULT_PRIVATE_KEY_ENV_VAR_NAME, private_key);
+ config.put(PRIVATE_KEY_PASSWORD_ENV_VAR, "MY_KEY_PASSWORD");
+ config.put(KEYSTORE_UPDATED_TIMESTAMP_PATH, KEYSTORE_UPDATED_TIMESTAMP_FILEPATH);
/*
* In order to test with real 'env' variables comment out this line and set appropriate env variable. This is
* done to avoid having a dependency on env in the unit test.
*/
- config.put("MY_KEYSTORE_PASSWORD", "cassandra");
+ config.put("MY_KEY_PASSWORD", "cassandra");
}
- @Test(expected = IOException.class)
- public void buildTrustManagerFactoryWithInvalidTruststoreFile() throws IOException
+ private void addUnencryptedKeystoreOptions(Map<String, Object> config)
{
- Map<String, Object> config = new HashMap<>();
- config.putAll(commonConfig);
- config.put(TRUSTSTORE_PATH, "/this/is/probably/not/a/file/on/your/test/machine");
-
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
- kubernetesSecretsSslContextFactory.checkedExpiry = false;
- kubernetesSecretsSslContextFactory.buildTrustManagerFactory();
+ config.put(DEFAULT_PRIVATE_KEY_ENV_VAR_NAME, unencrypted_private_key);
+ config.put(KEYSTORE_UPDATED_TIMESTAMP_PATH, KEYSTORE_UPDATED_TIMESTAMP_FILEPATH);
+ config.remove(DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME);
+ config.remove(PRIVATE_KEY_PASSWORD_ENV_VAR);
}
- @Test(expected = IOException.class)
- public void buildTrustManagerFactoryWithBadPassword() throws IOException
- {
- Map<String, Object> config = new HashMap<>();
- config.putAll(commonConfig);
- config.remove(TRUSTSTORE_PASSWORD_ENV_VAR);
- config.put(KubernetesSecretsSslContextFactory.DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME, "HomeOfBadPasswords");
-
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
- kubernetesSecretsSslContextFactory.checkedExpiry = false;
- kubernetesSecretsSslContextFactory.buildTrustManagerFactory();
- }
-
- @Test
- public void buildTrustManagerFactoryWithEmptyPassword() throws IOException
+ @Test(expected = SSLException.class)
+ public void buildTrustManagerFactoryWithInvalidTrustedCertificates() throws IOException
{
Map<String, Object> config = new HashMap<>();
config.putAll(commonConfig);
- config.put(TRUSTSTORE_PATH, "build/test/conf/cassandra_ssl_test.truststore-without-password");
- config.remove(TRUSTSTORE_PASSWORD_ENV_VAR);
- config.put(KubernetesSecretsSslContextFactory.DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME, "");
+ config.put("MY_TRUSTED_CERTIFICATES", trusted_certificates.replaceAll("\\s", String.valueOf(System.nanoTime())));
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory =
+ new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.checkedExpiry = false;
kubernetesSecretsSslContextFactory.buildTrustManagerFactory();
}
@@ -185,20 +157,21 @@ public class KubernetesSecretsSslContextFactoryTest
Map<String, Object> config = new HashMap<>();
config.putAll(commonConfig);
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.checkedExpiry = false;
TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory();
Assert.assertNotNull(trustManagerFactory);
}
- @Test(expected = IOException.class)
- public void buildKeyManagerFactoryWithInvalidKeystoreFile() throws IOException
+ @Test(expected = SSLException.class)
+ public void buildKeyManagerFactoryWithInvalidPrivateKey() throws IOException
{
Map<String, Object> config = new HashMap<>();
config.putAll(commonConfig);
- config.put(KEYSTORE_PATH, "/this/is/probably/not/a/file/on/your/test/machine");
+ config.put(DEFAULT_PRIVATE_KEY_ENV_VAR_NAME, private_key.replaceAll("\\s", String.valueOf(System.nanoTime())));
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory =
+ new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.checkedExpiry = false;
kubernetesSecretsSslContextFactory.buildKeyManagerFactory();
}
@@ -208,10 +181,11 @@ public class KubernetesSecretsSslContextFactoryTest
{
Map<String, Object> config = new HashMap<>();
config.putAll(commonConfig);
- config.put(KEYSTORE_PATH, "build/test/conf/cassandra_ssl_test.keystore");
- config.put(KubernetesSecretsSslContextFactory.DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME, "HomeOfBadPasswords");
+ config.put(DEFAULT_PRIVATE_KEY_ENV_VAR_NAME, private_key);
+ config.put(DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME, "HomeOfBadPasswords");
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory =
+ new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.buildKeyManagerFactory();
}
@@ -221,19 +195,60 @@ public class KubernetesSecretsSslContextFactoryTest
Map<String, Object> config = new HashMap<>();
config.putAll(commonConfig);
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory1 = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory1 = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
// Make sure the exiry check didn't happen so far for the private key
Assert.assertFalse(kubernetesSecretsSslContextFactory1.checkedExpiry);
addKeystoreOptions(config);
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory2 = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory2 = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
+ // Trigger the private key loading. That will also check for expired private key
+ kubernetesSecretsSslContextFactory2.buildKeyManagerFactory();
+ // Now we should have checked the private key's expiry
+ Assert.assertTrue(kubernetesSecretsSslContextFactory2.checkedExpiry);
+
+ // Make sure that new factory object preforms the fresh private key expiry check
+
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory3 =
+ new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
+ Assert.assertFalse(kubernetesSecretsSslContextFactory3.checkedExpiry);
+ kubernetesSecretsSslContextFactory3.buildKeyManagerFactory();
+ Assert.assertTrue(kubernetesSecretsSslContextFactory3.checkedExpiry);
+ }
+
+ @Test
+ public void buildKeyManagerFactoryWithMultipleCertsInCertChain() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addKeystoreOptions(config);
+ config.put(DEFAULT_PRIVATE_KEY_ENV_VAR_NAME, readFile(ENCRYPTED_PRIVATE_KEY_WITH_MULTIPLE_CERTS_IN_CERTCHAIN_FILEPATH));
+
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory2 = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
+ // Trigger the private key loading. That will also check for expired private key
+ kubernetesSecretsSslContextFactory2.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void buildKeyManagerFactoryHappyPathForUnencryptedKey() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory1 = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
+ // Make sure the exiry check didn't happen so far for the private key
+ Assert.assertFalse(kubernetesSecretsSslContextFactory1.checkedExpiry);
+
+ addUnencryptedKeystoreOptions(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory2 = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
// Trigger the private key loading. That will also check for expired private key
kubernetesSecretsSslContextFactory2.buildKeyManagerFactory();
// Now we should have checked the private key's expiry
Assert.assertTrue(kubernetesSecretsSslContextFactory2.checkedExpiry);
// Make sure that new factory object preforms the fresh private key expiry check
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory3 = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory3 =
+ new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
Assert.assertFalse(kubernetesSecretsSslContextFactory3.checkedExpiry);
kubernetesSecretsSslContextFactory3.buildKeyManagerFactory();
Assert.assertTrue(kubernetesSecretsSslContextFactory3.checkedExpiry);
@@ -246,7 +261,7 @@ public class KubernetesSecretsSslContextFactoryTest
config.putAll(commonConfig);
addKeystoreOptions(config);
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.checkedExpiry = false;
TrustManagerFactory trustManagerFactory = kubernetesSecretsSslContextFactory.buildTrustManagerFactory();
Assert.assertNotNull(trustManagerFactory);
@@ -266,7 +281,7 @@ public class KubernetesSecretsSslContextFactoryTest
config.putAll(commonConfig);
addKeystoreOptions(config);
- KubernetesSecretsSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsSslContextFactoryForTestOnly(config);
+ KubernetesSecretsPEMSslContextFactory kubernetesSecretsSslContextFactory = new KubernetesSecretsPEMSslContextFactoryForTestOnly(config);
kubernetesSecretsSslContextFactory.checkedExpiry = false;
KeyManagerFactory keyManagerFactory = kubernetesSecretsSslContextFactory.buildKeyManagerFactory();
Assert.assertNotNull(keyManagerFactory);
@@ -293,4 +308,39 @@ public class KubernetesSecretsSslContextFactoryTest
logger.warn("Failed to write to filePath {} from the mounted volume", filePath, e);
}
}
+
+ private static class KubernetesSecretsPEMSslContextFactoryForTestOnly extends KubernetesSecretsPEMSslContextFactory
+ {
+
+ public KubernetesSecretsPEMSslContextFactoryForTestOnly()
+ {
+ }
+
+ public KubernetesSecretsPEMSslContextFactoryForTestOnly(Map<String, Object> config)
+ {
+ super(config);
+ }
+
+ /*
+ * This is overriden to first give priority to the input map configuration since we should not be setting env
+ * variables from the unit tests. However, if the input map configuration doesn't have the value for the
+ * given key then fallback to loading from the real environment variables.
+ */
+ @Override
+ String getValueFromEnv(String envVarName, String defaultValue)
+ {
+ String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
+ if (StringUtils.isEmpty(envVarValue))
+ {
+ logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
+ return super.getValueFromEnv(envVarName, defaultValue);
+ }
+ else
+ {
+ logger.info("Configuration has environment variable {} with value {}. Will use that.",
+ envVarName, envVarValue);
+ return envVarValue;
+ }
+ }
+ }
}
diff --git a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java
index fec48fd..8b22fff 100644
--- a/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java
+++ b/examples/ssl-factory/test/unit/org/apache/cassandra/security/KubernetesSecretsSslContextFactoryTest.java
@@ -18,10 +18,10 @@
package org.apache.cassandra.security;
-import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
@@ -38,6 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.EncryptionOptions;
+import org.apache.cassandra.io.util.PathUtils;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH;
@@ -49,45 +50,9 @@ public class KubernetesSecretsSslContextFactoryTest
private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactoryTest.class);
private static final String TRUSTSTORE_PATH = EncryptionOptions.ConfigKey.TRUSTSTORE.toString();
private static final String KEYSTORE_PATH = EncryptionOptions.ConfigKey.KEYSTORE.toString();
-
- private Map<String, Object> commonConfig = new HashMap<>();
private final static String truststoreUpdatedTimestampFilepath = "build/test/conf/cassandra_truststore_last_updatedtime";
private final static String keystoreUpdatedTimestampFilepath = "build/test/conf/cassandra_keystore_last_updatedtime";
-
- private static class KubernetesSecretsSslContextFactoryForTestOnly extends KubernetesSecretsSslContextFactory
- {
-
- public KubernetesSecretsSslContextFactoryForTestOnly()
- {
- }
-
- public KubernetesSecretsSslContextFactoryForTestOnly(Map<String, Object> config)
- {
- super(config);
- }
-
- /*
- * This is overriden to first give priority to the input map configuration since we should not be setting env
- * variables from the unit tests. However, if the input map configuration doesn't have the value for the
- * given key then fallback to loading from the real environment variables.
- */
- @Override
- String getValueFromEnv(String envVarName, String defaultValue)
- {
- String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
- if (StringUtils.isEmpty(envVarValue))
- {
- logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
- return super.getValueFromEnv(envVarName, defaultValue);
- }
- else
- {
- logger.info("Configuration has env variable {} with value {}. Will use that.",
- envVarName, envVarValue);
- return envVarValue;
- }
- }
- }
+ private final Map<String, Object> commonConfig = new HashMap<>();
@BeforeClass
public static void prepare()
@@ -96,20 +61,13 @@ public class KubernetesSecretsSslContextFactoryTest
deleteFileIfExists(keystoreUpdatedTimestampFilepath);
}
- private static void deleteFileIfExists(String filePath)
+ private static void deleteFileIfExists(String file)
{
- try
+ Path filePath = Paths.get(file);
+ boolean deleted = PathUtils.tryDelete(filePath);
+ if (!deleted)
{
- logger.info("Deleting the file {} to prepare for the tests", new File(filePath).getAbsolutePath());
- File file = new File(filePath);
- if (file.exists())
- {
- file.delete();
- }
- }
- catch (Exception e)
- {
- logger.warn("File {} could not be deleted.", filePath, e);
+ logger.warn("File {} could not be deleted.", filePath);
}
}
@@ -293,4 +251,39 @@ public class KubernetesSecretsSslContextFactoryTest
logger.warn("Failed to write to filePath {} from the mounted volume", filePath, e);
}
}
+
+ private static class KubernetesSecretsSslContextFactoryForTestOnly extends KubernetesSecretsSslContextFactory
+ {
+
+ public KubernetesSecretsSslContextFactoryForTestOnly()
+ {
+ }
+
+ public KubernetesSecretsSslContextFactoryForTestOnly(Map<String, Object> config)
+ {
+ super(config);
+ }
+
+ /*
+ * This is overriden to first give priority to the input map configuration since we should not be setting env
+ * variables from the unit tests. However, if the input map configuration doesn't have the value for the
+ * given key then fallback to loading from the real environment variables.
+ */
+ @Override
+ String getValueFromEnv(String envVarName, String defaultValue)
+ {
+ String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
+ if (StringUtils.isEmpty(envVarValue))
+ {
+ logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
+ return super.getValueFromEnv(envVarName, defaultValue);
+ }
+ else
+ {
+ logger.info("Configuration has env variable {} with value {}. Will use that.",
+ envVarName, envVarValue);
+ return envVarValue;
+ }
+ }
+ }
}
diff --git a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
index 4912364..3d47509 100644
--- a/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
+++ b/src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
+import java.security.KeyStoreException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
@@ -37,6 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.util.File;
+import org.apache.cassandra.utils.Clock;
/**
* Abstract implementation for {@link ISslContextFactory} using file based, standard keystore format with the ability
@@ -124,26 +126,19 @@ abstract public class FileBasedSslContextFactory extends AbstractSslContextFacto
* @return KeyManagerFactory built from the file based keystore.
* @throws SSLException if any issues encountered during the build process
*/
+ @Override
protected KeyManagerFactory buildKeyManagerFactory() throws SSLException
{
+
try (InputStream ksf = Files.newInputStream(Paths.get(keystore)))
{
- KeyManagerFactory kmf = KeyManagerFactory.getInstance(
- algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : algorithm);
+ final String algorithm = this.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : this.algorithm;
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
KeyStore ks = KeyStore.getInstance(store_type);
ks.load(ksf, keystore_password.toCharArray());
if (!checkedExpiry)
{
- for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
- {
- String alias = aliases.nextElement();
- if (ks.getCertificate(alias).getType().equals("X.509"))
- {
- Date expires = ((X509Certificate) ks.getCertificate(alias)).getNotAfter();
- if (expires.before(new Date()))
- logger.warn("Certificate for {} expired on {}", alias, expires);
- }
- }
+ checkExpiredCerts(ks);
checkedExpiry = true;
}
kmf.init(ks, keystore_password.toCharArray());
@@ -161,12 +156,13 @@ abstract public class FileBasedSslContextFactory extends AbstractSslContextFacto
* @return TrustManagerFactory from the file based truststore
* @throws SSLException if any issues encountered during the build process
*/
+ @Override
protected TrustManagerFactory buildTrustManagerFactory() throws SSLException
{
try (InputStream tsf = Files.newInputStream(Paths.get(truststore)))
{
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(
- algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : algorithm);
+ final String algorithm = this.algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : this.algorithm;
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
KeyStore ts = KeyStore.getInstance(store_type);
ts.load(tsf, truststore_password.toCharArray());
tmf.init(ts);
@@ -178,10 +174,30 @@ abstract public class FileBasedSslContextFactory extends AbstractSslContextFacto
}
}
+ protected boolean checkExpiredCerts(KeyStore ks) throws KeyStoreException
+ {
+ boolean hasExpiredCerts = false;
+ final Date now = new Date(Clock.Global.currentTimeMillis());
+ for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+ {
+ String alias = aliases.nextElement();
+ if (ks.getCertificate(alias).getType().equals("X.509"))
+ {
+ Date expires = ((X509Certificate) ks.getCertificate(alias)).getNotAfter();
+ if (expires.before(now))
+ {
+ hasExpiredCerts = true;
+ logger.warn("Certificate for {} expired on {}", alias, expires);
+ }
+ }
+ }
+ return hasExpiredCerts;
+ }
+
/**
* Helper class for hot reloading SSL Contexts
*/
- private static class HotReloadableFile
+ protected static class HotReloadableFile
{
private final File file;
private volatile long lastModTime;
diff --git a/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java b/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java
new file mode 100644
index 0000000..8ecbec5
--- /dev/null
+++ b/src/java/org/apache/cassandra/security/PEMBasedSslContextFactory.java
@@ -0,0 +1,372 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.io.util.File;
+
+
+/**
+ * SslContextFactory for the <a href="">PEM standard</a> encoded PKCS#8 private keys and X509 certificates/public-keys.
+ * It parses the key material based on the standard defined in the <a href="https://datatracker.ietf.org/doc/html/rfc7468">RFC 7468</a>.
+ * It creates <a href="https://datatracker.ietf.org/doc/html/rfc5208">PKCS# 8</a> based private key and X509 certificate(s)
+ * for the public key to build the required keystore and the truststore managers that are used for the SSL context creation.
+ * Internally it builds Java {@link KeyStore} with <a href="https://datatracker.ietf.org/doc/html/rfc7292">PKCS# 12</a> <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#keystore-types">store type</a>
+ * to be used for keystore and the truststore managers.
+ * <p>
+ * This factory also supports 'hot reloading' of the key material, the same way as defined by {@link FileBasedSslContextFactory},
+ * <b>if it is file based</b>. This factory ignores the existing 'store_type' configuration used for other file based store
+ * types like JKS.
+ * <p>
+ * You can configure this factory with either inline PEM data or with the files having the required PEM data as shown
+ * below,
+ *
+ * <b>Configuration: PEM keys/certs defined inline (mind the spaces in the YAML!)</b>
+ * <pre>
+ * client/server_encryption_options:
+ * ssl_context_factory:
+ * class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ * parameters:
+ * private_key: |
+ * -----BEGIN ENCRYPTED PRIVATE KEY----- OR -----BEGIN PRIVATE KEY-----
+ * <your base64 encoded private key>
+ * -----END ENCRYPTED PRIVATE KEY----- OR -----END PRIVATE KEY-----
+ * -----BEGIN CERTIFICATE-----
+ * <your base64 encoded certificate chain>
+ * -----END CERTIFICATE-----
+ *
+ * private_key_password: "<your password if the private key is encrypted with a password>"
+ *
+ * trusted_certificates: |
+ * -----BEGIN CERTIFICATE-----
+ * <your base64 encoded certificate>
+ * -----END CERTIFICATE-----
+ * </pre>
+ *
+ * <b>Configuration: PEM keys/certs defined in files</b>
+ * <pre>
+ * client/server_encryption_options:
+ * ssl_context_factory:
+ * class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ * keystore: <file path to the keystore file in the PEM format with the private key and the certificate chain>
+ * keystore_password: "<your password if the private key is encrypted with a password>"
+ * truststore: <file path to the truststore file in the PEM format>
+ * </pre>
+ */
+public final class PEMBasedSslContextFactory extends FileBasedSslContextFactory
+{
+ public static final String DEFAULT_TARGET_STORETYPE = "PKCS12";
+ private static final Logger logger = LoggerFactory.getLogger(PEMBasedSslContextFactory.class);
+ private String pemEncodedKey;
+ private String keyPassword;
+ private String pemEncodedCertificates;
+ private boolean maybeFileBasedPrivateKey;
+ private boolean maybeFileBasedTrustedCertificates;
+
+ public PEMBasedSslContextFactory()
+ {
+ }
+
+ public PEMBasedSslContextFactory(Map<String, Object> parameters)
+ {
+ super(parameters);
+ pemEncodedKey = getString(ConfigKey.ENCODED_KEY.getKeyName());
+ keyPassword = getString(ConfigKey.KEY_PASSWORD.getKeyName());
+ if (StringUtils.isEmpty(keyPassword))
+ {
+ keyPassword = keystore_password;
+ }
+ else if (!StringUtils.isEmpty(keystore_password) && !keyPassword.equals(keystore_password))
+ {
+ throw new IllegalArgumentException("'keystore_password' and 'key_password' both configurations are given and the " +
+ "values do not match");
+ }
+ else
+ {
+ logger.warn("'keystore_password' and 'key_password' both are configured but since the values match it's " +
+ "okay. Ideally you should only specify one of them.");
+ }
+
+ if (!StringUtils.isEmpty(truststore_password))
+ {
+ logger.warn("PEM based truststore should not be using password. Ignoring the given value in " +
+ "'truststore_password' configuration.");
+ }
+
+ pemEncodedCertificates = getString(ConfigKey.ENCODED_CERTIFICATES.getKeyName());
+
+ maybeFileBasedPrivateKey = StringUtils.isEmpty(pemEncodedKey);
+ maybeFileBasedTrustedCertificates = StringUtils.isEmpty(pemEncodedCertificates);
+
+ enforceSinglePrivateKeySource();
+ enforceSingleTurstedCertificatesSource();
+ }
+
+ /**
+ * Decides if this factory has a keystore defined - key material specified in files or inline to the configuration.
+ *
+ * @return {@code true} if there is a keystore defined; {@code false} otherwise
+ */
+ @Override
+ public boolean hasKeystore()
+ {
+ return maybeFileBasedPrivateKey ? keystoreFileExists() :
+ !StringUtils.isEmpty(pemEncodedKey);
+ }
+
+ /**
+ * Checks if the keystore file exists.
+ *
+ * @return {@code true} if keystore file exists; {@code false} otherwise
+ */
+ private boolean keystoreFileExists()
+ {
+ return keystore != null && new File(keystore).exists();
+ }
+
+ /**
+ * Decides if this factory has a truststore defined - key material specified in files or inline to the
+ * configuration.
+ *
+ * @return {@code true} if there is a truststore defined; {@code false} otherwise
+ */
+ private boolean hasTruststore()
+ {
+ return maybeFileBasedTrustedCertificates ? truststoreFileExists() :
+ !StringUtils.isEmpty(pemEncodedCertificates);
+ }
+
+ /**
+ * Checks if the truststore file exists.
+ *
+ * @return {@code true} if truststore file exists; {@code false} otherwise
+ */
+ private boolean truststoreFileExists()
+ {
+ return truststore != null && new File(truststore).exists();
+ }
+
+ /**
+ * This enables 'hot' reloading of the key/trust stores based on the last updated timestamps if they are file based.
+ */
+ @Override
+ public synchronized void initHotReloading()
+ {
+ List<HotReloadableFile> fileList = new ArrayList<>();
+ if (maybeFileBasedPrivateKey && hasKeystore())
+ {
+ fileList.add(new HotReloadableFile(keystore));
+ }
+ if (maybeFileBasedTrustedCertificates && hasTruststore())
+ {
+ fileList.add(new HotReloadableFile(truststore));
+ }
+ if (!fileList.isEmpty())
+ {
+ hotReloadableFiles = fileList;
+ }
+ }
+
+ /**
+ * Builds required KeyManagerFactory from the PEM based keystore. It also checks for the PrivateKey's certificate's
+ * expiry and logs {@code warning} for each expired PrivateKey's certitificate.
+ *
+ * @return KeyManagerFactory built from the PEM based keystore.
+ * @throws SSLException if any issues encountered during the build process
+ */
+ @Override
+ protected KeyManagerFactory buildKeyManagerFactory() throws SSLException
+ {
+ try
+ {
+ if (hasKeystore())
+ {
+ if (maybeFileBasedPrivateKey)
+ {
+ pemEncodedKey = readPEMFile(keystore); // read PEM from the file
+ }
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+ algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : algorithm);
+ KeyStore ks = buildKeyStore();
+ if (!checkedExpiry)
+ {
+ checkExpiredCerts(ks);
+ checkedExpiry = true;
+ }
+ kmf.init(ks, keyPassword != null ? keyPassword.toCharArray() : null);
+ return kmf;
+ }
+ else
+ {
+ throw new SSLException("Must provide keystore or private_key in configuration for PEMBasedSSlContextFactory");
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SSLException("Failed to build key manager store for secure connections", e);
+ }
+ }
+
+ /**
+ * Builds TrustManagerFactory from the PEM based truststore.
+ *
+ * @return TrustManagerFactory from the PEM based truststore
+ * @throws SSLException if any issues encountered during the build process
+ */
+ @Override
+ protected TrustManagerFactory buildTrustManagerFactory() throws SSLException
+ {
+ try
+ {
+ if (hasTruststore())
+ {
+ if (maybeFileBasedTrustedCertificates)
+ {
+ pemEncodedCertificates = readPEMFile(truststore); // read PEM from the file
+ }
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : algorithm);
+ KeyStore ts = buildTrustStore();
+ tmf.init(ts);
+ return tmf;
+ }
+ else
+ {
+ throw new SSLException("Must provide truststore or trusted_certificates in configuration for " +
+ "PEMBasedSSlContextFactory");
+ }
+ }
+ catch (Exception e)
+ {
+ throw new SSLException("Failed to build trust manager store for secure connections", e);
+ }
+ }
+
+ private String readPEMFile(String file) throws IOException
+ {
+ return new String(Files.readAllBytes(Paths.get(file)));
+ }
+
+ /**
+ * Builds KeyStore object given the {@link #DEFAULT_TARGET_STORETYPE} out of the PEM formatted private key material.
+ * It uses {@code cassandra-ssl-keystore} as the alias for the created key-entry.
+ */
+ private KeyStore buildKeyStore() throws GeneralSecurityException, IOException
+ {
+ char[] keyPasswordArray = keyPassword != null ? keyPassword.toCharArray() : null;
+ PrivateKey privateKey = PEMReader.extractPrivateKey(pemEncodedKey, keyPassword);
+ Certificate[] certChainArray = PEMReader.extractCertificates(pemEncodedKey);
+ if (certChainArray == null || certChainArray.length == 0)
+ {
+ throw new SSLException("Could not read any certificates for the certChain for the private key");
+ }
+
+ KeyStore keyStore = KeyStore.getInstance(DEFAULT_TARGET_STORETYPE);
+ keyStore.load(null, null);
+ keyStore.setKeyEntry("cassandra-ssl-keystore", privateKey, keyPasswordArray, certChainArray);
+ return keyStore;
+ }
+
+ /**
+ * Builds KeyStore object given the {@link #DEFAULT_TARGET_STORETYPE} out of the PEM formatted certificates/public-key
+ * material.
+ * <p>
+ * It uses {@code cassandra-ssl-trusted-cert-<numeric-id>} as the alias for the created certificate-entry.
+ */
+ private KeyStore buildTrustStore() throws GeneralSecurityException, IOException
+ {
+ Certificate[] certChainArray = PEMReader.extractCertificates(pemEncodedCertificates);
+ if (certChainArray == null || certChainArray.length == 0)
+ {
+ throw new SSLException("Could not read any certificates from the given PEM");
+ }
+
+ KeyStore keyStore = KeyStore.getInstance(DEFAULT_TARGET_STORETYPE);
+ keyStore.load(null, null);
+ for (int i = 0; i < certChainArray.length; i++)
+ {
+ keyStore.setCertificateEntry("cassandra-ssl-trusted-cert-" + (i + 1), certChainArray[i]);
+ }
+ return keyStore;
+ }
+
+ /**
+ * Enforces that the configuration specified a sole source of loading private keys - either {@code keystore} (the
+ * actual file must exist) or {@code private_key}, not both.
+ */
+ private void enforceSinglePrivateKeySource()
+ {
+ if (keystoreFileExists() && !StringUtils.isEmpty(pemEncodedKey))
+ {
+ throw new IllegalArgumentException("Configuration must specify value for either keystore or private_key, " +
+ "not both for PEMBasedSSlContextFactory");
+ }
+ }
+
+ /**
+ * Enforces that the configuration specified a sole source of loading trusted certificates - either {@code
+ * truststore} (actual file must exist) or {@code trusted_certificates}, not both.
+ */
+ private void enforceSingleTurstedCertificatesSource()
+ {
+ if (truststoreFileExists() && !StringUtils.isEmpty(pemEncodedCertificates))
+ {
+ throw new IllegalArgumentException("Configuration must specify value for either truststore or " +
+ "trusted_certificates, not both for PEMBasedSSlContextFactory");
+ }
+ }
+
+ public enum ConfigKey
+ {
+ ENCODED_KEY("private_key"),
+ KEY_PASSWORD("private_key_password"),
+ ENCODED_CERTIFICATES("trusted_certificates");
+
+ final String keyName;
+
+ ConfigKey(String keyName)
+ {
+ this.keyName = keyName;
+ }
+
+ String getKeyName()
+ {
+ return keyName;
+ }
+ }
+}
diff --git a/src/java/org/apache/cassandra/security/PEMReader.java b/src/java/org/apache/cassandra/security/PEMReader.java
new file mode 100644
index 0000000..f539509
--- /dev/null
+++ b/src/java/org/apache/cassandra/security/PEMReader.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import com.google.common.collect.ImmutableSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+/**
+ * This is a helper class to read private keys and X509 certifificates encoded based on <a href="https://datatracker.ietf.org/doc/html/rfc1421">PEM (RFC 1421)</a>
+ * format. It can read Password Based Encrypted (PBE henceforth) private keys as well as non-encrypted private keys
+ * along with the X509 certificates/cert-chain based on the textual encoding defined in the <a href="https://datatracker.ietf.org/doc/html/rfc7468">RFC 7468</a>
+ * <p>
+ * The input private key must be in PKCS#8 format.
+ * <p>
+ * It returns PKCS#8 formatted private key and X509 certificates.
+ */
+public final class PEMReader
+{
+ /**
+ * The private key can be with any of these algorithms in order for this read to successfully parse it.
+ * Currently, supported algorithms are,
+ * <pre>
+ * RSA, DSA or EC
+ * </pre>
+ * The first one to be evaluated is RSA, being the most common for private keys.
+ */
+ public static final Set<String> SUPPORTED_PRIVATE_KEY_ALGORITHMS = ImmutableSet.of("RSA", "DSA", "EC");
+ private static final Logger logger = LoggerFactory.getLogger(PEMReader.class);
+ private static final Pattern CERT_PATTERN = Pattern.compile("-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+([a-z0-9+/=\\r\\n]+)-+END\\s+.*CERTIFICATE[^-]*-+", CASE_INSENSITIVE);
+ private static final Pattern KEY_PATTERN = Pattern.compile("-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+([a-z0-9+/=\\r\\n]+)-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", CASE_INSENSITIVE);
+
+ /**
+ * Extracts private key from the PEM content for the private key, assuming its not PBE.
+ *
+ * @param unencryptedPEMKey private key stored as PEM content
+ * @return {@link PrivateKey} upon successful reading of the private key
+ * @throws IOException in case PEM reading fails
+ * @throws GeneralSecurityException in case any issue encountered while reading the private key
+ */
+ public static PrivateKey extractPrivateKey(String unencryptedPEMKey) throws IOException, GeneralSecurityException
+ {
+ return extractPrivateKey(unencryptedPEMKey, null);
+ }
+
+ /**
+ * Extracts private key from the Password Based Encrypted PEM content for the private key.
+ *
+ * @param pemKey PBE private key stored as PEM content
+ * @param keyPassword password to be used for the private key decryption
+ * @return {@link PrivateKey} upon successful reading of the private key
+ * @throws IOException in case PEM reading fails
+ * @throws GeneralSecurityException in case any issue encountered while reading the private key
+ */
+ public static PrivateKey extractPrivateKey(String pemKey, String keyPassword) throws IOException,
+ GeneralSecurityException
+ {
+ PKCS8EncodedKeySpec keySpec;
+ String base64EncodedKey = extractBase64EncodedKey(pemKey);
+ byte[] derKeyBytes = decodeBase64(base64EncodedKey);
+
+ if (keyPassword != null)
+ {
+ logger.debug("Encrypted key's length: {}, key's password length: {}",
+ derKeyBytes.length, keyPassword.length());
+
+ EncryptedPrivateKeyInfo epki = new EncryptedPrivateKeyInfo(derKeyBytes);
+ logger.debug("Encrypted private key info's algorithm name: {}", epki.getAlgName());
+
+ AlgorithmParameters params = epki.getAlgParameters();
+ PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword.toCharArray());
+ Key encryptionKey = SecretKeyFactory.getInstance(epki.getAlgName()).generateSecret(pbeKeySpec);
+ pbeKeySpec.clearPassword();
+ logger.debug("Key algorithm: {}, key format: {}", encryptionKey.getAlgorithm(), encryptionKey.getFormat());
+
+ Cipher cipher = Cipher.getInstance(epki.getAlgName());
+ cipher.init(Cipher.DECRYPT_MODE, encryptionKey, params);
+ byte[] rawKeyBytes;
+ try
+ {
+ rawKeyBytes = cipher.doFinal(epki.getEncryptedData());
+ }
+ catch (BadPaddingException e)
+ {
+ throw new GeneralSecurityException("Failed to decrypt the private key data. Either the password " +
+ "provided for the key is wrong or the private key data is " +
+ "corrupted. msg=" + e.getMessage(), e);
+ }
+ logger.debug("Decrypted private key's length: {}", rawKeyBytes.length);
+
+ keySpec = new PKCS8EncodedKeySpec(rawKeyBytes);
+ }
+ else
+ {
+ logger.debug("Key length: {}", derKeyBytes.length);
+ keySpec = new PKCS8EncodedKeySpec(derKeyBytes);
+ }
+
+ PrivateKey privateKey = null;
+
+ /*
+ * Ideally we can inspect the OID (Object Identifier) from the private key with ASN.1 parser and identify the
+ * actual algorithm of the private key. For doing that, we have to use some special library like BouncyCastle.
+ * However in the absence of that, below brute-force approach can work- that is to try out all the supported
+ * private key algorithms given that there are only three major algorithms to verify against.
+ */
+ for (String privateKeyAlgorithm : SUPPORTED_PRIVATE_KEY_ALGORITHMS)
+ {
+ try
+ {
+ privateKey = KeyFactory.getInstance(privateKeyAlgorithm).generatePrivate(keySpec);
+ logger.info("Parsing for the private key finished with {} algorithm.", privateKeyAlgorithm);
+ return privateKey;
+ }
+ catch (Exception e)
+ {
+ logger.debug("Failed to parse the private key with {} algorithm. Will try the other supported " +
+ "algorithms.", privateKeyAlgorithm);
+ }
+ }
+ throw new GeneralSecurityException("The given private key could not be parsed with any of the supported " +
+ "algorithms. Please see PEMReader#SUPPORTED_PRIVATE_KEY_ALGORITHMS.");
+ }
+
+ /**
+ * Extracts the certificates/cert-chain from the PEM content.
+ *
+ * @param pemCerts certificates/cert-chain stored as PEM content
+ * @return X509 certiificate list
+ * @throws GeneralSecurityException in case any issue encountered while reading the certificates
+ */
+ public static Certificate[] extractCertificates(String pemCerts) throws GeneralSecurityException
+ {
+ List<Certificate> certificateList = new ArrayList<>();
+ List<String> base64EncodedCerts = extractBase64EncodedCerts(pemCerts);
+ for (String base64EncodedCertificate : base64EncodedCerts)
+ {
+ certificateList.add(generateCertificate(base64EncodedCertificate));
+ }
+ Certificate[] certificates = certificateList.toArray(new Certificate[0]);
+ return certificates;
+ }
+
+ /**
+ * Generates the X509 certificate object given the base64 encoded PEM content.
+ *
+ * @param base64Certificate base64 encoded PEM content for the certificate
+ * @return X509 certificate
+ * @throws GeneralSecurityException in case any issue encountered while reading the certificate
+ */
+ private static Certificate generateCertificate(String base64Certificate) throws GeneralSecurityException
+ {
+ byte[] decodedCertificateBytes = decodeBase64(base64Certificate);
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate certificate =
+ (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(decodedCertificateBytes));
+ logCertificateDetails(certificate);
+ return certificate;
+ }
+
+ /**
+ * Logs X509 certificate details for the debugging purpose with {@code INFO} level log.
+ * Namely, it prints - Subject DN, Issuer DN, Certificate serial number and the certificate expiry date which
+ * could be very valuable for debugging any certificate related issues.
+ *
+ * @param certificate certificate to log
+ */
+ private static void logCertificateDetails(X509Certificate certificate)
+ {
+ assert certificate != null;
+ logger.info("*********** Certificate Details *****************");
+ logger.info("Subject DN: {}", certificate.getSubjectDN());
+ logger.info("Issuer DN: {}", certificate.getIssuerDN());
+ logger.info("Serial Number: {}", certificate.getSerialNumber());
+ logger.info("Expiry: {}", certificate.getNotAfter());
+ }
+
+ /**
+ * Parses the PEM formatted private key based on the standard pattern specified by the <a href="https://datatracker.ietf.org/doc/html/rfc7468#section-11">RFC 7468</a>.
+ *
+ * @param pemKey private key stored as PEM content
+ * @return base64 string contained within the defined encapsulation boundaries by the above RFC
+ * @throws GeneralSecurityException in case any issue encountered while parsing the key
+ */
+ private static String extractBase64EncodedKey(String pemKey) throws GeneralSecurityException
+ {
+ Matcher matcher = KEY_PATTERN.matcher(pemKey);
+ if (matcher.find())
+ {
+ return matcher.group(1).replaceAll("\\s", "");
+ }
+ else
+ {
+ throw new GeneralSecurityException("Invalid private key format");
+ }
+ }
+
+ /**
+ * Parses the PEM formatted certificate/public-key based on the standard pattern specified by the
+ * <a href="https://datatracker.ietf.org/doc/html/rfc7468#section-13">RFC 7468</a>.
+ *
+ * @param pemCerts certificate/public-key stored as PEM content
+ * @return list of base64 encoded certificates within the defined encapsulation boundaries by the above RFC
+ * @throws GeneralSecurityException in case any issue encountered parsing the certificate
+ */
+ private static List<String> extractBase64EncodedCerts(String pemCerts) throws GeneralSecurityException
+ {
+ List<String> certificateList = new ArrayList<>();
+ Matcher matcher = CERT_PATTERN.matcher(pemCerts);
+ if (!matcher.find())
+ {
+ throw new GeneralSecurityException("Invalid certificate format");
+ }
+
+ for (int start = 0; matcher.find(start); start = matcher.end())
+ {
+ String certificate = matcher.group(1).replaceAll("\\s", "");
+ certificateList.add(certificate);
+ }
+ return certificateList;
+ }
+
+ /**
+ * Decodes given input in Base64 format.
+ *
+ * @param base64Input input to be decoded
+ * @return byte[] containing decoded bytes
+ * @throws GeneralSecurityException in case it fails to decode the given base64 input
+ */
+ private static byte[] decodeBase64(String base64Input) throws GeneralSecurityException
+ {
+ try
+ {
+ return Base64.getDecoder().decode(base64Input);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new GeneralSecurityException("Failed to decode given base64 input. msg=" + e.getMessage(), e);
+ }
+ }
+}
diff --git a/test/conf/cassandra-pem-jks-sslcontextfactory.yaml b/test/conf/cassandra-pem-jks-sslcontextfactory.yaml
new file mode 100644
index 0000000..1f10e6c
--- /dev/null
+++ b/test/conf/cassandra-pem-jks-sslcontextfactory.yaml
@@ -0,0 +1,150 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
+# implementation classes.
+#
+cluster_name: Test Cluster
+# memtable_allocation_type: heap_buffers
+memtable_allocation_type: offheap_objects
+commitlog_sync: batch
+commitlog_segment_size: 5MiB
+commitlog_directory: build/test/cassandra/commitlog
+# commitlog_compression:
+# - class_name: LZ4Compressor
+cdc_raw_directory: build/test/cassandra/cdc_raw
+cdc_enabled: false
+hints_directory: build/test/cassandra/hints
+partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
+listen_address: 127.0.0.1
+storage_port: 7012
+ssl_storage_port: 17012
+start_native_transport: true
+native_transport_port: 9042
+column_index_size: 4KiB
+saved_caches_directory: build/test/cassandra/saved_caches
+data_file_directories:
+ - build/test/cassandra/data
+disk_access_mode: mmap
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - seeds: "127.0.0.1:7012"
+endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
+dynamic_snitch: true
+client_encryption_options:
+ ssl_context_factory:
+ class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ parameters:
+ private_key: |
+ -----BEGIN ENCRYPTED PRIVATE KEY-----
+ MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
+ g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
+ xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
+ L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
+ sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
+ f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
+ RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
+ 0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
+ evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
+ tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
+ wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
+ K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
+ zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
+ mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
+ WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
+ jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
+ eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
+ DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
+ AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
+ Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
+ Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
+ ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
+ bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
+ hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
+ RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
+ pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
+ FujZhqbKUDbYAcqTkoQ=
+ -----END ENCRYPTED PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+ private_key_password: "cassandra"
+ trusted_certificates: |
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+server_encryption_options:
+ internode_encryption: none
+ keystore: test/conf/cassandra_ssl_test.keystore
+ keystore_password: cassandra
+ truststore: test/conf/cassandra_ssl_test.truststore
+ truststore_password: cassandra
+incremental_backups: true
+concurrent_compactors: 4
+compaction_throughput: 0MiB/s
+row_cache_class_name: org.apache.cassandra.cache.OHCProvider
+row_cache_size: 16MiB
+user_defined_functions_enabled: true
+scripted_user_defined_functions_enabled: true
+prepared_statements_cache_size: 1MiB
+corrupted_tombstone_strategy: exception
+stream_entire_sstables: true
+stream_throughput_outbound: 24MiB/s
+sasi_indexes_enabled: true
+materialized_views_enabled: true
+file_cache_enabled: true
diff --git a/test/conf/cassandra-pem-sslcontextfactory-invalidconfiguration.yaml b/test/conf/cassandra-pem-sslcontextfactory-invalidconfiguration.yaml
new file mode 100644
index 0000000..8c7d910
--- /dev/null
+++ b/test/conf/cassandra-pem-sslcontextfactory-invalidconfiguration.yaml
@@ -0,0 +1,147 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
+# implementation classes.
+#
+cluster_name: Test Cluster
+# memtable_allocation_type: heap_buffers
+memtable_allocation_type: offheap_objects
+commitlog_sync: batch
+commitlog_segment_size: 5MiB
+commitlog_directory: build/test/cassandra/commitlog
+# commitlog_compression:
+# - class_name: LZ4Compressor
+cdc_raw_directory: build/test/cassandra/cdc_raw
+cdc_enabled: false
+hints_directory: build/test/cassandra/hints
+partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
+listen_address: 127.0.0.1
+storage_port: 7012
+ssl_storage_port: 17012
+start_native_transport: true
+native_transport_port: 9042
+column_index_size: 4KiB
+saved_caches_directory: build/test/cassandra/saved_caches
+data_file_directories:
+ - build/test/cassandra/data
+disk_access_mode: mmap
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - seeds: "127.0.0.1:7012"
+endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
+dynamic_snitch: true
+client_encryption_options:
+ ssl_context_factory:
+ class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ parameters:
+ private_key: |
+ -----BEGIN ENCRYPTED PRIVATE KEY-----
+ MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
+ g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
+ xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
+ L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
+ sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
+ f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
+ RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
+ 0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
+ evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
+ tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
+ wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
+ K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
+ zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
+ mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
+ WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
+ jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
+ eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
+ DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
+ AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
+ Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
+ Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
+ ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
+ bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
+ hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
+ RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
+ pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
+ FujZhqbKUDbYAcqTkoQ=
+ -----END ENCRYPTED PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+ private_key_password: "cassandra"
+ trusted_certificates: |
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+ keystore: test/conf/cassandra_ssl_test.keystore.pem
+ keystore_password: cassandra
+ truststore: test/conf/cassandra_ssl_test.truststore.pem
+incremental_backups: true
+concurrent_compactors: 4
+compaction_throughput: 0MiB/s
+row_cache_class_name: org.apache.cassandra.cache.OHCProvider
+row_cache_size: 16MiB
+user_defined_functions_enabled: true
+scripted_user_defined_functions_enabled: true
+prepared_statements_cache_size: 1MiB
+corrupted_tombstone_strategy: exception
+stream_entire_sstables: true
+stream_throughput_outbound: 24MiB/s
+sasi_indexes_enabled: true
+materialized_views_enabled: true
+file_cache_enabled: true
diff --git a/test/conf/cassandra-pem-sslcontextfactory.yaml b/test/conf/cassandra-pem-sslcontextfactory.yaml
new file mode 100644
index 0000000..26d0f1f
--- /dev/null
+++ b/test/conf/cassandra-pem-sslcontextfactory.yaml
@@ -0,0 +1,151 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Testing for pluggable ssl_context_factory option for client and server encryption options with a valid and a missing
+# implementation classes.
+#
+cluster_name: Test Cluster
+# memtable_allocation_type: heap_buffers
+memtable_allocation_type: offheap_objects
+commitlog_sync: batch
+commitlog_segment_size: 5MiB
+commitlog_directory: build/test/cassandra/commitlog
+# commitlog_compression:
+# - class_name: LZ4Compressor
+cdc_raw_directory: build/test/cassandra/cdc_raw
+cdc_enabled: false
+hints_directory: build/test/cassandra/hints
+partitioner: org.apache.cassandra.dht.ByteOrderedPartitioner
+listen_address: 127.0.0.1
+storage_port: 7012
+ssl_storage_port: 17012
+start_native_transport: true
+native_transport_port: 9042
+column_index_size: 4KiB
+saved_caches_directory: build/test/cassandra/saved_caches
+data_file_directories:
+ - build/test/cassandra/data
+disk_access_mode: mmap
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - seeds: "127.0.0.1:7012"
+endpoint_snitch: org.apache.cassandra.locator.SimpleSnitch
+dynamic_snitch: true
+client_encryption_options:
+ ssl_context_factory:
+ class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ parameters:
+ private_key: |
+ -----BEGIN ENCRYPTED PRIVATE KEY-----
+ MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
+ g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
+ xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
+ L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
+ sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
+ f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
+ RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
+ 0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
+ evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
+ tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
+ wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
+ K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
+ zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
+ mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
+ WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
+ jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
+ eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
+ DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
+ AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
+ Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
+ Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
+ ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
+ bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
+ hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
+ RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
+ pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
+ FujZhqbKUDbYAcqTkoQ=
+ -----END ENCRYPTED PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+ private_key_password: "cassandra"
+ trusted_certificates: |
+ -----BEGIN CERTIFICATE-----
+ MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+ bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+ VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+ Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+ EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+ a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+ FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+ MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+ q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+ TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+ TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+ YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+ N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+ iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+ IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+ 6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+ qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+ HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+ n3MVF9w=
+ -----END CERTIFICATE-----
+server_encryption_options:
+ ssl_context_factory:
+ class_name: org.apache.cassandra.security.PEMBasedSslContextFactory
+ internode_encryption: none
+ keystore: test/conf/cassandra_ssl_test.keystore.pem
+ keystore_password: cassandra
+ truststore: test/conf/cassandra_ssl_test.truststore.pem
+incremental_backups: true
+concurrent_compactors: 4
+compaction_throughput: 0MiB/s
+row_cache_class_name: org.apache.cassandra.cache.OHCProvider
+row_cache_size: 16MiB
+user_defined_functions_enabled: true
+scripted_user_defined_functions_enabled: true
+prepared_statements_cache_size: 1MiB
+corrupted_tombstone_strategy: exception
+stream_entire_sstables: true
+stream_throughput_outbound: 24MiB/s
+sasi_indexes_enabled: true
+materialized_views_enabled: true
+file_cache_enabled: true
diff --git a/test/conf/cassandra_ssl_test.keystore.pem b/test/conf/cassandra_ssl_test.keystore.pem
new file mode 100644
index 0000000..ed981cc
--- /dev/null
+++ b/test/conf/cassandra_ssl_test.keystore.pem
@@ -0,0 +1,51 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/
+g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl
+xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29
+L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V
+sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/
+f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8
+RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR
+0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs
+evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU
+tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6
+wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN
+K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv
+zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5
+mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo
+WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ
+jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6
+eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny
+DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn
+AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY
+Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow
+Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut
+ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr
+bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH
+hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI
+RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw
+pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP
+FujZhqbKUDbYAcqTkoQ=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/test/conf/cassandra_ssl_test.truststore.pem b/test/conf/cassandra_ssl_test.truststore.pem
new file mode 100644
index 0000000..8806ce8
--- /dev/null
+++ b/test/conf/cassandra_ssl_test.truststore.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/test/conf/cassandra_ssl_test.unencrypted_keystore.pem b/test/conf/cassandra_ssl_test.unencrypted_keystore.pem
new file mode 100644
index 0000000..ce3d8e7
--- /dev/null
+++ b/test/conf/cassandra_ssl_test.unencrypted_keystore.pem
@@ -0,0 +1,50 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw
+efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv
+wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047
+DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB
+9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi
+G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM
+B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL
+uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU
+l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt
+bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y
+G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF
+FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u
+7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC
+0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W
+ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8
+N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw
+fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk
+cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh
+uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz
+9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv
+hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR
+QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9
+FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K
+37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj
+Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr
+RXKInnLInFD9JD94VqmMozo=
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV
+bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD
+VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh
+Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx
+EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu
+a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw
+FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d
+ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy
+q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2
+TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto
+TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA
+YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD
+N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v
+iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh
+IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv
+6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG
+qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa
+HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru
+n3MVF9w=
+-----END CERTIFICATE-----
diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigTest.java
new file mode 100644
index 0000000..49a3653
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryConfigTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import javax.net.ssl.SSLException;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+public class PEMBasedSslContextFactoryConfigTest
+{
+ @BeforeClass
+ public static void setupDatabaseDescriptor()
+ {
+ System.setProperty("cassandra.config", "cassandra-pem-sslcontextfactory.yaml");
+ }
+
+ @AfterClass
+ public static void tearDownDatabaseDescriptor()
+ {
+ System.clearProperty("cassandra.config");
+ }
+
+ @Test
+ public void testHappyPathInlinePEM() throws SSLException
+ {
+
+ Config config = DatabaseDescriptor.loadConfig();
+ config.client_encryption_options.applyConfig();
+
+ Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory",
+ config.client_encryption_options.ssl_context_factory.class_name);
+ Assert.assertEquals(config.client_encryption_options.ssl_context_factory.class_name,
+ config.client_encryption_options.sslContextFactoryInstance.getClass().getName());
+ PEMBasedSslContextFactory sslContextFactory =
+ (PEMBasedSslContextFactory) config.client_encryption_options.sslContextFactoryInstance;
+ sslContextFactory.buildKeyManagerFactory();
+ sslContextFactory.buildTrustManagerFactory();
+ }
+
+ @Test
+ public void testHappyPathFileBasedPEM() throws SSLException
+ {
+
+ Config config = DatabaseDescriptor.loadConfig();
+ config.server_encryption_options.applyConfig();
+
+ Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory",
+ config.server_encryption_options.ssl_context_factory.class_name);
+ Assert.assertEquals(config.server_encryption_options.ssl_context_factory.class_name,
+ config.server_encryption_options.sslContextFactoryInstance.getClass().getName());
+ PEMBasedSslContextFactory sslContextFactory =
+ (PEMBasedSslContextFactory) config.server_encryption_options.sslContextFactoryInstance;
+ sslContextFactory.buildKeyManagerFactory();
+ sslContextFactory.buildTrustManagerFactory();
+ }
+}
diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryInvalidConfigTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryInvalidConfigTest.java
new file mode 100644
index 0000000..2281137
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryInvalidConfigTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import javax.net.ssl.SSLException;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.exceptions.ConfigurationException;
+
+public class PEMBasedSslContextFactoryInvalidConfigTest
+{
+ @BeforeClass
+ public static void setupDatabaseDescriptor()
+ {
+ System.setProperty("cassandra.config", "cassandra-pem-sslcontextfactory-invalidconfiguration.yaml");
+ }
+
+ @AfterClass
+ public static void tearDownDatabaseDescriptor()
+ {
+ System.clearProperty("cassandra.config");
+ }
+
+ @Test(expected = ConfigurationException.class)
+ public void testFileBasedAndInlinePEMConfiguration() throws SSLException
+ {
+
+ Config config = DatabaseDescriptor.loadConfig();
+ config.client_encryption_options.applyConfig();
+
+ Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory",
+ config.client_encryption_options.ssl_context_factory.class_name);
+ Assert.assertEquals(config.client_encryption_options.ssl_context_factory.class_name,
+ config.client_encryption_options.sslContextFactoryInstance.getClass().getName());
+ PEMBasedSslContextFactory sslContextFactory =
+ (PEMBasedSslContextFactory) config.client_encryption_options.sslContextFactoryInstance;
+ sslContextFactory.buildKeyManagerFactory();
+ sslContextFactory.buildTrustManagerFactory();
+ }
+}
diff --git a/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java
new file mode 100644
index 0000000..243d300
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/PEMBasedSslContextFactoryTest.java
@@ -0,0 +1,449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.OpenSslContext;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+import org.apache.cassandra.config.ParameterizedClass;
+
+import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.ENCODED_CERTIFICATES;
+import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.ENCODED_KEY;
+import static org.apache.cassandra.security.PEMBasedSslContextFactory.ConfigKey.KEY_PASSWORD;
+
+public class PEMBasedSslContextFactoryTest
+{
+ private static final String private_key =
+ "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+ "MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/\n" +
+ "g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl\n" +
+ "xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29\n" +
+ "L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V\n" +
+ "sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/\n" +
+ "f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8\n" +
+ "RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR\n" +
+ "0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs\n" +
+ "evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU\n" +
+ "tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6\n" +
+ "wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN\n" +
+ "K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv\n" +
+ "zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5\n" +
+ "mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo\n" +
+ "WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ\n" +
+ "jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6\n" +
+ "eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny\n" +
+ "DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn\n" +
+ "AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY\n" +
+ "Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow\n" +
+ "Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut\n" +
+ "ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr\n" +
+ "bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH\n" +
+ "hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI\n" +
+ "RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw\n" +
+ "pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP\n" +
+ "FujZhqbKUDbYAcqTkoQ=\n" +
+ "-----END ENCRYPTED PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+ private static final String unencrypted_private_key =
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw\n" +
+ "efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv\n" +
+ "wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047\n" +
+ "DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB\n" +
+ "9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi\n" +
+ "G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM\n" +
+ "B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL\n" +
+ "uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU\n" +
+ "l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt\n" +
+ "bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y\n" +
+ "G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF\n" +
+ "FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u\n" +
+ "7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC\n" +
+ "0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W\n" +
+ "ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8\n" +
+ "N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw\n" +
+ "fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk\n" +
+ "cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh\n" +
+ "uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz\n" +
+ "9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv\n" +
+ "hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR\n" +
+ "QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9\n" +
+ "FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K\n" +
+ "37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj\n" +
+ "Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr\n" +
+ "RXKInnLInFD9JD94VqmMozo=\n" +
+ "-----END PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+ private static final String trusted_certificates =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+ private final Map<String, Object> commonConfig = new HashMap<>();
+
+ @Before
+ public void setup()
+ {
+ commonConfig.put(ENCODED_CERTIFICATES.getKeyName(), trusted_certificates);
+ commonConfig.put("require_client_auth", Boolean.FALSE);
+ commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
+ }
+
+ private void addKeyStoreOptions(Map<String, Object> config)
+ {
+ config.put(ENCODED_KEY.getKeyName(), private_key);
+ config.put(KEY_PASSWORD.getKeyName(), "cassandra");
+ }
+
+ private void addUnencryptedKeyStoreOptions(Map<String, Object> config)
+ {
+ config.put(ENCODED_KEY.getKeyName(), unencrypted_private_key);
+ }
+
+ private void addFileBaseTrustStoreOptions(Map<String, Object> config)
+ {
+ config.put("truststore", "test/conf/cassandra_ssl_test.truststore.pem");
+ }
+
+ private void addFileBaseKeyStoreOptions(Map<String, Object> config)
+ {
+ config.put("keystore", "test/conf/cassandra_ssl_test.keystore.pem");
+ config.put("keystore_password", "cassandra");
+ }
+
+ private void addFileBaseUnencryptedKeyStoreOptions(Map<String, Object> config)
+ {
+ config.put("keystore", "test/conf/cassandra_ssl_test.unencrypted_keystore.pem");
+ }
+
+ @Test
+ public void getSslContextOpenSSL() throws IOException
+ {
+ ParameterizedClass sslContextFactory = new ParameterizedClass(PEMBasedSslContextFactory.class.getSimpleName()
+ , new HashMap<>());
+ EncryptionOptions options = new EncryptionOptions().withTrustStore("test/conf/cassandra_ssl_test.truststore.pem")
+ .withKeyStore("test/conf/cassandra_ssl_test.keystore.pem")
+ .withKeyStorePassword("cassandra")
+ .withRequireClientAuth(false)
+ .withCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA")
+ .withSslContextFactory(sslContextFactory);
+ SslContext sslContext = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT);
+ Assert.assertNotNull(sslContext);
+ if (OpenSsl.isAvailable())
+ Assert.assertTrue(sslContext instanceof OpenSslContext);
+ else
+ Assert.assertTrue(sslContext instanceof SslContext);
+ }
+
+ @Test(expected = IOException.class)
+ public void buildTrustManagerFactoryWithInvalidTruststoreFile() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ config.remove("encoded_certificates");
+ config.put("truststore", "/this/is/probably/not/a/file/on/your/test/machine");
+
+ DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config);
+ defaultSslContextFactoryImpl.checkedExpiry = false;
+ defaultSslContextFactoryImpl.buildTrustManagerFactory();
+ }
+
+ @Test
+ public void buildTrustManagerFactoryHappyPath() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.checkedExpiry = false;
+ TrustManagerFactory trustManagerFactory = sslContextFactory.buildTrustManagerFactory();
+ Assert.assertNotNull(trustManagerFactory);
+ }
+
+ @Test
+ public void buildFileBasedTrustManagerFactoryHappyPath() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ config.remove(ENCODED_CERTIFICATES.getKeyName());
+ addFileBaseTrustStoreOptions(config);
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.checkedExpiry = false;
+ TrustManagerFactory trustManagerFactory = sslContextFactory.buildTrustManagerFactory();
+ Assert.assertNotNull(trustManagerFactory);
+ }
+
+ @Test(expected = IOException.class)
+ public void buildKeyManagerFactoryWithInvalidKeystoreFile() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ config.put("keystore", "/this/is/probably/not/a/file/on/your/test/machine");
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.checkedExpiry = false;
+ sslContextFactory.buildKeyManagerFactory();
+ }
+
+ @Test(expected = IOException.class)
+ public void buildKeyManagerFactoryWithBadPassword() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addKeyStoreOptions(config);
+ config.put("keystore_password", "HomeOfBadPasswords");
+
+ DefaultSslContextFactory defaultSslContextFactoryImpl = new DefaultSslContextFactory(config);
+ defaultSslContextFactoryImpl.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void buildKeyManagerFactoryHappyPath() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ PEMBasedSslContextFactory sslContextFactory1 = new PEMBasedSslContextFactory(config);
+ // Make sure the exiry check didn't happen so far for the private key
+ Assert.assertFalse(sslContextFactory1.checkedExpiry);
+
+ addKeyStoreOptions(config);
+ PEMBasedSslContextFactory sslContextFactory2 = new PEMBasedSslContextFactory(config);
+ // Trigger the private key loading. That will also check for expired private key
+ sslContextFactory2.buildKeyManagerFactory();
+ // Now we should have checked the private key's expiry
+ Assert.assertTrue(sslContextFactory2.checkedExpiry);
+
+ // Make sure that new factory object preforms the fresh private key expiry check
+ PEMBasedSslContextFactory sslContextFactory3 = new PEMBasedSslContextFactory(config);
+ Assert.assertFalse(sslContextFactory3.checkedExpiry);
+ sslContextFactory3.buildKeyManagerFactory();
+ Assert.assertTrue(sslContextFactory3.checkedExpiry);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void buildKeyManagerFactoryWithConflictingPasswordConfigs() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addKeyStoreOptions(config);
+ config.put("keystore_password", config.get("keyPassword") + "-conflict");
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void buildKeyManagerFactoryWithMatchingPasswordConfigs() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addKeyStoreOptions(config);
+ config.put("keystore_password", config.get("keyPassword"));
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void buildFileBasedKeyManagerFactoryHappyPath() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ PEMBasedSslContextFactory sslContextFactory1 = new PEMBasedSslContextFactory(config);
+ // Make sure the expiry check didn't happen so far for the private key
+ Assert.assertFalse(sslContextFactory1.checkedExpiry);
+
+ addFileBaseKeyStoreOptions(config);
+ PEMBasedSslContextFactory sslContextFactory2 = new PEMBasedSslContextFactory(config);
+ // Trigger the private key loading. That will also check for expired private key
+ sslContextFactory2.buildKeyManagerFactory();
+ // Now we should have checked the private key's expiry
+ Assert.assertTrue(sslContextFactory2.checkedExpiry);
+
+ // Make sure that new factory object preforms the fresh private key expiry check
+ PEMBasedSslContextFactory sslContextFactory3 = new PEMBasedSslContextFactory(config);
+ Assert.assertFalse(sslContextFactory3.checkedExpiry);
+ sslContextFactory3.buildKeyManagerFactory();
+ Assert.assertTrue(sslContextFactory3.checkedExpiry);
+ }
+
+ @Test
+ public void buildKeyManagerFactoryWithUnencryptedKey() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addUnencryptedKeyStoreOptions(config);
+
+ Assert.assertTrue("Unencrypted Key test must not specify a key password",
+ StringUtils.isEmpty((String) config.get(KEY_PASSWORD.getKeyName())));
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void buildKeyManagerFactoryWithFileBasedUnencryptedKey() throws IOException
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addFileBaseUnencryptedKeyStoreOptions(config);
+
+ Assert.assertTrue("Unencrypted Key test must not specify a key password",
+ StringUtils.isEmpty((String) config.get(KEY_PASSWORD.getKeyName())));
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ sslContextFactory.buildKeyManagerFactory();
+ }
+
+ @Test
+ public void testDisableOpenSslForInJvmDtests()
+ {
+ // The configuration name below is hard-coded intentionally to make sure we don't break the contract without
+ // changing the documentation appropriately
+ System.setProperty("cassandra.disable_tcactive_openssl", "true");
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ PEMBasedSslContextFactory sslContextFactory = new PEMBasedSslContextFactory(config);
+ Assert.assertEquals(SslProvider.JDK, sslContextFactory.getSslProvider());
+ System.clearProperty("cassandra.disable_tcactive_openssl");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMultiplePrivateKeySources()
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addUnencryptedKeyStoreOptions(config);
+
+ // Check with a valid file path for the keystore
+ addFileBaseUnencryptedKeyStoreOptions(config);
+ new PEMBasedSslContextFactory(config);
+ }
+
+ @Test
+ public void testMultiplePrivateKeySourcesWithInvalidKeystorePath()
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+ addUnencryptedKeyStoreOptions(config);
+
+ // Check with an invalid file path for the keystore
+ config.put("keystore", "/path/to/nowhere");
+ new PEMBasedSslContextFactory(config);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMultipleTrustedCertificatesSources()
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ // Check with a valid file path for the truststore
+ addFileBaseTrustStoreOptions(config);
+ new PEMBasedSslContextFactory(config);
+ }
+
+ @Test
+ public void testMultipleTrustedCertificatesSourcesWithInvalidTruststorePath()
+ {
+ Map<String, Object> config = new HashMap<>();
+ config.putAll(commonConfig);
+
+ // Check with an invalid file path for the truststore
+ config.put("truststore", "/path/to/nowhere");
+ new PEMBasedSslContextFactory(config);
+ }
+}
diff --git a/test/unit/org/apache/cassandra/security/PEMJKSSslContextFactoryConfigTest.java b/test/unit/org/apache/cassandra/security/PEMJKSSslContextFactoryConfigTest.java
new file mode 100644
index 0000000..8efd3e4
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/PEMJKSSslContextFactoryConfigTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import javax.net.ssl.SSLException;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.apache.cassandra.config.Config;
+import org.apache.cassandra.config.DatabaseDescriptor;
+
+public class PEMJKSSslContextFactoryConfigTest
+{
+ @BeforeClass
+ public static void setupDatabaseDescriptor()
+ {
+ System.setProperty("cassandra.config", "cassandra-pem-jks-sslcontextfactory.yaml");
+ }
+
+ @AfterClass
+ public static void tearDownDatabaseDescriptor() {
+ System.clearProperty("cassandra.config");
+ }
+
+ @Test
+ public void testPEMAndJKSCombination() throws SSLException
+ {
+
+ Config config = DatabaseDescriptor.loadConfig();
+ config.client_encryption_options.applyConfig();
+
+ Assert.assertEquals("org.apache.cassandra.security.PEMBasedSslContextFactory",
+ config.client_encryption_options.ssl_context_factory.class_name);
+ Assert.assertEquals(config.client_encryption_options.ssl_context_factory.class_name,
+ config.client_encryption_options.sslContextFactoryInstance.getClass().getName());
+ PEMBasedSslContextFactory clientSslContextFactory =
+ (PEMBasedSslContextFactory)config.client_encryption_options.sslContextFactoryInstance;
+ clientSslContextFactory.buildKeyManagerFactory();
+ clientSslContextFactory.buildTrustManagerFactory();
+
+ config.server_encryption_options.applyConfig();
+
+ Assert.assertEquals("org.apache.cassandra.security.DefaultSslContextFactory",
+ config.server_encryption_options.ssl_context_factory.class_name);
+ Assert.assertEquals(config.server_encryption_options.ssl_context_factory.class_name,
+ config.server_encryption_options.sslContextFactoryInstance.getClass().getName());
+ DefaultSslContextFactory serverSslContextFactory =
+ (DefaultSslContextFactory)config.server_encryption_options.sslContextFactoryInstance;
+ serverSslContextFactory.buildKeyManagerFactory();
+ serverSslContextFactory.buildTrustManagerFactory();
+ }
+}
diff --git a/test/unit/org/apache/cassandra/security/PEMReaderTest.java b/test/unit/org/apache/cassandra/security/PEMReaderTest.java
new file mode 100644
index 0000000..5f4a867
--- /dev/null
+++ b/test/unit/org/apache/cassandra/security/PEMReaderTest.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.security;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PEMReaderTest
+{
+ private static final String encoded_encrypted_key =
+ "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+ "MIIE6jAcBgoqhkiG9w0BDAEDMA4ECOWqSzq5PBIdAgIFxQSCBMjXsCK30J0aT3J/\n" +
+ "g5kcbmevTOY1pIhJGbf5QYYrMUPiuDK2ydxIbiPzoTE4/S+OkCeHhlqwn/YydpBl\n" +
+ "xgjZZ1Z5rLJHO27d2biuESqanDiBVXYuVmHmaifRnFy0uUTFkStB5mjVZEiJgO29\n" +
+ "L83hL60uWru71EVuVriC2WCfmZ/EXp6wyYszOqCFQ8Quk/rDO6XuaBl467MJbx5V\n" +
+ "sucGT6E9XKNd9hB14/Izb2jtVM5kqKxoiHpz1na6yhEYJiE5D1uOonznWjBnjwB/\n" +
+ "f0x+acpDfVDoJKTlRdz+DEcbOF7mb9lBVVjP6P/AAsmQzz6JKwHjvCrjYfQmyyN8\n" +
+ "RI4KRQnWgm4L3dtByLqY8HFU4ogisCMCgI+hZQ+OKMz/hoRO540YGiPcTRY3EOUR\n" +
+ "0bd5JxU6tCJDMTqKP9aSL2KmLoiLowdMkSPz7TCzLsZ2bGJemuCfpAs4XT1vXCHs\n" +
+ "evrUbOnh8et1IA8mZ9auThfqsZtNagJLEXA6hWIKp1FfVL3Q49wvMKZt4eTn/zwU\n" +
+ "tLL0m5yPo6/HAaOA3hbm/oghZS0dseshXl7PZrmZQtvYnIvjyoxEL7ducYDQCDP6\n" +
+ "wZ7Nzyh1QZAauSS15hl3vLFRZCA9hWAVgwQAviTvhB342O0i9qI7TQkcHk+qcTPN\n" +
+ "K+iGNbFZ8ma1izXNKSJ2PgI/QqFNIeJWvZrb9PhJRmaZVsTJ9fERm1ewpebZqkVv\n" +
+ "zMqMhlKgx9ggAaSKgnGZkwXwB6GrSbbzUrwRCKm3FieD1QE4VVYevaadVUU75GG5\n" +
+ "mrFKorJEH7kFZlic8OTjDksYnHbcgU36XZrGEXa2+ldVeGKL3CsXWciaQRcJg8yo\n" +
+ "WQDjZpcutGI0eMJWCqUkv8pYZC2/wZU4htCve5nVJUU4t9uuo9ex7lnwlLWPvheQ\n" +
+ "jUBMgzSRsZ+zwaIusvufAAxiKK/cJm4ubZSZPIjBbfd4U7VPxtirP4Accydu7EK6\n" +
+ "eG/MZwtAMFNJxfxUR+/aYzJU/q1ePw7fWVHrpt58t/22CX2SJBEiUGmSmuyER4Ny\n" +
+ "DPw6d6mhvPUS1jRhIZ9A81ht8MOX7VL5uVp307rt7o5vRpV1mo0iPiRHzGscMpJn\n" +
+ "AP36klEAUNTf0uLTKZa7KHiwhn5iPmsCrENHkOKJjxhRrqHjD2wy3YHs3ow2voyY\n" +
+ "Ua4Cids+c1hvRkNEDGNHm4+rKGFOGOsG/ZU7uj/6gflO4JXxNGiyTLflqMdWBvow\n" +
+ "Zd7hk1zCaGAAn8nZ0hPweGxQ4Q30I9IBZrimGxB0vjiUqNio9+qMf33dCHFJEuut\n" +
+ "ZGJMaUGVaPhXQcTy4uD5hzsPZV5xcsU4H3vBYyBcZgrusJ6OOgkuZQaU7p8rWQWr\n" +
+ "bUEVbXuZdwEmxsCe7H/vEVv5+aA4sF4kWnMMFL7/LIYaiEzkTqdJlRv/KyJJgcAH\n" +
+ "hg2BvR3XTAq8wiX0C98CdmTbsx2eyQdj5tCU606rEohFLKUxWkJYAKxCiUbxGGpI\n" +
+ "RheVmxkef9ErxJiq7hsAsGrSJvMtJuDKIasnD14SOEwD/7jRAq6WdL9VLpxtzlOw\n" +
+ "pWnIl8kUCO3WoaG9Jf+ZTIv2hnxJhaSzYrdXzGPNnaWKhBlwnXJRvQEdrIxZOimP\n" +
+ "FujZhqbKUDbYAcqTkoQ=\n" +
+ "-----END ENCRYPTED PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+
+ private static final String encoded_certificates =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+
+ private static final String encoded_key =
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw\n" +
+ "efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv\n" +
+ "wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047\n" +
+ "DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB\n" +
+ "9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi\n" +
+ "G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM\n" +
+ "B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL\n" +
+ "uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU\n" +
+ "l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt\n" +
+ "bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y\n" +
+ "G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF\n" +
+ "FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u\n" +
+ "7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC\n" +
+ "0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W\n" +
+ "ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8\n" +
+ "N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw\n" +
+ "fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk\n" +
+ "cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh\n" +
+ "uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz\n" +
+ "9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv\n" +
+ "hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR\n" +
+ "QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9\n" +
+ "FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K\n" +
+ "37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj\n" +
+ "Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr\n" +
+ "RXKInnLInFD9JD94VqmMozo=\n" +
+ "-----END PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9w=\n" +
+ "-----END CERTIFICATE-----";
+ private static final String encoded_unencrypted_ec_private_key =
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMLP6H2Wdl28J5PHU\n" +
+ "gMLApCsjONhbyMd5br0byJaQpXShRANCAASmX26IPehdE1wdLW2fVndT9QbjURro\n" +
+ "h74aMnzlmq8GIBWnRzpd+JVJlHgeWLZIDwapthGCYUGivtH27wiO3g7d\n" +
+ "-----END PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIBizCCATACCQCtgEKhNta70DAKBggqhkjOPQQDAjBNMQswCQYDVQQGEwJVUzEL\n" +
+ "MAkGA1UECAwCQ0ExETAPBgNVBAcMCFNhbiBKb3NlMREwDwYDVQQKDAhQZXJzb25h\n" +
+ "bDELMAkGA1UECwwCSVQwHhcNMjExMDE5MDAzMDU4WhcNMjIxMDE0MDAzMDU4WjBN\n" +
+ "MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFNhbiBKb3NlMREw\n" +
+ "DwYDVQQKDAhQZXJzb25hbDELMAkGA1UECwwCSVQwWTATBgcqhkjOPQIBBggqhkjO\n" +
+ "PQMBBwNCAASmX26IPehdE1wdLW2fVndT9QbjURroh74aMnzlmq8GIBWnRzpd+JVJ\n" +
+ "lHgeWLZIDwapthGCYUGivtH27wiO3g7dMAoGCCqGSM49BAMCA0kAMEYCIQDsNMGL\n" +
+ "b4+BEhgNXaXyHWkezUO/3hCmLDw2gUdwMXG+JQIhAIAm8wALKbb9jJDgFQTHyqGJ\n" +
+ "AVAkzYOwmRMYC9BHKjNs\n" +
+ "-----END CERTIFICATE-----";
+ private static final String encoded_encrypted_key_with_multiplecerts_in_chain =
+ "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+ "MIIE6TAbBgkqhkiG9w0BBQMwDgQI4QuRiKYzf88CAggABIIEyPRVmPp38SIFr8H3\n" +
+ "wi+oc6b+HJH7SPflXO6XZe4Tignw/aSyBTsLm2dWrzojRAYMIRd1xC7yQ2ffYrvx\n" +
+ "uoYbtOQeAminNqvwXdRTnwu6oC0rxdBT8RQ9NK7xL2tQyD/shmOeTJG/glXxaeqS\n" +
+ "rT0CZ5P5GJh6xdIWLEu3lEa3NSWVFE2YacUphmxBoaWjBjsJfWTgkF665SgP+2lh\n" +
+ "8R2WTcHrHjD8jR4jHB03wlup0LOmOwzplUmqHB9TyuA4wF6tlJajwBcPa0PNI6ny\n" +
+ "e9YcdcRr7Y0IxnPQr7PhQNV5AQb9TivwX4WaZxR+BXtwMglp+mz0ohjwLS3z6pqr\n" +
+ "tLrFhv2qcacSl+CKukFb9umV/QBkUk/iu+jwLcNJKPC965GWdieNbO0akBQpQsUN\n" +
+ "mqaF9DYHogW5lRnybl8WWPIR8tXmSCbSUIgzw4lRK+o15I4vaMI0NfkwFD/2y1sn\n" +
+ "t3m9LnVBukkpx3g/CPKd9PbZZeWpOTrnRJQfOu9Fj2lmkpGp0peCBqLJpO0pieVl\n" +
+ "87EQ0ZCErtAGLGIAhWnDUqRK0MaWZ+DMQNKYn5klF4YTVBkfRc9tQbIgBaa77wvz\n" +
+ "gvVWBuJtTFpCt9c8jByTH1gLbchC4bhLsy1nO7moevypMmNW4rqw9x5f0EIR3zCU\n" +
+ "L5/buoIh91TG5JB7BaIbVHtbB/Y2siARRXJibuw3ChBjqPOfzQ66j//NCMqhfTwT\n" +
+ "x2wn7L1BB4xyLJgVW9803FUTzaaL3EvJjzpdvrGC7vzcB6Jd0La9cjHhWSAPOKro\n" +
+ "nD9XPCbgLs10vW9g1Tc8drnZklhw6f7xrIQhWFg6VlwmVpvCQhEpX48rCCo2PH9X\n" +
+ "uzeJA+oqFEH3zfDp0r+q6jbAl+5TkkbBBgC2GCoI1vTYSKaWjeKKHkgzGG0QQLAz\n" +
+ "YXWMXvWKOME4wVPkeVxJv80RqDv0JsoOrnVoaFAzAHJMWa7UZQnjkrbVz/y8ZbV4\n" +
+ "oLJjQjvuOnU2PoXXs6SXbzOs1xx5zbX1UUH3Wc7/CCaUec2hemQJ5m6b1XJciyrY\n" +
+ "GHpMKWtXky9Mo1ruTP7ZH1nk03b4PTObKSx2gQD5AZ/ASuTeahMqMb/2PJkDkpHA\n" +
+ "sy8b1zOn2YTbf4K6NWVNIOkiaApmKhhX0Af6Lg8Wr2ymRTXdp/Um8f+SQLADpB/F\n" +
+ "xOydEN622wmihKDge9DrUFqPG/bdIiRGLXLg8efNboC6/cn/i/sheO7/YlrvcUNo\n" +
+ "qxDa/Bb1N/DgmtgAQ1ZP+AKjk6FKkwZRF1X/VZkZ6auscDlaPetF7razPeIJUrKN\n" +
+ "z/x4AD2txGYKmeYztYR577hPXBw+PPKdggRhIugb6z5Tk89C2pDEwfnByA/wcGJr\n" +
+ "w5avxrubosPrp0QtJpZMzouOvcD52VUiZzDfu9dqI/hpinyt5rETj5E19qxBjIZt\n" +
+ "X3Nq5lY2ktbyqVIo8Z8w4EUU+3XHZKqDwjyYvjxCxv5lVVnqvQrH9h3kENBMrPYQ\n" +
+ "4XonQHpUGG7g7pA3ylmHi+nEedr0H5qKHzyFZlRdI7CLVNoAtBSdwvmtGd2cVVXU\n" +
+ "EaToKNbHPXXYYPX/oVAWZYwD7PHXIRJkiEZnrFARNhLypicU7yjvejUPXcVy5HMh\n" +
+ "XqEbrODPp4VXfbYhVg==\n" +
+ "-----END ENCRYPTED PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXjCCAkYCAhI0MA0GCSqGSIb3DQEBBAUAMHcxCzAJBgNVBAYTAlVTMRMwEQYD\n" +
+ "VQQIDApDYWxpZm9ybmlhMREwDwYDVQQHDAhTYW4gSm9zZTEXMBUGA1UECgwOUGVy\n" +
+ "c29uYWwsIEluYy4xEDAOBgNVBAsMB1Jvb3QgQ0ExFTATBgNVBAMMDG15ZG9tYWlu\n" +
+ "LmNvbTAeFw0yMTExMjIyMjQ5MzlaFw0yMjExMjIyMjQ5MzlaMHIxCzAJBgNVBAYT\n" +
+ "AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYDVQQHDAhTYW4gSm9zZTEXMBUG\n" +
+ "A1UECgwOUGVyc29uYWwsIEluYy4xCzAJBgNVBAsMAklUMRUwEwYDVQQDDAxteWRv\n" +
+ "bWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5fdA7wwD9\n" +
+ "9e5RcdLscvGB+hqJUEHuNC53SYKg5X4Sf0H4ExQUbsy8UaoWzWHhgGbCtTvUVavl\n" +
+ "72xsO74ei0EblopW7QknF0kaTO8Vi3mxhUAdtZFLG/o0NS9J16HdGDGojJwuqU9+\n" +
+ "sMQt1w0HCTMlriELnxaUFKP7M9b0uK5VODEKJ38QKNGXUDt66D7BVYeT/6hz2cXK\n" +
+ "QWDoHk/JadALSzW5ES8KIHfxCLnl2TcKxQhJ4CnL8qeGvc8N3VyTh2AXajaJW5RB\n" +
+ "8Oy4CVoYxcdmP1IapxCD+yNcmNt9XpUTD+6eM5gnvtbye+MSfwPz2MW+fWEDZXOv\n" +
+ "3VxhJyTRFNVTAgMBAAEwDQYJKoZIhvcNAQEEBQADggEBADYK/pn6QG7bvUL0Xnnw\n" +
+ "1KPf1nx36gfJE2V0bNk4uyNNeYufMKS8gPLzC+a3RigCEDc+hIZFE5BJexHd7DXA\n" +
+ "CWgHZJtdjM/Xlgoxbf1yfGV3DWeIZlNFSFZujBIpbm1Ug2BAeV31YRWODPZlUSEZ\n" +
+ "0jv8NEs8/oEz9bM4jwRdn09lo4D9hE6o8qDnrzmN2LBZP5dDIJ6g/M+mq/SJFnho\n" +
+ "qBrfUABZhbgk2+tkZ89OI2xpASCLo6X/vqua2iho6km3x+cz6EI9BbvVr6xOOdVK\n" +
+ "m6gs/Bi4MGTh35jdmvyXoyBUOd1w3yBBj86qbEt2ZHYqreRTxntQYx06598Q9Dsi\n" +
+ "xdg=\n" +
+ "-----END CERTIFICATE-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDajCCAlICCQD/7mxPcMTPIDANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV\n" +
+ "UzETMBEGA1UECAwKQ2FsaWZvcm5pYTERMA8GA1UEBwwIU2FuIEpvc2UxFzAVBgNV\n" +
+ "BAoMDlBlcnNvbmFsLCBJbmMuMRAwDgYDVQQLDAdSb290IENBMRUwEwYDVQQDDAxt\n" +
+ "eWRvbWFpbi5jb20wHhcNMjExMTIyMjExODAwWhcNNDkwNDA5MjExODAwWjB3MQsw\n" +
+ "CQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTERMA8GA1UEBwwIU2FuIEpv\n" +
+ "c2UxFzAVBgNVBAoMDlBlcnNvbmFsLCBJbmMuMRAwDgYDVQQLDAdSb290IENBMRUw\n" +
+ "EwYDVQQDDAxteWRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" +
+ "AoIBAQCkIwuNGv3ckew/o2UwaDlYgXH9bh1jap4ZCb6qpjvR3tq9nCerY6XMli0Z\n" +
+ "Xxg0wMHDNUr/jmVYIdQjbz0DVNz/l6ZBJHzHCEgqR40pNM3NgC5sDyuNhF3WLNvj\n" +
+ "WgHEwYosfb/9kFRjKUPqqtJ0ccj87OP3XrE/4epCTdJdmugroAQSpXt1ZZfwwPO4\n" +
+ "K27DzMD9W01EmeLcUhMfrpUnKGCfL22c0sZZm/6Khk4BExC3pSILP/NREKeUEAHw\n" +
+ "+rxhNqbUyD/e4/DutdtJ5zONA+GVVGYCpu1Iy0W78Jve4MD2/TFPcEzf5omiWpPz\n" +
+ "WjpOWayD43ur0SZnYJ5haUlZ+bSLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABqN\n" +
+ "/eb+mKEw2zklPZuzy5uhOc7ImG2LP/ha3oc2ntwkqZQ2LmVA/2ZNVfcNrRRngIfn\n" +
+ "Ir9Kual7djmKmIQvanpnSyXOKOlIiuN0VOrewgzlBZZwlFJL/AH1l7K9uZfBbV5h\n" +
+ "oFfaR9wc+vRGsg3nqO9+hEuk6xbp0jk8QCt26EVhEPlijxzbxTQYiNPNSLuf/kPW\n" +
+ "C9xtIKSWIDT4N6DtH7BtHGQKQdRJ2b4SSUF4joEmBe6jcrLBeDybmuFtKqlVJKUk\n" +
+ "tzBd9CPseqMML1c518KzxlSkXNxTCa7PWEnuN5atLZ+pGGjxtGcDKkrZ9Cgi09G8\n" +
+ "MzB8b4C/goypyhBNlyI=\n" +
+ "-----END CERTIFICATE-----";
+ private static final String encoded_encrypted_dsa_key =
+ "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
+ "MIICkTAbBgkqhkiG9w0BBQMwDgQIL1GiUDca2x0CAggABIICcCrSiNBy5kZNC7RK\n" +
+ "fVF3IZ9Ecl00OYIjvBhlWGkaiNt9ZAeWPpYx57HSQAygzJ8ba+3jtz1dV8Bhz5D5\n" +
+ "4kggzolC820I/QLPxClH5R6ZPzDHGu/JFuNNZWASq2JHZfolEP+itdnz0F6VvQx9\n" +
+ "imngOGIJMkRWIzvwuCnq8xpNSEJHJcs8kyLBP/3qT+kUiEwJ5KrmJ8DQsHluwpyo\n" +
+ "GoiGEtiA+MNUSzc/DkuLxhC45k7K4afe4QzpWl9eR/z91F9Q06jdc5mnUWke8qCg\n" +
+ "ZopBVWbSI6fMZVlqLxKeSFljDZ+moW+2/Lh8cjEvNMccVTwWQE7JH9RLnVogqixV\n" +
+ "HIpN58wrlHd4uMVH47wD18A11AGEbghO1MEShX4SCEJIdZsr67bNx8mIkhSWjqAx\n" +
+ "BT9OmITzdbnR9sHi4CyEWhGMAMDp7YBySwpt+U7Y6DvRwWJbXUaF8zYJUlrp2IbH\n" +
+ "qdSE+oJPKxGB1s2B5KGrJUA0JkElkBUYm6ghXZlTI32w8HoV9fDOhjx1ATu7SdZY\n" +
+ "8DX0mwVVdc88Msr5RPxdeB3V4yN2iFJs4i3usicPkB0N+29LJ/lKQlm/pia0yl9j\n" +
+ "yDNN3R0RiCJdYHme4t1PqRqeTfjMauz05ObennQmkzMxD8mlBZ0zhaKL5I4TuQod\n" +
+ "PITFgYihTR9OYfa8lryvHQNCIi5iZ6M9myLUxbjPoeVBdp8pSlMAjmekEHo47vn3\n" +
+ "7IGF7AfnKVNymc/1Kim3WQx8D7nryCb8EUyb7BuNA7izGKq+NW51l39J8RGVcFSx\n" +
+ "sVkpFUbqGXusUgLWWwi21EJHCJceFnOWJRgsoeqNeCt5VCUSag==\n" +
+ "-----END ENCRYPTED PRIVATE KEY-----\n" +
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIEeDCCBB4CCQD9UojL1A7cmzAJBgcqhkjOOAQDME0xCzAJBgNVBAYTAlVTMQsw\n" +
+ "CQYDVQQIDAJDQTERMA8GA1UEBwwIU2FuIEpvc2UxETAPBgNVBAoMCFBlcnNvbmFs\n" +
+ "MQswCQYDVQQLDAJJVDAeFw0yMTEwMTgyMzU0MjRaFw0yMjEwMTgyMzU0MjRaME0x\n" +
+ "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTERMA8GA1UEBwwIU2FuIEpvc2UxETAP\n" +
+ "BgNVBAoMCFBlcnNvbmFsMQswCQYDVQQLDAJJVDCCA0YwggI5BgcqhkjOOAQBMIIC\n" +
+ "LAKCAQEAzxMsktQYQ06q71pwqWAa9YvDIkF34RJgQT5tqCZdzwtP7HXyrwaZBgLK\n" +
+ "oC4grIPKyUlPmIbm+oucucbTzDCYCx2d09VMVR471vqJi7vRCYnBIqMTTOSvbTGw\n" +
+ "8VuwA2prSt4TiqFYeqYM0Afv/KrI2MLNTP+z8RFfiLkijZIxZ9CzyHmTOEqHxOft\n" +
+ "Ln145NNcuPy3U7Bmh5k7i+RR+5jYweuLzOfTK4bTUnG111mjAhAeo+ST14ydfiuQ\n" +
+ "2e85UwLucVheh4REqm+n7g0k0B/+nG+9Os1nxfQVRVKHP4/iTrNLpf35EoPeh9XT\n" +
+ "D0BtV9lqQuttCB1aPLH66TKl9v4FowIhAMXhJ3LooBT7ypUWMtN07FvpPpoLERBr\n" +
+ "Pk9MeGwQnBPFAoIBAAcVYJ+RH1i8JFyD8MUwwOKgkmVzbRvc28B5F17oek76R6fi\n" +
+ "yyqyhHrkxmjwxktXuwRlWQBJqCTqpmMstLplqsVjcETHo7KRaHgDpT9tHf14PZvP\n" +
+ "qpoxYNpa5z4wPpOFhZ3K6VVaFUSlqSSHhaS1HqVzZC0FefFFd3TDKu0EWHDDkLM8\n" +
+ "luOxDUMydKpRtrVba+iF4kx9NcCyXjSMhFAOw3cWhGvoq6R3h4UkRJO67pzYtqK4\n" +
+ "yAETPsQKT0c8NUM+VUiPAHL/+f1EqaVtzatk9G0OHWZXgDxuhx2CAM6QfELew+ys\n" +
+ "D/QUUT0tXHQRxRCKmYE+uacwyQx5G0DeCHN0c9gDggEFAAKCAQBFGXF7flzfRH/8\n" +
+ "r/qDuNC+9y3fRtDWXaados+XQkzujizBBK9dkkAd/j/pgP5p7/bRvoeR507Oyrge\n" +
+ "6xBbfE7SlRu0cx08Ihlw7a6mCowMnP0psFBwW7npKJRYPXxRDpu9oUuuJHi1WYp7\n" +
+ "8/Ekg6hG21zAYO0JLGyU6aH5Rvuls754R+rxqveVqLiig5NTnfw7ymLb2kF1z8g7\n" +
+ "fDvX6OS242ceQIQHvq97cGnysEVSlavuHfbh3PKXklh91ip/BYuzanQjiEb97YIU\n" +
+ "DhMQyPGvk+iDdhD0PwgwOZB0P0mL7Xszw6p+lfzUc+g8jETjrnzksnVEg8On6q3Q\n" +
+ "RDJG9VnuMAkGByqGSM44BAMDSQAwRgIhAIay5hyZrwHslGXmTAx498903l7CQsdi\n" +
+ "fUrmGkxbPiZXAiEAitS8+gyG64c0jV7V/u8Z1NEL8MI47K2P9Jbn3cqzz3k=\n" +
+ "-----END CERTIFICATE-----";
+ private static final String invalid_encoded_private_key =
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCOSZVf8dLj1xLw\n" +
+ "efqjbogbAwhwRXd3tmEfQY1zHyudJF3XR1T0Xp26BKNvAYUxxZRDNg16M3prPRZv\n" +
+ "wkhDJdE9NaN+BpZPJUavRsZOfGDq/CHN8j/2PPKrn3G/b065JjV3GZjfV/Sln047\n" +
+ "DeZptaSyOg7ZN7F8qjGqxEcnw+szV/wTzhnjGjZMlcbOm4jFGQAn6xUOooQCGsoB\n" +
+ "9FgxKB0nvNG3xVe/2eCNfbS4DabT3Y1wfQqZ62hOa5ZS0rwT+pw3tQs3zFFY1Sfi\n" +
+ "G7qYbqZrKTheWIZGojVVm6mqH4yA2ofOe5N3RsBitCwU/D0dpnaG9Wcl98nmXueM\n" +
+ "B6Rk04v7AgMBAAECggEAYnxIKjrFz/JkJ5MmiszM5HV698r9YB0aqHnFIHPoykIL\n" +
+ "uiCjiumantDrFsCkosixULwvI/BRwbxstTpyrheU9psT6P1CONICVPvV8ylgJAYU\n" +
+ "l+ofn56cEXKxVuICSWFLDH7pM1479g+IJJQAchbKQpqxAGTuMu3SpvJolfuj5srt\n" +
+ "bM7/RYhJFLwDuvHNA3ivlogMneItP03+C25aaxstM+lBuBf68+n78zMgSvt6J/6Y\n" +
+ "G2TOMKnxveMlG2qu9l2lAw/2i8daG/qre08nTH7wpRx0gZLZqNpe45exkrzticzF\n" +
+ "FgWYjG2K2brX21jqHroFgMhdXF7zhhRgLoIeC0BrIQKBgQDCfGfWrJESKBbVai5u\n" +
+ "7wqD9nlzjv6N6FXfTDOPXO1vz5frdvtLVWbs0SMPy+NglkaZK0iqHvb9mf2of8eC\n" +
+ "0D5cmewjn7WCDBQMypIMYgT912ak/BBVuGXcxb6UgD+xARfSARo2C8NG1hfprw1W\n" +
+ "ad14CjS5xhFMs44HpVYhI7iPYwKBgQC7SqVG/b37vZ7CINemdvoMujLvvYXDJJM8\n" +
+ "N21LqNJfVXdukdH3T0xuLnh9Z/wPHjJDMF/9+1foxSEPHijtyz5P19EilNEC/3qw\n" +
+ "fI19+VZoY0mdhPtXSGzy+rbTE2v71QgwFLizSos14Gr+eNiIjF7FYccK05++K/zk\n" +
+ "cd8ZA3bwiQKBgQCl+HTFBs9mpz+VMOAfW2+l3hkXPNiPUc62mNkHZ05ZNNd44jjh\n" +
+ "uSf0wSUiveR08MmevQlt5K7zDQ8jVKh2QjB15gVXAVxsdtJFeDnax2trFP9LnLBz\n" +
+ "9sE2/qn9INU5wK0LUlWD+dXUBbCyg+jl7cJKRqtoPldVFYYHkFlIPqup8QKBgHXv\n" +
+ "hyuw1FUVDkdHzwOvn70r8q8sNHKxMVWVwWkHIZGOi+pAQGrusD4hXRX6yKnsZdIR\n" +
+ "QCD6iFy25R5T64nxlYdJaxPPid3NakB/7ckJnPOWseBSwMIxhQlr/nvjmve1Kba9\n" +
+ "FaEwq4B9lGIxToiNe4/nBiM3JzvlDxX67nUdzWOhAoGAdFvriyvjshSJ4JHgIY9K\n" +
+ "37BVB0VKMcFV2P8fLVWO5oyRtE1bJhU4QVpQmauABU4RGSojJ3NPIVH1wxmJeYtj\n" +
+ "Q3b7EZaqI6ovna2eK2qtUx4WwxhRaXTT8xueBI2lgL6sBSTGG+K69ZOzGQzG/Mfr\n" +
+ "RXKInnLInFD9JD94VqmMozoInvalidData=\n" +
+ "-----END PRIVATE KEY-----\n";
+ private static final String invalid_encoded_certificate =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDkTCCAnmgAwIBAgIETxH5JDANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQGEwdV\n" +
+ "bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD\n" +
+ "VQQKEwdVbmtub3duMRQwEgYDVQQLDAtzc2xfdGVzdGluZzEZMBcGA1UEAxMQQXBh\n" +
+ "Y2hlIENhc3NhbmRyYTAeFw0xNjAzMTgyMTI4MDJaFw0xNjA2MTYyMTI4MDJaMHkx\n" +
+ "EDAOBgNVBAYTB1Vua25vd24xEDAOBgNVBAgTB1Vua25vd24xEDAOBgNVBAcTB1Vu\n" +
+ "a25vd24xEDAOBgNVBAoTB1Vua25vd24xFDASBgNVBAsMC3NzbF90ZXN0aW5nMRkw\n" +
+ "FwYDVQQDExBBcGFjaGUgQ2Fzc2FuZHJhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
+ "MIIBCgKCAQEAjkmVX/HS49cS8Hn6o26IGwMIcEV3d7ZhH0GNcx8rnSRd10dU9F6d\n" +
+ "ugSjbwGFMcWUQzYNejN6az0Wb8JIQyXRPTWjfgaWTyVGr0bGTnxg6vwhzfI/9jzy\n" +
+ "q59xv29OuSY1dxmY31f0pZ9OOw3mabWksjoO2TexfKoxqsRHJ8PrM1f8E84Z4xo2\n" +
+ "TJXGzpuIxRkAJ+sVDqKEAhrKAfRYMSgdJ7zRt8VXv9ngjX20uA2m092NcH0Kmeto\n" +
+ "TmuWUtK8E/qcN7ULN8xRWNUn4hu6mG6mayk4XliGRqI1VZupqh+MgNqHznuTd0bA\n" +
+ "YrQsFPw9HaZ2hvVnJffJ5l7njAekZNOL+wIDAQABoyEwHzAdBgNVHQ4EFgQUcdiD\n" +
+ "N6aylI91kAd34Hl2AzWY51QwDQYJKoZIhvcNAQELBQADggEBAG9q29ilUgCWQP5v\n" +
+ "iHkZHj10gXGEoMkdfrPBf8grC7dpUcaw1Qfku/DJ7kPvMALeEsmFDk/t78roeNbh\n" +
+ "IYBLJlzI1HZN6VPtpWQGsqxltAy5XN9Xw9mQM/tu70ShgsodGmE1UoW6eE5+/GMv\n" +
+ "6Fg+zLuICPvs2cFNmWUvukN5LW146tJSYCv0Q/rCPB3m9dNQ9pBxrzPUHXw4glwG\n" +
+ "qGnGddXmOC+tSW5lDLLG1BRbKv4zxv3UlrtIjqlJtZb/sQMT6WtG2ihAz7SKOBHa\n" +
+ "HOWUwuPTetWIuJCKP7P4mWWtmSmjLy+BFX5seNEngn3RzJ2L8uuTJQ/88OsqgGru\n" +
+ "n3MVF9wInvalidData=\n" +
+ "-----END CERTIFICATE-----";
+
+ @Test
+ public void readEncryptedKey() throws IOException, GeneralSecurityException
+ {
+ PrivateKey privateKey = PEMReader.extractPrivateKey(encoded_encrypted_key, "cassandra");
+ Assert.assertNotNull(privateKey);
+ }
+
+ @Test
+ public void readEncryptedDSAKey() throws IOException, GeneralSecurityException
+ {
+ PrivateKey privateKey = PEMReader.extractPrivateKey(encoded_encrypted_dsa_key, "mytest");
+ Assert.assertNotNull(privateKey);
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void readEncryptedDSAKeyWithBadPassword() throws IOException, GeneralSecurityException
+ {
+ try
+ {
+ PEMReader.extractPrivateKey(encoded_encrypted_dsa_key, "bad-password");
+ } catch(GeneralSecurityException e) {
+ Assert.assertTrue(e.getMessage().startsWith("Failed to decrypt the private key data. Either the password " +
+ "provided for the key is wrong or the private key data is " +
+ "corrupted. msg="));
+ throw e;
+ }
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void readInvalidEncryptedKey() throws IOException, GeneralSecurityException
+ {
+ // Test by injecting junk data in the given key and making it invalid
+ PrivateKey privateKey = PEMReader.extractPrivateKey(encoded_encrypted_key.replaceAll("\\s",
+ String.valueOf(System.nanoTime())),
+ "cassandra");
+ Assert.assertNotNull(privateKey);
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void readInvalidBase64PrivateKey() throws IOException, GeneralSecurityException
+ {
+ PEMReader.extractPrivateKey(invalid_encoded_private_key);
+ }
+
+ @Test
+ public void readUnencryptedKey() throws IOException, GeneralSecurityException
+ {
+ PrivateKey privateKey = PEMReader.extractPrivateKey(encoded_key);
+ Assert.assertNotNull(privateKey);
+ }
+
+ @Test
+ public void readUnencryptedECKey() throws IOException, GeneralSecurityException
+ {
+ PrivateKey privateKey = PEMReader.extractPrivateKey(encoded_unencrypted_ec_private_key);
+ Assert.assertNotNull(privateKey);
+ }
+
+ @Test
+ public void readCertChain() throws GeneralSecurityException
+ {
+ Certificate[] certificates = PEMReader.extractCertificates(encoded_encrypted_key);
+ Assert.assertNotNull("CertChain must not be null", certificates);
+ Assert.assertTrue("CertChain must have only one certificate", certificates.length == 1);
+ }
+
+ @Test
+ public void readCertChainWithMoreThanOneCerts() throws GeneralSecurityException
+ {
+ Certificate[] certificates = PEMReader.extractCertificates(encoded_encrypted_key_with_multiplecerts_in_chain);
+ Assert.assertNotNull("CertChain must not be null", certificates);
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void readInvalidCertificate() throws GeneralSecurityException
+ {
+ // Test by injecting junk data in the given key and making it invalid
+ Certificate[] certificates = PEMReader.extractCertificates(encoded_encrypted_key.replaceAll("\\s",
+ String.valueOf(System.nanoTime())));
+ Assert.assertNotNull("CertChain must not be null", certificates);
+ Assert.assertTrue("CertChain must have only one certificate", certificates.length == 1);
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void readInvalidBase64Certificate() throws GeneralSecurityException
+ {
+ PEMReader.extractCertificates(invalid_encoded_certificate);
+ }
+
+ @Test
+ public void readTrustedCertificates() throws GeneralSecurityException
+ {
+ Certificate[] certificates = PEMReader.extractCertificates(encoded_certificates);
+ Assert.assertNotNull("Trusted certificate list must not be null", certificates);
+ Assert.assertTrue("Trusted certificate list must have only one certificate", certificates.length == 1);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void tamperSupportedAlgorithms() throws GeneralSecurityException
+ {
+ Set<String> original = PEMReader.SUPPORTED_PRIVATE_KEY_ALGORITHMS;
+ for (int i = 0; i < original.size(); i++)
+ {
+ original.remove("RSA");
+ original.remove("DSA");
+ original.remove("EC");
+ original.add("TAMPERED");
+ }
+ }
+}
diff --git a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
index 049d887..e5aa4b1 100644
--- a/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
+++ b/test/unit/org/apache/cassandra/security/SSLFactoryTest.java
@@ -74,6 +74,16 @@ public class SSLFactoryTest
.withKeyStorePassword("cassandra");
}
+ private ServerEncryptionOptions addPEMKeystoreOptions(ServerEncryptionOptions options)
+ {
+ ParameterizedClass sslContextFactoryClass = new ParameterizedClass("org.apache.cassandra.security.PEMBasedSslContextFactory",
+ new HashMap<>());
+ return options.withSslContextFactory(sslContextFactoryClass)
+ .withKeyStore("test/conf/cassandra_ssl_test.keystore.pem")
+ .withKeyStorePassword("cassandra")
+ .withTrustStore("test/conf/cassandra_ssl_test.truststore.pem");
+ }
+
@Test
public void testSslContextReload_HappyPath() throws IOException, InterruptedException
{
@@ -106,6 +116,38 @@ public class SSLFactoryTest
}
}
+ @Test
+ public void testPEMSslContextReload_HappyPath() throws IOException, InterruptedException
+ {
+ try
+ {
+ ServerEncryptionOptions options = addPEMKeystoreOptions(encryptionOptions)
+ .withInternodeEncryption(ServerEncryptionOptions.InternodeEncryption.all);
+
+ SSLFactory.initHotReloading(options, options, true);
+
+ SslContext oldCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT);
+ File keystoreFile = new File(options.keystore);
+
+ SSLFactory.checkCertFilesForHotReloading(options, options);
+
+ keystoreFile.trySetLastModified(System.currentTimeMillis() + 15000);
+
+ SSLFactory.checkCertFilesForHotReloading(options, options);
+ SslContext newCtx = SSLFactory.getOrCreateSslContext(options, true, ISslContextFactory.SocketType.CLIENT);
+
+ Assert.assertNotSame(oldCtx, newCtx);
+ }
+ catch (Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ DatabaseDescriptor.loadConfig();
+ }
+ }
+
@Test(expected = IOException.class)
public void testSslFactorySslInit_BadPassword_ThrowsException() throws IOException
{
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org