summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2022-06-11 14:42:25 -0700
committerStan Hu <stanhu@gmail.com>2022-06-26 00:11:42 -0700
commit4919ec7a1ef3bcf7a8b2da1a5369c9135845f55e (patch)
tree2ebab4c057f6191e72b7dd79b71f2fa180495147
parent34ec4ec81fa3b505a57095e7b07b38157ae57622 (diff)
downloadgitlab-shell-4919ec7a1ef3bcf7a8b2da1a5369c9135845f55e.tar.gz
gitlab-sshd: Add support for configuring host certificates
This adds support for specifying host certificates via the `host_cert_files` option and advertises the signed key to the client. This acts similarly to OpenSSH's `HostCertificate` parameter: gitlab-sshd attempts to match a host key to its certificate, and then substitutes the matching host key with a certificate signed by a trusted certificate authority's key. This is the first requirement to supporting SSH certificates. This will enable the client to trust the server if both trust a common certificate authority. The `TrustedUserCAKeys` option will need to be supported later for the server to trust all user keys signed by this certificate authority. Relates to https://gitlab.com/gitlab-org/gitlab-shell/-/issues/495
-rw-r--r--config.yml.example4
-rw-r--r--internal/config/config.go1
-rw-r--r--internal/sshd/server_config.go72
-rw-r--r--internal/sshd/server_config_test.go40
-rw-r--r--internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub1
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/ca38
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/ca.pub1
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/server-cert.pub1
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/server.pub1
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub1
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/server2.key38
-rw-r--r--internal/testhelper/testdata/testroot/certs/valid/server2.pub1
12 files changed, 191 insertions, 8 deletions
diff --git a/config.yml.example b/config.yml.example
index 2744fc9..501b61b 100644
--- a/config.yml.example
+++ b/config.yml.example
@@ -99,3 +99,7 @@ sshd:
- /run/secrets/ssh-hostkeys/ssh_host_rsa_key
- /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key
- /run/secrets/ssh-hostkeys/ssh_host_ed25519_key
+ host_key_certs:
+ - /run/secrets/ssh-hostkeys/ssh_host_rsa_key-cert.pub
+ - /run/secrets/ssh-hostkeys/ssh_host_ecdsa_key-cert.pub
+ - /run/secrets/ssh-hostkeys/ssh_host_ed25519_key-cert.pub
diff --git a/internal/config/config.go b/internal/config/config.go
index 61ff4c0..eda64da 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -36,6 +36,7 @@ type ServerConfig struct {
ReadinessProbe string `yaml:"readiness_probe"`
LivenessProbe string `yaml:"liveness_probe"`
HostKeyFiles []string `yaml:"host_key_files,omitempty"`
+ HostCertFiles []string `yaml:"host_cert_files,omitempty"`
MACs []string `yaml:"macs"`
KexAlgorithms []string `yaml:"kex_algorithms"`
Ciphers []string `yaml:"ciphers"`
diff --git a/internal/sshd/server_config.go b/internal/sshd/server_config.go
index 7fb73dc..e0d6ee9 100644
--- a/internal/sshd/server_config.go
+++ b/internal/sshd/server_config.go
@@ -39,17 +39,14 @@ var (
type serverConfig struct {
cfg *config.Config
hostKeys []ssh.Signer
+ hostKeyToCertMap map[string]*ssh.Certificate
authorizedKeysClient *authorizedkeys.Client
}
-func newServerConfig(cfg *config.Config) (*serverConfig, error) {
- authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
- }
-
+func parseHostKeys(keyFiles []string) []ssh.Signer {
var hostKeys []ssh.Signer
- for _, filename := range cfg.Server.HostKeyFiles {
+
+ for _, filename := range keyFiles {
keyRaw, err := os.ReadFile(filename)
if err != nil {
log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("Failed to read host key")
@@ -63,11 +60,70 @@ func newServerConfig(cfg *config.Config) (*serverConfig, error) {
hostKeys = append(hostKeys, key)
}
+
+ return hostKeys
+}
+
+func parseHostCerts(hostKeys []ssh.Signer, certFiles []string) map[string]*ssh.Certificate {
+ keyToCertMap := map[string]*ssh.Certificate{}
+ hostKeyIndex := make(map[string]int)
+
+ for index, hostKey := range hostKeys {
+ hostKeyIndex[string(hostKey.PublicKey().Marshal())] = index
+ }
+
+ for _, filename := range certFiles {
+ keyRaw, err := os.ReadFile(filename)
+ if err != nil {
+ log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to read host certificate")
+ continue
+ }
+ publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyRaw)
+ if err != nil {
+ log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("failed to parse host certificate")
+ continue
+ }
+
+ cert, ok := publicKey.(*ssh.Certificate)
+ if !ok {
+ log.WithFields(log.Fields{"filename": filename}).Warn("failed to decode host certificate")
+ continue
+ }
+
+ hostRawKey := string(cert.Key.Marshal())
+ index, found := hostKeyIndex[hostRawKey]
+ if found {
+ keyToCertMap[hostRawKey] = cert
+
+ certSigner, err := ssh.NewCertSigner(cert, hostKeys[index])
+ if err != nil {
+ log.WithError(err).WithFields(log.Fields{"filename": filename}).Warn("the host certificate doesn't match the host private key")
+ continue
+ }
+
+ hostKeys[index] = certSigner
+ } else {
+ log.WithFields(log.Fields{"filename": filename}).Warnf("no matching private key for certificate %s", filename)
+ }
+ }
+
+ return keyToCertMap
+}
+
+func newServerConfig(cfg *config.Config) (*serverConfig, error) {
+ authorizedKeysClient, err := authorizedkeys.NewClient(cfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to initialize GitLab client: %w", err)
+ }
+
+ hostKeys := parseHostKeys(cfg.Server.HostKeyFiles)
if len(hostKeys) == 0 {
return nil, fmt.Errorf("No host keys could be loaded, aborting")
}
- return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys}, nil
+ hostKeyToCertMap := parseHostCerts(hostKeys, cfg.Server.HostCertFiles)
+
+ return &serverConfig{cfg: cfg, authorizedKeysClient: authorizedKeysClient, hostKeys: hostKeys, hostKeyToCertMap: hostKeyToCertMap}, nil
}
func (s *serverConfig) getAuthKey(ctx context.Context, user string, key ssh.PublicKey) (*authorizedkeys.Response, error) {
diff --git a/internal/sshd/server_config_test.go b/internal/sshd/server_config_test.go
index f7e0575..d638222 100644
--- a/internal/sshd/server_config_test.go
+++ b/internal/sshd/server_config_test.go
@@ -5,6 +5,7 @@ import (
"crypto/dsa"
"crypto/rand"
"crypto/rsa"
+ "os"
"path"
"testing"
@@ -22,6 +23,45 @@ func TestNewServerConfigWithoutHosts(t *testing.T) {
require.Equal(t, "No host keys could be loaded, aborting", err.Error())
}
+func TestHostKeyAndCerts(t *testing.T) {
+ testhelper.PrepareTestRootDir(t)
+
+ srvCfg := config.ServerConfig{
+ Listen: "127.0.0.1",
+ ConcurrentSessionsLimit: 1,
+ HostKeyFiles: []string{
+ path.Join(testhelper.TestRoot, "certs/valid/server.key"),
+ },
+ HostCertFiles: []string{
+ path.Join(testhelper.TestRoot, "certs/valid/server-cert.pub"),
+ path.Join(testhelper.TestRoot, "certs/valid/server2-cert.pub"),
+ path.Join(testhelper.TestRoot, "certs/invalid/server-cert.pub"),
+ path.Join(testhelper.TestRoot, "certs/invalid-path.key"),
+ path.Join(testhelper.TestRoot, "certs/invalid/server.crt"),
+ },
+ }
+
+ cfg, err := newServerConfig(
+ &config.Config{GitlabUrl: "http://localhost", User: "user", Server: srvCfg},
+ )
+ require.NoError(t, err)
+
+ require.Len(t, cfg.hostKeys, 1)
+ require.Len(t, cfg.hostKeyToCertMap, 1)
+
+ // Check that the entry is pointing to the server's public key
+ data, err := os.ReadFile(path.Join(testhelper.TestRoot, "certs/valid/server.pub"))
+ require.NoError(t, err)
+
+ publicKey, _, _, _, err := ssh.ParseAuthorizedKey(data)
+ require.NoError(t, err)
+ require.NotNil(t, publicKey)
+ cert, ok := cfg.hostKeyToCertMap[string(publicKey.Marshal())]
+ require.True(t, ok)
+ require.NotNil(t, cert)
+ require.Equal(t, cert, cfg.hostKeys[0].PublicKey())
+}
+
func TestFailedAuthorizedKeysClient(t *testing.T) {
_, err := newServerConfig(&config.Config{GitlabUrl: "ftp://localhost"})
diff --git a/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
new file mode 100644
index 0000000..9239ed2
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/invalid/server-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql not_a_valid_cert.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca b/internal/testhelper/testdata/testroot/certs/valid/ca
new file mode 100644
index 0000000..884276b
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/ca.pub b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
new file mode 100644
index 0000000..f4e853e
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/ca.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDchQE3zT8Ekbm631TbfwXAgYB8xlCg5dhsW2JBmj5T5x1sYmUQaBX4hOgY3vxgnKmRsrw2Km0ww6BZnx67oMIkw3GM9vt7ddgauYc96grln2KgRIPDRBKTEMh/AWgtKfHT56JdLXfzKkQ2AtP+E4yX5+augDvhmm+S8ikr5GgCjWqVYJa0Ds3KCRCRZGCecCPiOQMqr1pPWIcZr2dBgu5O1+JfeXfn+g44wkWsODttvkQecGwp7AOgDF2o1vGnhb+xDJQXg1s4UsbNFq8Dp41F87H1Rdx3LS/35SAKa73aduI+Y0Gj3BjcP2mvBcBZ61MzUHdkIOTWGohxdGDqk0E0L17YW5tyGz1NystpmmviSAW9N6QZyh/WbajMlNWVwgmUmnL3manW5fDFM9t9coY+8q7s2uhHZq+QAh/BGPkT/wfCuAMfav/i5E9p1ELH2GbfwcjWfpLhiuuyYv198tbpIJ8Qbpzv5RENCvXxWr1jLEya7T0QrtWUMNfQkzRRqfk= test@test.example.org
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
new file mode 100644
index 0000000..105a02f
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgNGUtD+qw0Xj5NU2uj4+4LoCWPcvXP54F9Adw/hWN5LAAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yqlAAAAAAAAAAAAAAACAAAABnNlcnZlcgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGAapK5VzLDoRe88tnLORrH8VxLegTWPKGdn0k5Ye8tS5XUgd0N98gU669y4ErDNf0kPxlz40bjsfisEEtJ/N7m14IskCepScfZRh8w6QgPxbTOhmrc89xooqBUE50y5FU9hIIbzUrnEP+Dfu4IiFPToAguCa+KoAKiOX7lBQqEugV6uOWVZ2erPopEv+OiMLD8hXsuKKjQ+TomHZ/IjuXsFdXH7Vcl0VsPaAcxyCtDU7nCTJSTUoUZtMtwwpulp0e/zNmFrEn2Binz4jRaUlk3FM2fdbotviDQYeOY3npmxaWUvvQ/eKn0/DzUTAKAGr2LDa2XWnPhj51BS1XkNaUlnupdYmZ2Sok0R4U3bfVwokteREvAltGbXQSDtZwLS5NEY6vIdDrxpn5QRf9vGjqnc7piXxye9gcLne4YDUi24IhGyHrnWKCC0HjF7tuUhCOVKrqRdmHxRGWX3PlS8Xn6HHEPWU+YZnfT1V2W7LAFcDMozQbs4GPGzZdR3f3vCOJ8 server.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server.pub b/internal/testhelper/testdata/testroot/certs/valid/server.pub
new file mode 100644
index 0000000..784d80c
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCa17cb94P6q5qbDIWX7aMSjyeBIBPQVZ5jlkDBG90XgWC1MEu9sB1OfKLukcx6wJJSTLFccc9rMzhINXq6K7ks0oXSLP81jvqsu0WipIZSDKBNkdVtno1FcI1RnQ+yUP3nA4Ja9L233GA1evLrqTz6Z9k2ET5wVB+s7+k3lak24bJZN8qVRDDk1UveahuPe1KMj7DNKls8y9tNCgGJn9UeTLJzXlh2tt4/AUHZ0lvET9eCzKT9PBZJQWcCzqLXHa37jbc0ib2sgNN1bZhgkle/cxRx0MjEmdjRt4Z48wjKaf1khFQm0r9lebAxvna/vT5hNywbru5KbfUJHyM23yql
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
new file mode 100644
index 0000000..946beed
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2-cert.pub
@@ -0,0 +1 @@
+ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3XO30wn4+lFxc5fLsanH4Pvu0OuKGHoR94zZjxx7qAEAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgokAAAAAAAAAAAAAAAIAAAAHc2VydmVyMgAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEA3IUBN80/BJG5ut9U238FwIGAfMZQoOXYbFtiQZo+U+cdbGJlEGgV+IToGN78YJypkbK8NiptMMOgWZ8eu6DCJMNxjPb7e3XYGrmHPeoK5Z9ioESDw0QSkxDIfwFoLSnx0+eiXS138ypENgLT/hOMl+fmroA74ZpvkvIpK+RoAo1qlWCWtA7NygkQkWRgnnAj4jkDKq9aT1iHGa9nQYLuTtfiX3l35/oOOMJFrDg7bb5EHnBsKewDoAxdqNbxp4W/sQyUF4NbOFLGzRavA6eNRfOx9UXcdy0v9+UgCmu92nbiPmNBo9wY3D9prwXAWetTM1B3ZCDk1hqIcXRg6pNBNC9e2Fubchs9TcrLaZpr4kgFvTekGcof1m2ozJTVlcIJlJpy95mp1uXwxTPbfXKGPvKu7NroR2avkAIfwRj5E/8HwrgDH2r/4uRPadRCx9hm38HI1n6S4YrrsmL9ffLW6SCfEG6c7+URDQr18Vq9YyxMmu09EK7VlDDX0JM0Uan5AAABlAAAAAxyc2Etc2hhMi01MTIAAAGANOKG8Tq7kp9B5+CQEyb+mEatJOoQRV+4rpemWlfEw6TuVwQN2wSXc6XKBHzSG4NRnFkwk6GgiPLQEf4lBBKA8VYQnDKuhrHJlU4DFCRPw/aceHfCwNOruyJmuf91W3yEO/kYAd6EhkQiW/K3ky7BuXCqR34T2fBZSCeYhNcXWxhEMLoAuj0kEdX+YMNBmiPtinPE13KMFGyIVBm/ojgSZa8j4WnhDcK0cWv0OSGTgJF6q3hENCWRz2E1HroKUiABOy5Nca6gPVAi4OTd7gwER8eh9MngVHYorAJ3N9HjUh640SbL3zCC8f/lqIztqsHY0u3olsQ0gLXpFain+430HeyJlmVlsDZgQKRb90Mm1viSCKvHGpmVDYMimE9y0DCQS1i0yRGF1uSIPtuQ0NCbhS/HPKsT3nYgGCEuoB8aGOu3aGB/tmUkYXW+pwXRKqw0f/zX088XWYWvA+AR4hmmr6DDMnf/4EHgJp3xHTEwOBHCVj69xvlOawBNlL2X0b2p server2.pub
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.key b/internal/testhelper/testdata/testroot/certs/valid/server2.key
new file mode 100644
index 0000000..21b98a7
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+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
+-----END OPENSSH PRIVATE KEY-----
diff --git a/internal/testhelper/testdata/testroot/certs/valid/server2.pub b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
new file mode 100644
index 0000000..e866f64
--- /dev/null
+++ b/internal/testhelper/testdata/testroot/certs/valid/server2.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDK4MvpmiZ0cLS4+p3YEwcCwo4kVbJPTEeiIMuoEI7KJ3yqAjKJuns6VpnC6aRgu3LmuM4uBHcimQi415ClBOm4+tFiVsVNcAksA+QuU8rTEC6xPs96y1Y/qC+/WQn6+uKp1+fAspWsLpig3VXSTfq+YAcxZfTdO/6ck0kHVvxX096Ye7D0mdq2lWbGwSBlAbzU1wX/Znv5hLZD4DSG1cIjA9vQ/fJ9pwclZiS0qQ1VXdoIUAvL9tTAKj295VGT2NMGGYZQAQ0vXtM1YHOMAZ4XoPL1oVFjVEZoLRD58a6Dpe4hY8QKe6X1w7zU1rr5vVYrz7MTUa5pzsUSOeUTc3kyKJrExVkoLyBgYQq8qW9kYk/Ox9zHwTAKw9vwiIs2Mwe6nlxJ7cw4zykMsxPK4z9HkbZoTFWy3phuPFqs/WQoCvHTKNjWPab7UAameM6Dn0N83BF21tCTMWjkRCvtZVGIGZ7Y4cAiiN0OPzQWDasJ/IKVDRguZJRn3kgHCMYCgok=