From 38de6f078287079faa115d2a7a9ac8f6313296e4 Mon Sep 17 00:00:00 2001 From: "Chaudhry, Faisal" Date: Thu, 11 Nov 2021 18:42:48 -0500 Subject: [PATCH] process pem file with multiple certs when ca contains a chain ( root and sub ca ) and push pem file with chain to nodes instead of only first cert in pem file --- pki/cert/pem.go | 12 +++- pki/util.go | 24 ++++--- pki/util_test.go | 175 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 pki/util_test.go diff --git a/pki/cert/pem.go b/pki/cert/pem.go index b99e3665..da725bc7 100644 --- a/pki/cert/pem.go +++ b/pki/cert/pem.go @@ -23,6 +23,7 @@ import ( "encoding/pem" "errors" "fmt" + "strings" ) const ( @@ -62,7 +63,7 @@ func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte { return pem.EncodeToMemory(&block) } -// EncodeCertPEM returns PEM-endcoded certificate data +// EncodeCertPEM returns PEM-encoded certificate data func EncodeCertPEM(cert *x509.Certificate) []byte { block := pem.Block{ Type: CertificateBlockType, @@ -71,6 +72,15 @@ func EncodeCertPEM(cert *x509.Certificate) []byte { return pem.EncodeToMemory(&block) } +// EncodeCertsPEM returns PEM-encoded certificate data for multiple certs +func EncodeCertsPEM(certs []*x509.Certificate) string { + var pemList []string + for _, c := range certs { + pemList = append(pemList, string(EncodeCertPEM(c))) + } + return strings.Join(pemList, "\n") +} + // ParsePrivateKeyPEM returns a private key parsed from a PEM block in the supplied data. // Recognizes PEM blocks for "EC PRIVATE KEY", "RSA PRIVATE KEY", or "PRIVATE KEY" func ParsePrivateKeyPEM(keyData []byte) (interface{}, error) { diff --git a/pki/util.go b/pki/util.go index 784cf325..b2d7d534 100644 --- a/pki/util.go +++ b/pki/util.go @@ -217,7 +217,9 @@ func (c *CertificatePKI) ToEnv() []string { if c.Key != nil { env = append(env, c.KeyToEnv()) } - if c.Certificate != nil { + if len(c.CertificatePEM) > 0 { + env = append(env, fmt.Sprintf("%s=%s", c.EnvName, c.CertificatePEM)) + } else if c.Certificate != nil { env = append(env, c.CertToEnv()) } if c.Config != "" && c.ConfigEnvName != "" { @@ -287,6 +289,9 @@ func GetConfigTempPath(name string) string { } func ToCertObject(componentName, commonName, ouName string, certificate *x509.Certificate, key *rsa.PrivateKey, csrASN1 []byte) CertificatePKI { + return ToCertObjectWithCertPEM(componentName, commonName, ouName, certificate, key, csrASN1, "") +} +func ToCertObjectWithCertPEM(componentName, commonName, ouName string, certificate *x509.Certificate, key *rsa.PrivateKey, csrASN1 []byte, certPEM string) CertificatePKI { var config, configPath, configEnvName, certificatePEM, keyPEM string var csr *x509.CertificateRequest var csrPEM []byte @@ -299,7 +304,9 @@ func ToCertObject(componentName, commonName, ouName string, certificate *x509.Ce caCertPath := GetCertPath(CACertName) path := GetCertPath(componentName) keyPath := GetKeyPath(componentName) - if certificate != nil { + if len(certPEM) > 0 { + certificatePEM = certPEM + } else if certificate != nil { certificatePEM = string(cert.EncodeCertPEM(certificate)) } if key != nil { @@ -599,7 +606,7 @@ func ReadCertsAndKeysFromDir(certDir string) (map[string]CertificatePKI, error) logrus.Debugf("[certificates] reading file %s from directory [%s]", file.Name(), certDir) if strings.HasSuffix(file.Name(), ".pem") && !strings.HasSuffix(file.Name(), "-key.pem") && !strings.HasSuffix(file.Name(), "-csr.pem") { // fetching cert - cert, err := getCertFromFile(certDir, file.Name()) + certs, err := getCertsFromFile(certDir, file.Name()) if err != nil { return nil, err } @@ -609,7 +616,9 @@ func ReadCertsAndKeysFromDir(certDir string) (map[string]CertificatePKI, error) if err != nil { return nil, err } - certMap[certName] = ToCertObject(certName, getCommonName(certName), getOUName(certName), cert, key, nil) + firstCert := certs[0] + certPEM := cert.EncodeCertsPEM(certs) + certMap[certName] = ToCertObjectWithCertPEM(certName, getCommonName(certName), getOUName(certName), firstCert, key, nil, string(certPEM)) } } @@ -636,8 +645,7 @@ func getOUName(certName string) string { } } -func getCertFromFile(certDir string, fileName string) (*x509.Certificate, error) { - var certificate *x509.Certificate +func getCertsFromFile(certDir string, fileName string) ([]*x509.Certificate, error) { certPEM, _ := ioutil.ReadFile(filepath.Join(certDir, fileName)) if len(certPEM) > 0 { logrus.Debugf("Certificate file [%s/%s] content is greater than 0", certDir, fileName) @@ -645,9 +653,9 @@ func getCertFromFile(certDir string, fileName string) (*x509.Certificate, error) if err != nil { return nil, fmt.Errorf("failed to read certificate [%s]: %v", fileName, err) } - certificate = certificates[0] + return certificates, nil } - return certificate, nil + return nil, nil } func getKeyFromFile(certDir string, fileName string) (*rsa.PrivateKey, error) { diff --git a/pki/util_test.go b/pki/util_test.go new file mode 100644 index 00000000..03659435 --- /dev/null +++ b/pki/util_test.go @@ -0,0 +1,175 @@ +package pki + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "io/ioutil" + "os" + "regexp" + "testing" +) + +func TestReadCertsAndKeysFromDir(t *testing.T) { + + comments := regexp.MustCompile(`(subject|issuer)=.+[\r\n]+`) + certDir, err := ioutil.TempDir("", "certs-") + if err != nil { + t.Fatal(err) + } + for name, pem := range testPemFiles { + if err := ioutil.WriteFile(fmt.Sprintf("%s/%s.pem", certDir, name), []byte(pem), 0644); err != nil { + t.Fatal(err) + } + } + certs, err := ReadCertsAndKeysFromDir(certDir) + if err != nil { + t.Fatal(err) + } + for _, c := range certs { + received := c.CertificatePEM + expected := comments.ReplaceAllString(testPemFiles[c.Name], "") + assert.Equal(t, expected, received) + } + defer os.Remove(certDir) // clean up +} + +var testPemFiles = map[string]string{ + "sub-ca": `-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUPDmvvihp1mPMKGBYvrF8bOZe7h0wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAg +Fw0yMTA2MzAyMjE1MDBaGA8yMDUxMDYyMzIyMTUwMFowWTELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAKBgNVBAoTA0s4UzEMMAoGA1UE +CxMDUktFMRMwEQYDVQQDEwpSS0UgU3ViIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAwOHE6HJXPNZEHOtlH3rM4SJlS7pFtHjoSsc3+9VKZbmSwbap +lL9I07q3WIVdVaW/Rnua3xg7FsvdeebhrTD3n8VKsduhovStPDreq2cNe51k93Mw +m6L8yxBEIHIv/up/TmRBlkyXp93hWJMDrA+K9Oe/yGMh7m3AgEMAVcK+AexNuyTV +tanHiqMlMYLLYwEZTI91ZFWJBkZgmNg/dk5LHAt7EKzeXH3DhFMGwqlneIcaogFq +R4lW4xPKAF9gv0LbR/trjUKr+K+0qairut4EFDcYjohP2L15GUYa9ZmyiTH0OMCh +uAjF9Dxe+pae7wYMQl36dGt8eYT1UEhscBLzfwIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUkXe0wifbup4B8zFm +u6WPUHkIPsEwHwYDVR0jBBgwFoAU3a3eVyFyQxfIuuokDxSWNiFEbQcwDQYJKoZI +hvcNAQELBQADggEBAJ4H0SHXCQRBtTGFwpGeCco8m4Tfnew2/64+Ua7BTzEPP1Zm +y7n5Xw40wJ5iuC87s8Ngs1vPjN9/peDrcmd8uN0sjW0pnpQ8fDl4Ks7fHlasBLiU +gTy01uux+J4vW+FM5rs/BJUxwA/KP8JEStpSQxrUEKxffGNeb5LH+qQHl7f9+wxE +E/ePm+2k0e8tgimHpno4ZUxScXTOSciN5I1pTfO7LxgQUGdhKXlXGQzZsO/EwqpG +n7/oqtviNudU+inIGaeKhvjTVBD9pWu57oAEieTd3quLgeKiOpCEvXQp9vFRky4R +m392kbtzI0HWYLjZAnn0B6pEKo4iDoKvdOpSln0= +-----END CERTIFICATE----- +`, + "root-ca": `-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUKRrWt11PR7LF/tNEVq+nowy9Hh8wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAe +Fw0yMTA2MzAyMjE1MDBaFw0zMTA2MjgyMjE1MDBaMFoxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJOWTEMMAoGA1UEBxMDTElDMQwwCgYDVQQKEwNLOFMxDDAKBgNVBAsT +A1JLRTEUMBIGA1UEAxMLUktFIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCrHoz1iRA0ZekM3+G8M/mHCtTQMOVeZQvea0E9v47VPJ7iSGKp +/nyK4RPmbhch29nhtWRnQaqteJYVc3LaPk4MENac/UKYjwGlhhjYYumE/u7hTriv +7DPbrWfiDHyKHeBIiodZ6j45SaewWpU506dom0Yak09ltBgYipRAyJdw9nsnRcLA +y9tsne3Zj+mqC9myCKCEwfYq+i7IfkAgoU0iXrC6CBL9RjoV7o35SSS5Y8gEJqZH +gpVNbSYxA1hek+INW0Dx2lSferafsOdT8rYrV954cMu0fpQzU4FvUgbTb8lWQMx7 +Uni0NzO8YneK4ZcACisTDFMMlXH3Y+9cS7E/AgMBAAGjQjBAMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTdrd5XIXJDF8i66iQPFJY2 +IURtBzANBgkqhkiG9w0BAQsFAAOCAQEACRUl4VO1lRRN01lNf3RlN1mquNLBATxc +lRCoawrWwkYG5yXgUr41TbxrNVVHU1AnDhPRa2HdbT0zJ8afC8HMFexSjdhOPtnQ +R6JxREbuMQvWe/7OsTPR2yVHtw2kcn3FzbOL2/yb1DDzTEihSUoLCLr3Jnop6rPD +6iRAnqUegKckqKrf0ZztsIDS+a/azPpaUxfqmfQrRJzuZ6AWVQYWb4hDYVGg7gQK +5BPc30ireJ/juICjIhsQp/IJXVwQgf7N075x65rwWahHhRDYd/T5GG/xQoGw+fXB +QPGQKWLa3Z4YYqGAIZEYrOGQPty2VDlvzTe3XvlXf+wWhaTaZnl4pA== +-----END CERTIFICATE----- +`, + "ca-bundle": `-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUPDmvvihp1mPMKGBYvrF8bOZe7h0wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAg +Fw0yMTA2MzAyMjE1MDBaGA8yMDUxMDYyMzIyMTUwMFowWTELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAKBgNVBAoTA0s4UzEMMAoGA1UE +CxMDUktFMRMwEQYDVQQDEwpSS0UgU3ViIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAwOHE6HJXPNZEHOtlH3rM4SJlS7pFtHjoSsc3+9VKZbmSwbap +lL9I07q3WIVdVaW/Rnua3xg7FsvdeebhrTD3n8VKsduhovStPDreq2cNe51k93Mw +m6L8yxBEIHIv/up/TmRBlkyXp93hWJMDrA+K9Oe/yGMh7m3AgEMAVcK+AexNuyTV +tanHiqMlMYLLYwEZTI91ZFWJBkZgmNg/dk5LHAt7EKzeXH3DhFMGwqlneIcaogFq +R4lW4xPKAF9gv0LbR/trjUKr+K+0qairut4EFDcYjohP2L15GUYa9ZmyiTH0OMCh +uAjF9Dxe+pae7wYMQl36dGt8eYT1UEhscBLzfwIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUkXe0wifbup4B8zFm +u6WPUHkIPsEwHwYDVR0jBBgwFoAU3a3eVyFyQxfIuuokDxSWNiFEbQcwDQYJKoZI +hvcNAQELBQADggEBAJ4H0SHXCQRBtTGFwpGeCco8m4Tfnew2/64+Ua7BTzEPP1Zm +y7n5Xw40wJ5iuC87s8Ngs1vPjN9/peDrcmd8uN0sjW0pnpQ8fDl4Ks7fHlasBLiU +gTy01uux+J4vW+FM5rs/BJUxwA/KP8JEStpSQxrUEKxffGNeb5LH+qQHl7f9+wxE +E/ePm+2k0e8tgimHpno4ZUxScXTOSciN5I1pTfO7LxgQUGdhKXlXGQzZsO/EwqpG +n7/oqtviNudU+inIGaeKhvjTVBD9pWu57oAEieTd3quLgeKiOpCEvXQp9vFRky4R +m392kbtzI0HWYLjZAnn0B6pEKo4iDoKvdOpSln0= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUKRrWt11PR7LF/tNEVq+nowy9Hh8wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAe +Fw0yMTA2MzAyMjE1MDBaFw0zMTA2MjgyMjE1MDBaMFoxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJOWTEMMAoGA1UEBxMDTElDMQwwCgYDVQQKEwNLOFMxDDAKBgNVBAsT +A1JLRTEUMBIGA1UEAxMLUktFIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCrHoz1iRA0ZekM3+G8M/mHCtTQMOVeZQvea0E9v47VPJ7iSGKp +/nyK4RPmbhch29nhtWRnQaqteJYVc3LaPk4MENac/UKYjwGlhhjYYumE/u7hTriv +7DPbrWfiDHyKHeBIiodZ6j45SaewWpU506dom0Yak09ltBgYipRAyJdw9nsnRcLA +y9tsne3Zj+mqC9myCKCEwfYq+i7IfkAgoU0iXrC6CBL9RjoV7o35SSS5Y8gEJqZH +gpVNbSYxA1hek+INW0Dx2lSferafsOdT8rYrV954cMu0fpQzU4FvUgbTb8lWQMx7 +Uni0NzO8YneK4ZcACisTDFMMlXH3Y+9cS7E/AgMBAAGjQjBAMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTdrd5XIXJDF8i66iQPFJY2 +IURtBzANBgkqhkiG9w0BAQsFAAOCAQEACRUl4VO1lRRN01lNf3RlN1mquNLBATxc +lRCoawrWwkYG5yXgUr41TbxrNVVHU1AnDhPRa2HdbT0zJ8afC8HMFexSjdhOPtnQ +R6JxREbuMQvWe/7OsTPR2yVHtw2kcn3FzbOL2/yb1DDzTEihSUoLCLr3Jnop6rPD +6iRAnqUegKckqKrf0ZztsIDS+a/azPpaUxfqmfQrRJzuZ6AWVQYWb4hDYVGg7gQK +5BPc30ireJ/juICjIhsQp/IJXVwQgf7N075x65rwWahHhRDYd/T5GG/xQoGw+fXB +QPGQKWLa3Z4YYqGAIZEYrOGQPty2VDlvzTe3XvlXf+wWhaTaZnl4pA== +-----END CERTIFICATE----- +`, + "ca-bundle-with-comments": `subject=CN=RKE Sub CA, OU=RKE, O=K8S, L=LIC, ST=NY, C=US +subject=CN=RKE Root CA, OU=RKE, O=K8S, L=LIC, ST=NY, C=US +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUPDmvvihp1mPMKGBYvrF8bOZe7h0wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAg +Fw0yMTA2MzAyMjE1MDBaGA8yMDUxMDYyMzIyMTUwMFowWTELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAKBgNVBAoTA0s4UzEMMAoGA1UE +CxMDUktFMRMwEQYDVQQDEwpSS0UgU3ViIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAwOHE6HJXPNZEHOtlH3rM4SJlS7pFtHjoSsc3+9VKZbmSwbap +lL9I07q3WIVdVaW/Rnua3xg7FsvdeebhrTD3n8VKsduhovStPDreq2cNe51k93Mw +m6L8yxBEIHIv/up/TmRBlkyXp93hWJMDrA+K9Oe/yGMh7m3AgEMAVcK+AexNuyTV +tanHiqMlMYLLYwEZTI91ZFWJBkZgmNg/dk5LHAt7EKzeXH3DhFMGwqlneIcaogFq +R4lW4xPKAF9gv0LbR/trjUKr+K+0qairut4EFDcYjohP2L15GUYa9ZmyiTH0OMCh +uAjF9Dxe+pae7wYMQl36dGt8eYT1UEhscBLzfwIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUkXe0wifbup4B8zFm +u6WPUHkIPsEwHwYDVR0jBBgwFoAU3a3eVyFyQxfIuuokDxSWNiFEbQcwDQYJKoZI +hvcNAQELBQADggEBAJ4H0SHXCQRBtTGFwpGeCco8m4Tfnew2/64+Ua7BTzEPP1Zm +y7n5Xw40wJ5iuC87s8Ngs1vPjN9/peDrcmd8uN0sjW0pnpQ8fDl4Ks7fHlasBLiU +gTy01uux+J4vW+FM5rs/BJUxwA/KP8JEStpSQxrUEKxffGNeb5LH+qQHl7f9+wxE +E/ePm+2k0e8tgimHpno4ZUxScXTOSciN5I1pTfO7LxgQUGdhKXlXGQzZsO/EwqpG +n7/oqtviNudU+inIGaeKhvjTVBD9pWu57oAEieTd3quLgeKiOpCEvXQp9vFRky4R +m392kbtzI0HWYLjZAnn0B6pEKo4iDoKvdOpSln0= +-----END CERTIFICATE----- + +subject=CN=RKE Root CA, OU=RKE, O=K8S, L=LIC, ST=NY, C=US +-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUKRrWt11PR7LF/tNEVq+nowy9Hh8wDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk5ZMQwwCgYDVQQHEwNMSUMxDDAK +BgNVBAoTA0s4UzEMMAoGA1UECxMDUktFMRQwEgYDVQQDEwtSS0UgUm9vdCBDQTAe +Fw0yMTA2MzAyMjE1MDBaFw0zMTA2MjgyMjE1MDBaMFoxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJOWTEMMAoGA1UEBxMDTElDMQwwCgYDVQQKEwNLOFMxDDAKBgNVBAsT +A1JLRTEUMBIGA1UEAxMLUktFIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCrHoz1iRA0ZekM3+G8M/mHCtTQMOVeZQvea0E9v47VPJ7iSGKp +/nyK4RPmbhch29nhtWRnQaqteJYVc3LaPk4MENac/UKYjwGlhhjYYumE/u7hTriv +7DPbrWfiDHyKHeBIiodZ6j45SaewWpU506dom0Yak09ltBgYipRAyJdw9nsnRcLA +y9tsne3Zj+mqC9myCKCEwfYq+i7IfkAgoU0iXrC6CBL9RjoV7o35SSS5Y8gEJqZH +gpVNbSYxA1hek+INW0Dx2lSferafsOdT8rYrV954cMu0fpQzU4FvUgbTb8lWQMx7 +Uni0NzO8YneK4ZcACisTDFMMlXH3Y+9cS7E/AgMBAAGjQjBAMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTdrd5XIXJDF8i66iQPFJY2 +IURtBzANBgkqhkiG9w0BAQsFAAOCAQEACRUl4VO1lRRN01lNf3RlN1mquNLBATxc +lRCoawrWwkYG5yXgUr41TbxrNVVHU1AnDhPRa2HdbT0zJ8afC8HMFexSjdhOPtnQ +R6JxREbuMQvWe/7OsTPR2yVHtw2kcn3FzbOL2/yb1DDzTEihSUoLCLr3Jnop6rPD +6iRAnqUegKckqKrf0ZztsIDS+a/azPpaUxfqmfQrRJzuZ6AWVQYWb4hDYVGg7gQK +5BPc30ireJ/juICjIhsQp/IJXVwQgf7N075x65rwWahHhRDYd/T5GG/xQoGw+fXB +QPGQKWLa3Z4YYqGAIZEYrOGQPty2VDlvzTe3XvlXf+wWhaTaZnl4pA== +-----END CERTIFICATE----- +`, +}