add downstream mTLS integration test cases (#4234)

* integration test config: add downstream mTLS routes

Add two new CA certificates for use with downstream mTLS tests, and a
client certificate/key pair issued by each CA.

Add a few routes to the policy template that require a client CA. Update
the generated output configurations.

(based on commit ed63a6a6e7)

* add downstream mTLS integration test cases

These are modeled after the tests added to v0.17 in 83957a9, but here
the expected behavior is that requests with an invalid client
certificate will receive a 495 response only after authentication.
This commit is contained in:
Kenneth Jenkins 2023-06-13 10:25:21 -07:00 committed by GitHub
parent d96ca0611a
commit 3ebee1159c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 335 additions and 6 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -105,6 +105,14 @@ func getClient(t testing.TB) *http.Client {
}
}
// Returns a new http.Client configured with the same settings as getClient(),
// as well as a pointer to the wrapped http.Transport, so that the
// http.Transport can be easily customized.
func getClientWithTransport(t testing.TB) (*http.Client, *http.Transport) {
client := getClient(t)
return client, client.Transport.(loggingRoundTripper).transport.(*http.Transport)
}
func waitForHealthy(ctx context.Context) error {
client := getClient(nil)
check := func(endpoint string) error {
@ -194,3 +202,14 @@ func mustParseURL(str string) *url.URL {
}
return u
}
func loadCertificate(t *testing.T, certName string) tls.Certificate {
t.Helper()
certFile := filepath.Join(".", "tpl", "files", certName+".pem")
keyFile := filepath.Join(".", "tpl", "files", certName+"-key.pem")
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
t.Fatal(err)
}
return cert
}

View file

