Vendoring with Glide and delete function handler

This commit is contained in:
Alex Ellis
2017-04-26 19:13:42 +01:00
parent 1eaf13c6c8
commit 78af82021f
7137 changed files with 1688302 additions and 19 deletions

View File

@ -0,0 +1,21 @@
// +build go1.7
package tlsconfig
import (
"crypto/x509"
"runtime"
"github.com/Sirupsen/logrus"
)
// SystemCertPool returns a copy of the system cert pool,
// returns an error if failed to load or empty pool on windows.
func SystemCertPool() (*x509.CertPool, error) {
certpool, err := x509.SystemCertPool()
if err != nil && runtime.GOOS == "windows" {
logrus.Infof("Unable to use system certificate pool: %v", err)
return x509.NewCertPool(), nil
}
return certpool, err
}

View File

@ -0,0 +1,16 @@
// +build !go1.7
package tlsconfig
import (
"crypto/x509"
"github.com/Sirupsen/logrus"
)
// SystemCertPool returns an new empty cert pool,
// accessing system cert pool is supported in go 1.7
func SystemCertPool() (*x509.CertPool, error) {
logrus.Warn("Unable to use system certificate pool: requires building with go 1.7 or later")
return x509.NewCertPool(), nil
}

View File

@ -0,0 +1,246 @@
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
//
// As a reminder from https://golang.org/pkg/crypto/tls/#Config:
// A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified.
// A Config may be reused; the tls package will also not modify it.
package tlsconfig
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/Sirupsen/logrus"
"github.com/pkg/errors"
)
// Options represents the information needed to create client and server TLS configurations.
type Options struct {
CAFile string
// If either CertFile or KeyFile is empty, Client() will not load them
// preventing the client from authenticating to the server.
// However, Server() requires them and will error out if they are empty.
CertFile string
KeyFile string
// client-only option
InsecureSkipVerify bool
// server-only option
ClientAuth tls.ClientAuthType
// If ExclusiveRootPools is set, then if a CA file is provided, the root pool used for TLS
// creds will include exclusively the roots in that CA file. If no CA file is provided,
// the system pool will be used.
ExclusiveRootPools bool
MinVersion uint16
// If Passphrase is set, it will be used to decrypt a TLS private key
// if the key is encrypted
Passphrase string
}
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
var acceptedCBCCiphers = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
}
// DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
// options struct but wants to use a commonly accepted set of TLS cipher suites, with
// known weak algorithms removed.
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
// allTLSVersions lists all the TLS versions and is used by the code that validates
// a uint16 value as a TLS version.
var allTLSVersions = map[uint16]struct{}{
tls.VersionSSL30: {},
tls.VersionTLS10: {},
tls.VersionTLS11: {},
tls.VersionTLS12: {},
}
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
func ServerDefault() *tls.Config {
return &tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
MinVersion: tls.VersionTLS10,
PreferServerCipherSuites: true,
CipherSuites: DefaultServerAcceptedCiphers,
}
}
// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration.
func ClientDefault() *tls.Config {
return &tls.Config{
// Prefer TLS1.2 as the client minimum
MinVersion: tls.VersionTLS12,
CipherSuites: clientCipherSuites,
}
}
// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
// If we should verify the server, we need to load a trusted ca
var (
certPool *x509.CertPool
err error
)
if exclusivePool {
certPool = x509.NewCertPool()
} else {
certPool, err = SystemCertPool()
if err != nil {
return nil, fmt.Errorf("failed to read system certificates: %v", err)
}
}
pem, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err)
}
if !certPool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
}
logrus.Debugf("Trusting %d certs", len(certPool.Subjects()))
return certPool, nil
}
// isValidMinVersion checks that the input value is a valid tls minimum version
func isValidMinVersion(version uint16) bool {
_, ok := allTLSVersions[version]
return ok
}
// adjustMinVersion sets the MinVersion on `config`, the input configuration.
// It assumes the current MinVersion on the `config` is the lowest allowed.
func adjustMinVersion(options Options, config *tls.Config) error {
if options.MinVersion > 0 {
if !isValidMinVersion(options.MinVersion) {
return fmt.Errorf("Invalid minimum TLS version: %x", options.MinVersion)
}
if options.MinVersion < config.MinVersion {
return fmt.Errorf("Requested minimum TLS version is too low. Should be at-least: %x", config.MinVersion)
}
config.MinVersion = options.MinVersion
}
return nil
}
// IsErrEncryptedKey returns true if the 'err' is an error of incorrect
// password when tryin to decrypt a TLS private key
func IsErrEncryptedKey(err error) bool {
return errors.Cause(err) == x509.IncorrectPasswordError
}
// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
// If the private key is encrypted, 'passphrase' is used to decrypted the
// private key.
func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
// this section makes some small changes to code from notary/tuf/utils/x509.go
pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil {
return nil, fmt.Errorf("no valid private key found")
}
var err error
if x509.IsEncryptedPEMBlock(pemBlock) {
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase))
if err != nil {
return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
}
return keyBytes, nil
}
// getCert returns a Certificate from the CertFile and KeyFile in 'options',
// if the key is encrypted, the Passphrase in 'options' will be used to
// decrypt it.
func getCert(options Options) ([]tls.Certificate, error) {
if options.CertFile == "" && options.KeyFile == "" {
return nil, nil
}
errMessage := "Could not load X509 key pair"
cert, err := ioutil.ReadFile(options.CertFile)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
prKeyBytes, err := ioutil.ReadFile(options.KeyFile)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
tlsCert, err := tls.X509KeyPair(cert, prKeyBytes)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
return []tls.Certificate{tlsCert}, nil
}
// Client returns a TLS configuration meant to be used by a client.
func Client(options Options) (*tls.Config, error) {
tlsConfig := ClientDefault()
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
if !options.InsecureSkipVerify && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
if err != nil {
return nil, err
}
tlsConfig.RootCAs = CAs
}
tlsCerts, err := getCert(options)
if err != nil {
return nil, err
}
tlsConfig.Certificates = tlsCerts
if err := adjustMinVersion(options, tlsConfig); err != nil {
return nil, err
}
return tlsConfig, nil
}
// Server returns a TLS configuration meant to be used by a server.
func Server(options Options) (*tls.Config, error) {
tlsConfig := ServerDefault()
tlsConfig.ClientAuth = options.ClientAuth
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
}
return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err)
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = CAs
}
if err := adjustMinVersion(options, tlsConfig); err != nil {
return nil, err
}
return tlsConfig, nil
}

