diff --git a/sdk-platform-java/gax-java/gax-httpjson/BUILD.bazel b/sdk-platform-java/gax-java/gax-httpjson/BUILD.bazel index c4f78edb79a2..e97aedebabfd 100644 --- a/sdk-platform-java/gax-java/gax-httpjson/BUILD.bazel +++ b/sdk-platform-java/gax-java/gax-httpjson/BUILD.bazel @@ -53,6 +53,10 @@ java_library( srcs = glob(["src/test/java/**/*.java"]), javacopts = _JAVA_COPTS, plugins = ["//:auto_value_plugin"], + resources = glob([ + "src/test/resources/com/google/api/gax/httpjson/tls13TestCa.pem", + "src/test/resources/com/google/api/gax/httpjson/tls13TestServerCertAndKey.pem", + ]), visibility = ["//visibility:public"], deps = [":gax_httpjson"] + _COMPILE_DEPS + _TEST_COMPILE_DEPS, ) diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index 226c5a155feb..2bfcd40b305c 100644 --- a/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -54,6 +54,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -223,10 +224,15 @@ private NetHttpTransport.Builder configureMtls(NetHttpTransport.Builder builder) // and trust manager factory (TMF) are bound to Conscrypt's TLS implementation (supporting PQC // key exchange). SSLContext sslContext = SSLContext.getInstance("TLS", conscryptProvider); + // The TrustManagerFactory must come from the same provider as the SSLContext. On TLS 1.3, + // Conscrypt passes authType "GENERIC" to the trust manager, which the JDK (SunJSSE) PKIX + // trust manager rejects for CA-issued server certificates that carry a KeyUsage extension + // (e.g. Google front ends), failing the handshake with "Unknown authType: GENERIC". + // Conscrypt's trust manager loads the same default trust store as the JDK. SslUtils.initSslContext( sslContext, null, - SslUtils.getPkixTrustManagerFactory(), + TrustManagerFactory.getInstance("PKIX", conscryptProvider), mtlsKeyStore, "", SslUtils.getDefaultKeyManagerFactory()); diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTls13Test.java b/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTls13Test.java new file mode 100644 index 000000000000..c7374e986700 --- /dev/null +++ b/sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTls13Test.java @@ -0,0 +1,278 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.httpjson; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.util.SecurityUtils; +import com.google.api.gax.rpc.mtls.CertificateBasedAccess; +import com.google.api.gax.rpc.testing.FakeMtlsProvider; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +/** + * Handshake test for the Conscrypt-backed mTLS {@link HttpTransport} built by {@link + * InstantiatingHttpJsonChannelProvider}. + * + *

