From 51762f221ec8ab38da03149c8012e63aec0433dc Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 30 Oct 2020 15:43:34 -0700 Subject: [PATCH] feat: add mtls support for NetHttpTransport (#1147) * feat: support keystore in transport for mtls * fix format * update code * add tests * update test and doc * update names * create keystore from cert and key string * change certAndKey from string to inputstream * add mtls file * Update google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java Co-authored-by: Jeff Ching * Update google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java Co-authored-by: Jeff Ching * Update google-http-client/src/main/java/com/google/api/client/util/SslUtils.java Co-authored-by: Jeff Ching * Update google-http-client/src/main/java/com/google/api/client/util/SslUtils.java Co-authored-by: Jeff Ching * Update google-http-client/src/test/java/com/google/api/client/util/SecurityUtilsTest.java Co-authored-by: Jeff Ching * Update google-http-client/src/main/java/com/google/api/client/util/SslUtils.java Co-authored-by: Jeff Ching * update the code * fix name Co-authored-by: Jeff Ching --- .../google/api/client/http/HttpTransport.java | 9 +++ .../client/http/javanet/NetHttpTransport.java | 57 +++++++++++++++++-- .../google/api/client/util/SecurityUtils.java | 56 ++++++++++++++++++ .../com/google/api/client/util/SslUtils.java | 30 ++++++++++ .../http/javanet/NetHttpTransportTest.java | 27 +++++++++ .../api/client/util/SecurityUtilsTest.java | 45 +++++++++++++++ .../com/google/api/client/util/cert.pem | 14 +++++ .../google/api/client/util/mtlsCertAndKey.pem | 30 ++++++++++ .../com/google/api/client/util/privateKey.pem | 16 ++++++ 9 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 google-http-client/src/test/resources/com/google/api/client/util/cert.pem create mode 100644 google-http-client/src/test/resources/com/google/api/client/util/mtlsCertAndKey.pem create mode 100644 google-http-client/src/test/resources/com/google/api/client/util/privateKey.pem diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpTransport.java b/google-http-client/src/main/java/com/google/api/client/http/HttpTransport.java index d8b858c8e..d4fad3f87 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/HttpTransport.java +++ b/google-http-client/src/main/java/com/google/api/client/http/HttpTransport.java @@ -129,6 +129,15 @@ public boolean supportsMethod(String method) throws IOException { return Arrays.binarySearch(SUPPORTED_METHODS, method) >= 0; } + /** + * Returns whether the transport is mTLS. + * + * @return boolean indicating if the transport is mTLS. + */ + public boolean isMtls() { + return false; + } + /** * Builds a low level HTTP request for the given HTTP method. * diff --git a/google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java b/google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java index 3e90cb2c2..b44b36a62 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java +++ b/google-http-client/src/main/java/com/google/api/client/http/javanet/NetHttpTransport.java @@ -89,13 +89,16 @@ private static Proxy defaultProxy() { /** Host name verifier or {@code null} for the default. */ private final HostnameVerifier hostnameVerifier; + /** Whether the transport is mTLS. Default value is {@code false}. */ + private final boolean isMtls; + /** * Constructor with the default behavior. * *

Instead use {@link Builder} to modify behavior. */ public NetHttpTransport() { - this((ConnectionFactory) null, null, null); + this((ConnectionFactory) null, null, null, false); } /** @@ -104,10 +107,14 @@ public NetHttpTransport() { * system properties * @param sslSocketFactory SSL socket factory or {@code null} for the default * @param hostnameVerifier host name verifier or {@code null} for the default + * @param isMtls Whether the transport is mTLS. Default value is {@code false} */ NetHttpTransport( - Proxy proxy, SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier) { - this(new DefaultConnectionFactory(proxy), sslSocketFactory, hostnameVerifier); + Proxy proxy, + SSLSocketFactory sslSocketFactory, + HostnameVerifier hostnameVerifier, + boolean isMtls) { + this(new DefaultConnectionFactory(proxy), sslSocketFactory, hostnameVerifier, isMtls); } /** @@ -115,15 +122,18 @@ public NetHttpTransport() { * {@link DefaultConnectionFactory} is used * @param sslSocketFactory SSL socket factory or {@code null} for the default * @param hostnameVerifier host name verifier or {@code null} for the default + * @param isMtls Whether the transport is mTLS. Default value is {@code false} * @since 1.20 */ NetHttpTransport( ConnectionFactory connectionFactory, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier) { + HostnameVerifier hostnameVerifier, + boolean isMtls) { this.connectionFactory = getConnectionFactory(connectionFactory); this.sslSocketFactory = sslSocketFactory; this.hostnameVerifier = hostnameVerifier; + this.isMtls = isMtls; } private ConnectionFactory getConnectionFactory(ConnectionFactory connectionFactory) { @@ -141,6 +151,11 @@ public boolean supportsMethod(String method) { return Arrays.binarySearch(SUPPORTED_METHODS, method) >= 0; } + @Override + public boolean isMtls() { + return this.isMtls; + } + @Override protected NetHttpRequest buildRequest(String method, String url) throws IOException { Preconditions.checkArgument(supportsMethod(method), "HTTP method %s not supported", method); @@ -189,6 +204,9 @@ public static final class Builder { */ private ConnectionFactory connectionFactory; + /** Whether the transport is mTLS. Default value is {@code false}. */ + private boolean isMtls; + /** * Sets the HTTP proxy or {@code null} to use the proxy settings from system @@ -275,6 +293,33 @@ public Builder trustCertificates(KeyStore trustStore) throws GeneralSecurityExce return setSslSocketFactory(sslContext.getSocketFactory()); } + /** + * Sets the SSL socket factory based on a root certificate trust store and a client certificate + * key store. The client certificate key store will be used to establish mutual TLS. + * + * @param trustStore certificate trust store (use for example {@link SecurityUtils#loadKeyStore} + * or {@link SecurityUtils#loadKeyStoreFromCertificates}) + * @param mtlsKeyStore key store for client certificate and key to establish mutual TLS. (use + * for example {@link SecurityUtils#createMtlsKeyStore(InputStream)}) + * @param mtlsKeyStorePassword password for mtlsKeyStore parameter + */ + public Builder trustCertificates( + KeyStore trustStore, KeyStore mtlsKeyStore, String mtlsKeyStorePassword) + throws GeneralSecurityException { + if (mtlsKeyStore != null && mtlsKeyStore.size() > 0) { + this.isMtls = true; + } + SSLContext sslContext = SslUtils.getTlsSslContext(); + SslUtils.initSslContext( + sslContext, + trustStore, + SslUtils.getPkixTrustManagerFactory(), + mtlsKeyStore, + mtlsKeyStorePassword, + SslUtils.getDefaultKeyManagerFactory()); + return setSslSocketFactory(sslContext.getSocketFactory()); + } + /** * {@link Beta}
* Disables validating server SSL certificates by setting the SSL socket factory using {@link @@ -319,8 +364,8 @@ public NetHttpTransport build() { setProxy(defaultProxy()); } return this.proxy == null - ? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier) - : new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier); + ? new NetHttpTransport(connectionFactory, sslSocketFactory, hostnameVerifier, isMtls) + : new NetHttpTransport(this.proxy, sslSocketFactory, hostnameVerifier, isMtls); } } } diff --git a/google-http-client/src/main/java/com/google/api/client/util/SecurityUtils.java b/google-http-client/src/main/java/com/google/api/client/util/SecurityUtils.java index cf08e03ad..8f59a8747 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/SecurityUtils.java +++ b/google-http-client/src/main/java/com/google/api/client/util/SecurityUtils.java @@ -17,6 +17,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -31,6 +32,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.List; import javax.net.ssl.X509TrustManager; @@ -258,5 +260,59 @@ public static void loadKeyStoreFromCertificates( } } + /** + * Create a keystore for mutual TLS with the certificate and private key provided. + * + * @param certAndKey Certificate and private key input stream. The stream should contain one + * certificate and one unencrypted private key. If there are multiple certificates, only the + * first certificate will be used. + * @return keystore for mutual TLS. + */ + public static KeyStore createMtlsKeyStore(InputStream certAndKey) + throws GeneralSecurityException, IOException { + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(null); + + PemReader.Section certSection = null; + PemReader.Section keySection = null; + PemReader reader = new PemReader(new InputStreamReader(certAndKey)); + + while (certSection == null || keySection == null) { + // Read the certificate and private key. + PemReader.Section section = reader.readNextSection(); + if (section == null) { + break; + } + + if (certSection == null && "CERTIFICATE".equals(section.getTitle())) { + certSection = section; + } else if ("PRIVATE KEY".equals(section.getTitle())) { + keySection = section; + } + } + + if (certSection == null) { + throw new IllegalArgumentException("certificate is missing from certAndKey string"); + } + if (keySection == null) { + throw new IllegalArgumentException("private key is missing from certAndKey string"); + } + + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = + (X509Certificate) + certFactory.generateCertificate( + new ByteArrayInputStream(certSection.getBase64DecodedBytes())); + + PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(keySection.getBase64DecodedBytes()); + PrivateKey key = + KeyFactory.getInstance(cert.getPublicKey().getAlgorithm()).generatePrivate(keySpecPKCS8); + + // Fit the certificate and private key into the keystore. + keystore.setKeyEntry("alias", key, new char[] {}, new X509Certificate[] {cert}); + + return keystore; + } + private SecurityUtils() {} } diff --git a/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java b/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java index d4ed4f7cf..fc4b7900b 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java +++ b/google-http-client/src/main/java/com/google/api/client/util/SslUtils.java @@ -109,6 +109,36 @@ public static SSLContext initSslContext( return sslContext; } + /** + * Initializes the SSL context to the trust managers supplied by the trust manager factory for the + * given trust store, and to the key managers supplied by the key manager factory for the given + * key store. + * + * @param sslContext SSL context (for example {@link SSLContext#getInstance}) + * @param trustStore key store for certificates to trust (for example {@link + * SecurityUtils#getJavaKeyStore()}) + * @param trustManagerFactory trust manager factory (for example {@link + * #getPkixTrustManagerFactory()}) + * @param mtlsKeyStore key store for client certificate and key to establish mutual TLS + * @param mtlsKeyStorePassword password for mtlsKeyStore parameter + * @param keyManagerFactory key manager factory (for example {@link + * #getDefaultKeyManagerFactory()}) + */ + public static SSLContext initSslContext( + SSLContext sslContext, + KeyStore trustStore, + TrustManagerFactory trustManagerFactory, + KeyStore mtlsKeyStore, + String mtlsKeyStorePassword, + KeyManagerFactory keyManagerFactory) + throws GeneralSecurityException { + trustManagerFactory.init(trustStore); + keyManagerFactory.init(mtlsKeyStore, mtlsKeyStorePassword.toCharArray()); + sslContext.init( + keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + return sslContext; + } + /** * {@link Beta}
* Returns an SSL context in which all X.509 certificates are trusted. diff --git a/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java b/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java index a1bc3b348..338236e9b 100644 --- a/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java +++ b/google-http-client/src/test/java/com/google/api/client/http/javanet/NetHttpTransportTest.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.security.KeyStore; import junit.framework.TestCase; /** @@ -36,6 +37,32 @@ public class NetHttpTransportTest extends TestCase { "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE" }; + public void testNotMtlsWithoutClientCert() throws Exception { + KeyStore trustStore = KeyStore.getInstance("JKS"); + + NetHttpTransport transport = + new NetHttpTransport.Builder().trustCertificates(trustStore).build(); + assertFalse(transport.isMtls()); + } + + public void testIsMtlsWithClientCert() throws Exception { + KeyStore trustStore = KeyStore.getInstance("JKS"); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + + // Load client certificate and private key from secret.p12 file. + keyStore.load( + this.getClass() + .getClassLoader() + .getResourceAsStream("com/google/api/client/util/secret.p12"), + "notasecret".toCharArray()); + + NetHttpTransport transport = + new NetHttpTransport.Builder() + .trustCertificates(trustStore, keyStore, "notasecret") + .build(); + assertTrue(transport.isMtls()); + } + public void testExecute_mock() throws Exception { for (String method : METHODS) { boolean isPutOrPost = method.equals("PUT") || method.equals("POST"); diff --git a/google-http-client/src/test/java/com/google/api/client/util/SecurityUtilsTest.java b/google-http-client/src/test/java/com/google/api/client/util/SecurityUtilsTest.java index b9f91fd1e..21bdd9dc3 100644 --- a/google-http-client/src/test/java/com/google/api/client/util/SecurityUtilsTest.java +++ b/google-http-client/src/test/java/com/google/api/client/util/SecurityUtilsTest.java @@ -17,6 +17,8 @@ import com.google.api.client.testing.json.webtoken.TestCertificates; import com.google.api.client.testing.util.SecurityTestUtils; import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.KeyStore; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.X509Certificate; @@ -160,4 +162,47 @@ public void testVerifyX509() throws Exception { public void testVerifyX509WrongCa() throws Exception { assertNull(verifyX509(TestCertificates.BOGUS_CA_CERT)); } + + public void testCreateMtlsKeyStoreNoCert() throws Exception { + final InputStream certMissing = + getClass() + .getClassLoader() + .getResourceAsStream("com/google/api/client/util/privateKey.pem"); + + boolean thrown = false; + try { + SecurityUtils.createMtlsKeyStore(certMissing); + fail("should have thrown"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("certificate is missing from certAndKey string")); + thrown = true; + } + assertTrue("should have caught an IllegalArgumentException", thrown); + } + + public void testCreateMtlsKeyStoreNoPrivateKey() throws Exception { + final InputStream privateKeyMissing = + getClass().getClassLoader().getResourceAsStream("com/google/api/client/util/cert.pem"); + + boolean thrown = false; + try { + SecurityUtils.createMtlsKeyStore(privateKeyMissing); + fail("should have thrown"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("private key is missing from certAndKey string")); + thrown = true; + } + assertTrue("should have caught an IllegalArgumentException", thrown); + } + + public void testCreateMtlsKeyStoreSuccess() throws Exception { + InputStream certAndKey = + getClass() + .getClassLoader() + .getResourceAsStream("com/google/api/client/util/mtlsCertAndKey.pem"); + + KeyStore mtlsKeyStore = SecurityUtils.createMtlsKeyStore(certAndKey); + + assertEquals(1, mtlsKeyStore.size()); + } } diff --git a/google-http-client/src/test/resources/com/google/api/client/util/cert.pem b/google-http-client/src/test/resources/com/google/api/client/util/cert.pem new file mode 100644 index 000000000..56e1319bf --- /dev/null +++ b/google-http-client/src/test/resources/com/google/api/client/util/cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE +AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x +MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3 +MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ +GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ +Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB +AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM +MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4 +fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4 +uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1 +kWwa9n19NFiV0z3m6isj +-----END CERTIFICATE----- \ No newline at end of file diff --git a/google-http-client/src/test/resources/com/google/api/client/util/mtlsCertAndKey.pem b/google-http-client/src/test/resources/com/google/api/client/util/mtlsCertAndKey.pem new file mode 100644 index 000000000..d6c045125 --- /dev/null +++ b/google-http-client/src/test/resources/com/google/api/client/util/mtlsCertAndKey.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIICGzCCAYSgAwIBAgIIWrt6xtmHPs4wDQYJKoZIhvcNAQEFBQAwMzExMC8GA1UE +AxMoMTAwOTEyMDcyNjg3OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0x +MjEyMDExNjEwNDRaFw0yMjExMjkxNjEwNDRaMDMxMTAvBgNVBAMTKDEwMDkxMjA3 +MjY4NzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20wgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAL1SdY8jTUVU7O4/XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQ +GLW8Iftx9wfXe1zuaehJSgLcyCxazfyJoN3RiONBihBqWY6d3lQKqkgsRTNZkdFJ +Wdzl/6CxhK9sojh2p0r3tydtv9iwq5fuuWIvtODtT98EgphhncQAqkKoF3zVAgMB +AAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM +MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GBAD8XQEqzGePa9VrvtEGpf+R4 +fkxKbcYAzqYq202nKu0kfjhIYkYSBj6gi348YaxE64yu60TVl42l5HThmswUheW4 +uQIaq36JvwvsDP5Zoj5BgiNSnDAFQp+jJFBRUA5vooJKgKgMDf/r/DCOsbO6VJF1 +kWwa9n19NFiV0z3m6isj +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/ +XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ +oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu +uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy +sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK +/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY +lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1 +fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq +RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB +Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno +fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L +XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw +ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE +VUV6b25kqrcu +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/google-http-client/src/test/resources/com/google/api/client/util/privateKey.pem b/google-http-client/src/test/resources/com/google/api/client/util/privateKey.pem new file mode 100644 index 000000000..dd13e1c09 --- /dev/null +++ b/google-http-client/src/test/resources/com/google/api/client/util/privateKey.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL1SdY8jTUVU7O4/ +XrZLYTw0ON1lV6MQRGajFDFCqD2Fd9tQGLW8Iftx9wfXe1zuaehJSgLcyCxazfyJ +oN3RiONBihBqWY6d3lQKqkgsRTNZkdFJWdzl/6CxhK9sojh2p0r3tydtv9iwq5fu +uWIvtODtT98EgphhncQAqkKoF3zVAgMBAAECgYB51B9cXe4yiGTzJ4pOKpHGySAy +sC1F/IjXt2eeD3PuKv4m/hL4l7kScpLx0+NJuQ4j8U2UK/kQOdrGANapB1ZbMZAK +/q0xmIUzdNIDiGSoTXGN2mEfdsEpQ/Xiv0lyhYBBPC/K4sYIpHccnhSRQUZlWLLY +lE5cFNKC9b7226mNvQJBAPt0hfCNIN0kUYOA9jdLtx7CE4ySGMPf5KPBuzPd8ty1 +fxaFm9PB7B76VZQYmHcWy8rT5XjoLJHrmGW1ZvP+iDsCQQDAvnKoarPOGb5iJfkq +RrA4flf1TOlf+1+uqIOJ94959jkkJeb0gv/TshDnm6/bWn+1kJylQaKygCizwPwB +Z84vAkA0Duur4YvsPJijoQ9YY1SGCagCcjyuUKwFOxaGpmyhRPIKt56LOJqpzyno +fy8ReKa4VyYq4eZYT249oFCwMwIBAkAROPNF2UL3x5UbcAkznd1hLujtIlI4IV4L +XUNjsJtBap7we/KHJq11XRPlniO4lf2TW7iji5neGVWJulTKS1xBAkAerktk4Hsw +ErUaUG1s/d+Sgc8e/KMeBElV+NxGhcWEeZtfHMn/6VOlbzY82JyvC9OKC80A5CAE +VUV6b25kqrcu +-----END PRIVATE KEY----- \ No newline at end of file