Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

advancedtls: Add SNI logic to ServerOptions.GetCertificate #3697

Merged
merged 30 commits into from Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0650ecb
Add GetCertificateWithSNI func
Jun 16, 2020
c358cff
Add comments in GetCertificateWithSNI
Jun 16, 2020
1f0088b
Modify comments in GetCertificateWithSNI
Jun 16, 2020
edada75
Restructure SNI
Jun 18, 2020
cda3e2d
Modified comments
Jun 18, 2020
40eadfc
Fixed arguments name
Jun 18, 2020
9e74bb7
Review Comments #1
Jun 18, 2020
4adb63c
Review comments #2
Jun 18, 2020
62c0f22
Made tests compiled after changing the func signature
Jun 20, 2020
d2343cc
Cleaned up comments
Jun 21, 2020
292f227
Fixed advancedtls unit test
Jun 22, 2020
9e81777
Fixed unit test and integration test for advancedtls
cindyxue Jun 22, 2020
7eeb1d6
Clean up comments
Jun 22, 2020
b76410b
Clean up comments
Jun 22, 2020
3519dc6
Added unit test for getCertificateWithSNI
Jun 30, 2020
d2cc061
Added unit test for getCertificateWithSNI
cindyxue Jun 30, 2020
c811fa5
Merge branch 'master' into Cindy/SNI
cindyxue Jun 30, 2020
df72ccd
Separate out buildGetCertificateFunc for go1.9
Jul 6, 2020
b066d6a
Review comments #3
Jul 10, 2020
ec327de
Review comments #4
Jul 16, 2020
cc37b1d
Regenerated certificate and added a test case to test DNS field for SNI
Jul 16, 2020
3891625
Removed const var in function
Jul 22, 2020
ec83efb
Renamed GetCertificate and buildGetCertificateFunc
Jul 24, 2020
789296a
Resolved Easwars' comments
Jul 24, 2020
8ef1de7
Added server_cert_3.txt
Jul 24, 2020
c70c90b
Renamed serverPeerCert to serverCert
Jul 24, 2020
fea90e1
Added server_cert_1.txt and server_cert_2.txt
Jul 24, 2020
783ab29
Separated unit test for go1.10+ and go1.9
Jul 24, 2020
18570bb
Changed comparing method to cmp.Equal
Jul 25, 2020
d1206c6
Resolved minor issues
Jul 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 11 additions & 4 deletions security/advancedtls/advancedtls.go
Expand Up @@ -166,7 +166,7 @@ type ServerOptions struct {
// invoke this function every time asked to present certificates to the
// client when a new connection is established. This is known as peer
// certificate reloading.
GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate func(*tls.ClientHelloInfo) ([]*tls.Certificate, error)
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
// VerifyPeer is a custom verification check after certificate signature
// check.
// If this is set, we will perform this customized check after doing the
Expand Down Expand Up @@ -234,9 +234,16 @@ func (o *ServerOptions) config() (*tls.Config, error) {
clientAuth = tls.RequireAnyClientCert
}
config := &tls.Config{
ClientAuth: clientAuth,
Certificates: o.Certificates,
GetCertificate: o.GetCertificate,
ClientAuth: clientAuth,
Certificates: o.Certificates,
}
// The function getCertificateWithSNI is only able to perform SNI logic for go1.10 and above.
// It will return the first certificate in o.GetCertificate for go1.9.
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
if o.GetCertificate != nil {
getCertificateWithSNI := func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
return buildGetCertificateFunc(clientHello, o)
}
config.GetCertificate = getCertificateWithSNI
}
if clientCAs != nil {
config.ClientCAs = clientCAs
Expand Down
16 changes: 9 additions & 7 deletions security/advancedtls/advancedtls_integration_test.go
@@ -1,3 +1,5 @@
// +build go1.10
cindyxue marked this conversation as resolved.
Show resolved Hide resolved

/*
*
* Copyright 2020 gRPC authors.
Expand Down Expand Up @@ -197,7 +199,7 @@ func TestEnd2End(t *testing.T) {
clientVerifyFunc CustomVerificationFunc
clientVType VerificationType
serverCert []tls.Certificate
serverGetCert func(*tls.ClientHelloInfo) (*tls.Certificate, error)
serverGetCert func(*tls.ClientHelloInfo) ([]*tls.Certificate, error)
serverRoot *x509.CertPool
serverGetRoot func(params *GetRootCAsParams) (*GetRootCAsResults, error)
serverVerifyFunc CustomVerificationFunc
Expand Down Expand Up @@ -271,12 +273,12 @@ func TestEnd2End(t *testing.T) {
return &VerificationResults{}, nil
},
clientVType: CertVerification,
serverGetCert: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
serverGetCert: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) {
switch stage.read() {
case 0:
return &cs.serverPeer1, nil
return []*tls.Certificate{&cs.serverPeer1}, nil
default:
return &cs.serverPeer2, nil
return []*tls.Certificate{&cs.serverPeer2}, nil
}
},
serverRoot: cs.serverTrust1,
Expand Down Expand Up @@ -336,12 +338,12 @@ func TestEnd2End(t *testing.T) {
return nil, fmt.Errorf("custom authz check fails")
},
clientVType: CertVerification,
serverGetCert: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
serverGetCert: func(*tls.ClientHelloInfo) ([]*tls.Certificate, error) {
switch stage.read() {
case 0:
return &cs.serverPeer1, nil
return []*tls.Certificate{&cs.serverPeer1}, nil
default:
return &cs.serverPeer2, nil
return []*tls.Certificate{&cs.serverPeer2}, nil
}
},
serverRoot: cs.serverTrust1,
Expand Down
107 changes: 96 additions & 11 deletions security/advancedtls/advancedtls_test.go
@@ -1,3 +1,5 @@
// +build go1.10

/*
*
* Copyright 2019 gRPC authors.
Expand Down Expand Up @@ -102,7 +104,7 @@ func TestClientServerHandshake(t *testing.T) {
clientExpectHandshakeError bool
serverMutualTLS bool
serverCert []tls.Certificate
serverGetCert func(*tls.ClientHelloInfo) (*tls.Certificate, error)
serverGetCert func(*tls.ClientHelloInfo) ([]*tls.Certificate, error)
serverRoot *x509.CertPool
serverGetRoot func(params *GetRootCAsParams) (*GetRootCAsResults, error)
serverVerifyFunc CustomVerificationFunc
Expand Down Expand Up @@ -279,8 +281,8 @@ func TestClientServerHandshake(t *testing.T) {
clientVerifyFunc: clientVerifyFuncGood,
clientVType: CertVerification,
serverMutualTLS: true,
serverGetCert: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &serverPeerCert, nil
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert}, nil
},
serverGetRoot: getRootCAsForServer,
serverVerifyFunc: serverVerifyFunc,
Expand All @@ -300,8 +302,8 @@ func TestClientServerHandshake(t *testing.T) {
clientVerifyFunc: clientVerifyFuncGood,
clientVType: CertVerification,
serverMutualTLS: true,
serverGetCert: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &serverPeerCert, nil
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert}, nil
},
serverGetRoot: getRootCAsForServer,
serverVerifyFunc: serverVerifyFunc,
Expand All @@ -322,8 +324,8 @@ func TestClientServerHandshake(t *testing.T) {
clientVType: CertVerification,
clientExpectHandshakeError: true,
serverMutualTLS: true,
serverGetCert: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &serverPeerCert, nil
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert}, nil
},
serverGetRoot: getRootCAsForServer,
serverVerifyFunc: serverVerifyFunc,
Expand All @@ -344,8 +346,8 @@ func TestClientServerHandshake(t *testing.T) {
clientVerifyFunc: clientVerifyFuncGood,
clientVType: CertVerification,
serverMutualTLS: true,
serverGetCert: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &clientPeerCert, nil
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&clientPeerCert}, nil
},
serverGetRoot: getRootCAsForServer,
serverVerifyFunc: serverVerifyFunc,
Expand All @@ -366,8 +368,8 @@ func TestClientServerHandshake(t *testing.T) {
clientVType: CertVerification,
clientExpectHandshakeError: true,
serverMutualTLS: true,
serverGetCert: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &serverPeerCert, nil
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert}, nil
},
serverGetRoot: getRootCAsForClient,
serverVerifyFunc: serverVerifyFunc,
Expand Down Expand Up @@ -679,3 +681,86 @@ func TestOptionsConfig(t *testing.T) {
})
}
}

func TestGetCertificateSNI(t *testing.T) {
// Define const variables needed to set up ClientHelloInfo for testing.
const (
tlsEcdheRsaWith3desEdeCbcSha uint16 = 0xc012
)
const (
CurveP256 tls.CurveID = 23
)
const (
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
pointFormatUncompressed uint8 = 0
)
const (
VersionTLS10 uint16 = 0x0301
)
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
// Load server certificates for setting the serverGetCert callback function.
serverPeerCert1, err := tls.LoadX509KeyPair(testdata.Path("server_cert_1.pem"),
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
testdata.Path("server_key_1.pem"))
if err != nil {
t.Fatalf("Server is unable to parse peer certificates. Error: %v", err)
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
}
serverPeerCert2, err := tls.LoadX509KeyPair(testdata.Path("server_cert_2.pem"),
testdata.Path("server_key_2.pem"))
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Fatalf("Server is unable to parse peer certificates. Error: %v", err)
}
serverPeerCert3, err := tls.LoadX509KeyPair(testdata.Path("server_cert_3.pem"),
testdata.Path("server_key_3.pem"))
if err != nil {
t.Fatalf("Server is unable to parse peer certificates. Error: %v", err)
}
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
tests := []struct {
desc string
serverGetCert func(*tls.ClientHelloInfo) ([]*tls.Certificate, error)
serverName string
expectedCertificate tls.Certificate
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
}{
{
desc: "Selected certificate by SNI should be serverPeerCert1",
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert1, &serverPeerCert2, &serverPeerCert3}, nil
},
// "foo.bar.com" is the common name on server certificate server_cert_1.pem.
serverName: "foo.bar.com",
expectedCertificate: serverPeerCert1,
},
{
desc: "Selected certificate by SNI should be serverPeerCert2",
serverGetCert: func(info *tls.ClientHelloInfo) ([]*tls.Certificate, error) {
return []*tls.Certificate{&serverPeerCert1, &serverPeerCert2, &serverPeerCert3}, nil
},
// "foo.bar.server2.com" is the common name on server certificate server_cert_2.pem.
serverName: "foo.bar.server2.com",
expectedCertificate: serverPeerCert2,
},
}
for _, test := range tests {
test := test
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
t.Run(test.desc, func(t *testing.T) {
serverOptions := &ServerOptions{
GetCertificate: test.serverGetCert,
}
serverConfig, err := serverOptions.config()
if err != nil {
t.Fatalf("Unable to generate serverConfig. Error: %v", err)
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
}
clientHello := &tls.ClientHelloInfo{
CipherSuites: []uint16{tlsEcdheRsaWith3desEdeCbcSha},
ServerName: test.serverName,
SupportedCurves: []tls.CurveID{CurveP256},
SupportedPoints: []uint8{pointFormatUncompressed},
SupportedVersions: []uint16{VersionTLS10},
}
gotCertificate, err := serverConfig.GetCertificate(clientHello)
if err != nil {
t.Fatalf("Server is unable to parse peer certificates. Error: %v", err)
}
if !reflect.DeepEqual(*gotCertificate, test.expectedCertificate) {
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
t.Errorf("GetCertificate() = %v, want %v", gotCertificate, test.expectedCertificate)
}
})
}
}
53 changes: 53 additions & 0 deletions security/advancedtls/sni_110.go
@@ -0,0 +1,53 @@
// +build go1.10

/*
*
* Copyright 2019 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package advancedtls

import (
"crypto/tls"
"fmt"
)

// The function buildGetCertificateFunc returns the certificate that matches the SNI field
// for the given ClientHelloInfo, defaulting to the first element of o.GetCertificate.
func buildGetCertificateFunc(clientHello *tls.ClientHelloInfo, o *ServerOptions) (*tls.Certificate, error) {
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
if o.GetCertificate == nil {
return nil, fmt.Errorf("function GetCertificate must be specified")
}
certificates, err := o.GetCertificate(clientHello)
if err != nil {
return nil, err
}
if len(certificates) == 0 {
return nil, fmt.Errorf("no certificates configured")
}
// If users pass in only one certificate, return that certificate.
if len(certificates) == 1 {
return certificates[0], nil
}
// Choose the SNI certificate using SupportsCertificate.
for _, cert := range certificates {
if err := clientHello.SupportsCertificate(cert); err == nil {
return cert, nil
}
}
// If nothing matches, return the first certificate.
return certificates[0], nil
}
41 changes: 41 additions & 0 deletions security/advancedtls/sni_before_110.go
@@ -0,0 +1,41 @@
// +build !go1.10

/*
*
* Copyright 2019 gRPC authors.
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package advancedtls

import (
"crypto/tls"
"fmt"
)

// The function buildGetCertificateFunc returns the first element of o.GetCertificate.
func buildGetCertificateFunc(clientHello *tls.ClientHelloInfo, o *ServerOptions) (*tls.Certificate, error) {
if o.GetCertificate == nil {
return nil, fmt.Errorf("function GetCertificate must be specified")
}
certificates, err := o.GetCertificate(clientHello)
if err != nil {
return nil, err
}
if len(certificates) == 0 {
return nil, fmt.Errorf("no certificates configured")
}
return certificates[0], nil
}
15 changes: 15 additions & 0 deletions security/advancedtls/testdata/README.md
Expand Up @@ -40,3 +40,18 @@ commands we run:
$ openssl verify -verbose -CAfile ca_cert.pem subject_cert.pem

```

cindyxue marked this conversation as resolved.
Show resolved Hide resolved
Appendix
-------------
Documents the attributes for generated certificates.
1. `server_cert_1.pem`

Common name: `"foo.bar.com"`

2. `server_cert_2.pem`

Common name: `"foo.bar.server2.com"`

2. `server_cert_3.pem`

Common name: `"localhost"`
22 changes: 22 additions & 0 deletions security/advancedtls/testdata/server_cert_3.pem
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
cindyxue marked this conversation as resolved.
Show resolved Hide resolved
MIIDjzCCAncCFHFDpmSYUQBXce6Fz6xsA1+t00l7MA0GCSqGSIb3DQEBCwUAMIGD
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFNhbiBKb3NlMQ8w
DQYDVQQKDAZHb29nbGUxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
IjAgBgkqhkiG9w0BCQEWE2NpbmR5eHVlQGdvb2dsZS5jb20wHhcNMjAwNjI5MDA0
NDU2WhcNMjEwNjI5MDA0NDU2WjCBgzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNB
MREwDwYDVQQHDAhTYW4gSm9zZTEPMA0GA1UECgwGR29vZ2xlMQswCQYDVQQLDAJJ
VDESMBAGA1UEAwwJbG9jYWxob3N0MSIwIAYJKoZIhvcNAQkBFhNjaW5keXh1ZUBn
b29nbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsFT6w0At
sk90S5Scu+jvScOHlGyKQJxU8P0r6roLPVEDZtjMoozBJ318LQNIzX123kTRixii
dIGeiGteb9AeY0rivbmU/UbaAtoobLVbd4M1BqhSZzgpu1IDvs3OIlU1//E2UQxc
n9JxumyWrDfXu+i0qIRCiABKcUrnyfZ5pmY6M4KAtzMkOrVreUD0Egf6ateEYeok
yE8UOOQfgIc5Ak97GIlMnE4fIuY3rIXcVkfFgG5vBH63Bhz2JnOT+3aPvM7Ft0wP
eij515dlMpuusLcluDUZAAD1T5Zi36lA/X+1JAZiAa3zzU3rCjD2kWZ2C6OPN39t
l5ghHerEwvloJwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBG3A0L3Wl7yymPOFiq
rDCNdqb1NRNFVr3WkjJaVd6bTk+7L0MIT81aTydv8w3s5CJ07GA7EQzTT1w3KNld
mJq7/xLBNRX1DWMLFTzwx+ig93WrwiQLscWRf26SPKzB8x5eVY/LNJaS3zObDcOv
LULfgIyiTAOfFBoYbzpNVWUW7swD2aBB0f0Zk8ZfmuCvyKy9sxRK6oyV1ot64tFy
YSUpJxSBcaeTt5HXLA7QgTpHMYOUttZCaVs1/4WcrOC9wh24r1mXlPvWWMp5dAZq
aVz8ZXd00OLz6v8ag4QEq/wxTeLdf1U7kJkJ0cj9/nPMLXBERQ54hVvuj5ea3oOv
FY7W
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions security/advancedtls/testdata/server_key_3.pem
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCwVPrDQC2yT3RL
lJy76O9Jw4eUbIpAnFTw/Svqugs9UQNm2MyijMEnfXwtA0jNfXbeRNGLGKJ0gZ6I
a15v0B5jSuK9uZT9RtoC2ihstVt3gzUGqFJnOCm7UgO+zc4iVTX/8TZRDFyf0nG6
bJasN9e76LSohEKIAEpxSufJ9nmmZjozgoC3MyQ6tWt5QPQSB/pq14Rh6iTITxQ4
5B+AhzkCT3sYiUycTh8i5jeshdxWR8WAbm8EfrcGHPYmc5P7do+8zsW3TA96KPnX
l2Uym66wtyW4NRkAAPVPlmLfqUD9f7UkBmIBrfPNTesKMPaRZnYLo483f22XmCEd
6sTC+WgnAgMBAAECggEBAKAaOKlpr1QUYmJxqDHR2Nqf541zU8BAcbtyFBsHG0ds
NGuAc6Uvqf1iKYpxTfwObAx6bcwe7Ppd6oSxAEkDrWO4TdG4HDgvyakHTecOb+M+
xbmqwU8pJnMHj5ECKqTvu50M9aV3VO5kVJDRIaly9rWOH+JWXFZB9VWSjozI90GG
U11E5iSZI/ngUZL72eVqr5gOOD8lTaaLy8jHPnxOMpjZFAscPp1zy8y5DIf+Fgxc
kPzbhckxX8cpk2ATcdRK/UWNi3e/cyMxfeJvst6NwCgmXmDmfkyXMbU8wcEzEWPT
n4uf8gAaCR7fEJQa6Xo3s7q9WP86xnntaoer7V27KtkCgYEA5Y7uL2p2YcjrKfZ9
6VMPmJyDTH6G08/zyOjFu1P4UYOIS6L5CeEdQj7rw2em313dZOtVQN/uhE7CzqS1
0wpZ1Qbl1oVFd5On6r8Qz9Dhf+bRVbikPAtvwdETWpqHqXtYD++BIYpfyJXi2nnR
6TrWBWl/QT/uDYEKlZNdRWm8qu0CgYEAxKSLN3wSoepxXxC5A5/vrtvXxdLRZpxT
w5Tn4/Ihx8nB64XkrcdivsyLjVDmyseKVQvnk+/8Y5G5VT6bV1MweDJl4cNHqOMJ
xubNm0dnbGITYyXxEknyGyNHO3l7DGPTF+iENln5GwYHVIHZci87rLyn22yDszYe
Sg2p0p0yOOMCgYEAhMZA+efoBPMDIchSV2wVbz3Hq6zbKxPye1g3VfxhejRL1wOy
a4ZrN+/QybrgB/3wmfiL3PQorxonDwKxsBkikFZnBccbwOgEjYBppum4JBRLK/uZ
8qjiwQW+3f7XTK3s53quA8pXUFtxVNB1GyNOut6kHgduFx12E8Gihw17dJkCgYAJ
Jsc729BaLLj9/Z8+pfDXqG+QS1Fnfxr+3S01lI0x6RfXSDHuTHsx+f78oqk7ArJT
ZuxuHBsY3y1K5FECbWKyFVZcfWQWXgqUcSVcdqQ/jQjt8lQXz80uqiOkhvDNENBA
KpgMl39aXJt2uVxPThdu4JDHS3ONoZUjSSOAI8S0lQKBgQC1llqqsrVR88XWwrCj
BucenaOL0ej0ShBrY/mphRSXO8X20pn/sh7vAWrAltwGLjWkBalpmYLoRigPdZft
CwlIPnmwNeEOW83eAgc6uToUdagUjolO7Dc+vDLBVXj6DulYfsEhwH/2Y66V2Kd5
8TLhmj77CVDskX6lhRhYGS0FBA==
-----END PRIVATE KEY-----