diff --git a/CHANGELOG.md b/CHANGELOG.md index 405961f42e..23b0621ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## UNRELEASED +IMPROVEMENTS: +* Control Plane + * TLS: Support PKCS1 and PKCS8 private keys for Consul certificate authority. [[GH-843](https://github.com/hashicorp/consul-k8s/pull/843)] + BUG FIXES: * Control Plane * ACLs: Fix issue where if one or more servers fail to have their ACL tokens set on the initial run of server-acl-init diff --git a/control-plane/helper/cert/tls_util.go b/control-plane/helper/cert/tls_util.go index d56bcc9989..37e2f4ea97 100644 --- a/control-plane/helper/cert/tls_util.go +++ b/control-plane/helper/cert/tls_util.go @@ -17,6 +17,9 @@ import ( "time" ) +// NOTE: A lot of this code is taken from +// https://github.com/hashicorp/consul/blob/44c023a3020fdd139c5be330f318a3c12339f08e/agent/connect/parsing.go. + // GenerateCA generates a CA with the provided // common name valid for 10 years. It returns the private key as // a crypto.Signer and a PEM string and certificate @@ -162,6 +165,22 @@ func ParseSigner(pemValue string) (crypto.Signer, error) { switch block.Type { case "EC PRIVATE KEY": return x509.ParseECPrivateKey(block.Bytes) + + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(block.Bytes) + + case "PRIVATE KEY": + signer, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + pk, ok := signer.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("private key is not a valid format") + } + + return pk, nil + default: return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type) } diff --git a/control-plane/subcommand/tls-init/command_test.go b/control-plane/subcommand/tls-init/command_test.go index 7405754e18..e22254008a 100644 --- a/control-plane/subcommand/tls-init/command_test.go +++ b/control-plane/subcommand/tls-init/command_test.go @@ -58,50 +58,79 @@ func TestRun_FlagValidation(t *testing.T) { } func TestRun_CreatesServerCertificatesWithExistingCAAsFiles(t *testing.T) { - ui := cli.NewMockUi() - cmd := Command{UI: ui} - k8s := fake.NewSimpleClientset() - cmd.clientset = k8s - ca, err := ioutil.TempFile("", "") - require.NoError(t, err) - defer os.RemoveAll(ca.Name()) - err = ioutil.WriteFile(ca.Name(), []byte(caCert), 0644) - require.NoError(t, err) + cases := []struct { + caCert string + caKey string + algorithm string + }{ + { + caCert: caCertEC, + caKey: caKeyEC, + algorithm: "ec", + }, + { + caCert: caCertRSA, + caKey: caKeyRSA, + algorithm: "rsa", + }, + { + // caCertRSA is used because the key is just caKeyRSA encrypted. + caCert: caCertRSA, + caKey: caKeyPKCS8, + algorithm: "pkcs8", + }, + } - key, err := ioutil.TempFile("", "") - require.NoError(t, err) - defer os.RemoveAll(key.Name()) - err = ioutil.WriteFile(key.Name(), []byte(caKey), 0644) - require.NoError(t, err) + for _, c := range cases { + t.Run(c.algorithm, func(t *testing.T) { + ui := cli.NewMockUi() + cmd := Command{UI: ui} + k8s := fake.NewSimpleClientset() + cmd.clientset = k8s - flags := []string{"-name-prefix", "consul", "-ca", ca.Name(), "-key", key.Name()} + ca, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer os.RemoveAll(ca.Name()) + err = ioutil.WriteFile(ca.Name(), []byte(c.caCert), 0644) + require.NoError(t, err) - exitCode := cmd.Run(flags) - require.Equal(t, 0, exitCode) + key, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer os.RemoveAll(key.Name()) + err = ioutil.WriteFile(key.Name(), []byte(c.caKey), 0644) + require.NoError(t, err) - caCertBlock, _ := pem.Decode([]byte(caCert)) - caCertificate, err := x509.ParseCertificate(caCertBlock.Bytes) - require.NoError(t, err) + flags := []string{"-name-prefix", "consul", "-ca", ca.Name(), "-key", key.Name()} - serverCertSecret, err := k8s.CoreV1().Secrets("default").Get(context.Background(), "consul-server-cert", metav1.GetOptions{}) - require.NoError(t, err) - serverCert := serverCertSecret.Data[corev1.TLSCertKey] - serverKey := serverCertSecret.Data[corev1.TLSPrivateKeyKey] + exitCode := cmd.Run(flags) + require.Equal(t, 0, exitCode) - certBlock, _ := pem.Decode(serverCert) - certificate, err := x509.ParseCertificate(certBlock.Bytes) - require.NoError(t, err) - require.False(t, certificate.IsCA) - require.Equal(t, []string{"server.dc1.consul", "localhost"}, certificate.DNSNames) - require.Equal(t, []net.IP{net.ParseIP("127.0.0.1").To4()}, certificate.IPAddresses) + caCertBlock, _ := pem.Decode([]byte(c.caCert)) + caCertificate, err := x509.ParseCertificate(caCertBlock.Bytes) + require.NoError(t, err) - keyBlock, _ := pem.Decode(serverKey) - privateKey, err := x509.ParseECPrivateKey(keyBlock.Bytes) - require.NoError(t, err) - require.Equal(t, &privateKey.PublicKey, certificate.PublicKey) + serverCertSecret, err := k8s.CoreV1().Secrets("default").Get(context.Background(), "consul-server-cert", metav1.GetOptions{}) + require.NoError(t, err) + serverCert := serverCertSecret.Data[corev1.TLSCertKey] + serverKey := serverCertSecret.Data[corev1.TLSPrivateKeyKey] - require.NoError(t, certificate.CheckSignatureFrom(caCertificate)) + certBlock, _ := pem.Decode(serverCert) + certificate, err := x509.ParseCertificate(certBlock.Bytes) + require.NoError(t, err) + require.False(t, certificate.IsCA) + require.Equal(t, []string{"server.dc1.consul", "localhost"}, certificate.DNSNames) + require.Equal(t, []net.IP{net.ParseIP("127.0.0.1").To4()}, certificate.IPAddresses) + + keyBlock, _ := pem.Decode(serverKey) + privateKey, err := x509.ParseECPrivateKey(keyBlock.Bytes) + require.NoError(t, err) + require.Equal(t, &privateKey.PublicKey, certificate.PublicKey) + + require.NoError(t, certificate.CheckSignatureFrom(caCertificate)) + + }) + } } func TestRun_UpdatesServerCertificatesWithExistingCertsAsFiles(t *testing.T) { @@ -126,13 +155,13 @@ func TestRun_UpdatesServerCertificatesWithExistingCertsAsFiles(t *testing.T) { ca, err := ioutil.TempFile("", "") require.NoError(t, err) defer os.RemoveAll(ca.Name()) - err = ioutil.WriteFile(ca.Name(), []byte(caCert), 0644) + err = ioutil.WriteFile(ca.Name(), []byte(caCertEC), 0644) require.NoError(t, err) key, err := ioutil.TempFile("", "") require.NoError(t, err) defer os.RemoveAll(key.Name()) - err = ioutil.WriteFile(key.Name(), []byte(caKey), 0644) + err = ioutil.WriteFile(key.Name(), []byte(caKeyEC), 0644) require.NoError(t, err) flags := []string{"-name-prefix", "consul", "-ca", ca.Name(), "-key", key.Name(), "-additional-dnsname", "test.dns.name"} @@ -140,7 +169,7 @@ func TestRun_UpdatesServerCertificatesWithExistingCertsAsFiles(t *testing.T) { exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode) - caCertBlock, _ := pem.Decode([]byte(caCert)) + caCertBlock, _ := pem.Decode([]byte(caCertEC)) caCertificate, err := x509.ParseCertificate(caCertBlock.Bytes) require.NoError(t, err) @@ -176,7 +205,7 @@ func TestRun_CreatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -188,7 +217,7 @@ func TestRun_CreatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -201,7 +230,7 @@ func TestRun_CreatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode) - caCertBlock, _ := pem.Decode([]byte(caCert)) + caCertBlock, _ := pem.Decode([]byte(caCertEC)) caCertificate, err := x509.ParseCertificate(caCertBlock.Bytes) require.NoError(t, err) @@ -265,7 +294,7 @@ func TestRun_UpdatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -277,7 +306,7 @@ func TestRun_UpdatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -300,7 +329,7 @@ func TestRun_UpdatesServerCertificatesWithExistingCertsAsSecrets(t *testing.T) { exitCode := cmd.Run(flags) require.Equal(t, 0, exitCode) - caCertBlock, _ := pem.Decode([]byte(caCert)) + caCertBlock, _ := pem.Decode([]byte(caCertEC)) caCertificate, err := x509.ParseCertificate(caCertBlock.Bytes) require.NoError(t, err) @@ -337,7 +366,7 @@ func TestRun_CreatesServerCertificatesWithExpiryWithinSpecifiedDays(t *testing.T Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -349,7 +378,7 @@ func TestRun_CreatesServerCertificatesWithExpiryWithinSpecifiedDays(t *testing.T Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -382,7 +411,7 @@ func TestRun_CreatesServerCertificatesWithProvidedHosts(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -394,7 +423,7 @@ func TestRun_CreatesServerCertificatesWithProvidedHosts(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -428,7 +457,7 @@ func TestRun_CreatesServerCertificatesWithSpecifiedDomainAndDC(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -440,7 +469,7 @@ func TestRun_CreatesServerCertificatesWithSpecifiedDomainAndDC(t *testing.T) { Namespace: "default", }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -481,7 +510,7 @@ func TestRun_CreatesServerCertificatesInSpecifiedNamespace(t *testing.T) { Namespace: namespace, }, Data: map[string][]byte{ - corev1.TLSCertKey: []byte(caCert), + corev1.TLSCertKey: []byte(caCertEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -493,7 +522,7 @@ func TestRun_CreatesServerCertificatesInSpecifiedNamespace(t *testing.T) { Namespace: namespace, }, Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte(caKey), + corev1.TLSPrivateKeyKey: []byte(caKeyEC), }, Type: corev1.SecretTypeOpaque, }, metav1.CreateOptions{}) @@ -508,7 +537,7 @@ func TestRun_CreatesServerCertificatesInSpecifiedNamespace(t *testing.T) { } const ( - caCert string = `-----BEGIN CERTIFICATE----- + caCertEC string = `-----BEGIN CERTIFICATE----- MIIDPjCCAuWgAwIBAgIRAOjdIMIYBXgeoXBDydhFImcwCgYIKoZIzj0EAwIwgZEx CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw @@ -529,7 +558,7 @@ WBJ2jlEV/kttcHlcHpvyO3GHCp3AE+G4f27NWqYdYeACIDJkx6OjZBU7i4K3HSrO qlxZIl+NFZSHr8XS6BFNB8vc -----END CERTIFICATE-----` - caKey string = `-----BEGIN EC PRIVATE KEY----- + caKeyEC string = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIO+ASjxFB5gYZju94Ujx81ykp54K53b1TvQNQW/zgbFqoAoGCCqGSM49 AwEHoUQDQgAEvETlXGiuMdIH3nOTf/1RGYmBoZA9RaaDp1T9kcABGuzoxEA+P7VO rd4cnIiTnYqkslAdqcXWmoFEubPFTuKghw== @@ -560,4 +589,82 @@ MHcCAQEEINVuQ1fmOWH5HDG7wEqB1KObSs7q26czY7P+WLhtLDZPoAoGCCqGSM49 AwEHoUQDQgAEu6FUB4WbJpJmXe9cXC2vf9xlvLB5aj9lfAzg4uPL+yXHUpHMYyZy tdsZa0jHMVkDIrNoL8nmxu6X578xNY304w== -----END EC PRIVATE KEY-----` + + caCertRSA string = `-----BEGIN CERTIFICATE----- +MIIDGjCCAgICCQC9IJfDAbKSIjANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJD +QTEZMBcGA1UECAwQQnJpdGlzaCBDb2x1bWJpYTERMA8GA1UEBwwIVmFuY292ZXIx +EjAQBgNVBAoMCUhhc2hpQ29ycDAeFw0yMTExMDQyMjQ0MjJaFw0yMTEyMDQyMjQ0 +MjJaME8xCzAJBgNVBAYTAkNBMRkwFwYDVQQIDBBCcml0aXNoIENvbHVtYmlhMREw +DwYDVQQHDAhWYW5jb3ZlcjESMBAGA1UECgwJSGFzaGlDb3JwMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxTVd5sFGuVOZxkPv3tE69khUToVcRb85NRWW +eWBSyrTE4UWr06kG4reSTVrGgR2hojrP4nzVynu7EslCJITb6Df5sn34bKVDpqWJ +gDFJoEYoTzajRxEDjSkwau+iPhuaJ6pB97+JimOg0Jnqe0QVZ2NtjwgpXYSGkevn +iVxZHaurLxnhDry5KyDJ79p48c7aKNxAxU2syrhKkrWNJaCg4WVTOc/eQU4elUZb +TwYIZ/Zi4gOkS+vz0ceggRmXg5MzYT6cBlccHRrA7BaSRkoD7bNbDh+mRTz67UgO +KUjIi+o1TsUhmvO2Know+zIGd1mfAf9qFT+4KXPFh3yN5DeMkQIDAQABMA0GCSqG +SIb3DQEBCwUAA4IBAQC6LAK0NnmnxuWvKZano3hI9DPRlktB4LVfYSBNFnQllxUC +ZYBIouJXFKK4dTccMkgLlQU7hFXj/YWdSRmf78w/w0GbWYAnUDnGfYro7+ZRUtFV +v7FT+xV2hFe+2cp4+btux5kfqD6OC58Gp9FWXMzRhJCWSDAk2rIYJ2MM7og+ad+Q +mJAYoOBLuY1rXc080v0Vdcl3tQ24UvvvLhuyOyL795OaZZl3uVvbaNHpM8lfJNEg +XfsbHpePEKd9ORLV6jUirl0YheqY8Mdx5hfwFHi1FL4eH6vzRm6GF2hUkkfjOUGO +x8JijLHx5rnkFyNOynhoH8QlwYeMPZbc4js7DWuG +-----END CERTIFICATE-----` + + caKeyRSA string = `-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAxTVd5sFGuVOZxkPv3tE69khUToVcRb85NRWWeWBSyrTE4UWr +06kG4reSTVrGgR2hojrP4nzVynu7EslCJITb6Df5sn34bKVDpqWJgDFJoEYoTzaj +RxEDjSkwau+iPhuaJ6pB97+JimOg0Jnqe0QVZ2NtjwgpXYSGkevniVxZHaurLxnh +Dry5KyDJ79p48c7aKNxAxU2syrhKkrWNJaCg4WVTOc/eQU4elUZbTwYIZ/Zi4gOk +S+vz0ceggRmXg5MzYT6cBlccHRrA7BaSRkoD7bNbDh+mRTz67UgOKUjIi+o1TsUh +mvO2Know+zIGd1mfAf9qFT+4KXPFh3yN5DeMkQIDAQABAoH/E0Ii6WX2giKn4bTA +uAG2wFZP5Vsgp68E5yo0h6Xgb+s3Tsh+/yyCf6FtqCA1QmaiYjVcF8IZHqz2l98P +loFi+Ep/F+81U2bQNHX1947Yoc44IYQ0bbw7nI1pLQg5z9biNv1pc8hApkMUcUqW +m3MKpA4RpOYnI/rNKXLgKYnbKgptyniJSyFm+pOLgpSnwZIiSIBqHnppHS1b8Bjq +H2ZYqEYNB7dHLu0HpHB1zGVC4CAzmBqtLw4fNDp1lsHTis0SJpRuBiD5IZU6+1TS +QvgkmfJKStRFIT+0YRap0J+rSYtqwPalPPbV4ePfZTpj4d9Ll0Xx97Pn3oinUQgl +auhBAoGBAOIeH5tEaj3U8DTNGGMWmMBVediJEhqDITyjPVGoKZAt+29tqwlDg+aw +hEEGgaIOPE+mQ3CEbvJnRC4Z/ntYRJpv1arBziRQKfznyb3yyFvy/JBSkNEkyFhz +KHYv/uQyg8XHIAIE41IpKdUk9MZ6BVvHjFdBbU01KerPl8Dfm7aJAoGBAN9FNFyv +A61q44oCwGRTxpYzRshHkk7GsO4Jg/vMKpU8bOCvz8GLD7cx38Xt1bV+uUlNGZc6 +4EZxKrv0P9Fsj//WREc+0K11U3aN6HIYNdVV9Vel2v6Bis8+zTzNY6fSF4sx1Dw9 +5q1BkE6sP3IPHz58Tt0pWNW8lmucZS7Q3KPJAoGBAJuy0GKytlFDOe+xtfQtEBuH +//GpWMzmtFEzujprB8uezf6JTnd/hOipbTf1SfgTw1W5D8D/gAHsN5djEMdQHVUW +YtNExjRc+ryJwnHIJkyiQWUDZXKN2GKHUToojGQHoJLkLVcWlIzziTmaS+4LAXuU +KT+/7op2bBmivkTx9B+5AoGBALyJxhPWPra8onSyqiCOlg3UMxuBRM18/3+jTW7e +E79+DTsXe8smURkT5rFPi739yx1ZHBkWwLj7a2jYcuO4V0lleLbpFnLDtr1QTE+8 +ngkO02U2S13LqpojoFCN6G+Y/ASxCVXtt9Pqn5+v2MvKdUng0v/zoG6tGCC7Kr6D +5S3xAoGAZTYnX/rV1n2YuVvd12T9Xs2EBD9Q3FfL/oDfchU2nWiXyQA8HXhb8aBw +5Mw4BOuo0JgkSTqIxqda10tAViNeqlNiQKvOMt9y8Ugl4eEd4SdutmdTZuVPwK4/ +yLi0ot+KP/8sbKAZjcAiJJIZFsqVY4wRdhSo3jzI72Zsx9CqJvE= +-----END RSA PRIVATE KEY-----` + + // caKeyPKCS8 is caKeyRSA converted to PKCS8 form. + caKeyPKCS8 string = `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFNV3mwUa5U5nG +Q+/e0Tr2SFROhVxFvzk1FZZ5YFLKtMThRavTqQbit5JNWsaBHaGiOs/ifNXKe7sS +yUIkhNvoN/myffhspUOmpYmAMUmgRihPNqNHEQONKTBq76I+G5onqkH3v4mKY6DQ +mep7RBVnY22PCCldhIaR6+eJXFkdq6svGeEOvLkrIMnv2njxztoo3EDFTazKuEqS +tY0loKDhZVM5z95BTh6VRltPBghn9mLiA6RL6/PRx6CBGZeDkzNhPpwGVxwdGsDs +FpJGSgPts1sOH6ZFPPrtSA4pSMiL6jVOxSGa87YqejD7MgZ3WZ8B/2oVP7gpc8WH +fI3kN4yRAgMBAAECgf8TQiLpZfaCIqfhtMC4AbbAVk/lWyCnrwTnKjSHpeBv6zdO +yH7/LIJ/oW2oIDVCZqJiNVwXwhkerPaX3w+WgWL4Sn8X7zVTZtA0dfX3jtihzjgh +hDRtvDucjWktCDnP1uI2/WlzyECmQxRxSpabcwqkDhGk5icj+s0pcuApidsqCm3K +eIlLIWb6k4uClKfBkiJIgGoeemkdLVvwGOofZlioRg0Ht0cu7QekcHXMZULgIDOY +Gq0vDh80OnWWwdOKzRImlG4GIPkhlTr7VNJC+CSZ8kpK1EUhP7RhFqnQn6tJi2rA +9qU89tXh499lOmPh30uXRfH3s+feiKdRCCVq6EECgYEA4h4fm0RqPdTwNM0YYxaY +wFV52IkSGoMhPKM9UagpkC37b22rCUOD5rCEQQaBog48T6ZDcIRu8mdELhn+e1hE +mm/VqsHOJFAp/OfJvfLIW/L8kFKQ0STIWHModi/+5DKDxccgAgTjUikp1ST0xnoF +W8eMV0FtTTUp6s+XwN+btokCgYEA30U0XK8DrWrjigLAZFPGljNGyEeSTsaw7gmD ++8wqlTxs4K/PwYsPtzHfxe3VtX65SU0ZlzrgRnEqu/Q/0WyP/9ZERz7QrXVTdo3o +chg11VX1V6Xa/oGKzz7NPM1jp9IXizHUPD3mrUGQTqw/cg8fPnxO3SlY1byWa5xl +LtDco8kCgYEAm7LQYrK2UUM577G19C0QG4f/8alYzOa0UTO6OmsHy57N/olOd3+E +6KltN/VJ+BPDVbkPwP+AAew3l2MQx1AdVRZi00TGNFz6vInCccgmTKJBZQNlco3Y +YodROiiMZAegkuQtVxaUjPOJOZpL7gsBe5QpP7/uinZsGaK+RPH0H7kCgYEAvInG +E9Y+tryidLKqII6WDdQzG4FEzXz/f6NNbt4Tv34NOxd7yyZRGRPmsU+Lvf3LHVkc +GRbAuPtraNhy47hXSWV4tukWcsO2vVBMT7yeCQ7TZTZLXcuqmiOgUI3ob5j8BLEJ +Ve230+qfn6/Yy8p1SeDS//Ogbq0YILsqvoPlLfECgYBlNidf+tXWfZi5W93XZP1e +zYQEP1DcV8v+gN9yFTadaJfJADwdeFvxoHDkzDgE66jQmCRJOojGp1rXS0BWI16q +U2JAq84y33LxSCXh4R3hJ262Z1Nm5U/Arj/IuLSi34o//yxsoBmNwCIkkhkWypVj +jBF2FKjePMjvZmzH0Kom8Q== +-----END PRIVATE KEY-----` )