View File

@ -0,0 +1,17 @@
// +build go1.5
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
//
package tlsconfig
import (
"crypto/tls"
)
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
var clientCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}

View File

@ -0,0 +1,15 @@
// +build !go1.5
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
//
package tlsconfig
import (
"crypto/tls"
)
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
var clientCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}

View File

@ -0,0 +1,651 @@
package tlsconfig
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"reflect"
"testing"
)
// This is the currently active LetsEncrypt IdenTrust cross-signed CA cert. It expires Mar 17, 2021.
const (
systemRootTrustedCert = `
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
`
rsaPrivateKeyFile = "fixtures/key.pem"
certificateFile = "fixtures/cert.pem"
multiCertificateFile = "fixtures/multi.pem"
rsaEncryptedPrivateKeyFile = "fixtures/encrypted_key.pem"
certificateOfEncryptedKeyFile = "fixtures/cert_of_encrypted_key.pem"
)
// returns the name of a pre-generated, multiple-certificate CA file
// with both RSA and ECDSA certs.
func getMultiCert() string {
return multiCertificateFile
}
// returns the names of pre-generated key and certificate files.
func getCertAndKey() (string, string) {
return rsaPrivateKeyFile, certificateFile
}
// returns the names of pre-generated, encrypted private key and
// corresponding certificate file
func getCertAndEncryptedKey() (string, string) {
return rsaEncryptedPrivateKeyFile, certificateOfEncryptedKeyFile
}
// If the cert files and directory are provided but are invalid, an error is
// returned.
func TestConfigServerTLSFailsIfUnableToLoadCerts(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tempFile, err := ioutil.TempFile("", "cert-test")
if err != nil {
t.Fatal("Unable to create temporary empty file")
}
defer os.RemoveAll(tempFile.Name())
tempFile.Close()
for _, badFile := range []string{"not-a-file", tempFile.Name()} {
for i := 0; i < 3; i++ {
files := []string{cert, key, ca}
files[i] = badFile
result, err := Server(Options{
CertFile: files[0],
KeyFile: files[1],
CAFile: files[2],
ClientAuth: tls.VerifyClientCertIfGiven,
})
if err == nil || result != nil {
t.Fatal("Expected a non-real file to error and return a nil TLS config")
}
}
}
}
// If server cert and key are provided and client auth and client CA are not
// set, a tls config with only the server certs will be returned.
func TestConfigServerTLSServerCertsOnly(t *testing.T) {
key, cert := getCertAndKey()
keypair, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
t.Fatal("Unable to load the generated cert and key")
}
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
t.Fatal("Unexpected server certificates")
}
for i, cert := range tlsConfig.Certificates[0].Certificate {
if !bytes.Equal(cert, keypair.Certificate[i]) {
t.Fatal("Unexpected server certificates")
}
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, DefaultServerAcceptedCiphers) {
t.Fatal("Unexpected server cipher suites")
}
if !tlsConfig.PreferServerCipherSuites {
t.Fatal("Expected server to prefer cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS10 {
t.Fatal("Unexpected server TLS version")
}
}
// If client CA is provided, it will only be used if the client auth is >=
// VerifyClientCertIfGiven
func TestConfigServerTLSClientCANotSetIfClientAuthTooLow(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.RequestClientCert,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if tlsConfig.ClientAuth != tls.RequestClientCert {
t.Fatal("ClientAuth was not set to what was in the options")
}
if tlsConfig.ClientCAs != nil {
t.Fatalf("Client CAs should never have been set")
}
}
// If client CA is provided, it will only be used if the client auth is >=
// VerifyClientCertIfGiven
func TestConfigServerTLSClientCASet(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if tlsConfig.ClientAuth != tls.VerifyClientCertIfGiven {
t.Fatal("ClientAuth was not set to what was in the options")
}
basePool, err := SystemCertPool()
if err != nil {
basePool = x509.NewCertPool()
}
// because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots
if tlsConfig.ClientCAs == nil || len(tlsConfig.ClientCAs.Subjects()) != len(basePool.Subjects())+2 {
t.Fatalf("Client CAs were never set correctly")
}
}
// Exclusive root pools determines whether the CA pool will be a union of the system
// certificate pool and custom certs, or an exclusive or of the custom certs and system pool
func TestConfigServerExclusiveRootPools(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
caBytes, err := ioutil.ReadFile(ca)
if err != nil {
t.Fatal("Unable to read CA certs", err)
}
var testCerts []*x509.Certificate
for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} {
pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil {
t.Fatal("Malformed certificate")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Fatal("Unable to parse certificate")
}
testCerts = append(testCerts, cert)
}
// ExclusiveRootPools not set, so should be able to verify both system-signed certs
// and custom CA-signed certs
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs}); err != nil {
t.Fatalf("Unable to verify certificate %d: %v", i, err)
}
}
// ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable
// and custom CA-signed certs should be verifiable
tlsConfig, err = Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
ExclusiveRootPools: true,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs})
switch {
case i == 0 && err != nil:
t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err)
case i == 1 && err == nil:
t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err)
}
}
// No CA file provided, system cert should be verifiable only
tlsConfig, err = Server(Options{
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs})
switch {
case i == 1 && err != nil:
t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err)
case i == 0 && err == nil:
t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err)
}
}
}
// If a valid minimum version is specified in the options, the server's
// minimum version should be set accordingly
func TestConfigServerTLSMinVersionIsSetBasedOnOptions(t *testing.T) {
versions := []uint16{
tls.VersionTLS11,
tls.VersionTLS12,
}
key, cert := getCertAndKey()
for _, v := range versions {
tlsConfig, err := Server(Options{
MinVersion: v,
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if tlsConfig.MinVersion != v {
t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion)
}
}
}
// An error should be returned if the specified minimum version for the server
// is too low, i.e. less than VersionTLS10
func TestConfigServerTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) {
key, cert := getCertAndKey()
_, err := Server(Options{
MinVersion: tls.VersionSSL30,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned an error for minimum version below TLS10")
}
}
// An error should be returned if an invalid minimum version for the server is
// in the options struct
func TestConfigServerTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) {
key, cert := getCertAndKey()
_, err := Server(Options{
MinVersion: 1,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned error on invalid minimum version option")
}
}
// The root CA is never set if InsecureSkipBoolean is set to true, but the
// default client options are set
func TestConfigClientTLSNoVerify(t *testing.T) {
ca := getMultiCert()
tlsConfig, err := Client(Options{CAFile: ca, InsecureSkipVerify: true})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.RootCAs != nil {
t.Fatal("Should not have set Root CAs", err)
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
t.Fatal("Unexpected client cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected client TLS version")
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// The root CA is never set if InsecureSkipBoolean is set to false and root CA
// is not provided.
func TestConfigClientTLSNoRoot(t *testing.T) {
tlsConfig, err := Client(Options{})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.RootCAs != nil {
t.Fatal("Should not have set Root CAs", err)
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
t.Fatal("Unexpected client cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected client TLS version")
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// The RootCA is set if the file is provided and InsecureSkipVerify is false
func TestConfigClientTLSRootCAFileWithOneCert(t *testing.T) {
ca := getMultiCert()
tlsConfig, err := Client(Options{CAFile: ca})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
basePool, err := SystemCertPool()
if err != nil {
basePool = x509.NewCertPool()
}
// because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots
if tlsConfig.RootCAs == nil || len(tlsConfig.RootCAs.Subjects()) != len(basePool.Subjects())+2 {
t.Fatal("Root CAs not set properly", err)
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// An error is returned if a root CA is provided but the file doesn't exist.
func TestConfigClientTLSNonexistentRootCAFile(t *testing.T) {
tlsConfig, err := Client(Options{CAFile: "nonexistent"})
if err == nil || tlsConfig != nil {
t.Fatal("Should not have been able to configure client TLS", err)
}
}
// An error is returned if either the client cert or the key are provided
// but invalid or blank.
func TestConfigClientTLSClientCertOrKeyInvalid(t *testing.T) {
key, cert := getCertAndKey()
tempFile, err := ioutil.TempFile("", "cert-test")
if err != nil {
t.Fatal("Unable to create temporary empty file")
}
defer os.Remove(tempFile.Name())
tempFile.Close()
for i := 0; i < 2; i++ {
for _, invalid := range []string{"not-a-file", "", tempFile.Name()} {
files := []string{cert, key}
files[i] = invalid
tlsConfig, err := Client(Options{CertFile: files[0], KeyFile: files[1]})
if err == nil || tlsConfig != nil {
t.Fatal("Should not have been able to configure client TLS", err)
}
}
}
}
// The certificate is set if the client cert and client key are provided and
// valid.
func TestConfigClientTLSValidClientCertAndKey(t *testing.T) {
key, cert := getCertAndKey()
keypair, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
t.Fatal("Unable to load the generated cert and key")
}
tlsConfig, err := Client(Options{CertFile: cert, KeyFile: key})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected client certificates")
}
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
t.Fatal("Unexpected client certificates")
}
for i, cert := range tlsConfig.Certificates[0].Certificate {
if !bytes.Equal(cert, keypair.Certificate[i]) {
t.Fatal("Unexpected client certificates")
}
}
if tlsConfig.RootCAs != nil {
t.Fatal("Root CAs should not have been set", err)
}
}
// The certificate is set if the client cert and encrypted client key are
// provided and valid and passphrase can decrypt the key
func TestConfigClientTLSValidClientCertAndEncryptedKey(t *testing.T) {
key, cert := getCertAndEncryptedKey()
tlsConfig, err := Client(Options{
CertFile: cert,
KeyFile: key,
Passphrase: "FooBar123",
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected client certificates")
}
}
// The certificate is not set if the provided passphrase cannot decrypt
// the encrypted key.
func TestConfigClientTLSNotSetWithInvalidPassphrase(t *testing.T) {
key, cert := getCertAndEncryptedKey()
tlsConfig, err := Client(Options{
CertFile: cert,
KeyFile: key,
Passphrase: "InvalidPassphrase",
})
if !IsErrEncryptedKey(err) || tlsConfig != nil {
t.Fatal("Expected failure due to incorrect passphrase.")
}
}
// Exclusive root pools determines whether the CA pool will be a union of the system
// certificate pool and custom certs, or an exclusive or of the custom certs and system pool
func TestConfigClientExclusiveRootPools(t *testing.T) {
ca := getMultiCert()
caBytes, err := ioutil.ReadFile(ca)
if err != nil {
t.Fatal("Unable to read CA certs", err)
}
var testCerts []*x509.Certificate
for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} {
pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil {
t.Fatal("Malformed certificate")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Fatal("Unable to parse certificate")
}
testCerts = append(testCerts, cert)
}
// ExclusiveRootPools not set, so should be able to verify both system-signed certs
// and custom CA-signed certs
tlsConfig, err := Client(Options{CAFile: ca})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs}); err != nil {
t.Fatalf("Unable to verify certificate %d: %v", i, err)
}
}
// ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable
// and custom CA-signed certs should be verifiable
tlsConfig, err = Client(Options{
CAFile: ca,
ExclusiveRootPools: true,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs})
switch {
case i == 0 && err != nil:
t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err)
case i == 1 && err == nil:
t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err)
}
}
// No CA file provided, system cert should be verifiable only
tlsConfig, err = Client(Options{})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs})
switch {
case i == 1 && err != nil:
t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err)
case i == 0 && err == nil:
t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err)
}
}
}
// If a valid MinVersion is specified in the options, the client's
// minimum version should be set accordingly
func TestConfigClientTLSMinVersionIsSetBasedOnOptions(t *testing.T) {
key, cert := getCertAndKey()
tlsConfig, err := Client(Options{
MinVersion: tls.VersionTLS12,
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion)
}
}
// An error should be returned if the specified minimum version for the client
// is too low, i.e. less than VersionTLS12
func TestConfigClientTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) {
key, cert := getCertAndKey()
_, err := Client(Options{
MinVersion: tls.VersionTLS11,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned an error for minimum version below TLS12")
}
}
// An error should be returned if an invalid minimum version for the client is
// in the options struct
func TestConfigClientTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) {
key, cert := getCertAndKey()
_, err := Client(Options{
MinVersion: 1,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned error on invalid minimum version option")
}
}

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k1NO4wzCpxZ71Bo
SiYSWh8SE9jHtg6lz0QjMQXzFuLhpedjHJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR
9vhm5I+cm359gYU0gHawLw91oh4JCiwUu77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3
T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA+ypHfCPOi5F2wVjAyMnQXgVDkAhzefpu
JkhN1yUgb5WK4qoSuOUDUYq/bRosLdHXDJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4
YP6boZTOomLEDbBTSJWgX2/ybvY7o4sCw7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4
amWKrwIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEADuXjLtGk
tU5ql+LFB32Cc2Laa0iO8aqJccOcXYKg4FD0um+1+YQO1CBZZqWjItH4CuJl5+2j
Tc9sFgrIVH5CmvUkOUFPCNDAJtxBvF6RQqRpehjheHDaNsYo9JNKHKEJB6OJrDgy
N5krM5FKyAp/EDTbIrGIZFMdxQGxK5MfpfPkKK44JgOQM3QWeR+LqIpfd34MD1jZ
jjYdl0+quIHiIdFR0a4Uam7o9GfUmcWe1VFthLb5pNhV6t+wyuLyMXVMNacKZSz/
nOMWVQfgViZk6rHOPSMrFMc7Pp488I907MJKCryd21LcLqMuhb4BpWcJghnY8Lbs
uIPLsUHr3Pfp9Q==
-----END CERTIFICATE-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwNDIyMDQyMjM1WhcNMTgwNDIyMDQyMjM1WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GRTos+Ik6kQG7wn
8E4HqPwgWXbY0T59UQsrbR+YbyxbUKV67Pgl4VImuUmYaism6Tm3EFYzeom5baMc
vW0hC+WbwVr1rq5ddBE8akYhlPY40SxFlh563vOi7lcFGM7xuUbTlhtAhYa5xc5U
thHYa8Mdqc2kMrmU4JBhNHoRk2mnRBo2J2/8RfOfioM6mH0t/MVtB/jSGpcwbbfj
2twKOpB9CoX57szVo7+DCFHpLxeuop+69REu5Egc2a5BtBuUf0fkUBKuF7yUy2xI
IbgjCiGb3Z+PCIC0CjNt9wExowPAGfxAJ8s1nNlpZav3707VZRtz7Js1skRjm9aU
8fhYNQIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAcKCCV5Os
O2U7Ekp0jzOusV2+ykZzUe4sEds+ikblxK9SHV/pAPIVuAevdyE1LKmJ6ZGgeU2M
4MC6jC/XTlYNhsYCfKaJn53UscKI2urXFlk1Gv5VQP5EOrMWb76A5uj1nElxKe2C
bMVoUuMwRd9jnz6594D80jGGYpHRaF7yLtGbiflDjB+yv1OU6WnuVNr0nOb9ShR6
WPlrQj5TUSpRHF/oKy9LVWuxYA9aiY1YREDZhhauw9pGAMx1lImfJcJ077MdxN4A
DwKAx3ooajAu1n3McY1oncWW+rWs2Ptvp6lKMGoZ50ElEPCMw4/hPtPMLq/DTWNj
l342KLVWgchlIA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,68ce1d54f187b663e152d9c5dc900fd3
ZVBeXx7kWiF0yPOORntrN6BsyIJE7krqTVhRfk6GAllaLQv0jvb31XHB1oWOaqnx
tb7kUuoBeQdl1hs/iAnkDMc59WJfEK9A9cAD/SgxTgdENOrzFSRNEfqketLA4eHZ
2sOLkSfv58HwA0p0gzqSrLQBo/6ZtF/57HxH166PtErPNTS1Usu/f4Oj0UqxTfbZ
B5LHsepyNLt6q/15fcY0TFYJwvgEXa4SridjT+8bTz2T+bx3QFijGnl7EdkTElni
FIwnDjFZaAULqoyUIB1y8guEZVkaWKncxPdRfhId84HklWdrrLtP5D6db1xNNpsp
LzGdciD3phJp6K0hpl+WrhYxuCKURa27tXMCuYOFd1hw/kM29jFbxSIlNBGN4OLL
v4wYrJFM21iWsz9c7Cqw5Yls2Rsx0QrXRFIxwT25z+HNx1fysQxYuxf3r+e2oz8e
8Os7hvcxG2XDz01/zpx8kzxUcLuh+3o5UOYlo9z6qsjaD5NUXY+X90PUrVO9fk5y
8o8pnElPnV88Ihrog5YTYy6egiQWHhDk2I4qlYPOBQNKTLg3KulAcmC9vQ8mR5Sy
p3c3MTgh0A3Zk5Dib+sQ0tdbwDcB2JCTqGal1FNEW5Z7qTHA4Bdm2l7hGs8cRpy4
Ehkhv3s5wWmKcbwwlPuJ0UfPeDn6v9qE2/IkOy+jWgTpaFyWtXHc1/XdqMsJ8xN0
thJw/GMtNabB1+zuayJnvmbJd2qW1smsFTHqX3BovXIH4vx1hE2d0lJpEBynk+wr
gpPgrRoEiqsPcsRoVjvKH3qwJLRdcGYhKqhbvRdynlagCLmE8iAI99r82u6t+03h
YNpRbafY4ceAYyK0IlRiJvGkBMfH7bMXcBMmXyQSBF27ZpNidyZSCHrU5xyHqJZO
XWUhl9GHplBfueh5E831S7mDqobd8RqnUvKVygyEOol5VUFDrggTAAKKN9VzM3uT
MaVymt6fA7stzf01fT+Wi7uCm5legTXG3Ca+XxD6TdE0dNzewd5jDsuqwXnt1iC4
slvuLRZeRZDNvBd0G7Ohhp6jb2HHwkv9kQTZ+UEDbR/Gwxty4oT1MnwSE0mi9ZFN
6PTjrSxpIKe+mAhgzrepLMfATGayYQzucEArPG7Vp+NJva+j6FKloqrzXMjlP0hN
XSBr7AL+j+OR/tzDOoUG3xdsCl/u5hFTpjsW2ti870zoRUcK0fqJ9UIYjh66L7yT
KNkXsC+OcGuGkhtQ0gxx60OI7wp4bh2pKdT6e111/WTvXxVR2C3XhFBLUfNIz/7A
Oj+s0CaV4pBmCjIobLYpxC0ofLplwBLGf9xnsBiQF5dsgKgOhACeDmDMwqAJ3U/t
54hK/8Yb8W46Tjgbm0Qsj5gFXHofnyqDeQxAjsdCXsdMaPB8nyZpEkuQSEj9HlKW
xIEErVufkvqyrzhX1pxPs+C839Ueyeob6ZWQurqCLTdZh+3bhKcvi5iP+aLLjMWK
JT9tmAuFVkbPerqObVQFbnM4/re33YYD7QXCqta5bxcVeBI8N1HdwMYrDVhXelEx
mqGleUkkDHTWzAa3u1GKOzLXAYnD0TsTwml0+k+Rf0QMBiDJiKujfy7fGqfZF2vR
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1k1NO4wzCpxZ71BoSiYSWh8SE9jHtg6lz0QjMQXzFuLhpedj
HJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR9vhm5I+cm359gYU0gHawLw91oh4JCiwU
u77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA
+ypHfCPOi5F2wVjAyMnQXgVDkAhzefpuJkhN1yUgb5WK4qoSuOUDUYq/bRosLdHX
DJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4YP6boZTOomLEDbBTSJWgX2/ybvY7o4sC
w7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4amWKrwIDAQABAoIBAQC802wj9grbZJzS
A1WBUD6Hbi0tk6uVPR7YnD8t6QIivlL5LgLko2ruQKXjvxiMcai8gT7pp2bxa/d6
7/Yv2PxAlFH3qOLJhyeVsf7X2JVb/X8VmXXDYAiJbI0AHRX0FJ+lHoDK3nn+En9Q
zSqgyqBhz+s343uptauqWZ2kkE3VNyqlPBhmKc5NcbR7Sgb4nJ3CkNAcxRkl1NeI
BRFdsTUYRNR3Vd++OvOzI4uzZfCIeUVqx+r7/SeLW0UwqeprMm7g+hFQLfH+e9SA
9lx0EIRoQFwgvKju2eogpSwvkSlObXnESu5OHYtnc+jpsOC0EbQgO0d6CqVZiqjR
2dRYsZkhAoGBAO69loXSAsyqUj0rT5iq59PuMlBEAlW6hQTfl6c8bnu1JUo2s/CH
OJfswxfHN32qmi99WbK2iLyrnznNYsyPnYKW0ObwuoqAdrlydfu7Fq9HSOACoIvK
jRMOsiJtM3JX2bHHV7yIwJ1+h++o2Ly803j7tKtYsrRQVZiWeTcR2IRZAoGBAOXL
bJFLbAhm3zRqhbiWuORqqyLxrDmIB6RY8vTdX47vwzkFGZJlCuL+vs6877I6eOc9
wjH9qcOiJQJ4DWkAE+VS5PAPoj0UDRw7AkE9v3RwnmxvAfP5rPo5KimYxKq4yX6r
+Qc4ixwftCj0rxFoG4lnipwBFq4NXuHtIhbZXMZHAoGBAOGfatGtV9f0XyRP+jld
yxoO0p3oqAw86dlhNgFmq0NePo+UgxmdsW5i4z1lmJu6z1xyKoMq3q7vwtrtr6GD
WGhB/8tBVgnuvkUkVzw/44Bi7gxGb1OtaQXJra+7ZBN70tCgg9o5o080dWOZPruf
+Hst5eDJQpoGEd7S1lulEeqBAoGBAKAqdIak6izE/wg6wu+Q5lgW3SejCOakoKb1
dIoljkhDZ2/j1RoLoVXsNzRDzlIMnV6X1jYf1ubLqj4ZTUeFTVjGuVl1nCA0TJsD
qiOtFTfkkxeDG/pgaSeTFocdut4/o/nNhep5h8RXeKwfN7LLPH4+FAd+Xr98BEk2
jk8cu6RbAoGAHI9yRXKjlADBZLvxxMGHRfe7eK4PgABmluZLdsXzNmXxybrZDvdC
teipvIUSym7tvdDB6LHXKVp4mYeqHe/ktRatlhbQyPso2VPoMFQyuRBYKKFFAh0V
3d6EyTRnIxn/NW+XdcCUeufFfd+3BHyux68PyUsTtKRCJYfhExzJf70=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIC3DCCAcSgAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVIJDnNnM1iX7Xj8
bja4WsgHuENRBsBCROTDjQL1w7Ksin2jmCl/D7Gk9ifRJZ/HPE3BKo6B+3CDXygJ
Qvoe8SGWi6ae8lN4VgPoW7xDViAWhVmjIr+dNQXWD0hCq0YZuXyYSi5iXWeRaTvx
2eoG2VSkNnkc/0weEhX1nBGBscuz1UZqWp53m09eL7otngcNcdjmvLPiw4E3cric
UoLVonzf4ZE84Q7nNmfWfMKh4zJUyn8N766GAAoC6RAKsJ0xSDeRjkzSy7vGJKBv
nTBe6X1xyFZaN0mAjtRkYaxI9ZfI8K41Trhd88s4B4G61p70DY3dMLmuF8wGHVCF
lMMV6wIDAQABo0EwPzAOBgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwEgYDVR0TAQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAQEA
LriCH0FTaOFIBl+kxAKjs7puhIZoYLwQ8IReXdEU7kYjPff3X/eiO82A0GwMM9Fp
/RdMlZGDSLyZ1a/gKCz55j9J4MW8ZH7RSEQs3dJQCvEPDO6UdgKy4Ft9yNh/ba1J
8/n0CqR+0QNov6Qp7eMDkQaDvKgCaABn8at6VLtuifJXFKDGt0LrR7wkQBJ85SZB
9GdfNSPzEZkb4FQ2gPgAk7ySoQ6Hi6mogEORbtJ7+Xiq57J+cEZQV6TOuwYgBG4e
MW3h37+7V5a/absybik1F/gcx4IbEBd/7an6a+a2l5FeTED5kpzvD4+yrQAoY8lT
gccRdP0O4CsLn7zlLRidPQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBTzCB9qADAgECAgMDDT8wCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEdGVzdDAe
Fw0xNjAzMjgxODQxNDdaFw0yNzAzMjgxODQxNDdaMA8xDTALBgNVBAMTBHRlc3Qw
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy8xfFkSiJA10EC1MMJzkLgu6csocC
UNyix7zOqijLsASE4an5LQsZ1PuhgVYnL+B9rAcnXgJaLM8YOmLRPqNdo0EwPzAO
BgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUHAwMGBFUdJQAwEgYDVR0T
AQH/BAgwBgEB/wIBATAKBggqhkjOPQQDAgNIADBFAiEAwUrZY7fHwr4FWONiBJo6
97V9GAbj70ZJqV5M7rt+hMECIFY66kUrv0sG2vlhicSIGwSOdB3VcijdZSelzLn1
iRk5
-----END CERTIFICATE-----