diff --git a/src/main/java/io/axoniq/axonserver/connector/AxonServerConnectionFactory.java b/src/main/java/io/axoniq/axonserver/connector/AxonServerConnectionFactory.java index ab77d3b2..286951d7 100644 --- a/src/main/java/io/axoniq/axonserver/connector/AxonServerConnectionFactory.java +++ b/src/main/java/io/axoniq/axonserver/connector/AxonServerConnectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2023. AxonIQ + * Copyright (c) 2020-2026. AxonIQ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,12 +36,14 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -59,6 +61,7 @@ import java.util.function.UnaryOperator; import static io.axoniq.axonserver.connector.impl.ObjectUtils.randomHex; +import static java.util.Objects.requireNonNull; /** * The component which manages all the connections which an Axon client can establish with an Axon Server instance. Does @@ -380,6 +383,9 @@ public Builder token(String token) { /** * Configures the use of Transport Layer Security (TLS) using the default settings from the JVM. *

+ * Invoking this or any other transport security method replaces the transport security configuration of + * earlier invocations. + *

* Defaults to not using TLS. * * @return this builder for further configuration @@ -393,6 +399,9 @@ public Builder useTransportSecurity() { /** * Configures the use of Transport Layer Security (TLS) using the settings from the given {@code sslContext}. *

+ * Invoking this or any other transport security method replaces the transport security configuration of + * earlier invocations. + *

* Defaults to not using TLS. * * @param sslContext The context defining TLS parameters @@ -404,6 +413,100 @@ public Builder useTransportSecurity(SslContext sslContext) { return this; } + /** + * Configures the use of Transport Layer Security (TLS), verifying the server's certificate against the + * certificates in the given {@code trustedCertificates} file. Use this when Axon Server (or a proxy in front + * of it) uses a certificate that is not signed by a Certificate Authority trusted by the JVM by default. + *

+ * The given file must be in PEM format and is read once, when this method is invoked. A rotated certificate + * file is not picked up automatically; it requires reconfiguring the factory. + *

+ * Invoking this or any other transport security method replaces the transport security configuration of + * earlier invocations. + *

+ * Defaults to not using TLS. + * + * @param trustedCertificates A PEM file containing the CA certificates to verify the server's certificate + * against + * @return this builder for further configuration + * @see #useMutualTransportSecurity(File, File, File) + */ + public Builder useTransportSecurity(File trustedCertificates) { + return useTransportSecurity(buildSslContext(null, null, trustedCertificates)); + } + + /** + * Configures the use of mutual Transport Layer Security (mTLS), presenting the given client certificate to + * the server during the TLS handshake. The server's certificate is verified using the JVM's default trusted + * Certificate Authorities. + *

+ * The given files must be in PEM format, with the private key in PKCS#8 format. They are read once, when + * this method is invoked. Rotated certificate files are not picked up automatically; they require + * reconfiguring the factory. + *

+ * Invoking this or any other transport security method replaces the transport security configuration of + * earlier invocations. + *

+ * Defaults to not using TLS. + * + * @param clientCertificateChain A PEM file containing the client's certificate chain + * @param clientPrivateKey A PEM file containing the client's PKCS#8 private key + * @return this builder for further configuration + * @see #useMutualTransportSecurity(File, File, File) + */ + public Builder useMutualTransportSecurity(File clientCertificateChain, File clientPrivateKey) { + return useMutualTransportSecurity(clientCertificateChain, clientPrivateKey, null); + } + + /** + * Configures the use of mutual Transport Layer Security (mTLS), presenting the given client certificate to + * the server during the TLS handshake. The server's certificate is verified against the certificates in the + * given {@code trustedCertificates} file, or against the JVM's default trusted Certificate Authorities when + * {@code trustedCertificates} is {@code null}. + *

+ * The given files must be in PEM format, with the private key in PKCS#8 format. They are read once, when + * this method is invoked. Rotated certificate files are not picked up automatically; they require + * reconfiguring the factory. + *

+ * Invoking this or any other transport security method replaces the transport security configuration of + * earlier invocations. + *

+ * Defaults to not using TLS. + * + * @param clientCertificateChain A PEM file containing the client's certificate chain + * @param clientPrivateKey A PEM file containing the client's PKCS#8 private key + * @param trustedCertificates A PEM file containing the CA certificates to verify the server's certificate + * against, or {@code null} to use the JVM's default trusted Certificate + * Authorities + * @return this builder for further configuration + */ + public Builder useMutualTransportSecurity(File clientCertificateChain, + File clientPrivateKey, + File trustedCertificates) { + requireNonNull(clientCertificateChain, "clientCertificateChain may not be null"); + requireNonNull(clientPrivateKey, "clientPrivateKey may not be null"); + return useTransportSecurity(buildSslContext(clientCertificateChain, clientPrivateKey, + trustedCertificates)); + } + + private static SslContext buildSslContext(File clientCertificateChain, + File clientPrivateKey, + File trustedCertificates) { + SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); + if (clientCertificateChain != null) { + sslContextBuilder.keyManager(clientCertificateChain, clientPrivateKey); + } + if (trustedCertificates != null) { + sslContextBuilder.trustManager(trustedCertificates); + } + try { + return sslContextBuilder.build(); + } catch (Exception e) { + throw new IllegalArgumentException("Could not create an SSL context from the given certificate files", + e); + } + } + /** * Indicates whether the connector should always reconnect via the Routing Servers. When {@code true} (default), * the connector will contact the Routing Servers for a new destination each time a connection is dropped. When diff --git a/src/test/java/io/axoniq/axonserver/connector/MutualTlsConnectionTest.java b/src/test/java/io/axoniq/axonserver/connector/MutualTlsConnectionTest.java new file mode 100644 index 00000000..ead3879f --- /dev/null +++ b/src/test/java/io/axoniq/axonserver/connector/MutualTlsConnectionTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2026. AxonIQ + * + * 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 io.axoniq.axonserver.connector; + +import io.axoniq.axonserver.connector.impl.ServerAddress; +import io.axoniq.axonserver.grpc.control.ClientIdentification; +import io.axoniq.axonserver.grpc.control.PlatformInboundInstruction; +import io.axoniq.axonserver.grpc.control.PlatformInfo; +import io.axoniq.axonserver.grpc.control.PlatformOutboundInstruction; +import io.axoniq.axonserver.grpc.control.PlatformServiceGrpc; +import io.grpc.Server; +import io.grpc.TlsServerCredentials; +import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.concurrent.TimeUnit; +import java.util.function.UnaryOperator; + +import static io.axoniq.axonserver.connector.testutils.AssertUtils.assertWithin; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class validating the (mutual) TLS configuration options of the {@link AxonServerConnectionFactory}, against a + * stub PlatformService endpoint secured with TLS. + * + * @author Allard Buijze + */ +class MutualTlsConnectionTest { + + private Server server; + private int serverPort; + private AxonServerConnectionFactory connectionFactory; + + @AfterEach + void tearDown() { + if (connectionFactory != null) { + connectionFactory.shutdown(); + } + if (server != null) { + server.shutdownNow(); + } + } + + @Test + void connectsWithClientCertificateWhenServerRequiresClientAuth() throws Exception { + startServer(TlsServerCredentials.ClientAuth.REQUIRE); + + AxonServerConnection connection = connect( + factory -> factory.useMutualTransportSecurity(tlsFile("client.crt"), + tlsFile("client.key"), + tlsFile("ca.crt")) + ); + + assertWithin(10, TimeUnit.SECONDS, () -> assertTrue(connection.isConnected())); + } + + @Test + void failsToConnectWithoutClientCertificateWhenServerRequiresClientAuth() throws Exception { + startServer(TlsServerCredentials.ClientAuth.REQUIRE); + + AxonServerConnection connection = connect( + factory -> factory.useTransportSecurity(tlsFile("ca.crt")) + ); + + assertWithin(10, TimeUnit.SECONDS, () -> assertTrue(connection.isConnectionFailed())); + assertFalse(connection.isConnected()); + } + + @Test + void connectsWithTrustedCertificatesOnlyWhenServerDoesNotRequireClientAuth() throws Exception { + startServer(TlsServerCredentials.ClientAuth.NONE); + + AxonServerConnection connection = connect( + factory -> factory.useTransportSecurity(tlsFile("ca.crt")) + ); + + assertWithin(10, TimeUnit.SECONDS, () -> assertTrue(connection.isConnected())); + } + + @Test + void mutualTransportSecurityRejectsMissingCertificateOrKey() { + AxonServerConnectionFactory.Builder builder = AxonServerConnectionFactory.forClient("mtls-test"); + + assertThrows(NullPointerException.class, + () -> builder.useMutualTransportSecurity(null, tlsFile("client.key"))); + assertThrows(NullPointerException.class, + () -> builder.useMutualTransportSecurity(tlsFile("client.crt"), null)); + assertThrows(IllegalArgumentException.class, + () -> builder.useMutualTransportSecurity(new File("does-not-exist.crt"), + new File("does-not-exist.key"))); + } + + private void startServer(TlsServerCredentials.ClientAuth clientAuth) throws Exception { + TlsServerCredentials.Builder credentials = TlsServerCredentials.newBuilder() + .keyManager(tlsFile("server.crt"), + tlsFile("server.key")) + .clientAuth(clientAuth); + if (clientAuth != TlsServerCredentials.ClientAuth.NONE) { + credentials.trustManager(tlsFile("ca.crt")); + } + server = NettyServerBuilder.forPort(freePort(), credentials.build()) + .addService(new StubPlatformService()) + .build() + .start(); + } + + private AxonServerConnection connect(UnaryOperator tlsConfig) { + connectionFactory = tlsConfig.apply(AxonServerConnectionFactory.forClient("mtls-test") + .routingServers(new ServerAddress("localhost", + serverPort))) + .build(); + return connectionFactory.connect("default"); + } + + private int freePort() throws IOException { + try (ServerSocket socket = new ServerSocket(0)) { + serverPort = socket.getLocalPort(); + return serverPort; + } + } + + private File tlsFile(String name) { + try { + return new File(getClass().getResource("/tls/" + name).toURI()); + } catch (Exception e) { + throw new IllegalStateException("Could not locate test resource /tls/" + name, e); + } + } + + private static class StubPlatformService extends PlatformServiceGrpc.PlatformServiceImplBase { + + @Override + public void getPlatformServer(ClientIdentification request, StreamObserver responseObserver) { + responseObserver.onNext(PlatformInfo.newBuilder().setSameConnection(true).build()); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver openStream( + StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(PlatformInboundInstruction value) { + // no-op: the stub only needs to keep the stream open + } + + @Override + public void onError(Throwable t) { + // no-op + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } + } +} diff --git a/src/test/resources/tls/ca.crt b/src/test/resources/tls/ca.crt new file mode 100644 index 00000000..1217dfa3 --- /dev/null +++ b/src/test/resources/tls/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFzCCAf+gAwIBAgIULHat2vB2ieUzjtCWRdRkIXXWe00wDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPVGVzdCBUcnVzdGVkIENBMCAXDTI2MDcxNDA4MzU1M1oY +DzIxMjYwNjIwMDgzNTUzWjAaMRgwFgYDVQQDDA9UZXN0IFRydXN0ZWQgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo3ODm78QSpgh4Sy5S/USAbMqT +M6XaNe41+jGwoHrBaFaeCEXADtSSE8O/oFHrrbWnbFxpdMSrXHVCicnl2kPJxQzV +ezo5NuPDEdjKSfjA3Y3uCo71EBP3iUmUZdk9um2gX+t99dIIIq+A0stZs10IWdpr +NTPPelO5qtqO2m/3/aUjKDj9f47UfTaxi6aNseAlrll7ccNI2Csz9x+2zpftsq5y +kCI/AD2ZOkX+1dqi44sVGU6+x/QsFQB3QVRKsdgXIBp8CVzpt4PkzPzlKNq0WiEt +cAcv5kxkDe/e4ZBBnf5l+V4TxHF/z8LPwp8Mr47OpYtGCma25q+jPtK+quUxAgMB +AAGjUzBRMB0GA1UdDgQWBBQHS4hLKOqr4vBgRc8WoyJSVKLiBDAfBgNVHSMEGDAW +gBQHS4hLKOqr4vBgRc8WoyJSVKLiBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBTkMW0nxZM5+12JyzEbXdLW+Az9LGc9YdlLz/0YAaGYXremQXy +pVun2qx730vwkS8nTvat0WPPXfIR8QzO6WTf/BNB4drWN9znqYW58KY78tnSUjNb +uf707QeHCbICH1z+tErQf48JU6HeOLPvXvyvxc4RZAm4kavWbzsKDr8IJfMSh9RC +Aiv0zE/RUHL1wmd6w6UQqmzbzjqi3mv+h0GdRPrTlpus7GngTN8R5Bu0s05ONtSy +fqMTqdQthxdt1jWlnacPrn/LZ/ldAamqecazgBXuKNzqfjfvAOzCgtVl4KWmqjKT +xwyCT49G9g5taftkgR+9NTFKf/UWYbUUY6YQ +-----END CERTIFICATE----- diff --git a/src/test/resources/tls/ca.key b/src/test/resources/tls/ca.key new file mode 100644 index 00000000..93f5033a --- /dev/null +++ b/src/test/resources/tls/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCo3ODm78QSpgh4 +Sy5S/USAbMqTM6XaNe41+jGwoHrBaFaeCEXADtSSE8O/oFHrrbWnbFxpdMSrXHVC +icnl2kPJxQzVezo5NuPDEdjKSfjA3Y3uCo71EBP3iUmUZdk9um2gX+t99dIIIq+A +0stZs10IWdprNTPPelO5qtqO2m/3/aUjKDj9f47UfTaxi6aNseAlrll7ccNI2Csz +9x+2zpftsq5ykCI/AD2ZOkX+1dqi44sVGU6+x/QsFQB3QVRKsdgXIBp8CVzpt4Pk +zPzlKNq0WiEtcAcv5kxkDe/e4ZBBnf5l+V4TxHF/z8LPwp8Mr47OpYtGCma25q+j +PtK+quUxAgMBAAECggEAHuunMRJpbGLDvUdC+cZPo1aId5DnB0CF2JNyQt337G9v +IOvx3bu5d/AvkWjJZ2b9EuW4PEU5+Ajrnd3+Pt0LOaif831niW+AIEml0VDfQG2x +XgvH5p7lkReuBgLqVy/KgCngRMXdCklTqrquqsO5vtw5LxVkYdML/r0G3U6pXxbi +V0aHp/8FCZEUbIte0JeZgIUjnCHg1O80JDCMHtYhyIy3VOauTbeAVfcYGBPBOVpo +YwxOhZpBzaoDYZze0e9+6Jtcu01uId85HvAULRoMDoUmNQOBD2I96ke3sRhB1Ac+ +6quwq3K+dzjla9zqjpLnP8W6AUyhAwQhQxICK05fOwKBgQDWLTNQ67w04G9RyS7A +2JZQw2KnES0sswvL1HcjNkZ8PiFG5GWAN9YBNE7XiDj9EIOJdniEumav/9BCC4Td +JmSH2/s+Xp8fxIZonz27BIpHJaxWzsY8PXrnZx3NsAKZptX2bJq13hb57UDv7aZl +pgziYNMF4HEw09yHUTgDOpuJuwKBgQDJ1mtd9mv7z7j9wd90cZIqnhvd5/rzC0EH +BC6xQ9idPpofELBXHw0CZFsYTEVlZKVoBC1TZmWGLVxDCr4sZaWvtn45kZ66jWHv +yDdwnOqJsIzm1Z5+43WCpnxHEiXdAqBKPAVMglviCx8EcZMMMptA7lnQ8faN/J0/ +20NbjoBYAwKBgDj9uBz9TYyIeDa8IQvB1mXRSAwKvwmY7zei5rzg8WqYIcS6e+Pd +d73ETIJ/xiJY5ZeLhdBTxYVpveIFLKqoT7HzMGzNQuxyIA3w6b09HQqHlM9YoFlh +RrYSs1SLVHXMdjznc1eXrGd+4Xu2skN0JxOKDj0PFy+WAKWlUBFT5BWHAoGABL+8 +yvsvUz9x9NaI8W5yp/oMYc5Bg1Jydz3L83PLNvfwHGcCHyql3baKximVQGt70NS4 +VEwGe3+5ugIYs2a43UeH9MbOW0lUUpX9Z/LpOdAjoJLKJeYoL6jHJ8zvgyG6r8R1 +w3UVwF3OnwYlFZZjPMXe6yGmUXTuJkB8dz0FHA0CgYEAzzbTyQpTAX9aMb1/Ij0W +1hwc2eafCXmy4e5VkKf5YcVKDZMcSvkicC4VAlUxWOSQ/ZwXmv4mTamnlbXS2oW2 +Rdan6oD2FAUG5aI8LXyG1bfuJcp3dWjgfrfJB0cS134jXk+XKMdNvsi4WsV60hJa +Ksmu2ROmTUXC2meV+93V3Eg= +-----END PRIVATE KEY----- diff --git a/src/test/resources/tls/client.crt b/src/test/resources/tls/client.crt new file mode 100644 index 00000000..d5077b3a --- /dev/null +++ b/src/test/resources/tls/client.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICuTCCAaECFGQlQgq3PBbtJnTpsXCsSIiYtvhYMA0GCSqGSIb3DQEBCwUAMBox +GDAWBgNVBAMMD1Rlc3QgVHJ1c3RlZCBDQTAgFw0yNjA3MTQwODM1NTRaGA8yMTI2 +MDYyMDA4MzU1NFowFjEUMBIGA1UEAwwLdGVzdC1jbGllbnQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCVBtGyAy86lzCzmhqyZlmECQJZVHgPlT2Y4cRG +MUupbFCH1QOjqsV7nuMoW7EQYPU3Xs9aBrabij3BsbRCfxcZHNGVxjEAYdjPyqi1 +uMGcLvNrbj4XM6CD/kV4QZpj6wHX2yCO1onTvGK6f40MfoTbuFAqPS6RngO6fmH9 +ODuXawTH6o4bpYMKwUeK9HFpOtJvFQW+MePTmU4J7ZPhPs/8WZW8DmLV5TVZzZ6Y +dpYHLr15UHN7E6O4BRoHF8oLXPOoNBTq+xx1UFFY3XrJP4Xr9VVQQVQnO3V4Kv3I +mAPTrS/h9tn6QFQbxkYT8uUt/m6D5zgqvQCjSMWc/A4P2QXVAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBAH9HhGR+SDDHknn9kkV4BEl4uWZIHW18ppfEfzBPllhA55Y+ +ZqboPvuzoRbuYntGFL/P9Ws3svmoIDUfVQPDMmjm4Kujnjb1jDyY5bmDdQs2RjDH +HJnXPqxA0+mlNXrpbsstqqZdFa1WxeT+lqTgALrNT9teBnA4cBczoQFGHwH+ltRs +ZYaycFa4blgclOL75oScbiQigB1fWPY7QGMnEwQLGN2I6HhKYyy/dFua6wTLGIeG +3ifbYec4T/lwLOeT+kVQz8iDTN3fn75YnUortUdkZ0j5mVFxsx5oOKXj7RYdF0GA +I223lYDUCxGRnV+euTCPYTWFYOsERXIUt3iM+sk= +-----END CERTIFICATE----- diff --git a/src/test/resources/tls/client.key b/src/test/resources/tls/client.key new file mode 100644 index 00000000..c0f6b37d --- /dev/null +++ b/src/test/resources/tls/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVBtGyAy86lzCz +mhqyZlmECQJZVHgPlT2Y4cRGMUupbFCH1QOjqsV7nuMoW7EQYPU3Xs9aBrabij3B +sbRCfxcZHNGVxjEAYdjPyqi1uMGcLvNrbj4XM6CD/kV4QZpj6wHX2yCO1onTvGK6 +f40MfoTbuFAqPS6RngO6fmH9ODuXawTH6o4bpYMKwUeK9HFpOtJvFQW+MePTmU4J +7ZPhPs/8WZW8DmLV5TVZzZ6YdpYHLr15UHN7E6O4BRoHF8oLXPOoNBTq+xx1UFFY +3XrJP4Xr9VVQQVQnO3V4Kv3ImAPTrS/h9tn6QFQbxkYT8uUt/m6D5zgqvQCjSMWc +/A4P2QXVAgMBAAECggEAAWnSF84PBtwTuDeKlmB0a8iASloYrs2NMVokyyVt1wXB +6F17qxpakWu5ZX+j7BvLTF+v3j+PJrL9MNoI85Kyfju6fIeBNyrczEqBZpw06kPW +VBGOPhiW5pTFBZkaFOWcu+my9mY8/BilJA+QUvFcb7Wg1wG2o4CcGIrymxAk0PCm +1WeKctnS9U8W6HWSxn7cWudP2kjSBw8QjAUCocMdOUAvixuXM7EGLgdjHJ5cUPQA +8IDiicYuZrYhYx0s3iHNL8M2dR5jW1ElphOL3nmhm78Ix406JC/s1OhFerFVLW6V +nk5NbWhklthN8NsFEBUdvz+0k8Z8T0AUUJH7Ib3AIQKBgQDN//00KBpXfboAecC3 +Ah91RuJSONvXWrGTcCEEODpnO1bkqLui6v5XamTRtlE7Iu5C2USckTLBlF6mzV7J +yEZyMJkrqm4i/AtyFkQ+4/IO9Matq/lKOMxn/1pChhMl2cMg8HQ+t5I+Ef4BHLkb +nCJWaZLgEg6IMA7iUZdjCgYU4QKBgQC5MryLB40ks0BQqhObuyKCd4SUQtmob85V +ovCO7iGYphH0O/DIFH2FgTSj8NcwDRS4cchmDnclHOctNZQFt2ShZvc5blUNXXdM +r3AsDOItrpJrrcBz8RVmi/yfRGif9m3WUJUlz33BAwz/EREBHCepKJRMHmwAFfuG +JkO86NXbdQKBgF92yT15dDOldSQxSCjHWBK+RbW62c5kHjToWrSc5hve+PbAZywp +4LJcANrataxFFMtv6obwFuh0vKRqOgoiwNq8QM9mjOGzkg5N2VSXyB48OzJSpxVJ +Wxi60nZPseHxl6bVJ9nEsoYHSoHzcKkl4xfOlx014Pwl4U/qZdCk3YqhAoGAbjg/ +9g58dgcok9lk6h4pn7Q593MBzzkbd+QNf0NCQ3My5ER4PNvVCIupJJjFuRdP6aXu +Oq8JQdE91K46dlx7S4PYRxnfDKE/yyfufoj8Y2uuQN+b1mT485sEslUoX2tuW0qC +OYqPsTt7lKI3mI2FHSvFdrf0Vui+VUyYq+l2vbECgYEAxVPi1MdZTQqxqspKC+io +rD0E9klYnlTvtx1D8Of3v5ZgUfDndbwMSnm+eB0eDZToJol2xibT9Z9JVVXGo4cq +9yjQknrMOoe1uz4q2QB5d+t9+WxuXPSIy3hpx6YkR0ZBiOtmMu1IpuVC41i4gHJp +1M4ZQJG7uuAmcvTVpbGvxCQ= +-----END PRIVATE KEY----- diff --git a/src/test/resources/tls/server.crt b/src/test/resources/tls/server.crt new file mode 100644 index 00000000..9a142cc7 --- /dev/null +++ b/src/test/resources/tls/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHDCCAgSgAwIBAgIUZCVCCrc8Fu0mdOmxcKxIiJi2+FcwDQYJKoZIhvcNAQEL +BQAwGjEYMBYGA1UEAwwPVGVzdCBUcnVzdGVkIENBMCAXDTI2MDcxNDA4MzU1M1oY +DzIxMjYwNjIwMDgzNTUzWjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBXA8g/LiGaI7WfMOAYW7q1t801MfoCe30 +pegYXVYFyeoUyVbuCo4sffa6/y56bw4aP152eCJjPIpcI5263HmQMrGFGhnpHOJo +WSpL0lVKCKCAoiQcPKSkTJQjzFZEWxSJJRgFy+766a7k4OBw9NXwnnaqzlv/A2SR +e8u0dZAgnVs1xmtcN5qfLm7eqFO2raXx8yPwrJruvcKUDktfx8YYNVQCylcZ7z1/ +Asv+W2oku423QE0x6V0u/k9iOyJAjXXQgFhE3PHUT2m+8hYsfoAfBV/3y+4SW/N1 +vmmxNo8hcCZ0U7g8MW+nSweOpLhVINk5Xn3kLnuh4KEKB1jch9vnAgMBAAGjXjBc +MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATAdBgNVHQ4EFgQUFD7KcrbNUPRK +pHQBBqChuRfX4FgwHwYDVR0jBBgwFoAUB0uISyjqq+LwYEXPFqMiUlSi4gQwDQYJ +KoZIhvcNAQELBQADggEBAFA1d2kLDTuPxPfHsd7EtY3bExysFqm1Wmjw5KhjEGdf +xq3cX6m3E/txRg1gcI0N4ZSIw+r1AyOD2+z781oeleVlG8RaZ68H5HL6XwCiVZtf +r/I/jCRjx4wWb1tQtH487MfYypWFq/bwMZy5wlgQ565dLAebN/J1EYeUXNFBLJDa +YlArnK8XpEqxwOYrzZeKoKSvPAkMBxxESSfa0VC9b7/2wVPN4fUXB7KV+gfIDXWQ +sNg4V3EXMLb1UnYnEV9M8En0mGDpeJlUndOxxFMDcQma9i+SECieUAAGt7q7kqxx +l05bNVBJPMr7LnbDMvbLFtpPwIORnYKeNtwBLUrxHvw= +-----END CERTIFICATE----- diff --git a/src/test/resources/tls/server.key b/src/test/resources/tls/server.key new file mode 100644 index 00000000..472308da --- /dev/null +++ b/src/test/resources/tls/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBXA8g/LiGaI7W +fMOAYW7q1t801MfoCe30pegYXVYFyeoUyVbuCo4sffa6/y56bw4aP152eCJjPIpc +I5263HmQMrGFGhnpHOJoWSpL0lVKCKCAoiQcPKSkTJQjzFZEWxSJJRgFy+766a7k +4OBw9NXwnnaqzlv/A2SRe8u0dZAgnVs1xmtcN5qfLm7eqFO2raXx8yPwrJruvcKU +Dktfx8YYNVQCylcZ7z1/Asv+W2oku423QE0x6V0u/k9iOyJAjXXQgFhE3PHUT2m+ +8hYsfoAfBV/3y+4SW/N1vmmxNo8hcCZ0U7g8MW+nSweOpLhVINk5Xn3kLnuh4KEK +B1jch9vnAgMBAAECggEAAe9590qQTkgdpef5zP8srQ0Zjt3SYnA3AYvnltbYVZZ/ +0VVymU9hvPBF1/lV7cPO0FRyDZ2GOpgQinuQyJ4MWXUocbyhJcQ0P/ckhT3FvMf5 +U1BMLDwvKbXahcE129lNi1Hju6se6UQNjBz0Ii1RNudfKLaIFTVjfFFQ6K2Ek876 +9dkc68Q0w7vcpoLq35BvdPMvTHpUvA0MMIsUeTTSirRb9cD5ugPzdQHMVyGJ2z5q +HAEhXSUfzmxMn3juZA0g9hltD/xGbTtkExqks7Hd5EcQKXg9/JVrYAPAqKksopak +p6GalW+Cx66esMElGRDSUjuxxMl0ChBKiOVHVYQ/AQKBgQDmKcG3ofb92CE/gViF +Eo8W8kASeX8msuIP7zdGGJXlF7HqJ/5vtfCXrZDpIxhm8kRUtOm+Xg3JW1vfSfGk ++FpRwJ8nIbKOlhGFTU4BVTNZkJE/vxPEksExAiMWYWMZqD3Se2mAVa03gGrXAVfd +3noBLLVG90cfqwerUGa8oWYq7wKBgQDXEKw08gH3Vmqxj258gHyZYpIw0RQBWmHZ +DCY7zO7a8VUa+zZAwo8+a33ZIMW/xRd5oEsY3dBHg/vJKNnu2Y7X4B5lqOMz02LY +ix3pyD4LLtyKKPOw+ydDc/J6LMTnhGeyTeMcLYEDZMS6KXZG3nzv1T2BqhXdtrfy +I9i3PNs+iQKBgGPemIzqvXaWEo8wu806KVaGlSCvEWokUo0henVy84+tgWieI/wr +ERNvn6JZtRvuYZHz0jzlKMxHVQ0FU9IGZYJ1t1lOeOD/4uhZi9BuPNLPNQdZDOXN +3AA88iai6VJXu0Oag0PJaVjc4v8aORsvjvb1hE8fuv/VwYUnZPzSd7eHAoGBAKXc +buL6TXfiRrCfrJcKRI3KznIlnOcaPGI3E3mFKCTkgD0pxoKVSgHaxpjgxIGMT/l2 +HNSVpN6ytElEXybs1FQ6vVartGWwvgfA6pX035yiTGWlBaPVXkeQMU32HvlqTMrf +dKqzkU77tRjZhyVKy1Hvw9qvQOPX2e89pBJVNcT5AoGAPur/HCAXH06xkACVhi7g +cS5GYInn85KiFcj4Kxpaveu27ok0193gDXdsObHKw1hmxufacERx/m6VA/KMPgpk +DFriFTGt6+eF5kXXw+l7j59XZeWqBfiiMwNJ8SfoFONHy0WhkUQThARdO4bfoU9b +IzB+gIkEq3IiIBlHtvvDgOA= +-----END PRIVATE KEY-----