Bläddra i källkod

Merge pull request #251 from xoraes/master

Support for TLS
Ben Hood 11 år sedan
förälder
incheckning
d1e933ed77

+ 1 - 1
.travis.yml

@@ -23,7 +23,7 @@ install:
   - go get .
 
 script:
-  - bash integration.sh $CASS
+  - bash -x integration.sh $CASS
   - go vet .
 
 notifications:

+ 1 - 0
AUTHORS

@@ -31,4 +31,5 @@ Muir Manders <muir@retailnext.net>
 Sankar P <sankar.curiosity@gmail.com>
 Julien Da Silva <julien.dasilva@gmail.com>
 Dan Kennedy <daniel@firstcs.co.uk>
+Nick Dhupia<nick.dhupia@gmail.com>
 Yasuharu Goto <matope.ono@gmail.com>

+ 26 - 12
cassandra_test.go

@@ -19,28 +19,39 @@ import (
 	"testing"
 	"time"
 	"unicode"
-
 	"speter.net/go/exp/math/dec/inf"
 )
 
 var (
-	flagCluster  = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
-	flagProto    = flag.Int("proto", 2, "protcol version")
-	flagCQL      = flag.String("cql", "3.0.0", "CQL version")
-	flagRF       = flag.Int("rf", 1, "replication factor for test keyspace")
-	clusterSize  = flag.Int("clusterSize", 1, "the expected size of the cluster")
-	flagRetry    = flag.Int("retries", 5, "number of times to retry queries")
-	flagAutoWait = flag.Duration("autowait", 1000*time.Millisecond, "time to wait for autodiscovery to fill the hosts poll")
-	clusterHosts []string
+	flagCluster    = flag.String("cluster", "127.0.0.1", "a comma-separated list of host:port tuples")
+	flagProto      = flag.Int("proto", 2, "protcol version")
+	flagCQL        = flag.String("cql", "3.0.0", "CQL version")
+	flagRF         = flag.Int("rf", 1, "replication factor for test keyspace")
+	clusterSize    = flag.Int("clusterSize", 1, "the expected size of the cluster")
+	flagRetry      = flag.Int("retries", 5, "number of times to retry queries")
+	flagAutoWait   = flag.Duration("autowait", 1000*time.Millisecond, "time to wait for autodiscovery to fill the hosts poll")
+	flagRunSslTest = flag.Bool("runssl", false, "Set to true to run ssl test")
+	clusterHosts   []string
 )
 
 func init() {
-
 	flag.Parse()
 	clusterHosts = strings.Split(*flagCluster, ",")
 	log.SetFlags(log.Lshortfile | log.LstdFlags)
 }
 