Regression test for "Unknown authType: GENERIC": on TLS 1.3, Conscrypt passes the authType + * {@code "GENERIC"} to its trust manager. If the trust manager comes from the JDK (SunJSSE) rather + * than Conscrypt, it rejects server certificates that are issued by a trusted CA and carry a + * KeyUsage extension (as Google front ends' certificates do), and every mTLS HTTP/JSON handshake + * fails. A self-signed server certificate that is itself the trust anchor does not reproduce the + * issue, so the server here presents a CA-issued leaf with KeyUsage. + * + *

The transport always uses the default trust store, so this test points {@code + * javax.net.ssl.trustStore} at a temporary store containing only the test CA, and restores the + * previous value afterwards. + */ +@EnabledIf( + value = "isConscryptAndTls13Available", + disabledReason = "Conscrypt native library or TLS 1.3 is unavailable on this platform") +class InstantiatingHttpJsonChannelProviderTls13Test { + + private static final String RESOURCE_DIR = "com/google/api/gax/httpjson/"; + private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; + private static final String TRUST_STORE_PASSWORD_PROPERTY = "javax.net.ssl.trustStorePassword"; + private static final String TRUST_STORE_PASSWORD = "changeit"; + + @TempDir File tempDir; + + private String previousTrustStore; + private String previousTrustStorePassword; + private ExecutorService serverExecutor; + + /** Condition for {@link EnabledIf}; must be static because it is used at class level. */ + static boolean isConscryptAndTls13Available() { + return HttpJsonConscryptUtils.getConscryptProvider() != null && isTls13Supported(); + } + + @BeforeEach + void setUp() throws Exception { + previousTrustStore = System.getProperty(TRUST_STORE_PROPERTY); + previousTrustStorePassword = System.getProperty(TRUST_STORE_PASSWORD_PROPERTY); + File trustStoreFile = writeTrustStoreWithTestCa(); + System.setProperty(TRUST_STORE_PROPERTY, trustStoreFile.getAbsolutePath()); + System.setProperty(TRUST_STORE_PASSWORD_PROPERTY, TRUST_STORE_PASSWORD); + + serverExecutor = Executors.newSingleThreadExecutor(); + } + + @AfterEach + void tearDown() { + restoreProperty(TRUST_STORE_PROPERTY, previousTrustStore); + restoreProperty(TRUST_STORE_PASSWORD_PROPERTY, previousTrustStorePassword); + if (serverExecutor != null) { + serverExecutor.shutdownNow(); + } + } + + @Test + void createHttpTransport_withMtlsAndConscrypt_completesTls13HandshakeWithCaIssuedServerCert() + throws Exception { + CertificateBasedAccess certificateBasedAccess = Mockito.mock(CertificateBasedAccess.class); + Mockito.when(certificateBasedAccess.useMtlsClientCertificate()).thenReturn(true); + InstantiatingHttpJsonChannelProvider channelProvider = + InstantiatingHttpJsonChannelProvider.newBuilder() + .setEndpoint("localhost:443") + .setMtlsProvider( + new FakeMtlsProvider(FakeMtlsProvider.createTestMtlsKeyStore(), "", false)) + .setCertificateBasedAccess(certificateBasedAccess) + .build(); + HttpTransport transport = channelProvider.createHttpTransport(); + assertThat(transport).isNotNull(); + + final SSLServerSocket serverSocket = createTls13ServerSocket(); + try { + Future clientCertificatePresented = + serverExecutor.submit(() -> serveSingleRequest(serverSocket)); + + HttpResponse response = + transport + .createRequestFactory() + .buildGetRequest( + new GenericUrl("https://localhost:" + serverSocket.getLocalPort() + "/")) + .execute(); + try { + assertThat(response.getStatusCode()).isEqualTo(200); + assertThat(response.parseAsString()).isEqualTo("ok"); + } finally { + response.disconnect(); + } + assertThat(clientCertificatePresented.get(10, TimeUnit.SECONDS)).isTrue(); + } finally { + serverSocket.close(); + } + } + + /** Accepts one connection, answers one HTTP request, and reports if a client cert was sent. */ + private static boolean serveSingleRequest(SSLServerSocket serverSocket) throws Exception { + SSLSocket socket = (SSLSocket) serverSocket.accept(); + try { + socket.startHandshake(); + boolean clientCertificatePresented = socket.getSession().getPeerCertificates().length > 0; + BufferedReader reader = + new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII)); + String line; + while ((line = reader.readLine()) != null && !line.isEmpty()) { + // Drain request headers. + } + OutputStream out = socket.getOutputStream(); + out.write( + ("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok") + .getBytes(StandardCharsets.US_ASCII)); + out.flush(); + return clientCertificatePresented; + } finally { + socket.close(); + } + } + + /** + * Creates a JDK TLS 1.3 server that presents a CA-issued leaf certificate with KeyUsage and + * requires (but does not validate) a client certificate. + */ + private static SSLServerSocket createTls13ServerSocket() throws Exception { + KeyStore serverKeyStore; + InputStream certAndKey = openResource("tls13TestServerCertAndKey.pem"); + try { + serverKeyStore = SecurityUtils.createMtlsKeyStore(certAndKey); + } finally { + certAndKey.close(); + } + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(serverKeyStore, new char[0]); + + SSLContext serverContext = SSLContext.getInstance("TLSv1.3"); + serverContext.init( + keyManagerFactory.getKeyManagers(), new TrustManager[] {new AcceptAllTrustManager()}, null); + SSLServerSocket serverSocket = + (SSLServerSocket) + serverContext + .getServerSocketFactory() + .createServerSocket(0, 1, InetAddress.getLoopbackAddress()); + serverSocket.setEnabledProtocols(new String[] {"TLSv1.3"}); + serverSocket.setNeedClientAuth(true); + return serverSocket; + } + + private File writeTrustStoreWithTestCa() throws Exception { + X509Certificate caCertificate; + InputStream caPem = openResource("tls13TestCa.pem"); + try { + caCertificate = + (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(caPem); + } finally { + caPem.close(); + } + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + trustStore.setCertificateEntry("gax-test-ca", caCertificate); + File trustStoreFile = new File(tempDir, "truststore"); + OutputStream out = new FileOutputStream(trustStoreFile); + try { + trustStore.store(out, TRUST_STORE_PASSWORD.toCharArray()); + } finally { + out.close(); + } + return trustStoreFile; + } + + private static InputStream openResource(String name) { + InputStream stream = + InstantiatingHttpJsonChannelProviderTls13Test.class + .getClassLoader() + .getResourceAsStream(RESOURCE_DIR + name); + if (stream == null) { + throw new IllegalStateException("Missing test resource: " + RESOURCE_DIR + name); + } + return stream; + } + + private static boolean isTls13Supported() { + try { + SSLContext.getInstance("TLSv1.3"); + return true; + } catch (Exception e) { + return false; + } + } + + private static void restoreProperty(String key, String value) { + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + + /** Server-side trust manager: the test only checks that a client certificate is presented. */ + private static final class AcceptAllTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestCa.pem b/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestCa.pem new file mode 100644 index 000000000000..404d562e5297 --- /dev/null +++ b/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestCa.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIUZEyXXbTT3KXPPzN6Z6b30/GQ26wwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLR0FYIFRlc3QgQ0EwIBcNMjYwOTMwMDMzNzM5WhgPMjEy +NjA5MDYwMzM3MzlaMBYxFDASBgNVBAMMC0dBWCBUZXN0IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkLx+Kh78x1ktPpvGf3ITPS7xwr7rU6Pt/Z34 +AKodCSBCrt/d3BhkGiy8w3IqWN7mIy2ujdoKeUau4NJiYdz3A16XZ1nhvp7J3tK5 +a6SSxO95lVwy70nafTO8ApFYKawKLKNWvGqChM95AI6+Q7dTzzGArpwJxCK0p7dp +4bKjhpSOZaW46VdpCsLwoPJmXNlDpAL5O3nrvdnfilHVCqWHoAZk7ESwQcwm8vP6 +5suLUSvpagPfMRabFTEv91PsmVhB8KjSu8/kGPAC1F7RLyZK31a0Kb5wNwW+69cb +kxjUEhNux7/SHTF250kF2vQ+nFrnUQZSGM8cHU8qpeaOsTFDRwIDAQABo2MwYTAd +BgNVHQ4EFgQU7pHELECvL5hdK8Y3N1x6GZSzSjkwHwYDVR0jBBgwFoAU7pHELECv +L5hdK8Y3N1x6GZSzSjkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQELBQADggEBACCyzuu0K4HF0iMu1rj+adlpYUzl6uwkKBuL6fRJ +sTxe0ftgWWcFXJV+P6T+maGu7DMANqbADpWqQbAHMNbSXxPSfGgOd2tS+jg/OOef +go1MhsMUpgxSN9PvWAfhiVIhgF7pdjdVP2qiObx7F/Qo/Xmr0MEy61mmeCbWENBj +jC7DsCo5SWo6kMjvb6dz5G+f+4LElQKCQbBGAOm00XlERE/W7WfUMBzNFhLCHrVs +CRq5lIGxek8DEIC8oXtt/7g0KoKVkOCNj2ZXljEQgi4awoYHqlTRXw/nlctJJ8xs +DLmvfUObHIo66rliDG1Pco/Ce9FVewUJz2WQXJK/mGuuesA= +-----END CERTIFICATE----- diff --git a/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestServerCertAndKey.pem b/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestServerCertAndKey.pem new file mode 100644 index 000000000000..b424ecafa2c7 --- /dev/null +++ b/sdk-platform-java/gax-java/gax-httpjson/src/test/resources/com/google/api/gax/httpjson/tls13TestServerCertAndKey.pem @@ -0,0 +1,48 @@ +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIUA8zck3uCk3uCK7FqAgRrbQjayGEwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLR0FYIFRlc3QgQ0EwIBcNMjYwOTMwMDMzNzM5WhgPMjEy +NjA5MDYwMzM3MzlaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAMLalJEkZTlTkPbSayD72lYxibZ8JLtRP4OaK/Mr +yvhv8JX5JgSxY3fW2FkpYLBFO3fKFyyPRf2pJTHTpvBjyy/xZnyMHltT9uurF58b +kfAzZDLphJdICTXVL9cEHzCJ+AVFJ85bbLDhCcAd5urCZClqOD6QoM7aLsSurwLQ +eeAP44XwcX+2+VvNfB6YFUgjVreTD+c1xD9OsbMFQuWzv/uDkl6KciOkeuAGOOJh +8ylcugFDOfb9ZWWQRiDo9BiIc/6HIhNk8jo8hCgofcxR/MRZNRKOQ4ilsbNkrjo0 +MusuRQq2mcC4yLZRRo7Jtk2FQYysg2e0mbHKroQhmRIP2wcCAwEAAaOBkjCBjzAa +BgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFL5u77FZtvQz +x06t35j/m9a+PVVNMB8GA1UdIwQYMBaAFO6RxCxAry+YXSvGNzdcehmUs0o5MA0G +CSqGSIb3DQEBCwUAA4IBAQBwAaP5bHnvfObJcl7rKOSfXFKhjm0j5De8BNbS5Dcs +Z22gkuri5qgj8MEwGt4Wu4mxopYD18AbWKt8e7mC5+mJgkydSLvRtJxx5geNw0CD +8TJK8MTpJGJtBKoxC26kxZyHTM6GLO3iAXMzuDFbeW4xpESezoaKsctwlUaAQNO+ +taASUFDy9CJ24SDsiWBmSrTZoNqcCbB3yyYS6Wrx1Et1GYmeTtvx8qo9GjGoil/t +9mWgYhb52Wg+7EO5NQa1eIZ23ZgWg4w7CRqPyjqmOM/ygCmHvoNGuOdbMzSc8o5P +i9S6gTwaU7OBa8vrKdMk+LPkmcHs82qujVYqvU1rMV5a +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDC2pSRJGU5U5D2 +0msg+9pWMYm2fCS7UT+DmivzK8r4b/CV+SYEsWN31thZKWCwRTt3yhcsj0X9qSUx +06bwY8sv8WZ8jB5bU/brqxefG5HwM2Qy6YSXSAk11S/XBB8wifgFRSfOW2yw4QnA +HebqwmQpajg+kKDO2i7Erq8C0HngD+OF8HF/tvlbzXwemBVII1a3kw/nNcQ/TrGz +BULls7/7g5JeinIjpHrgBjjiYfMpXLoBQzn2/WVlkEYg6PQYiHP+hyITZPI6PIQo +KH3MUfzEWTUSjkOIpbGzZK46NDLrLkUKtpnAuMi2UUaOybZNhUGMrINntJmxyq6E +IZkSD9sHAgMBAAECggEAB9NPdCTKC+tXPxJkSwbSVfPXXATQVNWGf3qJmVKA7ElS +mDNRW47kN8PhHHOU9n9RTBZJp/h5Dx7j54Rgh/b6Cgrf2tClx3UvzXcpk7I/wi3O +MF1AffLeq9LMAQ6hUgm4dD26w4bYbqIbCiQpSfRUoXUqnRih3KzWg/+f97T3sA/h +SSNH67sQ/SmaqujmCDsXoVF8Ad0bGzEkEVbn7QgXX+C+DxxA6wGvRER7YRfzhxaz +e1B0rdRLTwlAycKOGPvp+7mVrs1KTELyj8rJp6dNzTpll1W4Y5MeJivSjTpEtPzN +Fo6Wh6+/+wLyXcYUknJMfTlt3GcYdAzhREEXBM34gQKBgQDz0/HtQPulpv8jtoms +m7TiIcRuOHbK+ojt5s9dQ6ikYuCOJ3CnCPLX5T7Eo7yl/NvPhqSr15GbqbHNutLt +FL9Gh5L8H+Z7i/hmmAou59WXIcQOfOS+/ZFi1rpE8Fhp10/dYRSznIG4vaN19DBF +Og4a4n5CR+mRgxcQvt32URiCvwKBgQDMlMJ/rCfN2SAQYXwIE8+7viknMVxyoC1T +ggAVfj+XUdEgPeD8+cKIuQ2E0ino4+wLve6zo5skcw+COKFS7QZcL8xT0oYjRX0I +SZFUGhUnuSDv8rbFx25m27IfQ/FicFFdOHK3n1MbX+I0IfKyYvFrL3DfSuKuZKam +ODIy6PFhuQKBgQCxKxvrDQDNSR6y0HwRE8LHXUnh3N1Ud97vHnsmhXcQm1gXcskE +Vhg+j7CgKWl2ItmFXYxh4O1IcnFjz5Bb/GtP0EsZMATFWNtRkMA1fPdSLZLMBCwi +KxVtACGw3gMgRPcfIN9t8xa/KeyQVLKOWrTNDC458w7gUKR94nJd/GcW+QKBgHiI +HJ/D+h+ZkP6VsNHBel6OhwiEWIdgP9Q3f30qSKUKmz93tZrWO/r5rDFY8UA5KNUy +LfTAq6Mp9zUt0D5fT6P4tVWdb1JQmn3LekgmpdglhnZYdb7I5Q7M2YD6Mrlgm09k +9pGg2QID1+4HIkpfaQVRihrEsBE3U8gSy+CaxbxhAoGBALZn4isHqbH/+H3em7Mc +ITRF2xPhHlCv0SHHn6UHoWVFYXmgF+bjX43wVSjmPL9PusSvH+gBxeYpKQSm9Hjp +eoQirtVJPCVmS5e8pKmmJBAUbRHk1umUR7lwgfTlTnIwEOCD+ApaqWByTdsHNPL1 +fazDXasxJ7y760qDNo7UCbec +-----END PRIVATE KEY-----