You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@rocketmq.apache.org by GitBox <gi...@apache.org> on 2017/12/13 11:39:22 UTC

[GitHub] vongosling closed pull request #202: [ROCKETMQ-315][ROCKETMQ-327] Enhance TLS feature

vongosling closed pull request #202: [ROCKETMQ-315][ROCKETMQ-327] Enhance TLS feature
URL: https://github.com/apache/rocketmq/pull/202
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java
index a066652d..e768c7f9 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerStartup.java
@@ -32,10 +32,11 @@
 import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.common.constant.LoggerName;
 import org.apache.rocketmq.remoting.common.RemotingUtil;
-import org.apache.rocketmq.remoting.common.SslMode;
+import org.apache.rocketmq.remoting.common.TlsMode;
 import org.apache.rocketmq.remoting.netty.NettyClientConfig;
 import org.apache.rocketmq.remoting.netty.NettyServerConfig;
 import org.apache.rocketmq.remoting.netty.NettySystemConfig;
+import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
 import org.apache.rocketmq.remoting.protocol.RemotingCommand;
 import org.apache.rocketmq.srvutil.ServerUtil;
 import org.apache.rocketmq.store.config.BrokerRole;
@@ -43,6 +44,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_ENABLE;
+
 public class BrokerStartup {
     public static Properties properties = null;
     public static CommandLine commandLine = null;
@@ -98,7 +101,9 @@ public static BrokerController createBrokerController(String[] args) {
             final BrokerConfig brokerConfig = new BrokerConfig();
             final NettyServerConfig nettyServerConfig = new NettyServerConfig();
             final NettyClientConfig nettyClientConfig = new NettyClientConfig();
-            nettyClientConfig.setUseTLS(NettySystemConfig.sslMode == SslMode.ENFORCING);
+
+            nettyClientConfig.setUseTLS(Boolean.parseBoolean(System.getProperty(TLS_ENABLE,
+                String.valueOf(TlsSystemConfig.tlsMode == TlsMode.ENFORCING))));
             nettyServerConfig.setListenPort(10911);
             final MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
 
diff --git a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
index 8f255f01..a9eabfe6 100644
--- a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+++ b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
@@ -19,6 +19,7 @@
 import org.apache.rocketmq.common.MixAll;
 import org.apache.rocketmq.common.UtilAll;
 import org.apache.rocketmq.remoting.common.RemotingUtil;
+import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
 
 /**
  * Client Common configuration
@@ -45,7 +46,7 @@
     private String unitName;
     private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "true"));
 
-    private boolean useTLS;
+    private boolean useTLS = TlsSystemConfig.tlsEnable;
 
     public String buildMQClientId() {
         StringBuilder sb = new StringBuilder();
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/SslMode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/TlsMode.java
similarity index 87%
rename from remoting/src/main/java/org/apache/rocketmq/remoting/common/SslMode.java
rename to remoting/src/main/java/org/apache/rocketmq/remoting/common/TlsMode.java
index cb1e85a2..996ef0dd 100644
--- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/SslMode.java
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/TlsMode.java
@@ -25,7 +25,7 @@
  *     <li><strong>enforcing:</strong> SSL is required, aka, non SSL connection will be rejected.</li>
  * </ol>
  */
-public enum SslMode {
+public enum TlsMode {
 
     DISABLED("disabled"),
     PERMISSIVE("permissive"),
@@ -33,14 +33,14 @@
 
     private String name;
 
-    SslMode(String name) {
+    TlsMode(String name) {
         this.name = name;
     }
 
-    public static SslMode parse(String mode) {
-        for (SslMode sslMode: SslMode.values()) {
-            if (sslMode.name.equals(mode)) {
-                return sslMode;
+    public static TlsMode parse(String mode) {
+        for (TlsMode tlsMode : TlsMode.values()) {
+            if (tlsMode.name.equals(mode)) {
+                return tlsMode;
             }
         }
 
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
index 6dc0457e..dcc80cba 100644
--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
@@ -34,6 +34,7 @@
 import io.netty.handler.timeout.IdleStateEvent;
 import io.netty.handler.timeout.IdleStateHandler;
 import io.netty.util.concurrent.DefaultEventExecutorGroup;
+import java.io.IOException;
 import java.net.SocketAddress;
 import java.security.cert.CertificateException;
 import java.util.Collections;
@@ -52,7 +53,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
-import javax.net.ssl.SSLException;
 import org.apache.rocketmq.remoting.ChannelEventListener;
 import org.apache.rocketmq.remoting.InvokeCallback;
 import org.apache.rocketmq.remoting.RPCHook;
@@ -131,9 +131,9 @@ public Thread newThread(Runnable r) {
 
         if (nettyClientConfig.isUseTLS()) {
             try {
-                sslContext = SslHelper.buildSslContext(true);
+                sslContext = TlsHelper.buildSslContext(true);
                 log.info("SSL enabled for client");
-            } catch (SSLException e) {
+            } catch (IOException e) {
                 log.error("Failed to create SSLContext", e);
             } catch (CertificateException e) {
                 log.error("Failed to create SSLContext", e);
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
index ec1927a6..cd6ed470 100644
--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
@@ -37,6 +37,7 @@
 import io.netty.handler.timeout.IdleStateEvent;
 import io.netty.handler.timeout.IdleStateHandler;
 import io.netty.util.concurrent.DefaultEventExecutorGroup;
+import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.security.cert.CertificateException;
 import java.util.NoSuchElementException;
@@ -46,7 +47,6 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.SSLException;
 import org.apache.rocketmq.remoting.ChannelEventListener;
 import org.apache.rocketmq.remoting.InvokeCallback;
 import org.apache.rocketmq.remoting.RPCHook;
@@ -54,7 +54,7 @@
 import org.apache.rocketmq.remoting.common.Pair;
 import org.apache.rocketmq.remoting.common.RemotingHelper;
 import org.apache.rocketmq.remoting.common.RemotingUtil;
-import org.apache.rocketmq.remoting.common.SslMode;
+import org.apache.rocketmq.remoting.common.TlsMode;
 import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
 import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
 import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
@@ -139,16 +139,16 @@ public Thread newThread(Runnable r) {
             });
         }
 
-        SslMode sslMode = NettySystemConfig.sslMode;
-        log.info("Server is running in TLS {} mode", sslMode.getName());
+        TlsMode tlsMode = TlsSystemConfig.tlsMode;
+        log.info("Server is running in TLS {} mode", tlsMode.getName());
 
-        if (sslMode != SslMode.DISABLED) {
+        if (tlsMode != TlsMode.DISABLED) {
             try {
-                sslContext = SslHelper.buildSslContext(false);
+                sslContext = TlsHelper.buildSslContext(false);
                 log.info("SSLContext created for server");
             } catch (CertificateException e) {
                 log.error("Failed to create SSLContext for server", e);
-            } catch (SSLException e) {
+            } catch (IOException e) {
                 log.error("Failed to create SSLContext for server", e);
             }
         }
@@ -189,7 +189,7 @@ public Thread newThread(Runnable r) {
                     public void initChannel(SocketChannel ch) throws Exception {
                         ch.pipeline()
                             .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME,
-                                new HandshakeHandler(NettySystemConfig.sslMode))
+                                new HandshakeHandler(TlsSystemConfig.tlsMode))
                             .addLast(defaultEventExecutorGroup,
                                 new NettyEncoder(),
                                 new NettyDecoder(),
@@ -326,12 +326,12 @@ public ExecutorService getCallbackExecutor() {
 
     class HandshakeHandler extends SimpleChannelInboundHandler<ByteBuf> {
 
-        private final SslMode sslMode;
+        private final TlsMode tlsMode;
 
         private static final byte HANDSHAKE_MAGIC_CODE = 0x16;
 
-        HandshakeHandler(SslMode sslMode) {
-            this.sslMode = sslMode;
+        HandshakeHandler(TlsMode tlsMode) {
+            this.tlsMode = tlsMode;
         }
 
         @Override
@@ -344,7 +344,7 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Excep
             byte b = msg.getByte(0);
 
             if (b == HANDSHAKE_MAGIC_CODE) {
-                switch (sslMode) {
+                switch (tlsMode) {
                     case DISABLED:
                         ctx.close();
                         log.warn("Clients intend to establish a SSL connection while this server is running in SSL disabled mode");
@@ -366,7 +366,7 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Excep
                         log.warn("Unknown TLS mode");
                         break;
                 }
-            } else if (sslMode == SslMode.ENFORCING) {
+            } else if (tlsMode == TlsMode.ENFORCING) {
                 ctx.close();
                 log.warn("Clients intend to establish an insecure connection while this server is running in SSL enforcing mode");
             }
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettySystemConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettySystemConfig.java
index b9c1f3fa..6357c03b 100644
--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettySystemConfig.java
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettySystemConfig.java
@@ -17,8 +17,6 @@
 
 package org.apache.rocketmq.remoting.netty;
 
-import org.apache.rocketmq.remoting.common.SslMode;
-
 public class NettySystemConfig {
     public static final String COM_ROCKETMQ_REMOTING_NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE =
         "com.rocketmq.remoting.nettyPooledByteBufAllocatorEnable";
@@ -31,12 +29,6 @@
     public static final String COM_ROCKETMQ_REMOTING_CLIENT_ONEWAY_SEMAPHORE_VALUE =
         "com.rocketmq.remoting.clientOnewaySemaphoreValue";
 
-    public static final String ORG_APACHE_ROCKETMQ_REMOTING_SSL_MODE = //
-        "org.apache.rocketmq.remoting.ssl.mode";
-
-    public static final String ORG_APACHE_ROCKETMQ_REMOTING_SSL_CONFIG_FILE = //
-        "org.apache.rocketmq.remoting.ssl.config.file";
-
     public static final boolean NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE = //
         Boolean.parseBoolean(System.getProperty(COM_ROCKETMQ_REMOTING_NETTY_POOLED_BYTE_BUF_ALLOCATOR_ENABLE, "false"));
     public static final int CLIENT_ASYNC_SEMAPHORE_VALUE = //
@@ -47,18 +39,4 @@
         Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_SOCKET_SNDBUF_SIZE, "65535"));
     public static int socketRcvbufSize =
         Integer.parseInt(System.getProperty(COM_ROCKETMQ_REMOTING_SOCKET_RCVBUF_SIZE, "65535"));
-
-    /**
-     * For server, three SSL modes are supported: disabled, permissive and enforcing.
-     * <ol>
-     *     <li><strong>disabled:</strong> SSL is not supported; any incoming SSL handshake will be rejected, causing connection closed.</li>
-     *     <li><strong>permissive:</strong> SSL is optional, aka, server in this mode can serve client connections with or without SSL;</li>
-     *     <li><strong>enforcing:</strong> SSL is required, aka, non SSL connection will be rejected.</li>
-     * </ol>
-     */
-    public static SslMode sslMode = //
-        SslMode.parse(System.getProperty(ORG_APACHE_ROCKETMQ_REMOTING_SSL_MODE, "permissive"));
-
-    public static String sslConfigFile = //
-        System.getProperty(ORG_APACHE_ROCKETMQ_REMOTING_SSL_CONFIG_FILE, "/etc/rocketmq/ssl.properties");
 }
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/SslHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/SslHelper.java
deleted file mode 100644
index ebadd968..00000000
--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/SslHelper.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.rocketmq.remoting.netty;
-
-import io.netty.handler.ssl.ClientAuth;
-import io.netty.handler.ssl.OpenSsl;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import io.netty.handler.ssl.SslProvider;
-import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
-import io.netty.handler.ssl.util.SelfSignedCertificate;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.cert.CertificateException;
-import java.util.Properties;
-import javax.net.ssl.SSLException;
-import org.apache.rocketmq.remoting.common.RemotingHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SslHelper {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING);
-
-    public static SslContext buildSslContext(boolean forClient) throws SSLException, CertificateException {
-
-        File configFile = new File(NettySystemConfig.sslConfigFile);
-        boolean testMode = !(configFile.exists() && configFile.isFile() && configFile.canRead());
-        Properties properties = null;
-
-        if (!testMode) {
-            properties = new Properties();
-            InputStream inputStream = null;
-            try {
-                inputStream = new FileInputStream(configFile);
-                properties.load(inputStream);
-            } catch (FileNotFoundException ignore) {
-            } catch (IOException ignore) {
-            } finally {
-                if (null != inputStream) {
-                    try {
-                        inputStream.close();
-                    } catch (IOException ignore) {
-                    }
-                }
-            }
-        }
-
-        SslProvider provider = null;
-        if (OpenSsl.isAvailable()) {
-            provider = SslProvider.OPENSSL;
-            LOGGER.info("Using OpenSSL provider");
-        } else {
-            provider = SslProvider.JDK;
-            LOGGER.info("Using JDK SSL provider");
-        }
-
-        if (forClient) {
-            if (testMode) {
-                return SslContextBuilder
-                    .forClient()
-                    .sslProvider(SslProvider.JDK)
-                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
-                    .build();
-            } else {
-                SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().sslProvider(SslProvider.JDK);
-
-                if ("false".equals(properties.getProperty("client.auth.server"))) {
-                    sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
-                } else {
-                    if (properties.containsKey("client.trustManager")) {
-                        sslContextBuilder.trustManager(new File(properties.getProperty("client.trustManager")));
-                    }
-                }
-
-                return sslContextBuilder.keyManager(
-                    properties.containsKey("client.keyCertChainFile") ? new File(properties.getProperty("client.keyCertChainFile")) : null,
-                    properties.containsKey("client.keyFile") ? new File(properties.getProperty("client.keyFile")) : null,
-                    properties.containsKey("client.password") ? properties.getProperty("client.password") : null)
-                    .build();
-            }
-        } else {
-
-            if (testMode) {
-                SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
-                return SslContextBuilder
-                    .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey())
-                    .sslProvider(SslProvider.JDK)
-                    .clientAuth(ClientAuth.OPTIONAL)
-                    .build();
-            } else {
-                return SslContextBuilder.forServer(
-                    properties.containsKey("server.keyCertChainFile") ? new File(properties.getProperty("server.keyCertChainFile")) : null,
-                    properties.containsKey("server.keyFile") ? new File(properties.getProperty("server.keyFile")) : null,
-                    properties.containsKey("server.password") ? properties.getProperty("server.password") : null)
-                    .sslProvider(provider)
-                    .trustManager(new File(properties.getProperty("server.trustManager")))
-                    .clientAuth(parseClientAuthMode(properties.getProperty("server.auth.client")))
-                    .build();
-            }
-        }
-    }
-
-    private static ClientAuth parseClientAuthMode(String authMode) {
-        if (null == authMode || authMode.trim().isEmpty()) {
-            return ClientAuth.NONE;
-        }
-
-        if ("optional".equalsIgnoreCase(authMode)) {
-            return ClientAuth.OPTIONAL;
-        }
-
-        return ClientAuth.REQUIRE;
-    }
-}
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java
new file mode 100644
index 00000000..3a74b4b6
--- /dev/null
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsHelper.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.rocketmq.remoting.netty;
+
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.ssl.util.SelfSignedCertificate;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.util.Properties;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_AUTHSERVER;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_CERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPASSWORD;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_TRUSTCERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_AUTHCLIENT;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_CERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPASSWORD;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_NEED_CLIENT_AUTH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_TRUSTCERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_TEST_MODE_ENABLE;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientAuthServer;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPassword;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientTrustCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerAuthClient;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPassword;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerNeedClientAuth;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerTrustCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable;
+
+public class TlsHelper {
+
+    public interface DecryptionStrategy {
+        /**
+         * Decrypt the target encrpted private key file.
+         *
+         * @param privateKeyEncryptPath A pathname string
+         * @param forClient tells whether it's a client-side key file
+         * @return An input stream for a decrypted key file
+         * @throws IOException if an I/O error has occurred
+         */
+        InputStream decryptPrivateKey(String privateKeyEncryptPath, boolean forClient) throws IOException;
+    }
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RemotingHelper.ROCKETMQ_REMOTING);
+
+    private static DecryptionStrategy decryptionStrategy = new DecryptionStrategy() {
+        @Override
+        public InputStream decryptPrivateKey(final String privateKeyEncryptPath,
+            final boolean forClient) throws IOException {
+            return new FileInputStream(privateKeyEncryptPath);
+        }
+    };
+
+
+    public static void registerDecryptionStrategy(final DecryptionStrategy decryptionStrategy) {
+        TlsHelper.decryptionStrategy = decryptionStrategy;
+    }
+
+    public static SslContext buildSslContext(boolean forClient) throws IOException, CertificateException {
+        File configFile = new File(TlsSystemConfig.tlsConfigFile);
+        extractTlsConfigFromFile(configFile);
+        logTheFinalUsedTlsConfig();
+
+        SslProvider provider;
+        if (OpenSsl.isAvailable()) {
+            provider = SslProvider.OPENSSL;
+            LOGGER.info("Using OpenSSL provider");
+        } else {
+            provider = SslProvider.JDK;
+            LOGGER.info("Using JDK SSL provider");
+        }
+
+        if (forClient) {
+            if (tlsTestModeEnable) {
+                return SslContextBuilder
+                    .forClient()
+                    .sslProvider(SslProvider.JDK)
+                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
+                    .build();
+            } else {
+                SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().sslProvider(SslProvider.JDK);
+
+
+                if (!tlsClientAuthServer) {
+                    sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
+                } else {
+                    if (!isNullOrEmpty(tlsClientTrustCertPath)) {
+                        sslContextBuilder.trustManager(new File(tlsClientTrustCertPath));
+                    }
+                }
+
+                return sslContextBuilder.keyManager(
+                    !isNullOrEmpty(tlsClientCertPath) ? new FileInputStream(tlsClientCertPath) : null,
+                    !isNullOrEmpty(tlsClientKeyPath) ? decryptionStrategy.decryptPrivateKey(tlsClientKeyPath, true) : null,
+                    !isNullOrEmpty(tlsClientKeyPassword) ? tlsClientKeyPassword : null)
+                    .build();
+            }
+        } else {
+
+            if (tlsTestModeEnable) {
+                SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
+                return SslContextBuilder
+                    .forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey())
+                    .sslProvider(SslProvider.JDK)
+                    .clientAuth(ClientAuth.OPTIONAL)
+                    .build();
+            } else {
+                SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(
+                    !isNullOrEmpty(tlsServerCertPath) ? new FileInputStream(tlsServerCertPath) : null,
+                    !isNullOrEmpty(tlsServerKeyPath) ? decryptionStrategy.decryptPrivateKey(tlsServerKeyPath, false) : null,
+                    !isNullOrEmpty(tlsServerKeyPassword) ? tlsServerKeyPassword : null)
+                    .sslProvider(provider);
+
+                if (!tlsServerAuthClient) {
+                    sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
+                } else {
+                    if (!isNullOrEmpty(tlsServerTrustCertPath)) {
+                        sslContextBuilder.trustManager(new File(tlsServerTrustCertPath));
+                    }
+                }
+
+                sslContextBuilder.clientAuth(parseClientAuthMode(tlsServerNeedClientAuth));
+                return sslContextBuilder.build();
+            }
+        }
+    }
+
+    private static void extractTlsConfigFromFile(final File configFile) {
+        if (!(configFile.exists() && configFile.isFile() && configFile.canRead())) {
+            LOGGER.info("Tls config file doesn't exist, skip it");
+            return;
+        }
+
+        Properties properties;
+        properties = new Properties();
+        InputStream inputStream = null;
+        try {
+            inputStream = new FileInputStream(configFile);
+            properties.load(inputStream);
+        } catch (IOException ignore) {
+        } finally {
+            if (null != inputStream) {
+                try {
+                    inputStream.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+
+        tlsTestModeEnable = Boolean.parseBoolean(properties.getProperty(TLS_TEST_MODE_ENABLE, String.valueOf(tlsTestModeEnable)));
+        tlsServerNeedClientAuth = properties.getProperty(TLS_SERVER_NEED_CLIENT_AUTH, tlsServerNeedClientAuth);
+        tlsServerKeyPath = properties.getProperty(TLS_SERVER_KEYPATH, tlsServerKeyPath);
+        tlsServerKeyPassword = properties.getProperty(TLS_SERVER_KEYPASSWORD, tlsServerKeyPassword);
+        tlsServerCertPath = properties.getProperty(TLS_SERVER_CERTPATH, tlsServerCertPath);
+        tlsServerAuthClient = Boolean.parseBoolean(properties.getProperty(TLS_SERVER_AUTHCLIENT, String.valueOf(tlsServerAuthClient)));
+        tlsServerTrustCertPath = properties.getProperty(TLS_SERVER_TRUSTCERTPATH, tlsServerTrustCertPath);
+
+        tlsClientKeyPath = properties.getProperty(TLS_CLIENT_KEYPATH, tlsClientKeyPath);
+        tlsClientKeyPassword = properties.getProperty(TLS_CLIENT_KEYPASSWORD, tlsClientKeyPassword);
+        tlsClientCertPath = properties.getProperty(TLS_CLIENT_CERTPATH, tlsClientCertPath);
+        tlsClientAuthServer = Boolean.parseBoolean(properties.getProperty(TLS_CLIENT_AUTHSERVER, String.valueOf(tlsClientAuthServer)));
+        tlsClientTrustCertPath = properties.getProperty(TLS_CLIENT_TRUSTCERTPATH, tlsClientTrustCertPath);
+    }
+
+    private static void logTheFinalUsedTlsConfig() {
+        LOGGER.info("Log the final used tls related configuration");
+        LOGGER.info("{} = {}", TLS_TEST_MODE_ENABLE, tlsTestModeEnable);
+        LOGGER.info("{} = {}", TLS_SERVER_NEED_CLIENT_AUTH, tlsServerNeedClientAuth);
+        LOGGER.info("{} = {}", TLS_SERVER_KEYPATH, tlsServerKeyPath);
+        LOGGER.info("{} = {}", TLS_SERVER_KEYPASSWORD, tlsServerKeyPassword);
+        LOGGER.info("{} = {}", TLS_SERVER_CERTPATH, tlsServerCertPath);
+        LOGGER.info("{} = {}", TLS_SERVER_AUTHCLIENT, tlsServerAuthClient);
+        LOGGER.info("{} = {}", TLS_SERVER_TRUSTCERTPATH, tlsServerTrustCertPath);
+
+        LOGGER.info("{} = {}", TLS_CLIENT_KEYPATH, tlsClientKeyPath);
+        LOGGER.info("{} = {}", TLS_CLIENT_KEYPASSWORD, tlsClientKeyPassword);
+        LOGGER.info("{} = {}", TLS_CLIENT_CERTPATH, tlsClientCertPath);
+        LOGGER.info("{} = {}", TLS_CLIENT_AUTHSERVER, tlsClientAuthServer);
+        LOGGER.info("{} = {}", TLS_CLIENT_TRUSTCERTPATH, tlsClientTrustCertPath);
+    }
+
+    private static ClientAuth parseClientAuthMode(String authMode) {
+        if (null == authMode || authMode.trim().isEmpty()) {
+            return ClientAuth.NONE;
+        }
+
+        for (ClientAuth clientAuth : ClientAuth.values()) {
+            if (clientAuth.name().equals(authMode.toUpperCase())) {
+                return clientAuth;
+            }
+        }
+
+        return ClientAuth.NONE;
+    }
+
+    /**
+     * Determine if a string is {@code null} or {@link String#isEmpty()} returns {@code true}.
+     */
+    private static boolean isNullOrEmpty(String s) {
+        return s == null || s.isEmpty();
+    }
+}
diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsSystemConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsSystemConfig.java
new file mode 100644
index 00000000..403bd6c9
--- /dev/null
+++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/TlsSystemConfig.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.rocketmq.remoting.netty;
+
+import io.netty.handler.ssl.SslContext;
+import org.apache.rocketmq.remoting.common.TlsMode;
+
+public class TlsSystemConfig {
+    public static final String TLS_SERVER_MODE = "tls.server.mode";
+    public static final String TLS_ENABLE = "tls.enable";
+    public static final String TLS_CONFIG_FILE = "tls.config.file";
+    public static final String TLS_TEST_MODE_ENABLE = "tls.test.mode.enable";
+
+    public static final String TLS_SERVER_NEED_CLIENT_AUTH = "tls.server.need.client.auth";
+    public static final String TLS_SERVER_KEYPATH = "tls.server.keyPath";
+    public static final String TLS_SERVER_KEYPASSWORD = "tls.server.keyPassword";
+    public static final String TLS_SERVER_CERTPATH = "tls.server.certPath";
+    public static final String TLS_SERVER_AUTHCLIENT = "tls.server.authClient";
+    public static final String TLS_SERVER_TRUSTCERTPATH = "tls.server.trustCertPath";
+
+    public static final String TLS_CLIENT_KEYPATH = "tls.client.keyPath";
+    public static final String TLS_CLIENT_KEYPASSWORD = "tls.client.keyPassword";
+    public static final String TLS_CLIENT_CERTPATH = "tls.client.certPath";
+    public static final String TLS_CLIENT_AUTHSERVER = "tls.client.authServer";
+    public static final String TLS_CLIENT_TRUSTCERTPATH = "tls.client.trustCertPath";
+
+
+    /**
+     * To determine whether use SSL in client-side, include SDK client and BrokerOuterAPI
+     */
+    public static boolean tlsEnable = Boolean.parseBoolean(System.getProperty(TLS_ENABLE, "false"));
+
+    /**
+     * To determine whether use test mode when initialize TLS context
+     */
+    public static boolean tlsTestModeEnable = Boolean.parseBoolean(System.getProperty(TLS_TEST_MODE_ENABLE, "true"));
+
+    /**
+     * Indicates the state of the {@link javax.net.ssl.SSLEngine} with respect to client authentication.
+     * This configuration item really only applies when building the server-side {@link SslContext},
+     * and can be set to none, require or optional.
+     */
+    public static String tlsServerNeedClientAuth = System.getProperty(TLS_SERVER_NEED_CLIENT_AUTH, "none");
+    /**
+     * The store path of server-side private key
+     */
+    public static String tlsServerKeyPath = System.getProperty(TLS_SERVER_KEYPATH, null);
+
+    /**
+     * The  password of the server-side private key
+     */
+    public static String tlsServerKeyPassword = System.getProperty(TLS_SERVER_KEYPASSWORD, null);
+
+    /**
+     * The store path of server-side X.509 certificate chain in PEM format
+     */
+    public static String tlsServerCertPath = System.getProperty(TLS_SERVER_CERTPATH, null);
+
+    /**
+     * To determine whether verify the client endpoint's certificate strictly
+     */
+    public static boolean tlsServerAuthClient = Boolean.parseBoolean(System.getProperty(TLS_SERVER_AUTHCLIENT, "false"));
+
+    /**
+     * The store path of trusted certificates for verifying the client endpoint's certificate
+     */
+    public static String tlsServerTrustCertPath = System.getProperty(TLS_SERVER_TRUSTCERTPATH, null);
+
+    /**
+     * The store path of client-side private key
+     */
+    public static String tlsClientKeyPath = System.getProperty(TLS_CLIENT_KEYPATH, null);
+
+    /**
+     * The  password of the client-side private key
+     */
+    public static String tlsClientKeyPassword = System.getProperty(TLS_CLIENT_KEYPASSWORD, null);
+
+    /**
+     * The store path of client-side X.509 certificate chain in PEM format
+     */
+    public static String tlsClientCertPath = System.getProperty(TLS_CLIENT_CERTPATH, null);
+
+    /**
+     * To determine whether verify the server endpoint's certificate strictly
+     */
+    public static boolean tlsClientAuthServer = Boolean.parseBoolean(System.getProperty(TLS_CLIENT_AUTHSERVER, "false"));
+
+    /**
+     * The store path of trusted certificates for verifying the server endpoint's certificate
+     */
+    public static String tlsClientTrustCertPath = System.getProperty(TLS_CLIENT_TRUSTCERTPATH, null);
+
+    /**
+     * For server, three SSL modes are supported: disabled, permissive and enforcing.
+     * For client, use {@link TlsSystemConfig#tlsEnable} to determine whether use SSL.
+     * <ol>
+     *     <li><strong>disabled:</strong> SSL is not supported; any incoming SSL handshake will be rejected, causing connection closed.</li>
+     *     <li><strong>permissive:</strong> SSL is optional, aka, server in this mode can serve client connections with or without SSL;</li>
+     *     <li><strong>enforcing:</strong> SSL is required, aka, non SSL connection will be rejected.</li>
+     * </ol>
+     */
+    public static TlsMode tlsMode = TlsMode.parse(System.getProperty(TLS_SERVER_MODE, "permissive"));
+
+    /**
+     * A config file to store the above TLS related configurations,
+     * except {@link TlsSystemConfig#tlsMode} and {@link TlsSystemConfig#tlsEnable}
+     */
+    public static String tlsConfigFile = System.getProperty(TLS_CONFIG_FILE, "/etc/rocketmq/tls.properties");
+}
diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
index 31c26473..0ecfaaa5 100644
--- a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
+++ b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
@@ -67,8 +67,11 @@ public boolean rejectRequest() {
     }
 
     public static RemotingClient createRemotingClient() {
-        NettyClientConfig config = new NettyClientConfig();
-        RemotingClient client = new NettyRemotingClient(config);
+        return createRemotingClient(new NettyClientConfig());
+    }
+
+    public static RemotingClient createRemotingClient(NettyClientConfig nettyClientConfig) {
+        RemotingClient client = new NettyRemotingClient(nettyClientConfig);
         client.start();
         return client;
     }
diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java
new file mode 100644
index 00000000..5e516dd7
--- /dev/null
+++ b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.rocketmq.remoting;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import org.apache.rocketmq.remoting.common.TlsMode;
+import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.TlsHelper;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_AUTHSERVER;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_CERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPASSWORD;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_KEYPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_CLIENT_TRUSTCERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_AUTHCLIENT;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_CERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPASSWORD;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_KEYPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_NEED_CLIENT_AUTH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.TLS_SERVER_TRUSTCERTPATH;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientAuthServer;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPassword;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientKeyPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsClientTrustCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsConfigFile;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsMode;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerAuthClient;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPassword;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerKeyPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerNeedClientAuth;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsServerTrustCertPath;
+import static org.apache.rocketmq.remoting.netty.TlsSystemConfig.tlsTestModeEnable;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TlsTest {
+    private RemotingServer remotingServer;
+    private RemotingClient remotingClient;
+
+    @Rule
+    public TestName name = new TestName();
+
+    @Rule
+    public TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws InterruptedException {
+        tlsMode = TlsMode.ENFORCING;
+        tlsTestModeEnable = false;
+        tlsServerNeedClientAuth = "require";
+        tlsServerKeyPath = getCertsPath("server.key");
+        tlsServerCertPath = getCertsPath("server.pem");
+        tlsServerAuthClient = true;
+        tlsServerTrustCertPath = getCertsPath("ca.pem");
+        tlsClientKeyPath = getCertsPath("client.key");
+        tlsClientCertPath = getCertsPath("client.pem");
+        tlsClientAuthServer = true;
+        tlsClientTrustCertPath = getCertsPath("ca.pem");
+        tlsClientKeyPassword = "1234";
+        tlsServerKeyPassword = "";
+
+        NettyClientConfig clientConfig = new NettyClientConfig();
+        clientConfig.setUseTLS(true);
+
+        if ("serverRejectsUntrustedClientCert".equals(name.getMethodName())) {
+            // Create a client. Its credentials come from a CA that the server does not trust. The client
+            // trusts both test CAs to ensure the handshake failure is due to the server rejecting the client's cert.
+            tlsClientKeyPath = getCertsPath("badClient.key");
+            tlsClientCertPath = getCertsPath("badClient.pem");
+        } else if ("serverAcceptsUntrustedClientCert".equals(name.getMethodName())) {
+            tlsClientKeyPath = getCertsPath("badClient.key");
+            tlsClientCertPath = getCertsPath("badClient.pem");
+            tlsServerAuthClient = false;
+        }
+        else if ("noClientAuthFailure".equals(name.getMethodName())) {
+            //Clear the client cert config to ensure produce the handshake error
+            tlsClientKeyPath = "";
+            tlsClientCertPath = "";
+        } else if ("clientRejectsUntrustedServerCert".equals(name.getMethodName())) {
+            tlsServerKeyPath = getCertsPath("badServer.key");
+            tlsServerCertPath = getCertsPath("badServer.pem");
+        } else if ("clientAcceptsUntrustedServerCert".equals(name.getMethodName())) {
+            tlsServerKeyPath = getCertsPath("badServer.key");
+            tlsServerCertPath = getCertsPath("badServer.pem");
+            tlsClientAuthServer = false;
+        } else if ("serverNotNeedClientAuth".equals(name.getMethodName())) {
+            tlsServerNeedClientAuth = "none";
+            tlsClientKeyPath = "";
+            tlsClientCertPath = "";
+        } else if ("serverWantClientAuth".equals(name.getMethodName())) {
+            tlsServerNeedClientAuth = "optional";
+        } else if ("serverWantClientAuth_ButClientNoCert".equals(name.getMethodName())) {
+            tlsServerNeedClientAuth = "optional";
+            tlsClientKeyPath = "";
+            tlsClientCertPath = "";
+        } else if ("serverAcceptsUnAuthClient".equals(name.getMethodName())) {
+            tlsMode = TlsMode.PERMISSIVE;
+            tlsClientKeyPath = "";
+            tlsClientCertPath = "";
+            clientConfig.setUseTLS(false);
+        } else if ("serverRejectsSSLClient".equals(name.getMethodName())) {
+            tlsMode = TlsMode.DISABLED;
+        }
+
+        remotingServer = RemotingServerTest.createRemotingServer();
+        remotingClient = RemotingServerTest.createRemotingClient(clientConfig);
+    }
+
+    @After
+    public void tearDown() {
+        remotingClient.shutdown();
+        remotingServer.shutdown();
+        tlsMode = TlsMode.PERMISSIVE;
+    }
+
+    /**
+     * Tests that a client and a server configured using two-way SSL auth can successfully
+     * communicate with each other.
+     */
+    @Test
+    public void basicClientServerIntegrationTest() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    @Test
+    public void serverNotNeedClientAuth() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    @Test
+    public void serverWantClientAuth_ButClientNoCert() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    @Test
+    public void serverAcceptsUnAuthClient() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    @Test
+    public void serverRejectsSSLClient() throws Exception {
+        try {
+            RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5);
+            failBecauseExceptionWasNotThrown(RemotingSendRequestException.class);
+        } catch (RemotingSendRequestException ignore) {
+        }
+    }
+
+    /**
+     * Tests that a server configured to require client authentication refuses to accept connections
+     * from a client that has an untrusted certificate.
+     */
+    @Test
+    public void serverRejectsUntrustedClientCert() throws Exception {
+        try {
+            RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 5);
+            failBecauseExceptionWasNotThrown(RemotingSendRequestException.class);
+        } catch (RemotingSendRequestException ignore) {
+        }
+    }
+
+    @Test
+    public void serverAcceptsUntrustedClientCert() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    /**
+     * Tests that a server configured to require client authentication actually does require client
+     * authentication.
+     */
+    @Test
+    public void noClientAuthFailure() throws Exception {
+        try {
+            RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3);
+            failBecauseExceptionWasNotThrown(RemotingSendRequestException.class);
+        } catch (RemotingSendRequestException ignore) {
+        }
+    }
+
+    /**
+     * Tests that a client configured using GrpcSslContexts refuses to talk to a server that has an
+     * an untrusted certificate.
+     */
+    @Test
+    public void clientRejectsUntrustedServerCert() throws Exception {
+        try {
+            RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3);
+            failBecauseExceptionWasNotThrown(RemotingSendRequestException.class);
+        } catch (RemotingSendRequestException ignore) {
+        }
+    }
+
+    @Test
+    public void clientAcceptsUntrustedServerCert() throws Exception {
+        requestThenAssertResponse();
+    }
+
+    @Test
+    public void testTlsConfigThroughFile() throws Exception {
+        File file = tempFolder.newFile("tls.config");
+        tlsTestModeEnable = true;
+
+        tlsConfigFile = file.getAbsolutePath();
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(TLS_SERVER_NEED_CLIENT_AUTH + "=require\n");
+        sb.append(TLS_SERVER_KEYPATH + "=/server.key\n");
+        sb.append(TLS_SERVER_CERTPATH + "=/server.pem\n");
+        sb.append(TLS_SERVER_KEYPASSWORD + "=2345\n");
+        sb.append(TLS_SERVER_AUTHCLIENT + "=true\n");
+        sb.append(TLS_SERVER_TRUSTCERTPATH + "=/ca.pem\n");
+        sb.append(TLS_CLIENT_KEYPATH + "=/client.key\n");
+        sb.append(TLS_CLIENT_KEYPASSWORD + "=1234\n");
+        sb.append(TLS_CLIENT_CERTPATH + "=/client.pem\n");
+        sb.append(TLS_CLIENT_AUTHSERVER + "=false\n");
+        sb.append(TLS_CLIENT_TRUSTCERTPATH + "=/ca.pem\n");
+
+        writeStringToFile(file.getAbsolutePath(), sb.toString());
+        TlsHelper.buildSslContext(false);
+
+        assertThat(tlsServerNeedClientAuth).isEqualTo("require");
+        assertThat(tlsServerKeyPath).isEqualTo("/server.key");
+        assertThat(tlsServerCertPath).isEqualTo("/server.pem");
+        assertThat(tlsServerKeyPassword).isEqualTo("2345");
+        assertThat(tlsServerAuthClient).isEqualTo(true);
+        assertThat(tlsServerTrustCertPath).isEqualTo("/ca.pem");
+        assertThat(tlsClientKeyPath).isEqualTo("/client.key");
+        assertThat(tlsClientKeyPassword).isEqualTo("1234");
+        assertThat(tlsClientCertPath).isEqualTo("/client.pem");
+        assertThat(tlsClientAuthServer).isEqualTo(false);
+        assertThat(tlsClientTrustCertPath).isEqualTo("/ca.pem");
+
+        tlsConfigFile = "/notFound";
+    }
+
+    private static void writeStringToFile(String path, String content) {
+        try {
+            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(path, true)));
+            out.println(content);
+            out.close();
+        } catch (IOException ignore) {
+        }
+    }
+
+    private static String getCertsPath(String fileName) {
+        File resourcesDirectory = new File("src/test/resources/certs");
+        return resourcesDirectory.getAbsolutePath() + "/" + fileName;
+    }
+
+    private static RemotingCommand createRequest() {
+        RequestHeader requestHeader = new RequestHeader();
+        requestHeader.setCount(1);
+        requestHeader.setMessageTitle("Welcome");
+        return RemotingCommand.createRequestCommand(0, requestHeader);
+    }
+
+    private void requestThenAssertResponse() throws Exception {
+        RemotingCommand response = remotingClient.invokeSync("localhost:8888", createRequest(), 1000 * 3);
+        assertTrue(response != null);
+        assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA);
+        assertThat(response.getExtFields()).hasSize(2);
+        assertThat(response.getExtFields().get("messageTitle")).isEqualTo("Welcome");
+    }
+}
diff --git a/remoting/src/test/resources/certs/badClient.key b/remoting/src/test/resources/certs/badClient.key
new file mode 100644
index 00000000..2dfd7ab1
--- /dev/null
+++ b/remoting/src/test/resources/certs/badClient.key
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICoTAbBgkqhkiG9w0BBQMwDgQIc2h7vaLYK6gCAggABIICgNrkvD1Xxez79Jgk
+WhRJg06CG8UthncfeuymR4hgp9HIneUzUHOoaf64mpxUbDWe3YOzA29REcBQsjF0
+Rpv+Uyg3cyDG14TmeRoSufOxB3MWLcIenoPPyNNtxe3XXmdkJTXX2YR0j7EOzH2v
+qlmuxmN4A7UonV5RdGxCz0sm7bU7EyZKdLO/DwBNxlX7ukcVLxAAqsc7ondclYj0
+SFJKk1nzfysCsk/Pq+q3PAVVpG6x5RFaLVS7Zt+gU6IEp+0S0eeYukkTjGh9PMPl
+wjCOcRiR3O+g4b3DevmW8TcoBqAZ2cFaf4lGhYlNBfa9PaQ3spJLL8l8xBbRIs8T
+3UnaFIa49r9DO/ZpCwpDeUE+URCx/SpcO6lchWQhdEuFt+DnFKOPYDSCHtHJSWHf
+9Z2bltjcYYPy/8nkPeqsO9vn4/r6jo+l7MYWKyWolLCW+7RYbpx5R2s4SBGtBP6w
+bwQOtOASbpG+mqTf7+ARpffHaZm9cKoKwobXigjDojPeaBCg5DgRuLIS1tO46Pjg
+USJ8sZilXifUwc6qRZ/2KiTSiJYCPMJD2ZTvK2Inkv2qzg6X3kw7CYCaW+iDL9zN
+e3ES7bps1wZ6D8cGq80WUQgrtpaGAXLzIv4FvM5yDoqrre/dh/XDO9l2hYfUmRVv
+rynKdSxjhhyHaK2ei8cX4LGEIlRNiu9ZIxSYeUAy37IJ0rVC7vtBWTh30JTeMRop
+iIPmygBMX2FEhQ2l/eS2lRhiybR0QXA4kCeJkVQas3aMMBGp2ThPNahLpzP82B7V
+f9137okQC95/KXRz/ZLYFsJtY/53206mG7gU/+dYsYI4slLAlnSe8k2sS0D9qkWJ
+VV9F7PM=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/remoting/src/test/resources/certs/badClient.pem b/remoting/src/test/resources/certs/badClient.pem
new file mode 100644
index 00000000..1d264d33
--- /dev/null
+++ b/remoting/src/test/resources/certs/badClient.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAdsCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV
+BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy
+b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw
+YWNoZS5vcmcwHhcNMTcxMjExMDk0NDExWhcNMTgwMTEwMDk0NDExWjCBhjELMAkG
+A1UEBhMCemgxCzAJBgNVBAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBh
+Y2hlMREwDwYDVQQLDAhyb2NrZXRtcTEWMBQGA1UEAwwNZm9vYmFyLmNsaWVudDEh
+MB8GCSqGSIb3DQEJARYSZm9vQGJhci5jbGllbnQuY29tMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQC+3bvrKGF1Y9/kN5UBtf8bXRtxn6L1W6mCRrX6aHBb+vQp
+BEYk3Pwu/OLd7TkOC5zwjCIPIlwV4FaYnWh0KooqpmvXuKJLAQBFa8yGWERYys73
+9a/U31cu6lndnG2lZfb47NTy+KdzDYsqB4GfnASqA7PbxJHDU4Fu7wp7gN3HRQID
+AQABMA0GCSqGSIb3DQEBBQUAA4IBAQBsFroSKr3MbCq1HjWpCLDEz2uS4LQV6L1G
+smNfGNY17ELOcY9uweBBXOsfKVOEizYJJqatbJlz6FmPkIbfsGW2Wospkp1gvYMy
+NGL27vX3rB5vOo5vdFITaaV9/dEu53A0iWdsn3wH/FJnMsqBmynb+/3FY+Lff9d1
+XBaXLr+DeBx4lrE8rWTvhWh8gqDkuNLBTygdH0+g8/xkqhQhLqjIlMCSnrG2cTfj
+LewizVcX/VZ6DNC2M2vjEFbCShclZHocG80N7udl5KNsLEU2jyO1F61Q0yo+VYGS
+7n8dRYgbOKyCjMdu69fAfZvp4aoy1SXqtjMphDh5R7y7mhP60e0A
+-----END CERTIFICATE-----
diff --git a/remoting/src/test/resources/certs/badServer.key b/remoting/src/test/resources/certs/badServer.key
new file mode 100644
index 00000000..de4f7c58
--- /dev/null
+++ b/remoting/src/test/resources/certs/badServer.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALBvxESq2VvSpJl1
+skv8SzyPYKgU8bZx37hEOCmoeYvd9gWNfeYZuITng2/5mpWX+zuAgKsgPU66YG0v
+++dT5GBQPr0Imb25IMl3xOY2eEiLeMokYiWbnA1C+pw1a27zMqk6pgbcRaMfLdh5
+npusWtqBzZIxqo1TpaOGEmyQTNRlAgMBAAECgYBSigbUZOTIWxObov7lI0MDMsPx
+/dJSGpWhe3CWtHUgJJdKY7XpJlE3A6Nuh+N0ZiQm4ufOpodnxDMGAXOj9ZAZY16Y
+i7I0ayXepcpTqYqo0o0+ze2x7SECAXe26bqvLRuKG2hpUyM59vAmll9gmQM5n8z4
+ZzoAzqRqkRHdo5bTxQJBAOF6SwSSfb8KEtTjWpJ48W1PO/NmKbW3QsNCWuk/w5p7
+E8L2g3nwakJiFmVNCga74rUbcgbCkw7y/lLeM8yC74MCQQDIUgCN/vuHm+eT85xk
+QoVKhDljXzLoog6wTUf5SMtrmUFTbQqyvw5xjHYdp3TWJM/Px8IyLxOr97sSnnft
+l7/3AkEAukYLv6U+GRs7X4DMDIG6AjIZNwXJo4PYtfMVo+i3seHH+6MoDw8c2eaq
+1dmFVPbXXgNkek04rHr2vIMxi90H/QJAAMOfUOtaFkhX986EGDXQwFoExgZE8XI8
+0BtbXO4UKJLrFuBhnBDygyhgAvjyjyaQzGAcs4hOcOd/BTEpj/R2PQJBANUKa9T9
+qWYhDhWN1Uj7qXhC1j2z/vTAzcYuwhpPRjt3RaVl27itI7cqiGquFhwfKZZFaOh5
+pnnWHv63YbGQ2Qc=
+-----END PRIVATE KEY-----
diff --git a/remoting/src/test/resources/certs/badServer.pem b/remoting/src/test/resources/certs/badServer.pem
new file mode 100644
index 00000000..ada0a94f
--- /dev/null
+++ b/remoting/src/test/resources/certs/badServer.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5DCCAcwCAQEwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV
+BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy
+b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw
+YWNoZS5vcmcwHhcNMTcxMjExMDk0MzE3WhcNMTgwMTEwMDk0MzE3WjB4MQswCQYD
+VQQGEwJ6aDELMAkGA1UECAwCemoxCzAJBgNVBAcMAmh6MQ8wDQYDVQQKDAZhcGFj
+aGUxETAPBgNVBAsMCHJvY2tldG1xMQ8wDQYDVQQDDAZmb29iYXIxGjAYBgkqhkiG
+9w0BCQEWC2Zvb0BiYXIuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw
+b8REqtlb0qSZdbJL/Es8j2CoFPG2cd+4RDgpqHmL3fYFjX3mGbiE54Nv+ZqVl/s7
+gICrID1OumBtL/vnU+RgUD69CJm9uSDJd8TmNnhIi3jKJGIlm5wNQvqcNWtu8zKp
+OqYG3EWjHy3YeZ6brFragc2SMaqNU6WjhhJskEzUZQIDAQABMA0GCSqGSIb3DQEB
+BQUAA4IBAQAx+0Se3yIvUOe23oQp6UecaHtfXJCZmi1p5WbwJi7jUcYz78JB8oBj
+tVsa+1jftJG+cJJxqgxo2IeIAVbcEteO19xm7dc8tgfH/Bl0rxQz4WEYKb2oF/EQ
+eRgcvj4uZ0d9WuprAvJgA4r0Slu2ZZ0cVkzi06NevTweTBYIKFzHaPShqUWEw8ki
+42V5jAtRve7sT0c4TH/01dd2fs3V4Ul3E2U3LOP6VizIfKckdht0Bh6B6/5L8wvH
+4l1f4ni7w34vXGANpmTP2FGjQQ3kYjKL7GzgMphh3Kozhil6g1GLMhxvp6ccCA9W
+m5g0cPa3RZnjI/FoD0lZ5S1Q5s9qXbLm
+-----END CERTIFICATE-----
diff --git a/remoting/src/test/resources/certs/ca.pem b/remoting/src/test/resources/certs/ca.pem
new file mode 100644
index 00000000..d33cb49c
--- /dev/null
+++ b/remoting/src/test/resources/certs/ca.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDyzCCArOgAwIBAgIJAKzXC2VLdPclMA0GCSqGSIb3DQEBBQUAMHwxCzAJBgNV
+BAYTAnpoMQswCQYDVQQIDAJ6ajELMAkGA1UEBwwCaHoxDzANBgNVBAoMBmFwYWNo
+ZTERMA8GA1UECwwIcm9ja2V0bXExDjAMBgNVBAMMBXl1a29uMR8wHQYJKoZIhvcN
+AQkBFhB5dWtvbkBhcGFjaGUub3JnMB4XDTE3MTIxMTA5MjUxNFoXDTE4MDExMDA5
+MjUxNFowfDELMAkGA1UEBhMCemgxCzAJBgNVBAgMAnpqMQswCQYDVQQHDAJoejEP
+MA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhyb2NrZXRtcTEOMAwGA1UEAwwFeXVr
+b24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFwYWNoZS5vcmcwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDLUZi/zPj+7sYbfTng/gJeHpvvrWZkiudNwh1t
+5kxAusrJyGBkGm+xmRPJeQPZzbhfwfrz/UiQSbjlyV4K+SEZuNIHBSU80aTnXFWg
+wIgIAKvu3ZwYkcTjSDBvZv1DgbRkuqAB5ExsJ4vovoNqZcsLFLKsqT1G7lTAwRKU
+/FTKgD4g/zvhEoolonzKuk7CPivfKWFzcTpe8zRQlI0O9+j9Pq38F+5yxP7atK/b
+uYw36Efgt8nbkjusWIyXibpDMbAUroJNNYlFnunb+XKLpslkrIrfLGiMUq2Ru940
+ooQaANYWzogRQeIofsMN6H9CCRXtVIzcgJJU3wWXGXPRuNr7AgMBAAGjUDBOMB0G
+A1UdDgQWBBTd3bmAcazOY2/TI/h4zaGhni+nJzAfBgNVHSMEGDAWgBTd3bmAcazO
+Y2/TI/h4zaGhni+nJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBp
+KRcnYsVtFZJejyt02+7SaMudTNRgh5SexFWsl1O7qWUc+fMVgMHHDzGRbkdevcdZ
+9uKDwUoa6M1wlOeosTTfQlH9b/AwW6QT7KqdpcpMXlmoV/PNCAVt2QeVclmplvqo
+Rx8qUHNckvvzNZt1W6AkBG93P0BLK/3FMJDyYmxkstwnpBPf/3A+t5k2INUI7yQf
+B3Tqzs/4iQ3idCLqz2WhTNUYpZOREtpJMcFaOdMsGNnIF+LvkKGij0MPVd/mwJtL
+UvQXwbOWpCS7A73sWFqPnrSzpi4VwcvAsi8lUYXsc0H064oagb58zvYz3kXqybcb
+KQntj5dP4C3lLHUTTcAV
+-----END CERTIFICATE-----
diff --git a/remoting/src/test/resources/certs/client.key b/remoting/src/test/resources/certs/client.key
new file mode 100644
index 00000000..c30daea2
--- /dev/null
+++ b/remoting/src/test/resources/certs/client.key
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICoTAbBgkqhkiG9w0BBQMwDgQI1vtPpDhOYRcCAggABIICgMHwgw0p9fx95R/+
+cWnNdEq8I3ZOOy2wDjammFvPrYXcCJzS3Xg/0GDJ8pdJRKrI7253e4u3mxf5oMuY
+RrvpB3KfdelU1k/5QKqOxL/N0gQafQLViN53f6JelyBEAmO1UxQtKZtkTrdZg8ZP
+0u1cPPWxmgNdn1Xx3taMw+Wo05ysHjnHJhOEDQ2WT3VXigiRmFSX3H567yjYMRD+
+zmvBq+qqR9JPbH9Cn7X1oRXX6c8VsZHWF/Ds0I4i+5zJxsSIuNZxjZw9XXNgXtFv
+7FEFC0HDgDQQUY/FNPUbmjQUp1y0YxoOBjlyIqBIx5FWxu95p2xITS0OimQPFT0o
+IngaSb+EKRDhqpLxxIVEbDdkQrdRqcmmLGJioAysExTBDsDwkaEJGOp44bLDM4QW
+SIA9SB01omuCXgn7RjUyVXb5g0Lz+Nvsfp1YXUkPDO9hILfz3eMHDSW7/FzbB81M
+r8URaTagQxBZnvIoCoWszLDXn3JwEjpZEA6y55Naptps3mMRf7+XMt42lX0e4y9a
+ogNu5Zw/RZD9YcaTjC2z5XeKiMCs1Ymhy9iuzbo+eRGESqzvUE4VirtsiEwxJRci
+JHAvuAl3X4XnpTty4ahOU+DihM9lALxdU68CN9++7mx581pYuvjzrV+Z5+PuptZX
+AjCZmkZLDh8TCHSzWRqvP/Hcvo9BjW8l1Lq6tOa222PefSNCc6gs6Hq+jUghbabZ
+/ux4WuFc0Zd6bfQWAZohSvd78/ixsdJPNGm2OP+LUIrEDKIkLuH1PM0uq4wzJZNu
+Bo7oJ5iFWF67u3MC8oq+BqOVKDNWaCMi7iiSrN2XW8FBo/rpx4Lf/VYREL+Y0mP6
+vzJrZqw=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/remoting/src/test/resources/certs/client.pem b/remoting/src/test/resources/certs/client.pem
new file mode 100644
index 00000000..31412fea
--- /dev/null
+++ b/remoting/src/test/resources/certs/client.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAdsCAQIwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV
+BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy
+b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw
+YWNoZS5vcmcwHhcNMTcxMjExMDkyOTUyWhcNMTgwMTEwMDkyOTUyWjCBhjELMAkG
+A1UEBhMCemgxCzAJBgNVBAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBh
+Y2hlMREwDwYDVQQLDAhyb2NrZXRtcTEWMBQGA1UEAwwNZm9vYmFyLmNsaWVudDEh
+MB8GCSqGSIb3DQEJARYSZm9vQGJhci5jbGllbnQuY29tMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDoz5Uo2ZN+1ywSQXORv6kDbDXVQ72ZxBp7a6ltFLh4xdk/
+yz7bBjmryz+cAh759s8DEdngl2cCnSiM0r5NC91zox/12Di4EWt3IPdJVe1s5/VD
+Bqt5zoxbYyDgz47c95cGALgLdTB/itBPgobghQYBanWPVBNLLltw19DLf1gd6QID
+AQABMA0GCSqGSIb3DQEBBQUAA4IBAQDEpVFFcFILdnEXjyDSbgJ8rxXaUYV2aK+a
+lgrYfoHBv83MlEuHbydmHBoTL7BmPIL7JCIfufvRnnyBwcECi0E6qFbvMYNoMy6b
+OUiTq3nAnPSSoOgi2MxFxpGaOE0s2dp4K9U5nV6bXKLIwIZbJAiZT6aPVenNYJbv
+4arzFDe0Yjs/l3VYn2k9TjiiU2fxaW/8Ikx6o9nGWLTKeX/WtXfBNISqOPIL5dPF
+eaf0YKCVzvBQ3dIJiUyanRP1BCJJFrCsrPpyu4xFprbjRmDTnOpYB6CdIas5TMC8
+6HzB1fSFoltNEiCjlnLlfjpb5ueSLSbs6h1A7VH7NUEmLmncSlHf
+-----END CERTIFICATE-----
diff --git a/remoting/src/test/resources/certs/privkey.pem b/remoting/src/test/resources/certs/privkey.pem
new file mode 100644
index 00000000..69555680
--- /dev/null
+++ b/remoting/src/test/resources/certs/privkey.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIBTqUKpwFlcUCAggA
+MBQGCCqGSIb3DQMHBAii5M3Oni0WEwSCBMj5amhdPBva0QxgsWWrSBkfvmc3QQbl
+YQE5gHXzL5oaG0lbXCLQe19pr9jFckdDH8uPIi+aRie3WpZXzYLdihjsV0CgE7iy
+90ac9fgzIZbJIk/WQIDgwUZm5dEYo2v+B0WwKiD5IHzlTlXM6HVv+DRdPwKjHjAt
+TCcCSPUgjKVxHWFtQzY7mqo8P8wcNQHGkEfoQQub6tEsUDeesjS0FoK5Z2oYsmhW
+d0PNuGXw3UIMbG109DmC2ILFuTf5WSc7mxI11qL9Z5wTmcFqN7KKb8+MIQEoteni
+HICOFfKQWn8er14lmYw9anQAyaeyF/JnYkmVB8vaHBFYs/5EFZtvznpEIIhLKCuG
+lve8PJQmfWuBlPdwwJhCXHrLvjfwku4jUF8febU0BHZ5HETaB195g8r9RWfdZcrG
+f3fMO4Kq/YoP6oSxKhMP4L2pwj57EMV9N5P87ZyDFNp9BwgIjCawDRUc1Gi9YKak
+rpDNabTCr0I3NW27VGGF9m/mby7BLragc01LgTH7SFWS+1D/61/V2YBDFmWn2yV4
+4eKGeBkR3w0m/nWWfNXko8UzM/hjJ4P7Njq8HXdvEpnbDRZWzwdTGWTEvn/TAI3h
+j7vmWUHdpOQgb0WGlvEUx3V9wi2Fc1rCseHtYZgLf3KdKYHauPAMSON7KBtKaFuU
+6685sUoJbhahN7ILfP3sDxM3VYjSvlPL00lgOdqDT/iO6pNXvnNnQROCCE4kcOQT
+uSnEu+wmFHj+QlC60ftRl6zGVqjBxf1+TGmzTEByAOfZtEQ8V/clzRI4BCxYbCAG
+mJSa+q1RSju8yClBkXGT2zfhUeNqJnXEIaD/uXCPVGg7hfLyCcVVSmL97aw9QAIe
+lBJN/4bdxXLnJaHFKyztRe9N97JAKY9HAPMKKhKtqprWB7LedTIPHtXnpoSjyTrG
+SEtlOTQ38s3v9bUPXqF7TYZb+ytj5bIQpl6+WqF9ZNj3gRyx7rcsILhBBg08olVQ
+WZwr7LlIxUcDlrbYmrwd9lsMz2nOW2CLCD7mVqJQa2Wm35l6vJHQAI0WiQlHnopC
+M2Y49JruWWim2lC8ZzHgTyiU54bIfkXKQxua8G5WGxpW4dDRrM1d0uYe9M1TOqvP
+jFxq+XEIj/LntJpY7XIZs+33wuNLIVvkee9zsap++zYNH+KIGmbXz/HUO6gYeJYw
+EeaBTLfXtNlgHV9TpMjj3Js6p8hMoVDx27kzPOXa3nrFbHiHmUYY39ZSBCE53Wew
+SKIr/FlKtthzJwJkoMNxxsZ1QcI6WgmRANSC4oP9OyM+hPnWlISJZsy9UlXRZNEJ
+1lI8P/FUbdk/hsRN98j4pb/hzI0yyG1tKaj0TBipjdEsxfthKdwS2sE1wG+F2hrg
+A1jG+UOYmreQjCbrzEiq0J0H4wSL6/s/zN7SyXIWBG0UFalWFiehG5242mEOyX03
+0Yi94mPhF/kcqYsWZ/JJo6cq/EqgIeIqEzkbKx+TOsXk13K2y6apgvCxeHDDv3yT
+DqQueRgFicl0319yEK8ARnREFBm8D5oqwMHjJzVzjrqhFGLW1jfQG9HEW5A+s8WF
+d+2OtH1o/jVdAPXoP1DnxdF+G7fNXDI4cyjejC7uhLuxHCOx648UpRE9+mCiI2IO
+LDM=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/remoting/src/test/resources/certs/server.key b/remoting/src/test/resources/certs/server.key
new file mode 100644
index 00000000..30df6961
--- /dev/null
+++ b/remoting/src/test/resources/certs/server.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOsmp4YtrIRsBdBQ
+LyPImafCRynTJls3NNF4g6nZr9e0efBY830gw9kBebcm603sdZNl95fzRr2+srXi
+5FJbG7Fmq1+F0xLNK/kKWirGtNMT2DubmhVdKyXYJSvInoGRkrQzbOG0MdAyzE6Q
+O6OjjNN+xGkmadWyCyNF6S8YqMJTAgMBAAECgYEAj0OlnOIG0Ube4+N2VN7KfqKm
+qJy0Ka6gx14dGUY/E7Qo9n27GujzaSq09RkJExiVKZBeIH1fBAtC5f2uDV7kpy0l
+uNpTpQkbw0g2EQLxDsVwaUEYbu+t9qVeXoDd1vFeoXHBuRwvI9UW1BrxVtvKODia
+5StU8Lw4yjcm2lQalwECQQD/sKj56thIsIY7D9qBHk7fnFLd8aYzhnP2GsbZX4V/
+T1KHRxr/8MqdNQX53DE5qcyM/Mqu95FIpTAniUtvcBujAkEA62+fAMYFTAEWj4Z4
+vCmcoPqfVPWhBKFR/wo3L8uUARiIzlbYNU3LIqC2s16QO50+bLUd41oVHNw9Y+uM
+fxQpkQJACg/WpncSadHghmR6UchyjCQnsqo2wyJQX+fv2VAD/d2OPtqSem3sW0Fh
+6dI7cax36zhrdXUyl2xAt92URV9hBwJALX93sdWSxnpbWsc449wCydVFH00MnfFz
+AB+ARLtJ0eBk58M+qyZqgDmgtQ8sPmkH3EgwC3SoKdiiAIJPt2s1EQJBAKnISZZr
+qB2F2PfAW2JJbQlrPyVzkxhv9XYdiVNOErmuxLFae3AI7nECgGuFBtvmeqzm2yRj
+7RBMCmzyWG7MF3o=
+-----END PRIVATE KEY-----
diff --git a/remoting/src/test/resources/certs/server.pem b/remoting/src/test/resources/certs/server.pem
new file mode 100644
index 00000000..b73e33ef
--- /dev/null
+++ b/remoting/src/test/resources/certs/server.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5DCCAcwCAQEwDQYJKoZIhvcNAQEFBQAwfDELMAkGA1UEBhMCemgxCzAJBgNV
+BAgMAnpqMQswCQYDVQQHDAJoejEPMA0GA1UECgwGYXBhY2hlMREwDwYDVQQLDAhy
+b2NrZXRtcTEOMAwGA1UEAwwFeXVrb24xHzAdBgkqhkiG9w0BCQEWEHl1a29uQGFw
+YWNoZS5vcmcwHhcNMTcxMjExMDkyNjIwWhcNMTgwMTEwMDkyNjIwWjB4MQswCQYD
+VQQGEwJ6aDELMAkGA1UECAwCemoxCzAJBgNVBAcMAmh6MQ8wDQYDVQQKDAZhcGFj
+aGUxETAPBgNVBAsMCHJvY2tldG1xMQ8wDQYDVQQDDAZmb29iYXIxGjAYBgkqhkiG
+9w0BCQEWC2Zvb0BiYXIuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDr
+JqeGLayEbAXQUC8jyJmnwkcp0yZbNzTReIOp2a/XtHnwWPN9IMPZAXm3JutN7HWT
+ZfeX80a9vrK14uRSWxuxZqtfhdMSzSv5CloqxrTTE9g7m5oVXSsl2CUryJ6BkZK0
+M2zhtDHQMsxOkDujo4zTfsRpJmnVsgsjRekvGKjCUwIDAQABMA0GCSqGSIb3DQEB
+BQUAA4IBAQCmhSgxU5PRhBD2qahj2eWKcmz3FCevXgfyN/EUrwI2dZTU5fXPP+m9
+YBLAYUINI0eYGWt0wlGJ6UFyEgt1fcXP3gqsye9fjECmWoae1kVjvYdaxYGsEXrM
+bxSum1D1bz6yRA+eSOaT5aesfw1ZL74AkIq5aRKQ4cgLGzlbIYeoa62XcAj6GrBo
+V2s/mvKCc1FPrqnpUlTTYFM9eRbEyC7HkOm9c+NAy6FqoLFr3tegH+q8ZxENDw4k
+z9gojQ6t1LDPOAmLGHwvMshHa841CwfOduSvzldtxzjnLVUvYB9cyXS1JXvuC9jj
+Q6BOXIYI+0HVgkJbcPOIYDlgC+g6QJqf
+-----END CERTIFICATE-----


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services