345 lines
12 KiB
Go
345 lines
12 KiB
Go
|
package certutil
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/fatih/structs"
|
||
|
"github.com/hashicorp/vault/api"
|
||
|
)
|
||
|
|
||
|
// Tests converting back and forth between a CertBundle and a ParsedCertBundle.
|
||
|
//
|
||
|
// Also tests the GetSubjKeyID, GetOctalFormatted, and
|
||
|
// ParsedCertBundle.getSigner functions.
|
||
|
func TestCertBundleConversion(t *testing.T) {
|
||
|
cbuts := []*CertBundle{
|
||
|
refreshRSACertBundle(),
|
||
|
refreshECCertBundle(),
|
||
|
}
|
||
|
|
||
|
for _, cbut := range cbuts {
|
||
|
pcbut, err := cbut.ToParsedCertBundle()
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error converting to parsed cert bundle: %s", err)
|
||
|
}
|
||
|
|
||
|
err = compareCertBundleToParsedCertBundle(cbut, pcbut)
|
||
|
if err != nil {
|
||
|
t.Fatalf(err.Error())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTLSConfig(t *testing.T) {
|
||
|
cbut := refreshRSACertBundle()
|
||
|
|
||
|
pcbut, err := cbut.ToParsedCertBundle()
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error getting parsed cert bundle: %s", err)
|
||
|
}
|
||
|
|
||
|
usages := []TLSUsage{
|
||
|
TLSUnknown,
|
||
|
TLSClient,
|
||
|
TLSServer,
|
||
|
TLSClient | TLSServer,
|
||
|
}
|
||
|
|
||
|
for _, usage := range usages {
|
||
|
tlsConfig, err := pcbut.GetTLSConfig(usage)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error getting tls config: %s", err)
|
||
|
}
|
||
|
if tlsConfig == nil {
|
||
|
t.Fatalf("Got nil tls.Config")
|
||
|
}
|
||
|
|
||
|
if len(tlsConfig.Certificates) != 1 {
|
||
|
t.Fatalf("Unexpected length in config.Certificates")
|
||
|
}
|
||
|
|
||
|
// Length should be 2, since we passed in a CA
|
||
|
if len(tlsConfig.Certificates[0].Certificate) != 2 {
|
||
|
t.Fatalf("Did not find both certificates in config.Certificates.Certificate")
|
||
|
}
|
||
|
|
||
|
if tlsConfig.Certificates[0].Leaf != pcbut.Certificate {
|
||
|
t.Fatalf("Leaf certificate does not match parsed bundle's certificate")
|
||
|
}
|
||
|
|
||
|
if tlsConfig.Certificates[0].PrivateKey != pcbut.PrivateKey {
|
||
|
t.Fatalf("Config's private key does not match parsed bundle's private key")
|
||
|
}
|
||
|
|
||
|
switch usage {
|
||
|
case TLSServer | TLSClient:
|
||
|
if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.IssuingCA.RawSubject) != 0 {
|
||
|
t.Fatalf("CA certificate not in client cert pool as expected")
|
||
|
}
|
||
|
if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.IssuingCA.RawSubject) != 0 {
|
||
|
t.Fatalf("CA certificate not in root cert pool as expected")
|
||
|
}
|
||
|
case TLSServer:
|
||
|
if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.IssuingCA.RawSubject) != 0 {
|
||
|
t.Fatalf("CA certificate not in client cert pool as expected")
|
||
|
}
|
||
|
if tlsConfig.RootCAs != nil {
|
||
|
t.Fatalf("Found root pools in config object when not expected")
|
||
|
}
|
||
|
case TLSClient:
|
||
|
if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.IssuingCA.RawSubject) != 0 {
|
||
|
t.Fatalf("CA certificate not in root cert pool as expected")
|
||
|
}
|
||
|
if tlsConfig.ClientCAs != nil {
|
||
|
t.Fatalf("Found root pools in config object when not expected")
|
||
|
}
|
||
|
default:
|
||
|
if tlsConfig.RootCAs != nil || tlsConfig.ClientCAs != nil {
|
||
|
t.Fatalf("Found root pools in config object when not expected")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCertBundleParsing(t *testing.T) {
|
||
|
jsonBundle := refreshRSACertBundle()
|
||
|
jsonString, err := json.Marshal(jsonBundle)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error marshaling testing certbundle to JSON: %s", err)
|
||
|
}
|
||
|
pcbut, err := ParsePKIJSON(jsonString)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
||
|
}
|
||
|
err = compareCertBundleToParsedCertBundle(jsonBundle, pcbut)
|
||
|
if err != nil {
|
||
|
t.Fatalf(err.Error())
|
||
|
}
|
||
|
|
||
|
secret := &api.Secret{
|
||
|
Data: structs.New(jsonBundle).Map(),
|
||
|
}
|
||
|
pcbut, err = ParsePKIMap(secret.Data)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
||
|
}
|
||
|
err = compareCertBundleToParsedCertBundle(jsonBundle, pcbut)
|
||
|
if err != nil {
|
||
|
t.Fatalf(err.Error())
|
||
|
}
|
||
|
|
||
|
pemBundle := strings.Join([]string{
|
||
|
jsonBundle.Certificate,
|
||
|
jsonBundle.IssuingCA,
|
||
|
jsonBundle.PrivateKey,
|
||
|
}, "\n")
|
||
|
pcbut, err = ParsePEMBundle(pemBundle)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Error during JSON bundle handling: %s", err)
|
||
|
}
|
||
|
err = compareCertBundleToParsedCertBundle(jsonBundle, pcbut)
|
||
|
if err != nil {
|
||
|
t.Fatalf(err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func compareCertBundleToParsedCertBundle(cbut *CertBundle, pcbut *ParsedCertBundle) error {
|
||
|
if cbut == nil {
|
||
|
return fmt.Errorf("Got nil bundle")
|
||
|
}
|
||
|
if pcbut == nil {
|
||
|
return fmt.Errorf("Got nil parsed bundle")
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case pcbut.Certificate == nil:
|
||
|
return fmt.Errorf("Parsed bundle has nil certificate")
|
||
|
case pcbut.PrivateKey == nil:
|
||
|
return fmt.Errorf("Parsed bundle has nil private key")
|
||
|
case pcbut.IssuingCA == nil:
|
||
|
return fmt.Errorf("Parsed bundle has nil issuing CA")
|
||
|
}
|
||
|
|
||
|
switch cbut.PrivateKey {
|
||
|
case privRSAKeyPem:
|
||
|
if pcbut.PrivateKeyType != RSAPrivateKey {
|
||
|
return fmt.Errorf("Parsed bundle has wrong private key type")
|
||
|
}
|
||
|
case privECKeyPem:
|
||
|
if pcbut.PrivateKeyType != ECPrivateKey {
|
||
|
return fmt.Errorf("Parsed bundle has wrong private key type")
|
||
|
}
|
||
|
default:
|
||
|
return fmt.Errorf("Parsed bundle has unknown private key type")
|
||
|
}
|
||
|
|
||
|
subjKeyID, err := GetSubjKeyID(pcbut.PrivateKey)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Error when getting subject key id: %s", err)
|
||
|
}
|
||
|
if bytes.Compare(subjKeyID, pcbut.Certificate.SubjectKeyId) != 0 {
|
||
|
return fmt.Errorf("Parsed bundle private key does not match subject key id")
|
||
|
}
|
||
|
|
||
|
cb, err := pcbut.ToCertBundle()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Thrown error during parsed bundle conversion: %s\n\nInput was: %#v", err, *pcbut)
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case len(cb.Certificate) == 0:
|
||
|
return fmt.Errorf("Bundle has nil certificate")
|
||
|
case len(cb.PrivateKey) == 0:
|
||
|
return fmt.Errorf("Bundle has nil private key")
|
||
|
case len(cb.IssuingCA) == 0:
|
||
|
return fmt.Errorf("Bundle has nil issuing CA")
|
||
|
}
|
||
|
|
||
|
switch cb.PrivateKeyType {
|
||
|
case "rsa":
|
||
|
if pcbut.PrivateKeyType != RSAPrivateKey {
|
||
|
return fmt.Errorf("Bundle has wrong private key type")
|
||
|
}
|
||
|
if cb.PrivateKey != privRSAKeyPem {
|
||
|
return fmt.Errorf("Bundle private key does not match")
|
||
|
}
|
||
|
case "ec":
|
||
|
if pcbut.PrivateKeyType != ECPrivateKey {
|
||
|
return fmt.Errorf("Bundle has wrong private key type")
|
||
|
}
|
||
|
if cb.PrivateKey != privECKeyPem {
|
||
|
return fmt.Errorf("Bundle private key does not match")
|
||
|
}
|
||
|
default:
|
||
|
return fmt.Errorf("Bundle has unknown private key type")
|
||
|
}
|
||
|
|
||
|
if cb.SerialNumber != GetOctalFormatted(pcbut.Certificate.SerialNumber.Bytes(), ":") {
|
||
|
return fmt.Errorf("Bundle serial number does not match")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func refreshRSACertBundle() *CertBundle {
|
||
|
return &CertBundle{
|
||
|
Certificate: certRSAPem,
|
||
|
PrivateKey: privRSAKeyPem,
|
||
|
IssuingCA: issuingCaPem,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func refreshECCertBundle() *CertBundle {
|
||
|
return &CertBundle{
|
||
|
Certificate: certECPem,
|
||
|
PrivateKey: privECKeyPem,
|
||
|
IssuingCA: issuingCaPem,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
privRSAKeyPem = `-----BEGIN RSA PRIVATE KEY-----
|
||
|
MIIEowIBAAKCAQEAt3ZJUaztCRiVg87P0y8T7QMNFQi61BCSIKepxXXWc7zi5JJS
|
||
|
MfQAstXJEqBYiShsSpYm6soiT6hX074t7wQAHGS3+u7qNogWpmTAUTUnNIM+QCxH
|
||
|
2Nc/kzYxaWajupVzgGvLeiqU3d4tIUk/ZkftvWJryr2hZc8zEN3C4pGS/2F+RQ+z
|
||
|
Ov+BpAI1BdbQGhF7m92vn6KS/iWsqmwHG9oChgvWeBHjWUI8qGauBc+it4S5RxfN
|
||
|
8JJIBXUIZtbaqFZzgjv8kUDyqoQGvkY/4Ce1K0bFJsM7wmMPv+5QscIBF4KWgN0k
|
||
|
TSMPPXfnn/QIAfhaYQkT9MjwGr6+B3SODNGCTQIDAQABAoIBAHtSwhprGbNhmS/P
|
||
|
F5ioLsbFpEedZKkksnXM/qxDd/K45/Qp/6KgmM+eMdmZe6pHR/QjVunBEqtlSBSH
|
||
|
5KykjcaIVbwSWdJqTH9xfm2YQ1BjYLcWjP1QQ+YbKb/mRO0phUiwLUlj0koKDWAw
|
||
|
srN4anFB9Z+FNTcQvwz5ZQWUQbH0neQtWO1nDvLsScgu1kchoEzJEJaFOQ1+HfGe
|
||
|
WxD766fZyqZQi5+cLrhOqHOGSlO+IFVe0hguiEHFr9LEPTXXkZtOR4wTf7j1Us8s
|
||
|
1KQ/jv01sx9S7HEbZJurzIjS23OywEUdJd1EsIE2lJV2QUwSiAsPYZOSQZlgOGzP
|
||
|
VRKVkGkCgYEA1u+pVP2r+xSxYy8KcdcRCdGGBh00VLx1yJRHWZ5YjF56hp0R0cG+
|
||
|
xGLar5KCdBpr4jJnQGIrx8lw3SDCt4EXlxgJxitXlBtiKByM7/mYRRfURr9WMRr4
|
||
|
88GQlWDbo2Xalnuac0qlkFqVIg0BaW+Z15A/E1L69aUxaR0ozlA9Jl8CgYEA2oNA
|
||
|
5F2otqzo9eNYucNAjihVhATd11DECQvbIQp/0bEJe0Znnzq/QIGIOVapC0VKGBwB
|
||
|
P5DuLL1P/nTPjjE/ZhjFuhMNM5PzC6obAjBh+gCpc+c+21Qerv7RKUTi2sGTzRHu
|
||
|
lpccRDfuF8bhzD6lAo50FpSmPE/ovZzb9+IsXtMCgYBVnUdM9HKh47846870Q5+k
|
||
|
0pHZM57ZtewQxoeZOgq5dxTFNCGZ9NvBLENBtlFCYBfjFQKt0azwutu7KUaGg+Ra
|
||
|
qheSmUccVsAFjEHTgQ9XTkOfHq39h2ns5ohqCBfVAUhNstR14iEK3BoVYyrRzcNw
|
||
|
6yNE1kPivzdsUFIlxC5nbwKBgDUUjT7sQX+eoTiZ8YOumo/t3Fglln4ncHeCGcj8
|
||
|
8+/MQbFgeOuFKdBRpvXGx2mle0pAA02dtz3G/xeg6IpyDCSQ//cjiaFt3yyGNeli
|
||
|
N2qznnY5RluhI5L+83BC+5iITY8TPBH4wzUPIRdFiLREw3DLigeyNG+SOcdVw1mD
|
||
|
56NhAoGBALFh3sGkhvPiI/G/i/5tGZVA/dS/4DVXOoHW43+ZDHWEwqiN6vTf/VVi
|
||
|
cm+8kcfLY1E5fSf/4e7mIQq7o5qVn9Y3HWsajS1FFeznJjPj4Jaa1HvegNcycAzs
|
||
|
XOQ7xy23/8wUupgNeD1mFdSFCXQ3UedsJuVBHsElPc5W74q4F4+F
|
||
|
-----END RSA PRIVATE KEY-----`
|
||
|
|
||
|
certRSAPem = `-----BEGIN CERTIFICATE-----
|
||
|
MIID+jCCAuSgAwIBAgIUcFCL9ESWTKLE6RqSYV7iZ78f1KcwCwYJKoZIhvcNAQEL
|
||
|
MBsxGTAXBgNVBAMMEFZhdWx0IFRlc3RpbmcgQ0EwHhcNMTUwNjE5MTcyMzA0WhcN
|
||
|
MTUwNzAzMTcyMzA0WjBPMRIwEAYDVQQDEwlsb2NhbGhvc3QxOTA3BgNVBAUTMDY0
|
||
|
MTIwMzIxNzY3NTk2MjQyMjU0OTg5MTUxMzAyMjg1NzQ0NTc0OTkzMjY3NjI2MzCC
|
||
|
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALd2SVGs7QkYlYPOz9MvE+0D
|
||
|
DRUIutQQkiCnqcV11nO84uSSUjH0ALLVyRKgWIkobEqWJurKIk+oV9O+Le8EABxk
|
||
|
t/ru6jaIFqZkwFE1JzSDPkAsR9jXP5M2MWlmo7qVc4Bry3oqlN3eLSFJP2ZH7b1i
|
||
|
a8q9oWXPMxDdwuKRkv9hfkUPszr/gaQCNQXW0BoRe5vdr5+ikv4lrKpsBxvaAoYL
|
||
|
1ngR41lCPKhmrgXPoreEuUcXzfCSSAV1CGbW2qhWc4I7/JFA8qqEBr5GP+AntStG
|
||
|
xSbDO8JjD7/uULHCAReCloDdJE0jDz1355/0CAH4WmEJE/TI8Bq+vgd0jgzRgk0C
|
||
|
AwEAAaOCAQQwggEAMA4GA1UdDwEB/wQEAwIAqDAdBgNVHSUEFjAUBggrBgEFBQcD
|
||
|
AQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUZHtkxSX5GVAYo3h8
|
||
|
B8TGJ36vTH4wHwYDVR0jBBgwFoAU5JzeXhccaOWk5X6vhuGV7NwLMVkwTgYDVR0R
|
||
|
BEcwRYIJbG9jYWxob3N0gg9mb28uZXhhbXBsZS5jb22CD2Jhci5leGFtcGxlLmNv
|
||
|
bYcEgAMFBocQ/gEAAAAAAAAAAAAAAAAAATAxBgNVHR8EKjAoMCagJKAihiBodHRw
|
||
|
Oi8vbG9jYWxob3N0OjgyMDAvdjEvcGtpL2NybDALBgkqhkiG9w0BAQsDggEBAAps
|
||
|
W2ZDOAfwWufclmGPHt+YRXXSTWvPfF/cBeg5Oq/F8qUCVMHqdE/+EDWzh+Kz8jp0
|
||
|
ggklnh76frROvHxygbVD2Hs9ACzgpnHPy8FYOdN+OblvAMtGlMyTq/5XheasmWdY
|
||
|
FFH/ft6tReG7BjGgfdyH8yL/R6b/RtU/qPlowfrZgAzOv7/ou6yRlfjIhsWbne/S
|
||
|
SQuGASRxRp3Txp7Cf3RcdCwVuiQhFLVeVHH+atTc8v2DO/CLfi9enQo96qUku8Bd
|
||
|
b5QPKIV0sQdtwGV5fo2JGd25rWpCo6TkAM9EeNkcVze8wgArSRk8zLkvM/5z+5sn
|
||
|
Qaka08px4wljGQ2Wc88=
|
||
|
-----END CERTIFICATE-----`
|
||
|
|
||
|
privECKeyPem = `-----BEGIN EC PRIVATE KEY-----
|
||
|
MGgCAQEEHM3nuYLlrvawBN9hGVcu9mpaCEr7LMe44a7oQOygBwYFK4EEACGhPAM6
|
||
|
AATBZ3VXwBE9oeSREpM5b25PW6WiuLb4EXWpKZyjj552QYKYe7QBuGe9wvvgOeCB
|
||
|
ovN3tSuGKzTiUA==
|
||
|
-----END EC PRIVATE KEY-----`
|
||
|
|
||
|
certECPem = `-----BEGIN CERTIFICATE-----
|
||
|
MIIDJDCCAg6gAwIBAgIUM3J02tw0ZvpHUVHv6t8kcoft2/MwCwYJKoZIhvcNAQEL
|
||
|
MBsxGTAXBgNVBAMMEFZhdWx0IFRlc3RpbmcgQ0EwHhcNMTUwNjE5MTcyODQyWhcN
|
||
|
MTUwNzAzMTcyODQyWjBPMRIwEAYDVQQDEwlsb2NhbGhvc3QxOTA3BgNVBAUTMDI5
|
||
|
MzcxMDk5Mzc2NDA3NDYyNjg3MTQzODcwMjc3Njg1OTkzMTkyMzkxNjM4MTE3MTBO
|
||
|
MBAGByqGSM49AgEGBSuBBAAhAzoABMFndVfAET2h5JESkzlvbk9bpaK4tvgRdakp
|
||
|
nKOPnnZBgph7tAG4Z73C++A54IGi83e1K4YrNOJQo4IBBDCCAQAwDgYDVR0PAQH/
|
||
|
BAQDAgCoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8E
|
||
|
AjAAMB0GA1UdDgQWBBQiFoWDvInznUGjdJPjBAyoxIkQITAfBgNVHSMEGDAWgBTk
|
||
|
nN5eFxxo5aTlfq+G4ZXs3AsxWTBOBgNVHREERzBFgglsb2NhbGhvc3SCD2Zvby5l
|
||
|
eGFtcGxlLmNvbYIPYmFyLmV4YW1wbGUuY29thwSAAwUGhxD+AQAAAAAAAAAAAAAA
|
||
|
AAABMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9sb2NhbGhvc3Q6ODIwMC92MS9w
|
||
|
a2kvY3JsMAsGCSqGSIb3DQEBCwOCAQEA0RU18OdSdt2k4FKWyUS7EhVFOybiUHof
|
||
|
1n9EeBoxd7fEP/IuQnJGr3CPV5LRFdHRxkihf4N5bRjsst7cqczaIZZLWkAj+P/2
|
||
|
JxBqv2Hm57dwaw2gtwt3GcYN/5j76fYaoZOgPMqas72vYgnBgdKQs8GYSoy7BVpC
|
||
|
x3nTYHwlOF+sM4wuVSi78lwkcgADF5GIWXrM3tYilmcT9fNbUgSvcVWdNTRJ0W+m
|
||
|
S2AF+4eby5PC9U8eIoCnZPRNmH0jZbNWzZyD0hDhBrDlaEbS2QXKRURPHzht/SqN
|
||
|
nWWcpQG3B8EI7p749dP5L+idi3ajHIH8vm/PK+o5TRrcHB585MlErQ==
|
||
|
-----END CERTIFICATE-----`
|
||
|
|
||
|
issuingCaPem = `-----BEGIN CERTIFICATE-----
|
||
|
MIIDUTCCAjmgAwIBAgIJAKM+z4MSfw2mMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||
|
BAMMEFZhdWx0IFRlc3RpbmcgQ0EwHhcNMTUwNjAxMjA1MTUzWhcNMjUwNTI5MjA1
|
||
|
MTUzWjAbMRkwFwYDVQQDDBBWYXVsdCBUZXN0aW5nIENBMIIBIjANBgkqhkiG9w0B
|
||
|
AQEFAAOCAQ8AMIIBCgKCAQEA1eKB2nFbRqTFs7KyZjbzB5VRCBbnLZfEXVP1c3bH
|
||
|
e+YGjlfl34cy52dmancUzOf1/Jfo+VglocjTLVy5wHSGJwQYs8b6pEuuvAVo/6wU
|
||
|
L5Z7ZlQDR4kDe5Q+xgoRT6Bi/Bs57E+fNYgyUq/YAUY5WLuC+ZliCbJkLnb15Itu
|
||
|
P1yVUTDXTYORRE3qJS5RRol8D3QvteG9LyPEc7C+jsm5iBCagyxluzU0dnEOib5q
|
||
|
7xwZncoMbQz+rZH3QnwOij41FOGRPazrD5Mv6xLBkFnE5VAJ+GIgvd4bpOwvYMuo
|
||
|
fvF4PS7SFzxkGssMLlICap6PFpKz86DpAoDxPuoZeOhU4QIDAQABo4GXMIGUMB0G
|
||
|
A1UdDgQWBBTknN5eFxxo5aTlfq+G4ZXs3AsxWTAfBgNVHSMEGDAWgBTknN5eFxxo
|
||
|
5aTlfq+G4ZXs3AsxWTAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vbG9jYWxob3N0
|
||
|
OjgyMDAvdjEvcGtpL2NybDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
|
||
|
BjANBgkqhkiG9w0BAQsFAAOCAQEAsINcA4PZm+OyldgNrwRVgxoSrhV1I9zszhc9
|
||
|
VV340ZWlpTTxFKVb/K5Hg+jMF9tv70X1HwlYdlutE6KdrsA3gks5zanh4/3zlrYk
|
||
|
ABNBmSD6SSU2HKX1bFCBAAS3YHONE5o1K5tzwLsMl5uilNf+Wid3NjFnQ4KfuYI5
|
||
|
loN/opnM6+a/O3Zua8RAuMMAv9wyqwn88aVuLvVzDNSMe5qC5kkuLGmRkNgY06rI
|
||
|
S/fXIHIOldeQxgYCqhdVmcDWJ1PtVaDfBsKVpRg1GRU8LUGw2E4AY+twd+J2FBfa
|
||
|
G/7g4koczXLoUM3OQXd5Aq2cs4SS1vODrYmgbioFsQ3eDHd1fg==
|
||
|
-----END CERTIFICATE-----`
|
||
|
)
|