From a11af9f26ec4f93008abb14342a035abd15b2f2f Mon Sep 17 00:00:00 2001 From: e-max Date: Fri, 2 Feb 2018 12:33:10 +0100 Subject: [PATCH] Add tls (#85) * add TLS encryption * accept TLS certificates directly, not though files * send ReplicaID = -1 * fix after review * add test for TLS * fix testserver * fix data race --- broker.go | 29 +++-- connection.go | 40 +++++++ connection_test.go | 172 ++++++++++++++++++++++++++++++ kafkatest/server.go | 5 +- proto/messages.go | 7 +- testkeys/ca.cnf | 85 +++++++++++++++ testkeys/ca.crt | 21 ++++ testkeys/generate_certificates.sh | 16 +++ testkeys/oats.cnf | 63 +++++++++++ testkeys/oats.crt | 77 +++++++++++++ testkeys/oats.extensions.cnf | 8 ++ testkeys/oats.key | 28 +++++ 12 files changed, 540 insertions(+), 11 deletions(-) create mode 100644 testkeys/ca.cnf create mode 100644 testkeys/ca.crt create mode 100755 testkeys/generate_certificates.sh create mode 100644 testkeys/oats.cnf create mode 100644 testkeys/oats.crt create mode 100644 testkeys/oats.extensions.cnf create mode 100644 testkeys/oats.key diff --git a/broker.go b/broker.go index 63d75db..9b46062 100644 --- a/broker.go +++ b/broker.go @@ -168,6 +168,16 @@ type BrokerConf struct { // logging frameworks. Used to notify and as replacement for stdlib `log` // package. Logger Logger + + //Settings for TLS encryption. + //You need to set all these parameters to enable TLS + + //TLS CA pem + TLSCa []byte + //TLS certificate + TLSCert []byte + //TLS key + TLSKey []byte } // NewBrokerConf returns the default broker configuration. @@ -311,7 +321,7 @@ func (b *Broker) fetchMetadata(topics ...string) (*proto.MetadataResp, error) { if _, ok := checkednodes[nodeID]; ok { continue } - conn, err := newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err := b.getConnection(addr) if err != nil { b.conf.Logger.Debug("cannot connect", "address", addr, @@ -336,7 +346,7 @@ func (b *Broker) fetchMetadata(topics ...string) (*proto.MetadataResp, error) { } for _, addr := range b.getInitialAddresses() { - conn, err := newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err := b.getConnection(addr) if err != nil { b.conf.Logger.Debug("cannot connect to seed node", "address", addr, @@ -475,7 +485,7 @@ func (b *Broker) muLeaderConnection(topic string, partition int32) (conn *connec delete(b.metadata.endpoints, tp) continue } - conn, err = newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err = b.getConnection(addr) if err != nil { b.conf.Logger.Info("cannot get leader connection: cannot connect to node", "address", addr, @@ -490,6 +500,13 @@ func (b *Broker) muLeaderConnection(topic string, partition int32) (conn *connec return nil, err } +func (b *Broker) getConnection(addr string) (*connection, error) { + if b.conf.TLSCa != nil && b.conf.TLSKey != nil && b.conf.TLSCert != nil { + return newTLSConnection(addr, b.conf.TLSCa, b.conf.TLSCert, b.conf.TLSKey, b.conf.DialTimeout, b.conf.ReadTimeout) + } + return newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) +} + // coordinatorConnection returns connection to offset coordinator for given group. // // Failed connection retry is controlled by broker configuration. @@ -526,7 +543,7 @@ func (b *Broker) muCoordinatorConnection(consumerGroup string) (conn *connection } addr := fmt.Sprintf("%s:%d", resp.CoordinatorHost, resp.CoordinatorPort) - conn, err := newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err := b.getConnection(addr) if err != nil { b.conf.Logger.Debug("cannot connect to node", "coordinatorID", resp.CoordinatorID, @@ -552,7 +569,7 @@ func (b *Broker) muCoordinatorConnection(consumerGroup string) (conn *connection // connection to node is cached so it was already checked continue } - conn, err := newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err := b.getConnection(addr) if err != nil { b.conf.Logger.Debug("cannot connect to node", "nodeID", nodeID, @@ -583,7 +600,7 @@ func (b *Broker) muCoordinatorConnection(consumerGroup string) (conn *connection } addr := fmt.Sprintf("%s:%d", resp.CoordinatorHost, resp.CoordinatorPort) - conn, err = newTCPConnection(addr, b.conf.DialTimeout, b.conf.ReadTimeout) + conn, err = b.getConnection(addr) if err != nil { b.conf.Logger.Debug("cannot connect to node", "coordinatorID", resp.CoordinatorID, diff --git a/connection.go b/connection.go index 2ec1c0c..b377340 100644 --- a/connection.go +++ b/connection.go @@ -3,6 +3,8 @@ package kafka import ( "bufio" "bytes" + "crypto/tls" + "crypto/x509" "errors" "fmt" "math" @@ -29,6 +31,44 @@ type connection struct { readTimeout time.Duration } +func newTLSConnection(address string, ca, cert, key []byte, timeout, readTimeout time.Duration) (*connection, error) { + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM(ca) + if !ok { + return nil, fmt.Errorf("Cannot parse root certificate") + } + + certificate, err := tls.X509KeyPair(cert, key) + if err != nil { + return nil, fmt.Errorf("Failed to parse key/cert for TLS: %s", err) + } + + conf := &tls.Config{ + Certificates: []tls.Certificate{certificate}, + RootCAs: roots, + } + + dialer := net.Dialer{ + Timeout: timeout, + KeepAlive: 30 * time.Second, + } + conn, err := tls.DialWithDialer(&dialer, "tcp", address, conf) + if err != nil { + return nil, err + } + c := &connection{ + stop: make(chan struct{}), + nextID: make(chan int32), + rw: conn, + respc: make(map[int32]chan []byte), + logger: &nullLogger{}, + readTimeout: readTimeout, + } + go c.nextIDLoop() + go c.readRespLoop() + return c, nil +} + // newConnection returns new, initialized connection or error func newTCPConnection(address string, timeout, readTimeout time.Duration) (*connection, error) { dialer := net.Dialer{ diff --git a/connection_test.go b/connection_test.go index ea7845b..554a4a6 100644 --- a/connection_test.go +++ b/connection_test.go @@ -1,6 +1,12 @@ package kafka import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "io/ioutil" + "log" "net" "reflect" "strings" @@ -10,10 +16,120 @@ import ( "github.com/optiopay/kafka/proto" ) +const TLSCaFile = "./testkeys/ca.crt" +const TLSCertFile = "./testkeys/oats.crt" +const TLSKeyFile = "./testkeys/oats.key" + type serializableMessage interface { Bytes() ([]byte, error) } +type TLSConf struct { + ca []byte + cert []byte + key []byte +} + +func getTLSConf() (*TLSConf, error) { + ca, err := ioutil.ReadFile(TLSCaFile) + if err != nil { + return nil, fmt.Errorf("Cannot read %s", TLSCaFile) + } + cert, err := ioutil.ReadFile(TLSCertFile) + if err != nil { + return nil, fmt.Errorf("Cannot read %s", TLSCertFile) + } + + key, err := ioutil.ReadFile(TLSKeyFile) + if err != nil { + return nil, fmt.Errorf("Cannot read %s", TLSKeyFile) + } + + return &TLSConf{ca: ca, cert: cert, key: key}, nil + +} + +//just read request before start to response +func readRequest(r io.Reader) error { + dec := proto.NewDecoder(r) + size := dec.DecodeInt32() + var read int32 = 0 + buf := make([]byte, size) + + for read < size { + n, err := r.Read(buf) + if err != nil { + return err + } + read += int32(n) + } + return nil +} + +func testTLSServer(messages ...serializableMessage) (net.Listener, error) { + tlsConf, err := getTLSConf() + if err != nil { + return nil, err + } + + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM(tlsConf.ca) + if !ok { + return nil, fmt.Errorf("Cannot parse root certificate") + } + + certificate, err := tls.X509KeyPair(tlsConf.cert, tlsConf.key) + if err != nil { + return nil, fmt.Errorf("Failed to parse key/cert for TLS: %s", err) + } + + conf := &tls.Config{ + Certificates: []tls.Certificate{certificate}, + RootCAs: roots, + } + + _ = conf + + ln, err := tls.Listen("tcp4", "localhost:22222", conf) + if err != nil { + return nil, err + } + + responses := make([][]byte, len(messages)) + for i, m := range messages { + b, err := m.Bytes() + if err != nil { + _ = ln.Close() + return nil, err + } + responses[i] = b + } + + go func() { + for { + cli, err := ln.Accept() + + if err != nil { + return + } + + go func(conn net.Conn) { + err := readRequest(conn) + if err != nil { + log.Panic(err) + } + + time.Sleep(time.Millisecond * 50) + for _, resp := range responses { + _, _ = cli.Write(resp) + } + err = cli.Close() + }(cli) + } + }() + return ln, nil +} + func testServer(messages ...serializableMessage) (net.Listener, error) { ln, err := net.Listen("tcp4", "") if err != nil { @@ -620,3 +736,59 @@ func TestNoServerResponse(t *testing.T) { t.Fatalf("could not close test server: %s", err) } } + +func TestTLSConnection(t *testing.T) { + resp1 := &proto.MetadataResp{ + CorrelationID: 1, + Brokers: []proto.MetadataRespBroker{ + { + NodeID: 666, + Host: "example.com", + Port: 999, + }, + }, + Topics: []proto.MetadataRespTopic{ + { + Name: "foo", + Partitions: []proto.MetadataRespPartition{ + { + ID: 7, + Leader: 7, + Replicas: []int32{7}, + Isrs: []int32{7}, + }, + }, + }, + }, + } + ln, err := testTLSServer(resp1) + if err != nil { + t.Fatalf("test server error: %s", err) + } + tlsConf, err := getTLSConf() + if err != nil { + t.Fatalf("cannot get tls parametes: %s", err) + } + _ = tlsConf + conn, err := newTLSConnection(ln.Addr().String(), tlsConf.ca, tlsConf.cert, tlsConf.key, time.Second, time.Second) + + if err != nil { + t.Fatalf("could not conect to test server: %s", err) + } + resp, err := conn.Metadata(&proto.MetadataReq{ + ClientID: "tester", + Topics: []string{"first", "second"}, + }) + if err != nil { + t.Fatalf("could not fetch response: %s", err) + } + if !reflect.DeepEqual(resp, resp1) { + t.Fatalf("expected different response %#v", resp) + } + if err := conn.Close(); err != nil { + t.Fatalf("could not close kafka connection: %s", err) + } + if err := ln.Close(); err != nil { + t.Fatalf("could not close test server: %s", err) + } +} diff --git a/kafkatest/server.go b/kafkatest/server.go index 6bcd0af..8f22acf 100644 --- a/kafkatest/server.go +++ b/kafkatest/server.go @@ -54,7 +54,7 @@ func NewServer(middlewares ...Middleware) *Server { topics: make(map[string]map[int32][]*proto.Message), offsets: make(map[string]map[int32]map[string]*topicOffset), middlewares: middlewares, - events: make(chan struct{}), + events: make(chan struct{}, 1000), } return s } @@ -370,8 +370,7 @@ func (s *Server) handleProduceRequest(nodeID int32, conn net.Conn, req *proto.Pr respParts[pi].Offset = int64(len(t[part.ID])) - 1 } } - close(s.events) - s.events = make(chan struct{}) + s.events <- struct{}{} return resp } diff --git a/proto/messages.go b/proto/messages.go index 5471a54..8c0141a 100644 --- a/proto/messages.go +++ b/proto/messages.go @@ -773,7 +773,9 @@ func (r *FetchReq) Bytes() ([]byte, error) { enc.Encode(r.CorrelationID) enc.Encode(r.ClientID) - enc.Encode(r.ReplicaID) + //enc.Encode(r.ReplicaID) + enc.Encode(int32(-1)) + enc.Encode(r.MaxWaitTime) enc.Encode(r.MinBytes) @@ -1808,7 +1810,8 @@ func (r *OffsetReq) Bytes() ([]byte, error) { enc.Encode(r.CorrelationID) enc.Encode(r.ClientID) - enc.Encode(r.ReplicaID) + //enc.Encode(r.ReplicaID) + enc.Encode(int32(-1)) if r.Version >= KafkaV2 { enc.Encode(r.IsolationLevel) diff --git a/testkeys/ca.cnf b/testkeys/ca.cnf new file mode 100644 index 0000000..3e12a40 --- /dev/null +++ b/testkeys/ca.cnf @@ -0,0 +1,85 @@ +# we use 'ca' as the default section because we're usign the ca command +# we use 'ca' as the default section because we're usign the ca command +[ ca ] +default_ca = my_ca + +[ my_ca ] +# a text file containing the next serial number to use in hex. Mandatory. +# This file must be present and contain a valid serial number. +serial = ./serial + +# the text database file to use. Mandatory. This file must be present though +# initially it will be empty. +database = ./index.txt + +# specifies the directory where new certificates will be placed. Mandatory. +new_certs_dir = ./newcerts + +# the file containing the CA certificate. Mandatory +certificate = ./ca.crt + +# the file contaning the CA private key. Mandatory +private_key = ./ca.key + +# the message digest algorithm. Remember to not use MD5 +default_md = sha1 + +# for how many days will the signed certificate be valid +default_days = 365 + +# a section with a set of variables corresponding to DN fields +policy = my_policy + +[ my_policy ] +# if the value is "match" then the field value must match the same field in the +# CA certificate. If the value is "supplied" then it must be present. +# Optional means it may be present. Any fields not mentioned are silently +# deleted. +countryName = match +stateOrProvinceName = supplied +organizationName = supplied +commonName = supplied +organizationalUnitName = optional +commonName = supplied + +[ ca ] +default_ca = my_ca + +[ my_ca ] +# a text file containing the next serial number to use in hex. Mandatory. +# This file must be present and contain a valid serial number. +serial = ./serial + +# the text database file to use. Mandatory. This file must be present though +# initially it will be empty. +database = ./index.txt + +# specifies the directory where new certificates will be placed. Mandatory. +new_certs_dir = ./newcerts + +# the file containing the CA certificate. Mandatory +certificate = ./ca.crt + +# the file contaning the CA private key. Mandatory +private_key = ./ca.key + +# the message digest algorithm. Remember to not use MD5 +default_md = sha1 + +# for how many days will the signed certificate be valid +default_days = 365 + +# a section with a set of variables corresponding to DN fields +policy = my_policy + +[ my_policy ] +# if the value is "match" then the field value must match the same field in the +# CA certificate. If the value is "supplied" then it must be present. +# Optional means it may be present. Any fields not mentioned are silently +# deleted. +countryName = match +stateOrProvinceName = supplied +organizationName = supplied +commonName = supplied +organizationalUnitName = optional +commonName = supplied diff --git a/testkeys/ca.crt b/testkeys/ca.crt new file mode 100644 index 0000000..b1b2a00 --- /dev/null +++ b/testkeys/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIJAI7GWD/acYEYMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV +BAYTAkRFMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxFTATBgNVBAMMDG9wdGlvcGF5LmNvbTAeFw0xODAxMjYx +MjA4MDJaFw0xODAyMjUxMjA4MDJaMFwxCzAJBgNVBAYTAkRFMRMwEQYDVQQIDApT +b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFTAT +BgNVBAMMDG9wdGlvcGF5LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMDX2bikJAXslERXUJ4aGbCUuNZgwmOZdYPZ/a4u0Mp4rxXxh5mv4kr/T0ec +43ONEgDhlPyagV5buxN4jBaxa31BJTIOCkc0kDVVP4Rl7wPrZ9dGD7P66DRmv+/T +7nUj/GZ8d7upA7It9JgCCheMiS3u9xutnnIuYatnCwg5hTD9WOqBtyPRNTmvEpCU +2ITIxfti/jd+qUm+uK40K5OSK9bZS9GCthaWCGTTPw6PgAPImJTKtovAOQzlVfyr +0B9AKEG5IZ/SwNo/gEVVdyqF7f4zQ2CXsZz/AA+xiIhI9PTwQlXmNcLn3c7hq+wU +dqizuSSd5QKBIVRFui8QytNVE+cCAwEAAaNQME4wHQYDVR0OBBYEFPRWqEYd4dBZ +nqH5sG3X+ByUjh3GMB8GA1UdIwQYMBaAFPRWqEYd4dBZnqH5sG3X+ByUjh3GMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADtauAuSlsnpz0d1R1fNU3eT +It9mxJdMu0UhGV+glTwJWqN6cPpzd73sSeME0IxSMgs2JQGo20wvEFlvXcH8hyo3 +g0RbuKJGrdXf440qrP8igrWGkgjgc+n4zPNcuErVmN3/SOm44apxzOTyhT7LH/vX +CZFQt5TpyfkHmEFttsPJSAkDVQBJZtsT6qHDUDGfTy06R3gXuv2wKH9WmG6+VcUq +py6+3E4wn4f/T9+sOIcZ6ZuememwtrVaXNyRvNgor6PEWGdY7jtYxHdR8SQdIkDK +7ibcYht2FtNCYcD/9wIBmb1h+dT1WuBDxG0V451LQyjqxOXX2saxt72DkaW2i5o= +-----END CERTIFICATE----- diff --git a/testkeys/generate_certificates.sh b/testkeys/generate_certificates.sh new file mode 100755 index 0000000..d68cf79 --- /dev/null +++ b/testkeys/generate_certificates.sh @@ -0,0 +1,16 @@ +find . -type f ! -name '*.sh' ! -name '*.cnf' -delete +rm -rf ./newcerts/ + +openssl genrsa -out example.org.key 2048 +openssl req -new -key example.org.key -out example.org.csr +openssl req -new -out oats.csr -config oats.cnf +openssl genrsa -out ca.key 2048 +openssl req -new -x509 -key ca.key -out ca.crt +mkdir newcerts +touch index.txt +echo '01' > serial +openssl ca -config ca.cnf -out example.org.crt -infiles example.org.csr +openssl ca -config ca.cnf -out oats.crt -extfile oats.extensions.cnf -in oats.csr + + + diff --git a/testkeys/oats.cnf b/testkeys/oats.cnf new file mode 100644 index 0000000..6462a05 --- /dev/null +++ b/testkeys/oats.cnf @@ -0,0 +1,63 @@ +# The main section is named req because the command we are using is req +# (openssl req ...) +[ req ] +# This specifies the default key size in bits. If not specified then 512 is +# used. It is used if the -new option is used. It can be overridden by using +# the -newkey option. +default_bits = 2048 + +# This is the default filename to write a private key to. If not specified the +# key is written to standard output. This can be overridden by the -keyout +# option. +default_keyfile = oats.key + +# If this is set to no then if a private key is generated it is not encrypted. +# This is equivalent to the -nodes command line option. For compatibility +# encrypt_rsa_key is an equivalent option. +encrypt_key = no + +# This option specifies the digest algorithm to use. Possible values include +# md5 sha1 mdc2. If not present then MD5 is used. This option can be overridden +# on the command line. +default_md = sha1 + +# if set to the value no this disables prompting of certificate fields and just +# takes values from the config file directly. It also changes the expected +# format of the distinguished_name and attributes sections. +prompt = no + +# if set to the value yes then field values to be interpreted as UTF8 strings, +# by default they are interpreted as ASCII. This means that the field values, +# whether prompted from a terminal or obtained from a configuration file, must +# be valid UTF8 strings. +utf8 = yes + +# This specifies the section containing the distinguished name fields to +# prompt for when generating a certificate or certificate request. +distinguished_name = my_req_distinguished_name + + +# this specifies the configuration file section containing a list of extensions +# to add to the certificate request. It can be overridden by the -reqexts +# command line switch. See the x509v3_config(5) manual page for details of the +# extension section format. +req_extensions = my_extensions + +[ my_req_distinguished_name ] +C = DE +ST = Berlin +L = Berlin +O = Optiopay +CN = optiopay.com + +[ my_extensions ] +basicConstraints=CA:FALSE +subjectAltName=@my_subject_alt_names +subjectKeyIdentifier = hash + +[ my_subject_alt_names ] +DNS.1 = localhost +DNS.2 = 0.0.0.0 +IP.1 = 0.0.0.0 +IP.2 = 127.0.0.1 + diff --git a/testkeys/oats.crt b/testkeys/oats.crt new file mode 100644 index 0000000..3b1ba7f --- /dev/null +++ b/testkeys/oats.crt @@ -0,0 +1,77 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=DE, ST=Some-State, O=Internet Widgits Pty Ltd, CN=optiopay.com + Validity + Not Before: Jan 26 12:08:07 2018 GMT + Not After : Jan 26 12:08:07 2019 GMT + Subject: C=DE, ST=Berlin, O=Optiopay, CN=optiopay.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:a9:82:7e:b7:18:31:ef:ee:0a:63:c1:dd:19:e5: + 3a:ed:bc:ab:37:96:49:0f:26:ea:cd:37:c1:f6:3f: + a4:41:7b:b3:4a:2e:f1:a1:08:5c:44:8f:fd:f0:4d: + 27:f5:5c:3e:d4:10:ab:6d:d8:85:2c:b5:a9:69:7b: + 07:20:cf:0b:7d:fc:aa:7b:85:3d:78:38:95:61:d3: + 98:25:62:d5:9c:1d:2e:79:a4:93:f3:bd:db:e5:0f: + 99:b8:30:ef:a0:14:74:88:7b:62:92:a0:87:32:09: + b8:cd:1c:2d:8b:41:5e:0a:d4:4f:c0:a0:6b:c2:ac: + f1:65:98:7f:3b:c8:be:d5:5c:8d:04:b6:64:30:36: + c7:59:61:ef:04:d2:61:83:94:79:e8:85:aa:3d:6e: + b7:94:97:91:4f:39:f7:a8:5e:2e:fd:cf:a8:09:5d: + a1:56:40:76:33:80:df:8b:2a:90:bc:f2:99:75:fb: + 23:43:94:f3:74:fd:69:bb:a0:73:77:9c:b2:41:72: + 98:19:05:1f:f9:4c:6f:a2:70:31:1e:e0:30:f2:11: + 64:8e:77:84:6a:0d:af:6f:b2:63:72:e9:0b:fc:67: + e3:c0:f9:ce:99:f3:55:19:36:ec:6c:11:12:b9:a0: + 33:a3:f9:06:49:45:f3:87:b2:d5:37:94:e9:09:b6: + dc:11 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Alternative Name: + DNS:localhost, DNS:0.0.0.0, IP Address:127.0.0.1 + X509v3 Subject Key Identifier: + 58:17:85:39:B1:0B:EA:0B:55:D0:46:92:51:15:06:FD:9B:5F:42:A8 + Signature Algorithm: sha1WithRSAEncryption + b9:ec:67:01:8e:39:0a:05:cf:2f:ab:cb:82:45:8e:57:86:34: + 85:1b:34:20:49:8c:88:0b:23:28:79:d8:e5:fb:51:5b:f8:bd: + f3:bc:22:74:72:99:90:44:fa:99:05:0f:82:10:95:f1:a5:ad: + b0:a2:81:ef:cd:bf:1d:0e:69:f7:c3:e6:90:f6:22:62:f3:bc: + a8:62:dc:d5:74:e3:78:7a:50:6d:e4:c5:31:fd:1b:37:a2:81: + c6:f4:21:25:02:a8:fd:51:5b:65:d2:7f:30:db:e6:85:3d:2b: + dc:2f:a4:cd:37:4d:ac:a4:c7:ca:03:0c:76:ef:52:7b:4d:68: + 5f:b8:54:38:d5:76:51:90:d1:21:d7:15:c1:0a:17:72:3d:a2: + 14:07:5f:57:46:02:b4:e6:d3:4f:1b:7f:a8:80:81:a3:14:fd: + b6:6e:d0:ac:73:27:87:37:17:fe:af:04:8d:19:41:de:aa:33: + a8:57:3a:66:a5:d0:d9:31:c9:57:2d:42:e7:52:5c:ec:e0:69: + c7:f3:a4:ce:35:a0:7a:f8:b1:a1:30:84:c2:78:1e:51:0d:9a: + e3:02:db:c9:75:3f:c3:4b:df:e7:ce:5c:cd:d9:ba:61:1f:15: + c7:a6:be:4e:c6:42:24:23:6e:0e:d7:c2:c7:9b:ff:45:67:de: + a0:12:fd:c2 +-----BEGIN CERTIFICATE----- +MIIDcDCCAligAwIBAgIBAjANBgkqhkiG9w0BAQUFADBcMQswCQYDVQQGEwJERTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMRUwEwYDVQQDDAxvcHRpb3BheS5jb20wHhcNMTgwMTI2MTIwODA3WhcN +MTkwMTI2MTIwODA3WjBIMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMREw +DwYDVQQKDAhPcHRpb3BheTEVMBMGA1UEAwwMb3B0aW9wYXkuY29tMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqYJ+txgx7+4KY8HdGeU67byrN5ZJDybq +zTfB9j+kQXuzSi7xoQhcRI/98E0n9Vw+1BCrbdiFLLWpaXsHIM8Lffyqe4U9eDiV +YdOYJWLVnB0ueaST873b5Q+ZuDDvoBR0iHtikqCHMgm4zRwti0FeCtRPwKBrwqzx +ZZh/O8i+1VyNBLZkMDbHWWHvBNJhg5R56IWqPW63lJeRTzn3qF4u/c+oCV2hVkB2 +M4DfiyqQvPKZdfsjQ5TzdP1pu6Bzd5yyQXKYGQUf+UxvonAxHuAw8hFkjneEag2v +b7JjcukL/GfjwPnOmfNVGTbsbBESuaAzo/kGSUXzh7LVN5TpCbbcEQIDAQABo1Ew +TzAJBgNVHRMEAjAAMCMGA1UdEQQcMBqCCWxvY2FsaG9zdIIHMC4wLjAuMIcEfwAA +ATAdBgNVHQ4EFgQUWBeFObEL6gtV0EaSURUG/ZtfQqgwDQYJKoZIhvcNAQEFBQAD +ggEBALnsZwGOOQoFzy+ry4JFjleGNIUbNCBJjIgLIyh52OX7UVv4vfO8InRymZBE ++pkFD4IQlfGlrbCige/Nvx0OaffD5pD2ImLzvKhi3NV043h6UG3kxTH9Gzeigcb0 +ISUCqP1RW2XSfzDb5oU9K9wvpM03Taykx8oDDHbvUntNaF+4VDjVdlGQ0SHXFcEK +F3I9ohQHX1dGArTm008bf6iAgaMU/bZu0KxzJ4c3F/6vBI0ZQd6qM6hXOmal0Nkx +yVctQudSXOzgacfzpM41oHr4saEwhMJ4HlENmuMC28l1P8NL3+fOXM3ZumEfFcem +vk7GQiQjbg7Xwseb/0Vn3qAS/cI= +-----END CERTIFICATE----- diff --git a/testkeys/oats.extensions.cnf b/testkeys/oats.extensions.cnf new file mode 100644 index 0000000..fb8c971 --- /dev/null +++ b/testkeys/oats.extensions.cnf @@ -0,0 +1,8 @@ +basicConstraints=CA:FALSE +subjectAltName=@my_subject_alt_names +subjectKeyIdentifier = hash + +[ my_subject_alt_names ] +DNS.1 = localhost +DNS.2 = 0.0.0.0 +IP = 127.0.0.1 diff --git a/testkeys/oats.key b/testkeys/oats.key new file mode 100644 index 0000000..ec87489 --- /dev/null +++ b/testkeys/oats.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpgn63GDHv7gpj +wd0Z5TrtvKs3lkkPJurNN8H2P6RBe7NKLvGhCFxEj/3wTSf1XD7UEKtt2IUstalp +ewcgzwt9/Kp7hT14OJVh05glYtWcHS55pJPzvdvlD5m4MO+gFHSIe2KSoIcyCbjN +HC2LQV4K1E/AoGvCrPFlmH87yL7VXI0EtmQwNsdZYe8E0mGDlHnohao9breUl5FP +OfeoXi79z6gJXaFWQHYzgN+LKpC88pl1+yNDlPN0/Wm7oHN3nLJBcpgZBR/5TG+i +cDEe4DDyEWSOd4RqDa9vsmNy6Qv8Z+PA+c6Z81UZNuxsERK5oDOj+QZJRfOHstU3 +lOkJttwRAgMBAAECggEAcfsRgcR/L7p8uf+N0O/W5C1NZB9mGQ4uLLglcJPyuyWW +tXdDeujIQkIgmwkGqXHRvX9IBqDlS98Lf6X+cQ1HyPuxF0XMw54otJM3Z0xCHqtw +qNMFuYwc5LXMw5IETzvx8CQncDkJL5hh72nXJiBaQCXrSreUPl9UIuCcDXM6/vQY +crYcRKgQ1UGXhnki7scOcqRZiRDRq5aXO3nm7gYXndoh+zIlD0TwJ8IUU9yUD/qG +9skA90BsA3Ns/cYpVaoow9zGmScV6985FSYV0inn6Ac5E20P1Cg+nwt4anih+GmC +rFhYjP1en3LC+N4lqkgT+74N8++10IPlLi4FqWVzwQKBgQDYibjiPrkkyYJTQns/ +JBIwfQrwTBTi2m04NXaOvCzOFjpavQmtFXuBkLXHWXb7mnFqf0bErPcl39t5mkmv +2T15CdKHfZz287EnqHmb990JdLCtC4PXTXuqy+O7mm2m4cnJqm32XWz8lDKx+Yor +7L0V1L/zMNSW8zLANNetffMXfQKBgQDIZrxzT7DdEIt8kzBj7LUfME1d9pa4AQ82 +3F10ujRd1nKQWhVaRxQCtp/lkrW+eEenvbRwzfVdGAdgMcWzrd+1f9AmmeL+1HQ8 +3fe6Ks0vtFCPk0ZhYDBoSJE+bqHZa+E3E6ushSzW/ipMpNn1XgmVXSoxdFWzHNgR +7m9SCuYDJQKBgQCk/ctyGHjaHvC+oBloswNpDt4W2uHPOL+f5JzBhYBeR3GPUhrd +V1EUGD9p9jFC5RsmGnTBx0lKApgov/cFlnHQmDtiIcRIJscFgMmoneQ+IDu+nteG +ZfOk4FZVzAVg8wneL445+lXg3ssB8THm5ivtRn47vyk9G3HAIoN0HlPrwQKBgHac +P+ur/g1JjpVj9J8f6a+VAiWcHma30c5xrSq6TuY/V71SZBLzwMc+WfMh20npnNeO +M28Up6W65BquOsp3HdIp3QQLzstu6YFhCw9358vVWF0yGNTnfp8qXDTsfATYkgrs +LI7Gn9x7wINuisZsKxXpd2Cf7108tDIgwmh3pN21AoGBAJP37+jbETECJTMhLUK3 +zXZ4gengxwxt2oE8wEPhImOVMDiBPg5pCdmQL6tFu/dOHjC9YDr4aBTmo3MqiJoz +hOpYqS5kqJVqb3So8ndxY80Pjj2tCEol7tA6BlBs3DLEWQ3zIBjkGqGsE5bKzTc1 +IV+dLLjOVQDjJLKyJHjpyBbu +-----END PRIVATE KEY-----