@ -12,8 +12,10 @@ import (
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pomerium/pomerium/integration/flows"
"github.com/pomerium/pomerium/internal/httputil"
)
func TestQueryStringParams(t *testing.T) {
@ -339,3 +341,137 @@ func TestLoadBalancer(t *testing.T) {
distribution)
})
}
func TestDownstreamClientCA(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute*10)
defer clearTimeout()
t.Run("no client cert", func(t *testing.T) {
res, err := flows.Authenticate(ctx, getClient(t),
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("untrusted client cert", func(t *testing.T) {
// Configure an http.Client with an untrusted client certificate.
cert := loadCertificate(t, "downstream-2-client")
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("valid client cert", func(t *testing.T) {
// Configure an http.Client with a trusted client certificate.
cert := loadCertificate(t, "downstream-1-client")
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-required.localhost.pomerium.io/"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
defer res.Body.Close()
var result struct {
Path string `json:"path"`
}
err = json.NewDecoder(res.Body).Decode(&result)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, "/", result.Path)
})
}
func TestMultipleDownstreamClientCAs(t *testing.T) {
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute*10)
defer clearTimeout()
// Initializes a new http.Client with the given certificate.
newClientWithCert := func(certName string) *http.Client {
cert := loadCertificate(t, certName)
client, transport := getClientWithTransport(t)
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
return client
}
// Asserts that we get a successful JSON response from the httpdetails
// service, matching the given path.
assertOK := func(res *http.Response, err error, path string) {
require.NoError(t, err, "unexpected http error")
defer res.Body.Close()
var result struct {
Path string `json:"path"`
}
err = json.NewDecoder(res.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, path, result.Path)
}
t.Run("cert1", func(t *testing.T) {
client := newClientWithCert("downstream-1-client")
// With cert1, we should get a valid response for the /ca1 path.
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
assertOK(res, err, "/ca1")
// With cert1, we should get an HTML error page for the /ca2 path.
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err)
res, err = client.Do(req)
require.NoError(t, err, "unexpected http error")
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
t.Run("cert2", func(t *testing.T) {
client := newClientWithCert("downstream-2-client")
// With cert2, we should get an HTML error page for the /ca1 path
// (after login).
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
// With cert2, we should get a valid response for the /ca2 path.
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err, "unexpected http error")
res, err = client.Do(req)
assertOK(res, err, "/ca2")
})
t.Run("no cert", func(t *testing.T) {
client := getClient(t)
// Without a client certificate, both paths should return an HTML error
// page (after login).
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://client-cert-overlap.localhost.pomerium.io/ca1"),
flows.WithEmail("user1@dogs.test"))
require.NoError(t, err)
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://client-cert-overlap.localhost.pomerium.io/ca2", nil)
require.NoError(t, err)
res, err = client.Do(req)
require.NoError(t, err, "unexpected http error")
res.Body.Close()
assert.Equal(t, httputil.StatusInvalidClientCertificate, res.StatusCode)
})
}

View file

@ -100,6 +100,28 @@ local Routes(mode, idp, dns_suffix) =
// path: '/tls-client-cert-disabled',
// allow_public_unauthenticated_access: true,
// },
// downstream mTLS
{
from: 'https://client-cert-required.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
tls_downstream_client_ca: std.base64(importstr '../files/downstream-ca-1.pem'),
allow_any_authenticated_user: true,
},
// overlapping downstream mTLS
{
from: 'https://client-cert-overlap.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
path: '/ca1',
tls_downstream_client_ca: std.base64(importstr '../files/downstream-ca-1.pem'),
allow_any_authenticated_user: true,
},
{
from: 'https://client-cert-overlap.localhost.pomerium.io',
to: 'http://trusted-httpdetails' + dns_suffix + ':8080',
path: '/ca2',
tls_downstream_client_ca: std.base64(importstr '../files/downstream-ca-2.pem'),
allow_any_authenticated_user: true,
},
// cors_allow_preflight option
{
from: 'https://httpdetails.localhost.pomerium.io',

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSzKbPR5qLcUlE
PN0zuxEMBeyyawlF2Rc08tKtUMPLPBlxle+kVFgx0ElWVGFzBJSm3FODQ0y/RGO1
iQ8MAadtro4MvkgSKKZVcEiSMt1kTMr6UDFsvAjDyFpE5lpVCFGeK6GWRd8oII4u
L0FgZdrNDjmW53AEF/ajWFxgzvrGq4t6gM9MBROD3FpAf11n1snW0/KI+joBxWoW
zXxx698t4LXFiMPTazxZ3hXTV7g4gu0EoG+V4eRCFX1CiBmm1UuqkdvQjkyyeeYI
HZm6WwOiyOOR+fr/77kGgn7IZ2GvzUJYSECueIUY2wFc170bB+GSMApUwbnM7/Q9
Gkjx9StRAgMBAAECggEBALUUOeLrBnXsXdyxT+9FLZKfcEGfsrp+jvHb+WAKdfaa
bNzmyJHoJk68NpNjLsbn024268JhSIcxD1n9H6DXtuSAQuPtfOmkY6YXKy/zY7zq
Pp6hzqMyL3FHJUv0ePp8h+gQYOE+qkDFHn3L+CP/uuHU62GW8+GAWEze9ZPvmPqZ
CLzgmgzyxOdTMDLHigoP7PPQ9Sz8h7E7l8uqq+QhDheEuqAffYOteiKfUN+XKwhB
PUgsoIj4+gmqXqQvROh3aH5NZo3dr2WSmLFCCDSkJksRTvffbcmIjj6533dC6382
Y3bKTpvnyaA6iF3XpdP4w1l/Ynx4CVLsJnXLOZZH2nECgYEA6dyvYVbwL/Cocp2a
PSIn6Gzeio3BndwblPSkJt+NmsUXeAKAWRtvuJI2nZ6fLyCOoDhf9RUMSw77X/Ge
T5+0JTOelNdTahQaOiOOvY7y/gWWwsx1tKnr2bveyVD6i/2cL+A7YzXaZJLo904y
v6dpP9OkkLImxRoxNrm6aVwe9DUCgYEA5sETjo31X0nq+QCs6JHHKur2MovPDNSh
B2FLUX5kYwvnTYmCRK39+J454t0SO6MlceSYEyBiMTH0SLaM9JQH8wEI0Ym7wBN7
DLOAvjocWIrZZpS/4L6jW0b7oC4PxUQaD0aiuqNGMqO/cXTx/udqDL70jfLc64tv
mzHjW27/Bi0CgYAPIF4brrLkdu1+VGFYmO/54ajXT2n5mvYRwW0osocHPr5Q3eCN
Yu/sAVEVCuCC/Nkc1einApCD/lkWEJvLzKmrSlZ0jCTyFJDJt+kQq8Fd2uCwgucO
I3uE3tj/LoS2L1Y95oQQ27ffj30QGffAW8S8AyV+0ncdgp8ySfsbu4CpDQKBgEEB
Fx9LgloLmrP2qr8BghfnBOAMXpGAvLQ1hmA4uNLmIYzINfhfA3KsMCZmnKJMPVou
SWDp+H9fUS/CrUahBPRB0Fgm8ssTMdDMKHJuMFoJE2FAeiU3zxEBmBTxpOOvYZ/4
CBOHt1jApEez6BAk5M0eZgou5mk8aH5RuXiidhkpAoGAT4MWXdn8O/Kgg2plJvYH
xb2Z05A6LnaIHhGPlzBj7dvt9ACipHKZ+fZ3Wz0yMCE6IzeMAbeKUcFC3A40lb/e
aDptEpYz77f8SenRuWD7DKD097vljxC8q5QYXIXim8lgxk82s0MCKv9e5SA4sr7D
MA89mTaJ7H4F06tCWR1v+zk=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID3TCCAkWgAwIBAgIQJx0/X8QkwahKtxQfwmLQJjANBgkqhkiG9w0BAQsFADA6
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExGDAWBgNVBAMTD2Rvd25z
dHJlYW0gQ0EgMTAeFw0yMzA2MDgxOTE3NDNaFw0yNTA5MDgxOTE3NDNaMEcxJzAl
BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEcMBoGA1UEAxMT
ZG93bnN0cmVhbSBjbGllbnQgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANLMps9HmotxSUQ83TO7EQwF7LJrCUXZFzTy0q1Qw8s8GXGV76RUWDHQSVZU
YXMElKbcU4NDTL9EY7WJDwwBp22ujgy+SBIoplVwSJIy3WRMyvpQMWy8CMPIWkTm
WlUIUZ4roZZF3yggji4vQWBl2s0OOZbncAQX9qNYXGDO+sari3qAz0wFE4PcWkB/
XWfWydbT8oj6OgHFahbNfHHr3y3gtcWIw9NrPFneFdNXuDiC7QSgb5Xh5EIVfUKI
GabVS6qR29COTLJ55ggdmbpbA6LI45H5+v/vuQaCfshnYa/NQlhIQK54hRjbAVzX
vRsH4ZIwClTBuczv9D0aSPH1K1ECAwEAAaNSMFAwDgYDVR0PAQH/BAQDAgWgMB0G
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAfBgNVHSMEGDAWgBTR9TQM/FI9
uD4QhnQZBotAjEBjCzANBgkqhkiG9w0BAQsFAAOCAYEAeIly4fUkmO1OQIvqhml5
7UA9/zAzSH8NcQKtI4FGn9XzgoQQc5XO94RHUb292v8Rcgt/HDJEp0LV/gsnJc39
xqh84Yo8OwiCFraIlEN/icdLN6Uz/iUuwo/Fo3JFnRbNmCm9G3JMHqHZG2sAFPgf
rfHiTbykRltuwIGVathGm1jDG2IxR/nm3tykhRQx/1pv9ab6siO+Mhqu4rRVoXw7
KcSb1h5mg2cHYzfby/Wj2nbJBWNyWlpmC7ZiUvXa9k9aTFUoPVHClAYZjmqecr32
+/QRWHABdO7560dbvVONoni9MoMEVtEUafZL904b5PC2nTFdNHtPmf3pz+mFYYqS
UfLcMfaES2ArGz7jjlaU1pnpjgFR+txGD38Jx9HOhwxyIWwrQwteqcYsoJsIwsnG
fcywkPmgRen1WkvoKBg9UNHx6NPH5Qe/+8gfVlsG2yrNdXR9Y78xy4oDgrVCawC6
MzqOfHrlNMYKmYvfYoTkYKHgKIiI7hYZNFLcaBmucEeu
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDo77dsqXWhNzop
Ah4fzjt4xQ6fevkjrB0gNHvmkkm9oIfgWZfy2f/Fs8qQAGae2QiWJzu+huYqOjNu
ysMf2xuFtm/LlGXtgysy7rqXk1MX2cfRPaENevwvFG89FY9WTaVqP9BQF6N3X9EA
xMHHb4yGN7LAbyf4ZGRgjqxmVv4DxQT1vAG4bcOGVO5h0HBVb7cLnSsHveAl0scr
6X1jRxu35tkeedqVTtfRaCIeh0NZ3hdHW3um2ee5mR9G1PdiCt3VrEDrwGeab0UV
y2ab7s7LBDvjVAiKjqEK6zIyT63cMHEwkcw2ltoNK84Pxjp6AAWzEjmEoRzjxLw9
KGOrywdFAgMBAAECggEAOCv2k6GZ2DK1N8Zm9UyKUulSSWsiQVP0Ahwl9Mg6OBxK
J/PzDIE9iLN+mZM/K/cyduMnKR7myjAWzxTeUQRJcsk31GQI4GnNNQ8UuBCy7QF7
ih3OhK2vurq2yIg2dJ1s0l5pIadRqCvW/tRz1mA0BvnPhVBI10F+YzqEoHlB8C5Q
6TfRq49zYTNQwrHJX23pmJFkmCyQF2VVUHnBhPPNgO1XzcUE9RvOmoXkcf9ITfF4
50SUPcSu8qT6vfSMf8ZXZqGpR21s7VO+ig9Pu7OuRDAMKJoYJXrVrQbWrrtKWiDb
uZokRKnRIs4zxx0VQOMuV910RcB2Fh8mJ4gbAVIjUQKBgQD0wiLgiyHP3YxuRqCV
dHlP1Ro+NJsxC67qVvVJ1FQ/Z1421TY5YIUIIJP0WOXXWTFUEbdLinM5pY+vrF13
2zhciLl584sQtfGYdUh9tKQowUJ96Cvfx48hqSfi+B+PdkOSlexGceYML+9bv/fj
7NZYBDRdI1ejmvdViDErBdlqJwKBgQDzopP2z9iSQ4gg5EPwoTeRrWyeApxMKarh
mOe6i22rcsN0bTYbwu7xWIHHI/mBCm4Plt9q10BPfYWwFyQyzj5mZTqR816sDTw8
HamxMmWivFsJ2BElNHfl40b5m/6ZSfJ6AR64ppezKLtFUtZd11g2emMTFCur2LBv
Fi86DPKCswKBgQDmwN0W67Vd/xb1pLqjDSOOI3BRc1FZBRXMs10gVQF0oWTOt2ce
4yOozOYQuXs/80QxaV5w5r/JepCj5BJe/Jv/iZusIPNcNtzmirRkZ1lZXxLsF2a9
3KvR5WPIdXtzyuDcI2KV664ikVLOIod4KILQim4/3SE0CtbimbsZP7OBgwKBgF69
sQZASvqNskkAGv9e4eoPY3aKk1b2uRGjUTF0eMA8D4+dzHW9Nwe5IDZdYe/xK116
kQTOEZ8Xs74HMbdkBlXxNXZ+Cimjt/G0o0FZ1LYUwt4wHHK4VYua9wWnyLi5TcZt
2xF5DvLHexN5JA036YC91PsdU+IukGWSXekYEdILAoGAP3CrbySzV1Po1yTX+kYt
tSb2Lhe9S6QfWKaA6hyc3Bj+tgk1kpyWRf/3fINd2n4viu0JcMTDI0fRfyZRav/B
Fjv9TSbu0myQ0LtW72BdZcvbtyIo14Wmy7typGhxOPncpYaIT1j4qc9p+Tzqth3k
YPgxmEn1uMhj/9WhgAtZR0M=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID3jCCAkagAwIBAgIRANcKfnUkdzDoL6WFkmhnB0wwDQYJKoZIhvcNAQELBQAw
OjEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMRgwFgYDVQQDEw9kb3du
c3RyZWFtIENBIDIwHhcNMjMwNjA5MDA0NjQwWhcNMjUwOTA5MDA0NjQwWjBHMScw
JQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxHDAaBgNVBAMT
E2Rvd25zdHJlYW0gY2xpZW50IDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDo77dsqXWhNzopAh4fzjt4xQ6fevkjrB0gNHvmkkm9oIfgWZfy2f/Fs8qQ
AGae2QiWJzu+huYqOjNuysMf2xuFtm/LlGXtgysy7rqXk1MX2cfRPaENevwvFG89
FY9WTaVqP9BQF6N3X9EAxMHHb4yGN7LAbyf4ZGRgjqxmVv4DxQT1vAG4bcOGVO5h
0HBVb7cLnSsHveAl0scr6X1jRxu35tkeedqVTtfRaCIeh0NZ3hdHW3um2ee5mR9G
1PdiCt3VrEDrwGeab0UVy2ab7s7LBDvjVAiKjqEK6zIyT63cMHEwkcw2ltoNK84P
xjp6AAWzEjmEoRzjxLw9KGOrywdFAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIFoDAd
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUCxQ2cBa5
YzqVzampiNCx8KwFFyQwDQYJKoZIhvcNAQELBQADggGBAJ1epspiLvScsM/kGlJo
w6AbRCXDFr3w00Y1eTXFPXO+lyB7o6yusKD36NUYUAh64fKH+nMILGZiwQChBRs6
d6BYLxU4ic8MC1utgcAVsqi8+2S3CFxnBuwjE5ilEDOoSSZWhMP/qXP7CC8/4Mr7
SpiqHCoOn6Rg7Ve+Xet/GbQ0qQ9KqIoJb2ZJlZxOh8IsNDAFhFOIYKJoNISSkpGq
6+/eTylbhVLigB6p8tMuNxbHN617Hg3XueKUPYjZngdaN2UCv2ZEdbyQVt4J3ghP
IYWBYybjBjNzRgQEi4db91aOGKlVxqPRKaBLwNkEdpNyycEHKaZHc0AsnYTQik7T
KZLwd6Yu/q207P4LKgeZXrr4LXxNJvC72Ipt5vohhOcPloiRIak9iTarfOieVOQ9
NvLyXFhiQGR87lo3HSUMQQoOCDG07VxKOoq1jbSmNqYWYBcvsOMPeVL9dPJv2qFj
D+hGzQiLMZqwI+HKRo3n5jdQGlkTZd0KXuHzjpcokp8BcA==
-----END CERTIFICATE-----

View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEPDCCAqSgAwIBAgIJAKmtj1u+hOdzMA0GCSqGSIb3DQEBCwUAMDoxHjAcBgNV
BAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEYMBYGA1UEAxMPZG93bnN0cmVhbSBD
QSAxMB4XDTIzMDYwODE4NTgyMloXDTMzMDYwODE4NTgyMlowOjEeMBwGA1UEChMV
bWtjZXJ0IGRldmVsb3BtZW50IENBMRgwFgYDVQQDEw9kb3duc3RyZWFtIENBIDEw
ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDue3fuI704DazewdWmKJQs
YGYR2ZapQQeQynXaqOhqMOLTc7M18uVOnfhvFVtUB5OCtxL2TMmy8/ytIQlU8CUc
bUo1AFcXu1MGORJNu5zbJymsrOE8fKqopb3muGNRM6tulIHhpRCcF3m8pKFBZBWs
CR7A2MhgKHJvd1yVMc6/GpO/RqIHiFAiCV9XguadKTwapPJ54vJwBDZoDM4/qA34
xFR1uCAzob0D4yFW/C7u57SMZDjSy2jxxZkcFQAvmRPPgzutaAHuRUUnPhw3f9PF
+DLNDeo6kXdS6aQOb/weCPl/VjlskXyvgNuzGE2xixZYBQwpXAE8AuBcXNvlxT0T
1oyoU8aggymnTFWnLmN/ipQ7+9CHS2+apFDG7nrf9q5UgLtRiVLOytoVxWDOhoY5
pqbS05aDjWXbXyPf2e318Ntjc6Hl7nSffHlCGsb/zqiJnJX6ti/k0VR1WHJZyu7e
CYeu+mtqNATrS7h+nBUMNZ9Bb1EIHQOJ/yyToULy/nECAwEAAaNFMEMwDgYDVR0P
AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNH1NAz8Uj24
PhCGdBkGi0CMQGMLMA0GCSqGSIb3DQEBCwUAA4IBgQBltym8hRgXSAaGTZAciCBc
sRtyEkQ584oHUiOmaKvITjnHys/EiETnNaxRw7t/69DKe5g4UaqgdlMwecjJk/Hl
jSvXI4mAUERkcIJIEJspMapsEp5QcTAlvskoXjNPFrOW+x0iOLdAM41x5kBDQRkc
+N2ie0ITJ5ZX530Ai4ukt76NZNIOio5xoHs1q170kn6xwfS12x1g7CksHlN5Mbw1
wtFFeLfQCZVXPNspH7LHJUkrULSTyhleZFJ3ZZqqT9oybpDUhdZB0nZJ6ZC1JiQo
2HMwIFV+OsEEG7fNzHhbVKaJmaiOiW2t/CpltebVLSTinz2LmZhzVFRT+y/cdhn3
5IsQHzGwEKKtL5XfqJjqWhry+mw/vb+Rze6yy9Li7FkBnetQq8Tb0a2u/UHyzqTA
NVhu1wgbRD93vnZqGOkb0gzMRPJC/KibNvFRfaeDXDOiW69Npm/xxXBO/My0CWF1
p7cQCkgpkStnWEmm/48WiwGcFWTC2W+mims7JcIpSpc=
-----END CERTIFICATE-----

View file

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEPDCCAqSgAwIBAgIJAPjvgLbEIVj/MA0GCSqGSIb3DQEBCwUAMDoxHjAcBgNV
BAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEYMBYGA1UEAxMPZG93bnN0cmVhbSBD
QSAyMB4XDTIzMDYwOTAwNDQzOFoXDTMzMDYwOTAwNDQzOFowOjEeMBwGA1UEChMV
bWtjZXJ0IGRldmVsb3BtZW50IENBMRgwFgYDVQQDEw9kb3duc3RyZWFtIENBIDIw
ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC/8Kog2Zz8e68EGpfiXN7u
Xgau38h63ydspucrjhtnSTWXtHO1hYLmUYWAewi79iGYzOuYgWCD3cFxd+tMKLrB
yoriJ3KioTtY0pmyLDJ1TXMSaFGgnZqjXHmjMvio0x/jQNkCbYkFBGQSZZvkA8sQ
m5AsRDeIUPkPlhFMnb2x4iRcLBP6zDNFfX+y1qSolKbh3K9/E3PT4Unja8gObzCJ
nrOcF5SBqTOjRHif/S/wZ9TSFWzLmqGLhq73RahyTiaYP46UvJhrNb5Mo9Hbb94/
4zS5B2Zuo4pshSZDWpqwvBecQN0VaLVvIymuSyg5TzuH4ktM0ptzv6rXinDla7rz
Mu/FrFVQPksOhTDt5UCSqODwPZiO7g5ST0s+jMpbp1XN8KP2prtElUWdabvHlb0M
D2E0hHVi444YkQxZaCoed2obrTB2Df2CwHATgFKvLF1SGS2Q9v0pbUc6Z+0o912b
nRfGzi2p7iBsWULuINI3nbNAzlmWPmGiwV1SY1Y0dU8CAwEAAaNFMEMwDgYDVR0P
AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAsUNnAWuWM6
lc2pqYjQsfCsBRckMA0GCSqGSIb3DQEBCwUAA4IBgQBU6YiRXQ4jkrqugtuLj2a5
AQ+URPlfkFFN0BDpWCIzV50w+Y1ZtH2HvGX44zDjbQTwv+AU4T+F75C8Pnc5yvYo
v6FIMOOZIrvilokyVf3dKRC3Y2cQac4u64aQk+XR/qjiYoFK0B9yw8UA3O7wA46b
ceoZUFZLc5oSsnB9tW72i8lEkBFt2X62rqSQNGYtzCV64bM+ezCsBYPaCIKW0ARB
0CbNFGoaPJzAuuGukvOcBDytJ3RJBXJ7l3626KNGxCLsRMcDcTxvXBf7gFWtetW9
kuofvlJMiPi3BDMl/FAE5ikj0UR47rjYUxM2SF6F+z8pEcPcePSYzClMECL9a/02
I12sEnU3Rf+RpwSTHSCjyXGtWl4dGSJlOElwrYMBAyX62dfFY9GEGgHCnyO1tj39
JIhgiIBEZsBL9LOOK8vTYzZ5kBkZ1NXh2Bj3nS/B/M5zotp4/S6P30Li44/Jbpvc
70fXruF69zwPMc5b3x7yX7hPLYHk0hm3BOWaodPI4t0=
-----END CERTIFICATE-----