Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/contour: hot-reload certificates and key #2198

Merged
merged 1 commit into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -217,26 +217,47 @@ func (ctx *serveContext) grpcOptions() []grpc.ServerOption {
// tlsconfig returns a new *tls.Config. If the context is not properly configured
// for tls communication, tlsconfig returns nil.
func (ctx *serveContext) tlsconfig() *tls.Config {

err := ctx.verifyTLSFlags()
check(err)

cert, err := tls.LoadX509KeyPair(ctx.contourCert, ctx.contourKey)
check(err)
// Define a closure that lazily loads certificates and key at TLS handshake
// to ensure that latest certificates are used in case they have been rotated.
loadConfig := func() (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(ctx.contourCert, ctx.contourKey)
if err != nil {
return nil, err
}

ca, err := ioutil.ReadFile(ctx.caFile)
if err != nil {
return nil, err
}

certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, fmt.Errorf("unable to append certificate in %s to CA pool", ctx.caFile)
}

return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
Rand: rand.Reader,
}, nil
}

ca, err := ioutil.ReadFile(ctx.caFile)
// Attempt to load certificates and key to catch configuration errors early.
_, err = loadConfig()
check(err)

certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(ca); !ok {
log.Fatalf("unable to append certificate in %s to CA pool", ctx.caFile)
}

return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
Rand: rand.Reader,
ClientAuth: tls.RequireAndVerifyClientCert,
Rand: rand.Reader,
GetConfigForClient: func(*tls.ClientHelloInfo) (*tls.Config, error) {
jpeach marked this conversation as resolved.
Show resolved Hide resolved
config, err := loadConfig()
check(err)
return config, err
},
jpeach marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
180 changes: 177 additions & 3 deletions cmd/contour/servecontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@
package main

import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net"
"os"
"path/filepath"
"reflect"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/projectcontour/contour/internal/assert"
"google.golang.org/grpc"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -181,7 +190,7 @@ leaderelection:
t.Run(name, func(t *testing.T) {
got := newServeContext()
err := yaml.Unmarshal([]byte(tc.yamlIn), got)
checkErr(t, err)
checkFatalErr(t, err)
want := tc.want()

if diff := cmp.Diff(*want, *got, cmp.AllowUnexported(serveContext{})); diff != "" {
Expand All @@ -191,9 +200,174 @@ leaderelection:
}
}

func checkErr(t *testing.T, err error) {
// Testdata for this test case can be re-generated by running:
// make gencerts
// cp certs/*.pem cmd/contour/testdata/X/
func TestServeContextCertificateHandling(t *testing.T) {
tests := map[string]struct {
serverCredentialsDir string
clientCredentialsDir string
expectedServerCert string
expectError bool
}{
"successful TLS connection established": {
serverCredentialsDir: "testdata/1",
clientCredentialsDir: "testdata/1",
expectedServerCert: "testdata/1/contourcert.pem",
expectError: false,
},
"rotating server credentials returns new server cert": {
serverCredentialsDir: "testdata/2",
clientCredentialsDir: "testdata/2",
expectedServerCert: "testdata/2/contourcert.pem",
expectError: false,
},
"rotating server credentials again to ensure rotation can be repeated": {
serverCredentialsDir: "testdata/1",
clientCredentialsDir: "testdata/1",
expectedServerCert: "testdata/1/contourcert.pem",
expectError: false,
},
"fail to connect with client certificate which is not signed by correct CA": {
serverCredentialsDir: "testdata/2",
clientCredentialsDir: "testdata/1",
expectedServerCert: "testdata/2/contourcert.pem",
expectError: true,
},
}

// Create temporary directory to store certificates and key for the server.
configDir, err := ioutil.TempDir("", "contour-testdata-")
checkFatalErr(t, err)
defer os.RemoveAll(configDir)

ctx := serveContext{
caFile: filepath.Join(configDir, "CAcert.pem"),
contourCert: filepath.Join(configDir, "contourcert.pem"),
contourKey: filepath.Join(configDir, "contourkey.pem"),
}

// Initial set of credentials must be linked into temp directory before
// starting the tests to avoid error at server startup.
err = linkFiles("testdata/1", configDir)
checkFatalErr(t, err)

// Start a dummy server.
opts := ctx.grpcOptions()
g := grpc.NewServer(opts...)
if g == nil {
t.Error("failed to create server")
}

address := "localhost:8001"
l, err := net.Listen("tcp", address)
checkFatalErr(t, err)

go func() {
err = g.Serve(l)
checkFatalErr(t, err)
}()
defer g.Stop()

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// Link certificates and key to temp dir used by serveContext.
err = linkFiles(tc.serverCredentialsDir, configDir)
checkFatalErr(t, err)
receivedCert, err := tryConnect(address, tc.clientCredentialsDir)
gotError := err != nil
if gotError != tc.expectError {
t.Errorf("Unexpected result when connecting to the server: %s", err)
}
if err == nil {
expectedCert, err := loadCertificate(tc.expectedServerCert)
checkFatalErr(t, err)
assert.Equal(t, receivedCert, expectedCert)
}
})
}
}