+func addSslOptions(cluster *ClusterConfig) *ClusterConfig {
+	if *flagRunSslTest {
+		cluster.SslOpts = &SslOptions{
+			CertPath:               "testdata/pki/gocql.crt",
+			KeyPath:                "testdata/pki/gocql.key",
+			CaPath:                 "testdata/pki/ca.crt",
+			EnableHostVerification: false,
+		}
+	}
+	return cluster
+}
+
 var initOnce sync.Once
 
 func createTable(s *Session, table string) error {
@@ -61,7 +72,7 @@ func createCluster() *ClusterConfig {
 	if *flagRetry > 0 {
 		cluster.RetryPolicy = &SimpleRetryPolicy{NumRetries: *flagRetry}
 	}
-
+	cluster = addSslOptions(cluster)
 	return cluster
 }
 
@@ -114,7 +125,7 @@ func TestRingDiscovery(t *testing.T) {
 		cluster.RetryPolicy = &SimpleRetryPolicy{NumRetries: *flagRetry}
 	}
 	cluster.DiscoverHosts = true
-
+	cluster = addSslOptions(cluster)
 	session, err := cluster.CreateSession()
 	if err != nil {
 		t.Errorf("got error connecting to the cluster %v", err)
@@ -136,6 +147,7 @@ func TestRingDiscovery(t *testing.T) {
 
 func TestEmptyHosts(t *testing.T) {
 	cluster := NewCluster()
+	cluster = addSslOptions(cluster)
 	if session, err := cluster.CreateSession(); err == nil {
 		session.Close()
 		t.Error("expected err, got nil")
@@ -162,6 +174,7 @@ func TestInvalidKeyspace(t *testing.T) {
 	cluster.ProtoVersion = *flagProto
 	cluster.CQLVersion = *flagCQL
 	cluster.Keyspace = "invalidKeyspace"
+	cluster = addSslOptions(cluster)
 	session, err := cluster.CreateSession()
 	if err != nil {
 		if err != ErrNoConnectionsStarted {
@@ -472,6 +485,7 @@ func TestCreateSessionTimeout(t *testing.T) {
 		t.Fatal("no startup timeout")
 	}()
 	c := NewCluster("127.0.0.1:1")
+	c = addSslOptions(c)
 	_, err := c.CreateSession()
 
 	if err == nil {

+ 1 - 0
cluster.go

@@ -62,6 +62,7 @@ type ClusterConfig struct {
 	DiscoverHosts    bool          // If set, gocql will attempt to automatically discover other members of the Cassandra cluster (default: false)
 	MaxPreparedStmts int           // Sets the maximum cache size for prepared statements globally for gocql (default: 1000)
 	Discovery        DiscoveryConfig
+	SslOpts          *SslOptions
 }
 
 // NewCluster generates a new config for the default cluster implementation.

+ 44 - 2
conn.go

@@ -6,8 +6,11 @@ package gocql
 
 import (
 	"bufio"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"net"
 	"sync"
 	"sync/atomic"
@@ -44,6 +47,16 @@ func (p PasswordAuthenticator) Success(data []byte) error {
 	return nil
 }
 
+type SslOptions struct {
+	CertPath               string
+	KeyPath                string
+	CaPath                 string //optional depending on server config
+	// If you want to verify the hostname and server cert (like a wildcard for cass cluster) then you should turn this on
+	// This option is basically the inverse of InSecureSkipVerify
+	// See InSecureSkipVerify in http://golang.org/pkg/crypto/tls/ for more info
+	EnableHostVerification bool
+}
+
 type ConnConfig struct {
 	ProtoVersion  int
 	CQLVersion    string
@@ -52,6 +65,7 @@ type ConnConfig struct {
 	Compressor    Compressor
 	Authenticator Authenticator
 	Keepalive     time.Duration
+	SslOpts       *SslOptions
 }
 
 // Conn is a single connection to a Cassandra node. It can be used to execute
@@ -80,8 +94,36 @@ type Conn struct {
 // Connect establishes a connection to a Cassandra node.
 // You must also call the Serve method before you can execute any queries.
 func Connect(addr string, cfg ConnConfig, pool ConnectionPool) (*Conn, error) {
-	conn, err := net.DialTimeout("tcp", addr, cfg.Timeout)
-	if err != nil {
+	var (
+		err  error
+		conn net.Conn
+	)
+
+	if cfg.SslOpts != nil {
+		certPool := x509.NewCertPool()
+		//ca cert is optional
+		if cfg.SslOpts.CaPath != "" {
+			pem, err := ioutil.ReadFile(cfg.SslOpts.CaPath)
+			if err != nil {
+				return nil, err
+			}
+			if !certPool.AppendCertsFromPEM(pem) {
+				return nil, errors.New("Failed parsing or appending certs")
+			}
+		}
+		mycert, err := tls.LoadX509KeyPair(cfg.SslOpts.CertPath, cfg.SslOpts.KeyPath)
+		if err != nil {
+			return nil, err
+		}
+		config := tls.Config{
+			Certificates: []tls.Certificate{mycert},
+			RootCAs:      certPool,
+		}
+		config.InsecureSkipVerify = !cfg.SslOpts.EnableHostVerification
+		if conn, err = tls.Dial("tcp", addr, &config); err != nil {
+			return nil, err
+		}
+	} else if conn, err = net.DialTimeout("tcp", addr, cfg.Timeout); err != nil {
 		return nil, err
 	}
 

+ 51 - 0
conn_test.go

@@ -12,6 +12,9 @@ import (
 	"sync/atomic"
 	"testing"
 	"time"
+	"crypto/tls"
+	"crypto/x509"
+	"io/ioutil"
 )
 
 type TestServer struct {
@@ -36,6 +39,31 @@ func TestSimple(t *testing.T) {
 	}
 }
 
+func TestSSLSimple(t *testing.T) {
+	srv := NewSSLTestServer(t)
+	defer srv.Stop()
+
+	db, err := createTestSslCluster(srv.Address).CreateSession()
+	if err != nil {
+		t.Errorf("NewCluster: %v", err)
+	}
+
+	if err := db.Query("void").Exec(); err != nil {
+		t.Error(err)
+	}
+}
+
+func createTestSslCluster(hosts string) *ClusterConfig {
+	cluster := NewCluster(hosts)
+	cluster.SslOpts = &SslOptions{
+		CertPath:               "testdata/pki/gocql.crt",
+		KeyPath:                "testdata/pki/gocql.key",
+		CaPath:                 "testdata/pki/ca.crt",
+		EnableHostVerification: false,
+	}
+	return cluster
+}
+
 func TestClosed(t *testing.T) {
 	srv := NewTestServer(t)
 	defer srv.Stop()
@@ -211,6 +239,29 @@ func NewTestServer(t *testing.T) *TestServer {
 	return srv
 }
 
+func NewSSLTestServer(t *testing.T) *TestServer {
+	pem, err := ioutil.ReadFile("testdata/pki/ca.crt")
+	certPool := x509.NewCertPool()
+	if !certPool.AppendCertsFromPEM(pem) {
+		t.Errorf("Failed parsing or appending certs")
+	}
+	mycert, err := tls.LoadX509KeyPair("testdata/pki/cassandra.crt", "testdata/pki/cassandra.key")
+	if err != nil {
+		t.Errorf("could not load cert")
+	}
+	config := &tls.Config{
+		Certificates: []tls.Certificate{mycert},
+		RootCAs:      certPool,
+	}
+	listen, err := tls.Listen("tcp", "127.0.0.1:0", config)
+	if err != nil {
+		t.Fatal(err)
+	}
+	srv := &TestServer{Address: listen.Addr().String(), listen: listen, t: t}
+	go srv.serve()
+	return srv
+}
+
 func (srv *TestServer) serve() {
 	defer srv.listen.Close()
 	for {

+ 1 - 0
connectionpool.go

@@ -169,6 +169,7 @@ func (c *SimplePool) connect(addr string) error {
 		Compressor:    c.cfg.Compressor,
 		Authenticator: c.cfg.Authenticator,
 		Keepalive:     c.cfg.SocketKeepalive,
+		SslOpts:       c.cfg.SslOpts,
 	}
 
 	conn, err := Connect(addr, cfg, c)

+ 2 - 3
integration.sh

@@ -11,7 +11,7 @@ function run_tests() {
 	sed -i '/#MAX_HEAP_SIZE/c\MAX_HEAP_SIZE="256M"' ~/.ccm/repository/$version/conf/cassandra-env.sh
 	sed -i '/#HEAP_NEWSIZE/c\HEAP_NEWSIZE="100M"' ~/.ccm/repository/$version/conf/cassandra-env.sh
 
-	ccm updateconf 'concurrent_reads: 2' 'concurrent_writes: 2' 'rpc_server_type: sync' 'rpc_min_threads: 2' 'rpc_max_threads: 2' 'write_request_timeout_in_ms: 5000' 'read_request_timeout_in_ms: 5000'
+	ccm updateconf 'client_encryption_options.enabled: true' 'client_encryption_options.keystore: testdata/pki/.keystore' 'client_encryption_options.keystore_password: cassandra' 'client_encryption_options.require_client_auth: true' 'client_encryption_options.truststore: testdata/pki/.truststore' 'client_encryption_options.truststore_password: cassandra' 'concurrent_reads: 2' 'concurrent_writes: 2' 'rpc_server_type: sync' 'rpc_min_threads: 2' 'rpc_max_threads: 2' 'write_request_timeout_in_ms: 5000' 'read_request_timeout_in_ms: 5000'
 	ccm start
 	ccm status
 
@@ -20,7 +20,7 @@ function run_tests() {
 		proto=1
 	fi
 
-	go test -cover -v -proto=$proto -rf=3 -cluster=$(ccm liveset) -clusterSize=$clusterSize -autowait=2000ms ./... > results
+	go test -cover -v -runssl -proto=$proto -rf=3 -cluster=$(ccm liveset) -clusterSize=$clusterSize -autowait=2000ms ./... > results
 
 	cat results
 	cover=`cat results | grep coverage: | grep -o "[0-9]\{1,3\}" | head -n 1`
@@ -30,5 +30,4 @@ function run_tests() {
 	fi
 	ccm clear
 }
-
 run_tests $1

BIN
testdata/pki/.keystore


BIN
testdata/pki/.truststore


+ 20 - 0
testdata/pki/ca.crt

@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLzCCAhegAwIBAgIJAIKbAXgemwsjMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
+BAMTCWNhc3NhbmRyYTAeFw0xNDA5MTkyMTE4MTNaFw0yNDA5MTYyMTE4MTNaMBQx
+EjAQBgNVBAMTCWNhc3NhbmRyYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5fX0l1WDNa+mO1krxw7k8lfUQn+Ec4L3Mqv6IstGoNdCPq4YRA+SXRD5YC
+k/UXrFBWh9Hbs849GiuTYMPdj9HDLYz40RaQjM9GbieS23iy3UStQ0tKhxaaG6FN
+6XBypXFKCTsanu0TkEoDGhAkSzAMcCAC3gkFBzMrZ5qt4HEzjY9rasZ2gthN+xop
+nq3t4dDkE8HGaiFJcFvqTor7xmrnAaPjrPzUpvOF/ObIC09omwg/KXdPRx4DKPon
+gCMKEE3ckebKnJvbsRX3WO8H5nTHBYZ6v1JxLZz5pqmV+P0NGxldCARM0gCQUBz5
+wjMJkD/3e1ETC+q6uwfnAG0hlD8CAwEAAaOBgzCBgDAdBgNVHQ4EFgQUjHzn0nYF
+iXEaI1vUWbRR4lwKXOgwRAYDVR0jBD0wO4AUjHzn0nYFiXEaI1vUWbRR4lwKXOih
+GKQWMBQxEjAQBgNVBAMTCWNhc3NhbmRyYYIJAIKbAXgemwsjMAwGA1UdEwQFMAMB
+Af8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBCYDdIhtf/Y12Et947
+am1B8TzSX+/iQ1V1J3JtvgD5F4fvNjfArat/I3D277WREUTAc76o16BCp2OBGqzO
+zf9MvZPkjkAUoyU0TtPUEHyqxq4gZxbWKugIZGYkmQ1hCvSIgA5UnjRL3dylMmZb
+Y33JJA2QY63FZwnhmWsM8FYZwh+8MzVCQx3mgXC/k/jS6OuYyIT/KjxQHHjyr5ZS
+zAAQln1IcZycLfh1w5MtCFahCIethFcVDnWUWYPcPGDGgMJW7WBpNZdHbLxYY8cI
+eCc3Hcrbdc/CG5CaLJeqUidBayjnlUIO/NNgglkJ1KhQzkM6bd+37e0AX1hLIqx7
+gIZR
+-----END CERTIFICATE-----

+ 30 - 0
testdata/pki/ca.key

@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,54C8072C0FF3B3A3
+
+27eijmHdgB+s3beNPmU0+iz+muxMD0BVvWkDzyec/uawMv/Cn4c3mYXOcsFxS3BL
++qLT9MEttOmjqhHSaVrDYOPKoJIMpn+bVeKiR08V89icO36shEPy1feGqanagKtw
+ecgzFDBTA8ZbqjAhftXlhTwxADebvNms/2aDh5Aw04vIcbo8nQ/8z1Wz8O7Firsn
+kaseSTMTC6lxc+pa2V1X6mN0/2UpDi55bZbx1Z/mQ3+1CsdHOx0p7m/KY2m3ysov
+XluaC0sqmzHkcwNgDhUs3Jh+apE33vXzLGU+W4BDOwrYJiL6KpspZW/mJj3OEx8B
+8xdAZU3a/ei8NUA/lDStGmcYX+dOysExwJ6GMrCBm9iufZiefDQCQ8yRqWnr6Zop
+lsFd+CqHNWYxfWDI1pSUBw3bsgIjevI0f0B7PxkFEF0DmIhCgB324/uqToRzGsOF
+4MSVg6cSK7Sjo/u3r8r75A3aUAcY8NbR3peiZfAPMsTiUcfp4DoU+MJTqkX5PyQq
+FNxHOJoARZqjjQ2IhZiUQWfIINHvZ8F9G2K7VaES8A0EATyUghqaRyeLbyI3IYdW
+pGZBzrpGtdFlk9AVetHDDlY+gQiurtYhxOsxvlxJJuTj8FV+A5NWSElfPele0OiR
+iprE3xkFSk3whHu5L1vnzamvdSlnBWOAE7pQD7kQA6NmcEw/tqnXK0dVdAw8RIFh
+4BKgv0sNrXzBgnzE8+bKLUf1a2Byc/YKuBrI7EpSZ9/VHYvOcgmOxNxMmRS6NYd1
+Ly+agQn0AyvsDmSlBZBp8GCzVp6JYBMDKSXyPVN8+wjK9OQM0PZdEdXouMwPCOVN
+oNSjhmMtfjOsnG2SZ9tRas3p0qFdfh/N/E6Q7QHG3WD3cUIEweFV9ji1FTSRUrIa
+shuKug8MUfNjvDJNMsdGyf6Hi/7Iik++42Rq3ZdTy0ZVkj5snv5yBN77pr2M/J4b
+M+dsXjyXPO4SDW3kP/e3RnLRlWmUv1PNdOmNDdjBBUTKgVZ3ur+4HmSY1iDvhlUF
+/hz2tz3/XUKQwYuv3KJVlBhLrniXeES36GK+JQadIszrjwb5N4q4p6xrIdIR7XgR
+TJCSL1NGPLeQyjK6byWLNPRcCGrvnxWs0k0ev6trMRJL1EjsIFDCJam9szhcXkZP
+iYl1d7ZMKPS3cAqCjdaFRSe65cZ+qI/cqxiv122orq/jkDY7ZSA9rWywY4YnYQ7A
+BqvcPzC/6K0bteXqmMQkIy/84aSnEts6ecb/4s5e5xXLhHe0dchG0HkasC/Gb+v/
+m9NOqACTerWvSD+Ecv9OvnBjP+GTlA1g7xTiRANLXsTJuiJomtxewXcV6kGZEMmZ
+QWerGtPJGGUx36WRWrMiPeBfWZoIbjYGPmOO5mYNXMTjABGGWcFnKAqWUKsFihi9
+pC0OpZ7A0dtc9uSm0ZmsHUc3XENMHTeeEN+qgWxVKcMzRKEcnapu/0OcHrOUHDZf
+qPoG4EkNnG9kPMq3HzvFPx3qbQ017yl87vAkWy/Edo+ojfHoNghRBVGCw1zt/BMN
+eJbFFHop+rQ87omz8WIL4K+zVf91rJ0REVAJssQVDo16O5wrMo+f+c8v2GANQks5
+-----END RSA PRIVATE KEY-----

+ 83 - 0
testdata/pki/cassandra.crt

@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=cassandra
+        Validity
+            Not Before: Sep 19 21:18:48 2014 GMT
+            Not After : Sep 16 21:18:48 2024 GMT
+        Subject: CN=cassandra
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:e5:9c:20:9e:de:98:73:44:41:0d:37:4c:62:c3:
+                    9f:87:5f:9b:4f:aa:cf:f6:90:6e:a5:e0:89:88:7a:
+                    00:c6:bb:d7:80:87:69:2e:fa:f0:35:59:80:6e:82:
+                    25:c8:b3:6c:f6:a4:97:97:93:93:ea:f0:70:70:a4:
+                    e1:b7:aa:da:c1:99:66:9b:93:04:3a:ce:0b:83:07:
+                    06:22:3d:a6:db:7f:68:0f:49:80:bd:86:a8:bb:54:
+                    6d:38:5f:0f:b0:fa:1b:97:24:ae:cc:9d:37:98:7e:
+                    76:cc:e3:1b:45:1b:21:25:17:02:c0:1a:c5:fb:76:
+                    c3:8b:93:d7:c5:85:14:0a:5c:a4:12:e7:18:69:98:
+                    f5:76:cd:78:cd:99:5a:29:65:f1:68:20:97:d3:be:
+                    09:b3:68:1b:f2:a3:a2:9a:73:58:53:7e:ed:86:32:
+                    a3:5a:d5:46:03:f9:b3:b4:ec:63:71:ba:bb:fb:6f:
+                    f9:82:63:e4:55:47:7a:7a:e4:7b:17:6b:d7:e6:cf:
+                    3b:c9:ab:0c:30:15:c9:ed:c7:d6:fc:b6:72:b2:14:
+                    7d:c7:f3:7f:8a:f4:63:70:64:8e:0f:db:e8:3a:45:
+                    47:cd:b9:7b:ae:c8:31:c1:52:d1:3e:34:12:b7:73:
+                    e7:ba:89:86:9a:36:ed:a0:5a:69:d0:d4:e3:b6:16:
+                    85:af
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                4A:D3:EC:63:07:E0:8F:1A:4E:F5:09:43:90:9F:7A:C5:31:D1:8F:D8
+            X509v3 Authority Key Identifier: 
+                keyid:8C:7C:E7:D2:76:05:89:71:1A:23:5B:D4:59:B4:51:E2:5C:0A:5C:E8
+                DirName:/CN=cassandra
+                serial:82:9B:01:78:1E:9B:0B:23
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Key Usage: 
+                Digital Signature, Key Encipherment
+    Signature Algorithm: sha256WithRSAEncryption
+        ac:bc:80:82:2d:6d:f1:a0:46:eb:00:05:d2:25:9a:83:66:57:
+        40:51:6e:ff:db:e3:28:04:7b:16:63:74:ec:55:a0:c0:5b:47:
+        13:e1:5a:a5:6d:22:d0:e5:fe:c1:51:e8:f6:c6:9c:f9:be:b7:
+        be:82:14:e4:a0:b2:0b:9f:ee:68:bc:ac:17:0d:13:50:c6:9e:
+        52:91:8c:a0:98:db:4e:2d:f6:3d:6e:85:0a:bb:b9:dd:01:bf:
+        ad:52:dd:6e:e4:41:01:a5:93:58:dd:3f:cf:bf:15:e6:25:aa:
+        a0:4f:98:0d:75:8a:3f:5b:ba:67:37:f6:b1:0b:3f:21:34:97:
+        50:9a:85:97:2b:b6:05:41:9a:f3:cf:c4:92:23:06:ab:3e:87:
+        98:30:eb:cb:d3:83:ab:04:7d:5c:b9:f0:12:d1:43:b3:c5:7d:
+        33:9a:2e:2b:80:3a:66:be:f1:8c:08:37:7a:93:9c:9b:60:60:
+        53:71:16:70:86:df:ca:5f:a9:0b:e2:8b:3d:af:02:62:3b:61:
+        30:da:53:89:e3:d8:0b:88:04:9a:93:6a:f6:28:f8:dd:0d:8f:
+        0c:82:5b:c0:e5:f8:0d:ad:06:76:a7:3b:4b:ae:54:37:25:15:
+        f5:0c:67:0f:77:c5:c4:97:68:09:c3:02:a7:a0:46:10:1c:d1:
+        95:3a:4c:94
+-----BEGIN CERTIFICATE-----
+MIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwljYXNz
+YW5kcmEwHhcNMTQwOTE5MjExODQ4WhcNMjQwOTE2MjExODQ4WjAUMRIwEAYDVQQD
+EwljYXNzYW5kcmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlnCCe
+3phzREENN0xiw5+HX5tPqs/2kG6l4ImIegDGu9eAh2ku+vA1WYBugiXIs2z2pJeX
+k5Pq8HBwpOG3qtrBmWabkwQ6zguDBwYiPabbf2gPSYC9hqi7VG04Xw+w+huXJK7M
+nTeYfnbM4xtFGyElFwLAGsX7dsOLk9fFhRQKXKQS5xhpmPV2zXjNmVopZfFoIJfT
+vgmzaBvyo6Kac1hTfu2GMqNa1UYD+bO07GNxurv7b/mCY+RVR3p65HsXa9fmzzvJ
+qwwwFcntx9b8tnKyFH3H83+K9GNwZI4P2+g6RUfNuXuuyDHBUtE+NBK3c+e6iYaa
+Nu2gWmnQ1OO2FoWvAgMBAAGjgZUwgZIwCQYDVR0TBAIwADAdBgNVHQ4EFgQUStPs
+YwfgjxpO9QlDkJ96xTHRj9gwRAYDVR0jBD0wO4AUjHzn0nYFiXEaI1vUWbRR4lwK
+XOihGKQWMBQxEjAQBgNVBAMTCWNhc3NhbmRyYYIJAIKbAXgemwsjMBMGA1UdJQQM
+MAoGCCsGAQUFBwMBMAsGA1UdDwQEAwIFoDANBgkqhkiG9w0BAQsFAAOCAQEArLyA
+gi1t8aBG6wAF0iWag2ZXQFFu/9vjKAR7FmN07FWgwFtHE+FapW0i0OX+wVHo9sac
++b63voIU5KCyC5/uaLysFw0TUMaeUpGMoJjbTi32PW6FCru53QG/rVLdbuRBAaWT
+WN0/z78V5iWqoE+YDXWKP1u6Zzf2sQs/ITSXUJqFlyu2BUGa88/EkiMGqz6HmDDr
+y9ODqwR9XLnwEtFDs8V9M5ouK4A6Zr7xjAg3epOcm2BgU3EWcIbfyl+pC+KLPa8C
+YjthMNpTiePYC4gEmpNq9ij43Q2PDIJbwOX4Da0Gdqc7S65UNyUV9QxnD3fFxJdo
+CcMCp6BGEBzRlTpMlA==
+-----END CERTIFICATE-----

+ 27 - 0
testdata/pki/cassandra.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA5Zwgnt6Yc0RBDTdMYsOfh1+bT6rP9pBupeCJiHoAxrvXgIdp
+LvrwNVmAboIlyLNs9qSXl5OT6vBwcKTht6rawZlmm5MEOs4LgwcGIj2m239oD0mA
+vYaou1RtOF8PsPoblySuzJ03mH52zOMbRRshJRcCwBrF+3bDi5PXxYUUClykEucY
+aZj1ds14zZlaKWXxaCCX074Js2gb8qOimnNYU37thjKjWtVGA/mztOxjcbq7+2/5
+gmPkVUd6euR7F2vX5s87yasMMBXJ7cfW/LZyshR9x/N/ivRjcGSOD9voOkVHzbl7
+rsgxwVLRPjQSt3PnuomGmjbtoFpp0NTjthaFrwIDAQABAoIBAQChjdjl73kUoVGk
+GuSEGWCFv59nzqfEtJsl23bpr+4b5s8agCxiAe5Bm1fiaXBsZtKkN+rxm8TX6ZUz
+rM+ki3KgBW9Mx4SSW6d96dNHBFoC1wJAv1b2A2l1ZVHz9+7ydwgysHzNO1GC2nh8
+cM8fMJeBoU8uG6hx5n5wFvYa5CfVoUQh8+Oq0b+mVxEFKHmRPnWp9/jPzL5eBIdr
+ulbDt9S3dKJtouHgHBUNdkq/7Ex3QeHrUOahX6Y4eX1rzLnfLYY+0J4EA2PCKvgQ
+bfKCxVnnzL6ywviH8eS3ql6OvTfnbK9kCRw7WxX9CC50qKj3EmwC/51MPhWohWlq
+jw3qf38BAoGBAPPNyb3vUiyUqoErZxxIPFc2ob3vCjj06cvi7uKpOgrkdgC3iBhz
+aCFQ28r7LrxLAHaKvNvwp71Lc7WYo8WWkLI1DVn0dx+GiQYW3DbNcwZOS40ZQz5L
+zsjEcG4+cnZmuqGZBMNvQ+xUjkuucxvxPWKpEKM18GfDjgEkKbmDr+uNAoGBAPEY
+kVSfSZGtP0MoXIfRkrxBlhvCj9m+p60P37pyHrJBrlrwvxB7x3Oz8S70D6kV8s2g
+vVHgOS3VPj17VaQG8a3jBLKjzp5JLe34G8D1Ny8GqDc2wzOBtZySpJbifXuSUSPk
+cqF7yiu1cD/wRPlwyWxBX9ZbaxvxnIUwLLd3ygkrAoGBAKQaw42uVkCdvPr/DQOT
+d9I4erxO9zGJYQmU8bjtsZz9VJR89QWIQPIT7C3/zuB9F42zKxZcMXwQGo2EddAc
+3b6mSRtgmwJEW10W7BmTRrZa4y3RcFqxSjoHR6pdLEyYL01woy0taqnb7H/yp5aK
+VghfxkwllXEyxxXrko5FnpdNAoGBANeJLBunz2BxrnW+doJhZDnytFya4nk6TbKU
+12FaNoEL4PCh+12kGtogSwS74eg6m/citT2mI9gKpHrYcOaT4qmeo4uEj+nH6Eyv
+Gzi0wCHFZMr/pSC92/teyc+uKZo4Y1ugFq6w+Tt8GB7BERiisR+bji8XSTkRFemn
++MIIUFFDAoGAM8Va2Q5aTUkfg2mYlNLqT2tUAXVEhbmzjPA6laSo25PQEYWmX7vj
+hiU0DPCDJQ/PlPI23xYtDDLNk83Zbx+Oj29GO5pawJY9NvFI8n60EFXfLbP1nEdG
+j077QZNZOKfcgJirWi3+RrHSAK4tFftCe7rkV8ZmlMRBY3SDxzKOGcc=
+-----END RSA PRIVATE KEY-----

+ 83 - 0
testdata/pki/gocql.crt

@@ -0,0 +1,83 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN=cassandra
+        Validity
+            Not Before: Sep 19 21:18:33 2014 GMT
+            Not After : Sep 16 21:18:33 2024 GMT
+        Subject: CN=gocql
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ae:e9:fa:9e:fd:e2:69:85:1d:08:0f:35:68:bc:
+                    63:7b:92:50:7f:73:50:fc:42:43:35:06:b3:5c:9e:
+                    27:1e:16:05:69:ec:88:d5:9c:4f:ef:e8:13:69:7a:
+                    b5:b3:7f:66:6d:14:00:2e:d6:af:5b:ff:2c:90:91:
+                    a6:11:07:72:5e:b0:37:c0:6d:ff:7b:76:2b:fe:de:
+                    4c:d2:8d:ce:43:3b:1a:c4:1d:de:b6:d8:26:08:25:
+                    89:59:a1:4b:94:a3:57:9e:19:46:28:6e:97:11:7c:
+                    e6:b7:41:96:8f:42:dd:66:da:86:d2:53:dd:d8:f5:
+                    20:cd:24:8b:0f:ab:df:c4:10:b2:64:20:1d:e0:0f:
+                    f4:2d:f6:ca:94:be:83:ac:3e:a8:4a:77:b6:08:97:
+                    3a:7e:7b:e0:3e:ab:68:cf:ee:f6:a1:8e:bf:ec:be:
+                    06:d1:ad:6c:ed:4f:35:d1:04:97:08:33:b1:65:5b:
+                    61:32:8d:4b:f0:30:35:4b:8b:6b:06:f2:1a:72:8c:
+                    69:bd:f3:b2:c4:a4:a4:70:45:e3:67:a2:7a:9f:2e:
+                    cb:28:2d:9f:68:03:f1:c7:d9:4f:83:c9:3d:8c:34:
+                    04:0a:3b:13:87:92:e1:f7:e3:79:7e:ab:c0:25:b1:
+                    e5:38:09:44:3e:31:df:12:d4:dc:7b:0e:35:bf:ee:
+                    25:5f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Subject Key Identifier: 
+                9F:F1:B2:C4:82:34:D0:2F:FF:E9:7F:19:F1:3B:51:57:BF:E8:95:BB
+            X509v3 Authority Key Identifier: 
+                keyid:8C:7C:E7:D2:76:05:89:71:1A:23:5B:D4:59:B4:51:E2:5C:0A:5C:E8
+                DirName:/CN=cassandra
+                serial:82:9B:01:78:1E:9B:0B:23
+
+            X509v3 Extended Key Usage: 
+                TLS Web Client Authentication
+            X509v3 Key Usage: 
+                Digital Signature
+    Signature Algorithm: sha256WithRSAEncryption
+        12:aa:1b:a6:58:27:52:32:c9:46:19:32:d3:69:ae:95:ad:23:
+        55:ad:12:65:da:2c:4c:72:f3:29:bd:2b:5a:97:3b:b7:68:8b:
+        68:80:77:55:e6:32:81:f1:f5:20:54:ba:0e:2b:86:90:d8:44:
+        cf:f2:9f:ec:4d:39:67:4e:36:6c:9b:49:4a:80:e6:c1:ed:a4:
+        41:39:19:16:d2:88:df:17:0c:46:5a:b9:88:53:f5:67:19:f0:
+        1f:9a:51:40:1b:40:12:bc:57:db:de:dd:d3:f5:a8:93:68:30:
+        ac:ba:4e:ee:6b:af:f8:13:3d:11:1a:fa:90:93:d0:68:ce:77:
+        5f:85:8b:a4:95:2a:4c:25:7b:53:9c:44:43:b1:d9:fe:0c:83:
+        b8:19:2a:88:cc:d8:d1:d9:b3:04:eb:45:9b:30:5e:cb:61:e0:
+        e1:88:23:9c:b0:34:79:62:82:0d:f8:10:ed:96:bb:a0:fd:0d:
+        02:cb:c5:d3:47:1f:35:a7:e3:39:31:56:d5:b3:eb:2f:93:8f:
+        18:b4:b7:3c:00:03:a7:b4:1c:17:72:91:7e:b6:f6:36:17:3d:
+        f6:54:3b:87:84:d1:9b:43:d1:88:42:64:20:7a:e3:cc:f7:05:
+        98:0e:1c:51:da:20:b7:9b:49:88:e8:c6:e1:de:0d:f5:56:4f:
+        79:41:d0:7f
+-----BEGIN CERTIFICATE-----
+MIIDNTCCAh2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwljYXNz
+YW5kcmEwHhcNMTQwOTE5MjExODMzWhcNMjQwOTE2MjExODMzWjAQMQ4wDAYDVQQD
+EwVnb2NxbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7p+p794mmF
+HQgPNWi8Y3uSUH9zUPxCQzUGs1yeJx4WBWnsiNWcT+/oE2l6tbN/Zm0UAC7Wr1v/
+LJCRphEHcl6wN8Bt/3t2K/7eTNKNzkM7GsQd3rbYJggliVmhS5SjV54ZRihulxF8
+5rdBlo9C3WbahtJT3dj1IM0kiw+r38QQsmQgHeAP9C32ypS+g6w+qEp3tgiXOn57
+4D6raM/u9qGOv+y+BtGtbO1PNdEElwgzsWVbYTKNS/AwNUuLawbyGnKMab3zssSk
+pHBF42eiep8uyygtn2gD8cfZT4PJPYw0BAo7E4eS4ffjeX6rwCWx5TgJRD4x3xLU
+3HsONb/uJV8CAwEAAaOBlTCBkjAJBgNVHRMEAjAAMB0GA1UdDgQWBBSf8bLEgjTQ
+L//pfxnxO1FXv+iVuzBEBgNVHSMEPTA7gBSMfOfSdgWJcRojW9RZtFHiXApc6KEY
+pBYwFDESMBAGA1UEAxMJY2Fzc2FuZHJhggkAgpsBeB6bCyMwEwYDVR0lBAwwCgYI
+KwYBBQUHAwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBAQASqhumWCdS
+MslGGTLTaa6VrSNVrRJl2ixMcvMpvStalzu3aItogHdV5jKB8fUgVLoOK4aQ2ETP
+8p/sTTlnTjZsm0lKgObB7aRBORkW0ojfFwxGWrmIU/VnGfAfmlFAG0ASvFfb3t3T
+9aiTaDCsuk7ua6/4Ez0RGvqQk9BozndfhYuklSpMJXtTnERDsdn+DIO4GSqIzNjR
+2bME60WbMF7LYeDhiCOcsDR5YoIN+BDtlrug/Q0Cy8XTRx81p+M5MVbVs+svk48Y
+tLc8AAOntBwXcpF+tvY2Fz32VDuHhNGbQ9GIQmQgeuPM9wWYDhxR2iC3m0mI6Mbh
+3g31Vk95QdB/
+-----END CERTIFICATE-----

+ 27 - 0
testdata/pki/gocql.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArun6nv3iaYUdCA81aLxje5JQf3NQ/EJDNQazXJ4nHhYFaeyI
+1ZxP7+gTaXq1s39mbRQALtavW/8skJGmEQdyXrA3wG3/e3Yr/t5M0o3OQzsaxB3e
+ttgmCCWJWaFLlKNXnhlGKG6XEXzmt0GWj0LdZtqG0lPd2PUgzSSLD6vfxBCyZCAd
+4A/0LfbKlL6DrD6oSne2CJc6fnvgPqtoz+72oY6/7L4G0a1s7U810QSXCDOxZVth
+Mo1L8DA1S4trBvIacoxpvfOyxKSkcEXjZ6J6ny7LKC2faAPxx9lPg8k9jDQECjsT
+h5Lh9+N5fqvAJbHlOAlEPjHfEtTcew41v+4lXwIDAQABAoIBAQCCP9XSwzfwX6Fo
+uPqKjY5/HEs5PQPXdPha6ixyEYsLilZptCuI9adI/MZHy4q2qW36V+Ry/IcEuJXU
+6cCB+cue2xYJA2A17Z+BYMRQHiy0P7UEyUFpYrefZWRMDCIeAyxhnGxz+zYfXaTo
+Xbzh3WbFCoFO6gjPYGoWmNm8x74PXyunNaMa/gWFECX5MMBXoOk5xSFGbHzI2Cds
+iT7sdCQJVbBs7yidYwNqPWQuOwrskFinPIFSc7bZ0Sx9wO3XTIrQFCE94v/AN6yR
+9Q37ida54g5tgtoeg/5EGsUM++i4wqJVoT3tWUHv1jBozO4Lm65uWR/1HcrusVnr
+x0TM9SaBAoGBAOMeaZdUrCJXnIiSoqCGDvZmylTAeOo6n2RAiviOYxVB4GP/SSjh
+8VeddFhYT1GCmZ+YjIXnRWK+dSqVukzCuf5xW5mWY7PDNGZe2P6O78lXnY4cb8Nc
+Uo9/S2aPnNmNHL2TYVBYUiZj+t2azIQEFvRth4Vu/AHRUG41/USxpwm/AoGBAMUo
+GX0xgSFAVpHnTLdzWrHNRrzHgYN8ywPKFgNOASvdgW0BFoqXEvVGc1Ak6uW82m1/
+L9ChOzWjCY7CoT+LPmdUVyGT9/UAPtWeLfo8Owl4tG91jQjePmJFvLoXErryCFRt
+SOOvCsTTTq2gN3PREHxY3dj2kJqaCBLCEzx3cYxhAoGBAIUxdrc6/t/9BV3KsPj2
+5Zt3WL0vSzoCOyut9lIiHtV+lrvOIPeK2eCKBIsy7wFcV/+SlQaKRNTN4SSiPml5
+4V3o2NFPsxTfK8HFafiPluw7J7kJ0Dl/0SM6gduZ6WBkMzCyV+WohjTheWOwvrPF
+OjkKaunD1qKyQDsCCo/Yp589AoGAdKgnfNZf68bf8nEECcBtt6sY4fbCgYTDszhO
+EiKDuurT/CWaquJ9SzgmXxOZEdrO+9838aCVIkWYECrFso23nPhgnfOp0gQVKdzw
+o5Ij9JTBXvoVO1wVWZyd8RZZ9Nflad9IM8CNBK1rbnzQkuzvbkQ+8HPkWDYv9Ll1
+HGAohcECgYBQeirIumumj1B17WD/KmNe0U0qCHHp+oSW4W2r7pjlEVZzeQmggX4O
+anbEngyQaZKeUiUOj9snBDmzLv7S+j5p7Us4d1fbp70sCKuK6tcAnROU8gK8IGiI
+I01ypD8Z1Mb556qek56eRWlr71sy6wI1lbQa856cUBvePajUOKsKsw==
+-----END RSA PRIVATE KEY-----