func checkFatalErr(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Error(err)
t.Fatal(err)
}
}

// linkFiles creates symbolic link of files in src directory to the dst directory.
func linkFiles(src string, dst string) error {
absSrc, err := filepath.Abs(src)
if err != nil {
return err
}

matches, err := filepath.Glob(filepath.Join(absSrc, "*"))
if err != nil {
return err
}

for _, filename := range matches {
basename := filepath.Base(filename)
os.Remove(filepath.Join(dst, basename))
err := os.Symlink(filename, filepath.Join(dst, basename))
if err != nil {
return err
}
}

return nil
}

// tryConnect tries to establish TLS connection to the server.
// If successful, return the server certificate.
func tryConnect(address string, clientCredentialsDir string) (*x509.Certificate, error) {
clientCert := filepath.Join(clientCredentialsDir, "envoycert.pem")
clientKey := filepath.Join(clientCredentialsDir, "envoykey.pem")
cert, err := tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
return nil, err
}

clientConfig := &tls.Config{
ServerName: "localhost",
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", address, clientConfig)
if err != nil {
return nil, err
}
defer conn.Close()

err = peekError(conn)
if err != nil {
return nil, err
}

return conn.ConnectionState().PeerCertificates[0], nil
}

func loadCertificate(path string) (*x509.Certificate, error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode(buf)
return x509.ParseCertificate(block.Bytes)
}

// peekError is a workaround for TLS 1.3: due to shortened handshake, TLS alert
// from server is received at first read from the socket.
// To receive alert for bad certificate, this function tries to read one byte.
// Adapted from https://golang.org/src/crypto/tls/handshake_client_test.go
func peekError(conn net.Conn) error {
_ = conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
_, err := conn.Read(make([]byte, 1))
if err != nil {
if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
return err
}
}
return nil
}
20 changes: 20 additions & 0 deletions cmd/contour/testdata/1/CAcert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDPzCCAiegAwIBAgIUHA9n4BBwtiDHvIN4Eq8isFCk+3wwDQYJKoZIhvcNAQEL
BQAwLzEYMBYGA1UECgwPUHJvamVjdCBDb250b3VyMRMwEQYDVQQDDApDb250b3Vy
IENBMB4XDTIwMDEyOTE1MzkyNFoXDTI1MDEyNzE1MzkyNFowLzEYMBYGA1UECgwP
UHJvamVjdCBDb250b3VyMRMwEQYDVQQDDApDb250b3VyIENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyX+m7eJzYLHBRIaDftgDovBjtMGQJ8jzNezF
c6uE6PcsQhk7nIj8Czgd8Jr/AsHY8a4x/Pddm86dYzl6XBWSS2ogJYUzCaXAr59q
qT6g5j0/rHlxzWeFge+0EjPQuAphI4kZ4j9Ua014KaMLCxVFXH/c96OHxC3/L1vh
0BKyceVWKfbvn23kc47xylFkFQnmbYxIEkJA4/Jq6ewsBScB1mp24s+xyl1m6ePO
WhMo7Od2xQsUhGKDJYMFV7cXmwDXR2QudZE+OvcaSoVYMktOpyh07RSRQEkx4URY
gc61nkFRekZIc6rfbbK9xMQtcWkbvtopqDiyt1PfWbHaelTgvwIDAQABo1MwUTAd
BgNVHQ4EFgQU02g0ldN022nR81UCP3fN0PkYu7YwHwYDVR0jBBgwFoAU02g0ldN0
22nR81UCP3fN0PkYu7YwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AQEAmcm2vkBudVY70WcIpUZlRU8NOmUKb6wtSugJ+vPBDyDDN1H+OH0u5l4yXFA8
87uOS8Z1n+Sxw5fKH1rp/TVbwm++nVJ7RjalRQBG5skdx6J7qEL9H2vx9VHG/7Ib
G+NL95TBtk9042KJFiZXsY8KPA4JSEFPWVIcXmaq2iTJxSMHGD3MQv33yjgXH81U
e09f27Vfbl+YFvkSljZJKxXjgwPVDlo5Sv/9e4mS51dhraEvJV+dubz7EVv/R7F/
pHX+tVn0+WSfero8cIfhhj1H9+Cn5lnUUHYnnnbrLh5j8ZldniW2nXkOWR5LQN0J
CwkA744hrtGj9dDmI/vwcpQ1tw==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions cmd/contour/testdata/1/CAkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJf6bt4nNgscFE
hoN+2AOi8GO0wZAnyPM17MVzq4To9yxCGTuciPwLOB3wmv8CwdjxrjH8912bzp1j
OXpcFZJLaiAlhTMJpcCvn2qpPqDmPT+seXHNZ4WB77QSM9C4CmEjiRniP1RrTXgp
owsLFUVcf9z3o4fELf8vW+HQErJx5VYp9u+fbeRzjvHKUWQVCeZtjEgSQkDj8mrp
7CwFJwHWanbiz7HKXWbp485aEyjs53bFCxSEYoMlgwVXtxebANdHZC51kT469xpK
hVgyS06nKHTtFJFASTHhRFiBzrWeQVF6Rkhzqt9tsr3ExC1xaRu+2imoOLK3U99Z
sdp6VOC/AgMBAAECggEAThXG5cbsuVsJL3oFOUGS3zDTIrgkGhbYkVwpBHNCdVlb
8F2A6V94dQyRJa2bB0GBxd6ghoyB3SBLg6lBjq/ZWppMzN16ctGmAyj/F4kqpy6z
Hy6M+HGWnkz69lbYGooDLNczjabHGzIRT+lcHTZoA7mVMu5Pban5iyvLLWwhdNsf
q9d7pYHt/TxpkkBTZOpDB8B2jFWMKcxBFSdeUE/0DzT5LkqPfgzq//86XGnc2wSj
KAhJipfASApYHwlleqi2OfXiGF+sVQdbqdiVT1SbJheSuRC3yMZc0Njeyf9iUsM+
LL70lUKRnM+PXENyPNpRGkU6RlDEQ3pnjkNI9UOEMQKBgQDvX8ehWsCkBQmSSfbi
YYSxuu/hzfu/crA+g3fe1mdGwcBipum5kxgF2rj5i4c7RbqqDEqRtFFEpmMoQ8D8
bHsK3SwcKFPNtX64/OQkTWq+nD0pvVi/ymS/R199Z9K13Fpb93CLU0xftXXDT4yE
UN4NQrFc+zdqEidHeLCxMdwU5wKBgQDXfmwnQk/vz45yaissw+yBanlWpRUEq2fI
acvZ3xWl+1ffkEzY+NeEeIz52BcMf7JwCkD753hSoNh507850GeHlRZ+o2Kq7XAR
N1SVZelG590p2xP1aSE0GnyfeuQd0HZEGrpje8Moi2vMQ2oYd47BoTMlZ2mqawbD
URMm1pWCaQKBgCXegTaFpPRN17XM/cHSq6tyZ4DRlYI0Iq3BHrWiNbR78nOo9FDn
dGV4tMrFyB8YaO9+Ak4KuNCjggxcq6tDfjO5ycCqoJdqnyGk4HLdzIVbMlHoIqI0
4rtgDztHsY4Tzje+bY+dHfgGPRso+pH0OSzf4C9Vju648H3eGhXuTWMBAoGBAJNq
n9AnlAmownjQ2mJQUZ2i2gkE+6DrJR88CMEt1GBs1gtRatDPQpgT49UTF4lsXgQ5
b4UkLvLPp+eHjHyfbgOZYP8XBGuL7KtKX6moQvJHscttXHT5C0bai8CJ0D35Gr6y
Tim6Q6Kb5g2hXJYKS/V4MkX3PZjgiIrbDq/2AedpAoGBAIBHeCNIZtydwp8l3imp
zO67h/X1Gp6OOuJe7PK0LgocBCTTmFQrezNgctHEWccV6TlXPYMG5ZrRX5cc4R8D
e7YBvgq2tNg4baRqJLV5/4YRZk93yAB30D18gqeloua8MigxGyT+Usjb0BLRWmVj
HTS5+5+m8y3RxoThspWO8DkY
-----END PRIVATE KEY-----
21 changes: 21 additions & 0 deletions cmd/contour/testdata/1/contourcert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDhzCCAm+gAwIBAgIUQ791kdZBTnbqToyBUaKKLuIaqqYwDQYJKoZIhvcNAQEL
BQAwLzEYMBYGA1UECgwPUHJvamVjdCBDb250b3VyMRMwEQYDVQQDDApDb250b3Vy
IENBMB4XDTIwMDEyOTE1MzkyNFoXDTI1MDEyNzE1MzkyNFowLDEYMBYGA1UECgwP
UHJvamVjdCBDb250b3VyMRAwDgYDVQQDDAdjb250b3VyMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAw6pOu3k8fO6TejESmIKuTFGIuZf/PW86cMImfeHw
DahGVffvbLTK0YNpTeeyuACFH3vgMPMhBpPUVRK/te8cK02kWQGNROQgQMM9KKyb
y//zQTze28SKW2kCrmBg5NWPITjTuZCpx+A/OxX8PTWNQUHXByCshC9GGt+Ks65q
kqD2EuMT5tWeedBWFDYZVYxSxG0AHs41MHbtm9Z642u1q45akNQ2HF24PPH7cxF0
xKkNHZrdBx4XIst3fvkSTyu9CHG/jbstmHNE5ODo4+4Jk5dl3GUP93BmmPXrcOy+
VLYZZdvPPlzGBbENRdxs4gONJf0bZE5QF4huyzjt/ISBtwIDAQABo4GdMIGaMB8G
A1UdIwQYMBaAFNNoNJXTdNtp0fNVAj93zdD5GLu2MAkGA1UdEwQCMAAwCwYDVR0P
BAQDAgTwMF8GA1UdEQRYMFaCB2NvbnRvdXKCCTEyNy4wLjAuMYIWY29udG91ci5w
cm9qZWN0Y29udG91coIoY29udG91ci5wcm9qZWN0Y29udG91ci5zdmMuY2x1c3Rl
ci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAig7VDaLjtUeHdTtRXEItZEEgLWcI
6sP6XA1pUcBoCy6lRaVbeqM5Y3tm+HPX0RQoqH200Ub2b3yB84La4V2cy4+DlAoz
gASHOn6Sop7TR7TwFlHEh/r+dAXw2iENYY8oXexmCpa53s1+9WbkzWw1y5wW0fuS
DuIEMYpZvp7mFZD7lzDHQU8ulorddtNVhC60VKPoBr6pWLfT2eYulZTuGt3oUeT5
x7rxWLFiEi7TT+5dcxEfKwn0bknyjrCWyt0bTkJNl/1anSm8RccmTDY3kGj9YZsY
hZyX+069Mi553cRwd95hWPzttJVSrNhvRCj91IpZgTYEUHLWvWhhs36xFg==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions cmd/contour/testdata/1/contourkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAw6pOu3k8fO6TejESmIKuTFGIuZf/PW86cMImfeHwDahGVffv
bLTK0YNpTeeyuACFH3vgMPMhBpPUVRK/te8cK02kWQGNROQgQMM9KKyby//zQTze
28SKW2kCrmBg5NWPITjTuZCpx+A/OxX8PTWNQUHXByCshC9GGt+Ks65qkqD2EuMT
5tWeedBWFDYZVYxSxG0AHs41MHbtm9Z642u1q45akNQ2HF24PPH7cxF0xKkNHZrd
Bx4XIst3fvkSTyu9CHG/jbstmHNE5ODo4+4Jk5dl3GUP93BmmPXrcOy+VLYZZdvP
PlzGBbENRdxs4gONJf0bZE5QF4huyzjt/ISBtwIDAQABAoIBAA0cERBgjBv2xCzQ
suVDBDia0eVVeMV9+VVqvLd8duADYUsLRKBs8JXfDyQoHQJVDpZQb3H4KENPjk9w
5SVkcue32QYZo4R1IHAWZLef8QRXDs5VLL1eysJbI9HZJUTPxjo8m3r4ZVe9/56O
14qmVuODbMvOdaCZpkHQrnNhgUR3pCWAE61NFuCEnxve2CH09jmZosItl7RQx2g4
lbAfAlg5zd87E9g1mbslfsWwbFvKcbGktGeTzA8VgYj/Fik/ObJmOWe9B2DTTo8K
HBELnv5fVRLSfs51YC3lTNDOVrrWuwdMTofLlcl+0RB6vEA0akpRsJJfvgfmXdYS
LPSknzkCgYEA/x68xfi4a/otJYhqqsw/TI+iy7B8t/lchcuYMAmg41ipWFKWpGrP
Uer19Ca/yJ7cgcfgyg+65HdRxby4izcixXnrSIqA2op7wswqxhNR/d7H8+c4ejMz
+GUwBbFL5yp5D6LhPl+DFGEsB1bm4D51VavLTC+MLdDQo+PZ9fXH+xUCgYEAxFcS
0k9QyIe2VYnfmpCzGUvTr916XXJPEUtvGH7rJsnR8EjLwtMcmXcM/7VO0Btf5rAD
EHFmXg2BPMcOB6DYWOITRfvMXJ/avk0kuhjc385TwzhroVAW9skrtTxiMaNNi4nE
EHxQmBoiz8ElbiaixKPDr163f97NYR7BnNqgjJsCgYEA4fiW2n/40mNxE7qmSIzL
UIQ1fVfg0JAGHNh9/6a3pEgHD51vo0icRAHrQwwDea8Ev8uMV03hi7YIby4/A8id
eu8HsWREx8472wo+pN2+FTD8SRS4GL07vjvacmBdS+959ZifDEFLeIStm/6kV2Hb
Sjv1wZCoCHjaJSCQEeVW8hUCgYB7KPuffS15fNf9dE0VUetm1M/nI5EciRXcDWuU
/BhZ7oOIrMFUZsUr5yf6Rys3E9TmikaBzACgwuvsyhic5GKR7s6UOc0J1SSL9yww
qGP06CJW9U9ekzS0faCzQTt/U6bS/wpEJRcRMmPf2pK2M/oqS2f22/1Tctl2MKrW
z7WiPQKBgHBFlEkTHjC+nqvCy9tfyW3dqseqWa8/mpn0DYyEudYUJMTam339QxVB
5ewYnCU7sSAqVH3BNTQUVWh2KJ2gVitAT3nAkUZASPI5LFrQAiLERvii6oeuhE4L
WwSKVFuctBnrT2ZDTtfhAtIQ8ebkF2kUOhmLmwfaRismiIPt4Nwu
-----END RSA PRIVATE KEY-----
20 changes: 20 additions & 0 deletions cmd/contour/testdata/1/envoycert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDPzCCAiegAwIBAgIUQ791kdZBTnbqToyBUaKKLuIaqqcwDQYJKoZIhvcNAQEL
BQAwLzEYMBYGA1UECgwPUHJvamVjdCBDb250b3VyMRMwEQYDVQQDDApDb250b3Vy
IENBMB4XDTIwMDEyOTE1MzkyNFoXDTI1MDEyNzE1MzkyNFowKjEYMBYGA1UECgwP
UHJvamVjdCBDb250b3VyMQ4wDAYDVQQDDAVlbnZveTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBANYg1iHTm1qwE/LeDci65o6wd+goE354cyU8Z2oQFPPq
VQhzW5AFxnloMBOS9sbCEAWveLmHrjBvKnkAjcuOBmTWfVnCaQlm+j/Cspqx9xvF
N2RBoVrQaOKdiqEnBudTCV6wmcUKCMrtoS3ShQ81PJ7wArA7ERbzmRq3Ys3eceuo
OUJATSZzplaY3eTfBXOh7V4+Vb1hwngdOmetbj+9+xgWi39Xp7Zh6U9DL7dH2Qg8
dOlY/E54kRuw13XffFdtIwSIRxFhlI2LqeRyhKWp21p9zkCcp+7Nfm3wOnRO62K9
B25DsVQSGB+gUuakYeF6YLSH8nO50WHgl4g0yi0RoMcCAwEAAaNYMFYwHwYDVR0j
BBgwFoAU02g0ldN022nR81UCP3fN0PkYu7YwCQYDVR0TBAIwADALBgNVHQ8EBAMC
BPAwGwYDVR0RBBQwEoIFZW52b3mCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsFAAOC
AQEALAyIvQal5g2bKDhF2+/5VkaYV18A27RsmGNZaL5Hd0+066AB0WCCCVbSrzTa
6DlJfElBcT88ddBrf1bB+h4FyQZlNUg+ewl9RfIVOdErYSkFeO5aeCWD2ieLxdH2
f4W24v+/H8a5daC4K0l0kK3FFM8MUDQy2BAfcbgphehME70Rj6/xX3IZKU4c+Cjb
VIWTyr+RnWgki3iRpyTSQFOkdRtEKVsk+S/mSIOoofbGby85jXpzL2z3XNpoX94K
9rOa76miggoH50TpqDjkWz+wbdQodhw3k8Yjn6s4jW0DiUK7c1ecjrNJW+1GfAOx
cUa45Lfpy5XmCzB+wAISoGvqlQ==
-----END CERTIFICATE-----
Loading