You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by bb...@apache.org on 2020/08/19 22:50:58 UTC

[geode-native] branch develop updated: GEODE-8398: Add SNI support to .NET API (#634)

This is an automated email from the ASF dual-hosted git repository.

bbender pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode-native.git


The following commit(s) were added to refs/heads/develop by this push:
     new 557e018  GEODE-8398: Add SNI support to .NET API  (#634)
557e018 is described below

commit 557e018cdc8952c51ebeb7958fc5c9c076cb20f8
Author: Michael Martell <mm...@pivotal.io>
AuthorDate: Wed Aug 19 15:50:49 2020 -0700

    GEODE-8398: Add SNI support to .NET API  (#634)
    
    - incorporate SNI proxy support into native client API
    - Remove redundant initialization of string (PR feedback)
    - TcpSslConn ctor was getting a string "host:port" instead of just "host", and needed to split off just the hostname
    - Remove hard-coded path to Docker binaries
    - Update "Skip" comment for SNI tests
    
    Co-authored-by: Blake Bender <bb...@vmware.com>
---
 clicache/integration-test2/CMakeLists.txt          |   1 +
 clicache/integration-test2/Config.cs.in            |   5 +
 clicache/integration-test2/SNITests.cs             | 127 +++++++++++++++++++++
 clicache/src/PoolFactory.cpp                       |  16 +++
 clicache/src/PoolFactory.hpp                       |  14 +++
 cppcache/include/geode/PoolFactory.hpp             |   5 +
 cppcache/integration/framework/TestConfig.cpp.in   |   1 +
 cppcache/integration/framework/TestConfig.h        |   2 +-
 cppcache/integration/test/SNITest.cpp              |  82 +++++++------
 .../sni-test-config/geode-config/truststore.p12    | Bin 0 -> 8983 bytes
 .../geode-config/truststore_sni.pem                |  68 +++++++++++
 cppcache/src/PoolAttributes.cpp                    |  39 +------
 cppcache/src/PoolAttributes.hpp                    |  14 ++-
 cppcache/src/PoolFactory.cpp                       |  21 +++-
 cppcache/src/TcpSslConn.cpp                        |  13 ++-
 cppcache/src/TcpSslConn.hpp                        |  21 +++-
 cppcache/src/TcrConnection.cpp                     |  20 +++-
 cppcache/src/ThinClientLocatorHelper.cpp           |  28 ++++-
 cppcache/src/ThinClientLocatorHelper.hpp           |   5 +
 cppcache/src/ThinClientPoolDM.cpp                  |   4 +-
 cppcache/src/ThinClientPoolDM.hpp                  |   2 +
 sni-test-config/docker-compose.yml                 |  43 +++++++
 sni-test-config/geode-config/gemfire.properties    |  19 +++
 sni-test-config/geode-config/gfsecurity.properties |  27 +++++
 .../geode-config/locator-maeve-keystore.jks        | Bin 0 -> 2048 bytes
 .../geode-config/server-clementine-keystore.jks    | Bin 0 -> 2059 bytes
 .../geode-config/server-dolores-keystore.jks       | Bin 0 -> 2050 bytes
 sni-test-config/geode-config/truststore.jks        | Bin 0 -> 8095 bytes
 sni-test-config/geode-config/truststore.p12        | Bin 0 -> 8983 bytes
 sni-test-config/geode-config/truststore_sni.pem    |  68 +++++++++++
 sni-test-config/haproxy.cfg                        |  44 +++++++
 sni-test-config/scripts/forever                    |  20 ++++
 sni-test-config/scripts/geode-starter-2.gfsh       |  23 ++++
 sni-test-config/scripts/geode-starter.gfsh         |  22 ++++
 ssl_keys/client_keys/truststore_sni.pem            |  68 +++++++++++
 35 files changed, 714 insertions(+), 108 deletions(-)

diff --git a/clicache/integration-test2/CMakeLists.txt b/clicache/integration-test2/CMakeLists.txt
index 3437621..c41a4d2 100644
--- a/clicache/integration-test2/CMakeLists.txt
+++ b/clicache/integration-test2/CMakeLists.txt
@@ -48,6 +48,7 @@ add_library( ${PROJECT_NAME} SHARED
   packages.config
   AutoSerializationTests.cs
   SerializationTests.cs
+  SNITests.cs
 )
 
 set_source_files_properties(
diff --git a/clicache/integration-test2/Config.cs.in b/clicache/integration-test2/Config.cs.in
index 9ec3f65..4111ec2 100644
--- a/clicache/integration-test2/Config.cs.in
+++ b/clicache/integration-test2/Config.cs.in
@@ -38,4 +38,9 @@ public class Config
   {
 	get { return @"@CMAKE_CURRENT_SOURCE_DIR@/../../ssl_keys/client_keys"; }
   }
+
+  public static string SniConfigPath
+  {
+	get { return @"@CMAKE_CURRENT_SOURCE_DIR@/../../sni-test-config"; }
+  }
 }
diff --git a/clicache/integration-test2/SNITests.cs b/clicache/integration-test2/SNITests.cs
new file mode 100644
index 0000000..f23818b
--- /dev/null
+++ b/clicache/integration-test2/SNITests.cs
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using Xunit;
+using PdxTests;
+using System.Collections;
+using System.Collections.Generic;
+using Xunit.Abstractions;
+
+namespace Apache.Geode.Client.IntegrationTests
+{
+    [Trait("Category", "Integration")]
+    public class SNITests : TestBase, IDisposable
+    {
+        string currentWorkingDirectory;
+        Process dockerProcess;
+        private readonly Cache cache_;
+
+        public SNITests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
+        {
+            currentWorkingDirectory = Directory.GetCurrentDirectory();
+            var clientTruststore = Config.SslClientKeyPath + @"/truststore_sni.pem";
+
+
+
+            var cacheFactory = new CacheFactory();
+            cacheFactory.Set("log-level", "none");
+            cacheFactory.Set("log-file", "c:/temp/SNITest-csharp.log");
+            cacheFactory.Set("ssl-enabled", "true");
+            cacheFactory.Set("ssl-truststore", clientTruststore);
+
+            cache_ = cacheFactory.Create();
+
+            var dc = Process.Start(@"docker-compose.exe", "-f " + Config.SniConfigPath + "/docker-compose.yml" + " up -d");
+            dc.WaitForExit();
+
+            var d = Process.Start(@"docker.exe", "exec -t geode gfsh run --file=/geode/scripts/geode-starter.gfsh");
+            d.WaitForExit();
+        }
+
+        public void Dispose()
+        {
+
+            var dockerComposeProc = Process.Start(@"docker-compose.exe", "stop");
+            dockerComposeProc.WaitForExit();
+
+            var dockerProc = Process.Start(@"docker.exe", "container prune -f");
+            dockerProc.WaitForExit();
+
+        }
+
+        private string RunDockerCommand(string dockerCommand)
+        {
+            ProcessStartInfo startInfo = new ProcessStartInfo();
+            startInfo.RedirectStandardOutput = true;
+            startInfo.UseShellExecute = false;
+            startInfo.FileName = @"docker.exe";
+            startInfo.Arguments = dockerCommand;
+            dockerProcess = Process.Start(startInfo);
+            String rVal = dockerProcess.StandardOutput.ReadToEnd();
+            dockerProcess.WaitForExit();
+            return rVal;
+        }
+
+        private int ParseProxyPort(string proxyString)
+        {
+            int colonPosition = proxyString.IndexOf(":");
+            string portNumberString = proxyString.Substring(colonPosition + 1);
+            return Int32.Parse(portNumberString);
+        }
+
+        [Fact (Skip = "Disabled until we iron out our Docker issues on Windows")]
+        public void ConnectViaProxy()
+        {
+            var portString = RunDockerCommand("port haproxy");
+            var portNumber = ParseProxyPort(portString);
+
+            cache_.GetPoolManager()
+                .CreateFactory()
+                .SetSniProxy("localhost", portNumber)
+                .AddLocator("locator-maeve", 10334)
+                .Create("pool");
+
+            var region = cache_.CreateRegionFactory(RegionShortcut.PROXY)
+                              .SetPoolName("pool")
+                              .Create<string, string>("jellyfish");
+
+            region.Put("1", "one");
+            var value = region.Get("1");
+
+            Assert.Equal("one", value);
+            cache_.Close();
+        }
+
+        [Fact (Skip = "Disabled until we iron out our Docker issues on Windows")]
+        public void ConnectionWithoutProxyFails()
+        {
+            cache_.GetPoolManager()
+                .CreateFactory()
+                .AddLocator("localhost", 10334)
+                .Create("pool");
+
+            var region = cache_.CreateRegionFactory(RegionShortcut.PROXY)
+                              .SetPoolName("pool")
+                              .Create<string, string>("region");
+
+            Assert.Throws<NotConnectedException>(() => region.Put("1", "one"));
+        }
+    }
+}
\ No newline at end of file
diff --git a/clicache/src/PoolFactory.cpp b/clicache/src/PoolFactory.cpp
index c72da35..9c62562 100644
--- a/clicache/src/PoolFactory.cpp
+++ b/clicache/src/PoolFactory.cpp
@@ -285,6 +285,22 @@ namespace Apache
           return this;
 		  }
 
+		  PoolFactory^ PoolFactory::SetSniProxy(String^ hostname, Int32 port)
+      {
+			  _GF_MG_EXCEPTION_TRY2/* due to auto replace */
+
+			  try
+			  {
+			    m_nativeptr->get()->setSniProxy( marshal_as<std::string>(hostname), port );
+			  }
+			  finally
+			  {
+			    GC::KeepAlive(m_nativeptr);
+			  }
+
+			  _GF_MG_EXCEPTION_CATCH_ALL2/* due to auto replace */
+          return this;
+		  }
 
 		  PoolFactory^ PoolFactory::SetSubscriptionEnabled( Boolean enabled )
       {
diff --git a/clicache/src/PoolFactory.hpp b/clicache/src/PoolFactory.hpp
index e6f703e..7d0e3c3 100644
--- a/clicache/src/PoolFactory.hpp
+++ b/clicache/src/PoolFactory.hpp
@@ -280,6 +280,20 @@ namespace Apache
         PoolFactory^ AddServer(String^ host, Int32 port);
 
         /// <summary>
+        /// Set proxy info for SNI connection.
+        /// </summary>
+        /// <remarks>
+        /// Used for connecting via SNI proxy.
+        /// </remarks>
+        /// <param>
+        /// host the host name or ip address that the server is listening on.
+        /// </param>
+        /// <param>
+        /// port the port that the server is listening on.
+        /// </param>
+        PoolFactory^ SetSniProxy(String^ hostname, Int32 port);
+
+        /// <summary>
         /// Enable subscriptions.
         /// </summary>
         /// <remarks>
diff --git a/cppcache/include/geode/PoolFactory.hpp b/cppcache/include/geode/PoolFactory.hpp
index 195e28d..d554482 100644
--- a/cppcache/include/geode/PoolFactory.hpp
+++ b/cppcache/include/geode/PoolFactory.hpp
@@ -427,6 +427,11 @@ class APACHE_GEODE_EXPORT PoolFactory {
   PoolFactory& addServer(const std::string& host, int port);
 
   /**
+   * Set proxy info for SNI connection.  Used for connecting via SNI proxy.
+   */
+  PoolFactory& setSniProxy(const std::string& hostname, const int port);
+
+  /**
    * If set to <code>true</code> then the created pool will have
    * server-to-client
    * subscriptions enabled.
diff --git a/cppcache/integration/framework/TestConfig.cpp.in b/cppcache/integration/framework/TestConfig.cpp.in
index f937640..486cdd1 100644
--- a/cppcache/integration/framework/TestConfig.cpp.in
+++ b/cppcache/integration/framework/TestConfig.cpp.in
@@ -24,6 +24,7 @@ const char *getFrameworkString(FrameworkVariable name) {
     case FrameworkVariable::TestCacheXmlDir: return "@CMAKE_CURRENT_SOURCE_DIR@/../../integration-test/resources";
     case FrameworkVariable::TestClientSslKeysDir: return "@CMAKE_CURRENT_SOURCE_DIR@/../../../ssl_keys/client_keys";
     case FrameworkVariable::TestServerSslKeysDir: return "@CMAKE_CURRENT_SOURCE_DIR@/../../../ssl_keys/server_keys";
+    case FrameworkVariable::TestSniConfigPath: return "@CMAKE_CURRENT_SOURCE_DIR@/../../../sni-test-config";
     default: return "";
   }
 }
diff --git a/cppcache/integration/framework/TestConfig.h b/cppcache/integration/framework/TestConfig.h
index b7362d0..30203ae 100644
--- a/cppcache/integration/framework/TestConfig.h
+++ b/cppcache/integration/framework/TestConfig.h
@@ -20,7 +20,7 @@
 #ifndef INTEGRATION_TEST_FRAMEWORK_CONFIG_H
 #define INTEGRATION_TEST_FRAMEWORK_CONFIG_H
 
-enum class FrameworkVariable {JavaObjectJarPath, GfShExecutable, TestCacheXmlDir, TestClientSslKeysDir, TestServerSslKeysDir};
+enum class FrameworkVariable {JavaObjectJarPath, GfShExecutable, TestCacheXmlDir, TestClientSslKeysDir, TestServerSslKeysDir, TestSniConfigPath};
 
 const char *getFrameworkString(FrameworkVariable name);
 
diff --git a/cppcache/integration/test/SNITest.cpp b/cppcache/integration/test/SNITest.cpp
index 7e70619..e5052b7 100644
--- a/cppcache/integration/test/SNITest.cpp
+++ b/cppcache/integration/test/SNITest.cpp
@@ -28,6 +28,7 @@
 #include <geode/RegionShortcut.hpp>
 
 #include "framework/Cluster.h"
+#include "framework/TestConfig.h"
 
 namespace snitest {
 
@@ -41,28 +42,26 @@ class SNITest : public ::testing::Test {
  protected:
   SNITest() {
     certificatePassword = std::string("apachegeode");
+    clientSslKeysDir = boost::filesystem::path(
+        getFrameworkString(FrameworkVariable::TestClientSslKeysDir));
     currentWorkingDirectory = boost::filesystem::current_path();
+    sniConfigPath = boost::filesystem::path(
+        getFrameworkString(FrameworkVariable::TestSniConfigPath));
   }
 
   ~SNITest() override = default;
 
   void SetUp() override {
     auto systemRVal = 0;
-#if defined(_WIN32)
-    std::string sniDir(currentWorkingDirectory.string());
-    sniDir += "/../sni-test-config";
-    SetCurrentDirectory(sniDir.c_str());
-#else
-    systemRVal = chdir("./sni-test-config");
-    if (systemRVal == -1) {
-      BOOST_LOG_TRIVIAL(error) << "chdir returned: " << systemRVal;
-    }
-#endif
+    std::string dockerComposeCmd = "docker-compose -f " +
+                                   sniConfigPath.string() +
+                                   "/docker-compose.yml" + " up -d";
+    const char* dcc = dockerComposeCmd.c_str();
 
-    systemRVal = std::system("docker-compose up -d");
+    systemRVal = std::system(dcc);
     if (systemRVal == -1) {
       BOOST_LOG_TRIVIAL(error)
-          << "std::system(\"docker-compose up -d\") returned: " << systemRVal;
+          << "std::system(\"docker-compose\") returned: " << systemRVal;
     }
 
     systemRVal = std::system(
@@ -80,9 +79,14 @@ class SNITest : public ::testing::Test {
     if (systemRVal == -1) {
       BOOST_LOG_TRIVIAL(error) << "std::system returned: " << systemRVal;
     }
+
+    systemRVal = std::system("docker container prune -f");
+    if (systemRVal == -1) {
+      BOOST_LOG_TRIVIAL(error) << "std::system returned: " << systemRVal;
+    }
   }
 
-  std::string makeItSo(const char* command) {
+  std::string runDockerCommand(const char* command) {
     std::string commandOutput;
 #if defined(_WIN32)
     std::unique_ptr<FILE, decltype(&_pclose)> pipe(_popen(command, "r"),
@@ -108,41 +112,51 @@ class SNITest : public ::testing::Test {
   }
 
   std::string certificatePassword;
+  boost::filesystem::path clientSslKeysDir;
   boost::filesystem::path currentWorkingDirectory;
+  boost::filesystem::path sniConfigPath;
 };
 
-TEST_F(SNITest, DISABLED_connectViaProxyTest) {
+#if defined(_WIN32)
+TEST_F(SNITest, DISABLE_connectViaProxyTest) {
+#else
+TEST_F(SNITest, connectViaProxyTest) {
+#endif
   const auto clientTruststore =
-      (currentWorkingDirectory /
-       boost::filesystem::path("sni-test-config/geode-config/truststore.jks"));
+      (clientSslKeysDir / boost::filesystem::path("/truststore_sni.pem"));
 
   auto cache = CacheFactory()
-                   .set("log-level", "DEBUG")
+                   .set("log-level", "debug")
+                   .set("log-file", "SNITest.log")
                    .set("ssl-enabled", "true")
                    .set("ssl-truststore", clientTruststore.string())
                    .create();
 
-  auto portString = makeItSo("docker port haproxy");
+  auto portString = runDockerCommand("docker port haproxy");
   auto portNumber = parseProxyPort(portString);
 
   cache.getPoolManager()
       .createFactory()
-      .addLocator("localhost", portNumber)
+      .setSniProxy("localhost", portNumber)
+      .addLocator("locator-maeve", 10334)
       .create("pool");
 
   auto region = cache.createRegionFactory(RegionShortcut::PROXY)
                     .setPoolName("pool")
-                    .create("region");
+                    .create("jellyfish");
 
   region->put("1", "one");
 
   cache.close();
 }
 
-TEST_F(SNITest, connectionFailsTest) {
+#if defined(_WIN32)
+TEST_F(SNITest, DISABLE_connectWithoutProxyFails) {
+#else
+TEST_F(SNITest, connectWithoutProxyFails) {
+#endif
   const auto clientTruststore =
-      (currentWorkingDirectory /
-       boost::filesystem::path("sni-test-config/geode-config/truststore.jks"));
+      (clientSslKeysDir / boost::filesystem::path("/truststore_sni.pem"));
 
   auto cache = CacheFactory()
                    .set("log-level", "DEBUG")
@@ -152,7 +166,8 @@ TEST_F(SNITest, connectionFailsTest) {
 
   cache.getPoolManager()
       .createFactory()
-      .addLocator("localhost", 10334)
+      .setSniProxy("badProxyName", 40000)
+      .addLocator("locator-maeve", 10334)
       .create("pool");
 
   auto region = cache.createRegionFactory(RegionShortcut::PROXY)
@@ -164,23 +179,4 @@ TEST_F(SNITest, connectionFailsTest) {
   cache.close();
 }
 
-TEST_F(SNITest, doNothingTest) {
-  const auto clientTruststore =
-      (currentWorkingDirectory /
-       boost::filesystem::path("sni-test-config/geode-config/truststore.jks"));
-
-  auto cache = CacheFactory()
-                   .set("log-level", "DEBUG")
-                   .set("ssl-enabled", "true")
-                   .set("ssl-truststore", clientTruststore.string())
-                   .create();
-
-  cache.getPoolManager()
-      .createFactory()
-      .addLocator("localhost", 10334)
-      .create("pool");
-
-  cache.close();
-}
-
 }  // namespace snitest
diff --git a/cppcache/integration/test/sni-test-config/geode-config/truststore.p12 b/cppcache/integration/test/sni-test-config/geode-config/truststore.p12
new file mode 100644
index 0000000..339d775
Binary files /dev/null and b/cppcache/integration/test/sni-test-config/geode-config/truststore.p12 differ
diff --git a/cppcache/integration/test/sni-test-config/geode-config/truststore_sni.pem b/cppcache/integration/test/sni-test-config/geode-config/truststore_sni.pem
new file mode 100644
index 0000000..1857ce6
--- /dev/null
+++ b/cppcache/integration/test/sni-test-config/geode-config/truststore_sni.pem
@@ -0,0 +1,68 @@
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICtDCCAZygAwIBAgIEXozGnzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFz
+ZXJ2ZXItY2xlbWVudGluZTAeFw0yMDA0MDcxODI5NTFaFw0yNTA0MDcxODI5NTFa
+MBwxGjAYBgNVBAMMEXNlcnZlci1jbGVtZW50aW5lMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6IzshjujS5c58AH8nJHBhlqjfNpacoNxhxykeCVsExa9
+vi0l8ezi35pte06j7gpMWhDYHokrHaw6ymp9iTi7D91yIPGeMMNUli8DnzgAzpeY
+V8SGgkrVBalkVe0GimAHXMrzeZF+8D2BEdvDAsIUbrZRACElPlLUoiO93xZZ8ad+
+fAfLVetH4lDJ54FT7ia+St6L0QxSrDLvrqmc/58ZunkQBnQcd4tMjCD1kX4l+5Q1
+eF+Rc/SbY+/8HfyCZcA98voC3dKF13U+0YAf/0ahin+8Ckm6BL/StUxFNftTtJ7l
+iKf56Y3FbSQ84Q9Te8feb05XidkF74Gifa4Q7gOzjwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQDKvYcnVFryhupo156bB33BU14KN8b5joVyQLeGb2Tx+icZd/jFhqSQ
+c3f8VV+aG9+CtRi/6wesdzf9/CVF+J4ARJ7j3i60NlJi4vQJlZnou+JSBgbBiDkW
+p12ITsw7l1k2zxH8hoMPNbMK1EC/+uwVRJt92L52uShLw9zKtE4MLZxZVa7Amkf4
+zRc78fHwwPXoMjLcQxw+8JRjlciWr/hZccuppXI4qb17l6HAMvW4vCslao0c9pSp
+Opg5Q0PwVXFROIvCANdxNI9ptSrH78Thxh4rggnHs+OZF02D22oTkjquU4Xrar3u
+FXlIS8UmdkqAXGIJf0pqa48aXcqeipRe
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAZagAwIBAgIEXozE5DANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5z
+ZXJ2ZXItZG9sb3JlczAeFw0yMDA0MDcxODIyMjhaFw0yNTA0MDcxODIyMjhaMBkx
+FzAVBgNVBAMMDnNlcnZlci1kb2xvcmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAyRTzsWsih9Boz2/aRFJsgJNDn8/C207kpvJ9lj0uBWNdZGJ86T4i
+CwvIyMFvxeYQB0qO0AHf6FvJfMgunRlCj3fD01s7AHj8kCFoM/akgo04M7iJfSkU
+dDCVuRbrFtz31akNckyxRw/oORiQ6NYGxnuAvtFdjE8jFc77WVXVU5QuqVEueJXs
+HM+t6VGEn+7GwPsSJMIuEERd+05ZlghB1HoQD4Wu4+b/CXU+8aFRad0HRXHInBl0
+0QABETcMtpe3xIotC7H1nsAMipb0jyl3p+1a49FbrAktsiko8Y2iRVv3kZ58xfx9
+2Unmw+ViEb5bVRFytqb5AIgARI/+XX1zBwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
+AQB39QXR3HLEju8B1oNCH1UciZetMxvORC2fwgXhqjbJ2YkHlykaLAAKv6DOSyc2
+HE40F2Q/Y0p0NC41+4YIiujgzKWaDI1Gw22PlceE2B49dO8evmldN2NixkirJbtm
+bEtjINAxHXbhXn8GgUKJxSqtFPTX/fG7OCYvkvGItQAhSrGo9r5ACuDYkTZsBAZp
+9jHc50TZsQ7od4jsPXrtZ6S2doOA0TdQ/+XzNyoadbG0YZbRtUVmhJN7gQfkBcjH
+/AnYeYJL1kg39AuO3PsFhgWCsR2eNizGCh7CnHx7xpJnLYAw/01TGidsku/oYFiI
+5SthBjGC992gTekW54hYtMBU
+-----END CERTIFICATE-----
diff --git a/cppcache/src/PoolAttributes.cpp b/cppcache/src/PoolAttributes.cpp
index 2b4230d..206e1a4 100644
--- a/cppcache/src/PoolAttributes.cpp
+++ b/cppcache/src/PoolAttributes.cpp
@@ -44,46 +44,13 @@ PoolAttributes::PoolAttributes()
       m_subsEnabled(PoolFactory::DEFAULT_SUBSCRIPTION_ENABLED),
       m_multiuserSecurityMode(PoolFactory::DEFAULT_MULTIUSER_SECURE_MODE),
       m_isPRSingleHopEnabled(PoolFactory::DEFAULT_PR_SINGLE_HOP_ENABLED),
-      m_serverGrp(PoolFactory::DEFAULT_SERVER_GROUP) {}
+      m_serverGrp(PoolFactory::DEFAULT_SERVER_GROUP),
+      m_sniProxyPort(0) {}
+
 std::shared_ptr<PoolAttributes> PoolAttributes::clone() {
   return std::make_shared<PoolAttributes>(*this);
 }
 
-/** Return true if all the attributes are equal to those of other. */
-bool PoolAttributes::operator==(const PoolAttributes& other) const {
-  if (m_isThreadLocalConn != other.m_isThreadLocalConn) return false;
-  if (m_freeConnTimeout != other.m_freeConnTimeout) return false;
-  if (m_loadCondInterval != other.m_loadCondInterval) return false;
-  if (m_sockBufferSize != other.m_sockBufferSize) return false;
-  if (m_readTimeout != other.m_readTimeout) return false;
-  if (m_minConns != other.m_minConns) return false;
-  if (m_maxConns != other.m_maxConns) return false;
-  if (m_retryAttempts != other.m_retryAttempts) return false;
-  if (m_statsInterval != other.m_statsInterval) return false;
-  if (m_redundancy != other.m_redundancy) return false;
-  if (m_msgTrackTimeout != other.m_msgTrackTimeout) return false;
-  if (m_subsAckInterval != other.m_subsAckInterval) return false;
-  if (m_idleTimeout != other.m_idleTimeout) return false;
-  if (m_pingInterval != other.m_pingInterval) return false;
-  if (m_updateLocatorListInterval != other.m_updateLocatorListInterval) {
-    return false;
-  }
-  if (m_subsEnabled != other.m_subsEnabled) return false;
-  if (m_multiuserSecurityMode != other.m_multiuserSecurityMode) return false;
-  if (m_isPRSingleHopEnabled != other.m_isPRSingleHopEnabled) return false;
-  if (m_serverGrp != other.m_serverGrp) return false;
-
-  if (m_initLocList.size() != other.m_initLocList.size()) return false;
-  if (m_initServList.size() != other.m_initServList.size()) return false;
-
-  if (!compareVectorOfStrings(m_initLocList, other.m_initLocList)) return false;
-  if (!compareVectorOfStrings(m_initServList, other.m_initServList)) {
-    return false;
-  }
-
-  return true;
-}
-
 bool PoolAttributes::compareVectorOfStrings(
     const std::vector<std::string>& thisVector,
     const std::vector<std::string>& otherVector) {
diff --git a/cppcache/src/PoolAttributes.hpp b/cppcache/src/PoolAttributes.hpp
index 3fee900..44d9633 100644
--- a/cppcache/src/PoolAttributes.hpp
+++ b/cppcache/src/PoolAttributes.hpp
@@ -121,6 +121,14 @@ class PoolAttributes {
     m_statsInterval = statisticInterval;
   }
 
+  const std::string& getSniProxyHost() const { return m_sniProxyHost; }
+
+  void setSniProxyHost(const std::string& host) { m_sniProxyHost = host; }
+
+  int getSniProxyPort() const { return m_sniProxyPort; }
+
+  void setSniProxyPort(const int port) { m_sniProxyPort = port; }
+
   const std::string& getServerGroup() const { return m_serverGrp; }
 
   void setServerGroup(std::string group) { m_serverGrp = group; }
@@ -168,9 +176,6 @@ class PoolAttributes {
 
   std::shared_ptr<PoolAttributes> clone();
 
-  /** Return true if all the attributes are equal to those of other. */
-  bool operator==(const PoolAttributes& other) const;
-
  private:
   bool m_isThreadLocalConn;
   std::chrono::milliseconds m_freeConnTimeout;
@@ -197,6 +202,9 @@ class PoolAttributes {
   std::vector<std::string> m_initLocList;
   std::vector<std::string> m_initServList;
 
+  std::string m_sniProxyHost;
+  int m_sniProxyPort;
+
   static bool compareVectorOfStrings(
       const std::vector<std::string>& thisVector,
       const std::vector<std::string>& otherVector);
diff --git a/cppcache/src/PoolFactory.cpp b/cppcache/src/PoolFactory.cpp
index 0ee2518..9a43f64 100644
--- a/cppcache/src/PoolFactory.cpp
+++ b/cppcache/src/PoolFactory.cpp
@@ -185,6 +185,13 @@ PoolFactory& PoolFactory::addServer(const std::string& host, int port) {
   return *this;
 }
 
+PoolFactory& PoolFactory::setSniProxy(const std::string& hostname,
+                                      const int port) {
+  m_attrs->setSniProxyHost(hostname);
+  m_attrs->setSniProxyPort(port);
+  return *this;
+}
+
 PoolFactory& PoolFactory::setSubscriptionEnabled(bool enabled) {
   m_attrs->setSubscriptionEnabled(enabled);
   return *this;
@@ -304,15 +311,17 @@ PoolFactory& PoolFactory::addCheck(const std::string& host, int port) {
                                    std::to_string(port));
   }
 
-  ACE_INET_Addr addr(port, host.c_str());
+  if (m_attrs->getSniProxyHost().empty()) {
+    ACE_INET_Addr addr(port, host.c_str());
 #ifdef WITH_IPV6
-  // check unknown host
-  // ACE will not initialize port if hostname is not resolved.
-  if (port != addr.get_port_number()) {
+    // check unknown host
+    // ACE will not initialize port if hostname is not resolved.
+    if (port != addr.get_port_number()) {
 #else
-  if (!(addr.get_ip_address())) {
+    if (!(addr.get_ip_address())) {
 #endif
-    throw IllegalArgumentException("Unknown host " + host);
+      throw IllegalArgumentException("Unknown host " + host);
+    }
   }
   return *this;
 }
diff --git a/cppcache/src/TcpSslConn.cpp b/cppcache/src/TcpSslConn.cpp
index a72843e..8ede9a3 100644
--- a/cppcache/src/TcpSslConn.cpp
+++ b/cppcache/src/TcpSslConn.cpp
@@ -29,7 +29,6 @@
 namespace apache {
 namespace geode {
 namespace client {
-
 std::atomic_flag TcpSslConn::initialized_ = ATOMIC_FLAG_INIT;
 
 void TcpSslConn::createSocket(ACE_HANDLE sock) {
@@ -48,12 +47,16 @@ void TcpSslConn::connect() {
            std::to_string(inetAddress_.get_port_number()) + " waiting " +
            to_string(timeout_));
 
+  if (!sniHostname_.empty()) {
+    SSL_set_tlsext_host_name(stream_->ssl(), sniHostname_.c_str());
+  }
+
   ACE_SSL_SOCK_Connector conn;
   ACE_Time_Value actTimeout(timeout_);
-  if (ACE_SSL_SOCK_Connector{}.connect(
-          *stream_, inetAddress_,
-          timeout_ > std::chrono::microseconds::zero() ? &actTimeout
-                                                       : nullptr) == -1) {
+  if (conn.connect(*stream_, inetAddress_,
+                   timeout_ > std::chrono::microseconds::zero()
+                       ? &actTimeout
+                       : nullptr) == -1) {
     const auto lastError = ACE_OS::last_error();
     if (lastError == ETIME || lastError == ETIMEDOUT) {
       throw TimeoutException(
diff --git a/cppcache/src/TcpSslConn.hpp b/cppcache/src/TcpSslConn.hpp
index 6fca35f..6a543b1 100644
--- a/cppcache/src/TcpSslConn.hpp
+++ b/cppcache/src/TcpSslConn.hpp
@@ -50,6 +50,7 @@ class TcpSslConn : public TcpConn {
   const std::string trustStoreFile_;
   const std::string privateKeyFile_;
   const std::string password_;
+  std::string sniHostname_;
   std::unique_ptr<ACE_SSL_SOCK_Stream> stream_;
 
  protected:
@@ -61,11 +62,23 @@ class TcpSslConn : public TcpConn {
   void initSsl();
 
  public:
+  TcpSslConn(const std::string& ipaddr, std::chrono::microseconds waitSeconds,
+             int32_t maxBuffSizePool, const std::string& sniProxyHostname,
+             uint16_t sniProxyPort, std::string publicKeyFile,
+             std::string privateKeyFile, std::string password)
+      : TcpConn(sniProxyHostname, sniProxyPort, waitSeconds, maxBuffSizePool),
+        trustStoreFile_(std::move(publicKeyFile)),
+        privateKeyFile_(std::move(privateKeyFile)),
+        password_(std::move(password)),
+        sniHostname_(ipaddr.substr(0, ipaddr.find(':'))) {
+    initSsl();
+  }
+
   TcpSslConn(const std::string& hostname, uint16_t port,
-             std::chrono::microseconds waitSeconds, int32_t maxBuffSizePool,
-             std::string publicKeyFile, std::string privateKeyFile,
-             std::string password)
-      : TcpConn(hostname, port, waitSeconds, maxBuffSizePool),
+             std::chrono::microseconds connect_timeout, int32_t maxBuffSizePool,
+             const std::string& publicKeyFile,
+             const std::string& privateKeyFile, const std::string& password)
+      : TcpConn(hostname.c_str(), port, connect_timeout, maxBuffSizePool),
         trustStoreFile_(std::move(publicKeyFile)),
         privateKeyFile_(std::move(privateKeyFile)),
         password_(std::move(password)) {
diff --git a/cppcache/src/TcrConnection.cpp b/cppcache/src/TcrConnection.cpp
index 8698fa5..bae9a4a 100644
--- a/cppcache/src/TcrConnection.cpp
+++ b/cppcache/src/TcrConnection.cpp
@@ -432,10 +432,19 @@ Connector* TcrConnection::createConnection(
                                ->getDistributedSystem()
                                .getSystemProperties();
   if (systemProperties.sslEnabled()) {
-    socket = new TcpSslConn(address, connectTimeout, maxBuffSizePool,
-                            systemProperties.sslTrustStore(),
-                            systemProperties.sslKeyStore(),
-                            systemProperties.sslKeystorePassword());
+    auto sniHostname = m_poolDM->getSNIProxyHostname();
+    auto sniPort = m_poolDM->getSNIPort();
+    if (sniHostname.empty()) {
+      socket = new TcpSslConn(address, connectTimeout, maxBuffSizePool,
+                              systemProperties.sslTrustStore(),
+                              systemProperties.sslKeyStore(),
+                              systemProperties.sslKeystorePassword());
+    } else {
+      socket = new TcpSslConn(
+          address, connectTimeout, maxBuffSizePool, sniHostname, sniPort,
+          systemProperties.sslTrustStore(), systemProperties.sslKeyStore(),
+          systemProperties.sslKeystorePassword());
+    }
   } else {
     socket = new TcpConn(address, connectTimeout, maxBuffSizePool);
   }
@@ -514,7 +523,8 @@ inline ConnErrType TcrConnection::sendData(
   std::chrono::microseconds defaultWaitSecs = std::chrono::seconds(2);
   if (defaultWaitSecs > sendTimeout) defaultWaitSecs = sendTimeout;
   LOGDEBUG(
-      "before send len %zu sendTimeoutSec = %s checkConnected = %d m_connected "
+      "before send len %zu sendTimeoutSec = %s checkConnected = %d "
+      "m_connected "
       "%d",
       length, to_string(sendTimeout).c_str(), checkConnected, m_connected);
   while (length > 0 && sendTimeout > std::chrono::microseconds::zero()) {
diff --git a/cppcache/src/ThinClientLocatorHelper.cpp b/cppcache/src/ThinClientLocatorHelper.cpp
index e7174ce..a8c58c5 100644
--- a/cppcache/src/ThinClientLocatorHelper.cpp
+++ b/cppcache/src/ThinClientLocatorHelper.cpp
@@ -66,6 +66,18 @@ ThinClientLocatorHelper::ThinClientLocatorHelper(
   }
 }
 
+ThinClientLocatorHelper::ThinClientLocatorHelper(
+    const std::vector<std::string>& locatorAddresses,
+    const std::string& sniProxyHost, int sniProxyPort,
+    const ThinClientPoolDM* poolDM)
+    : m_poolDM(poolDM),
+      m_sniProxyHost(sniProxyHost),
+      m_sniProxyPort(sniProxyPort) {
+  for (auto&& locatorAddress : locatorAddresses) {
+    m_locHostPort.emplace_back(locatorAddress);
+  }
+}
+
 Connector* ThinClientLocatorHelper::createConnection(
     Connector*& conn, const char* hostname, int32_t port,
     std::chrono::microseconds waitSeconds, int32_t maxBuffSizePool) {
@@ -75,10 +87,18 @@ Connector* ThinClientLocatorHelper::createConnection(
                                ->getDistributedSystem()
                                .getSystemProperties();
   if (systemProperties.sslEnabled()) {
-    socket = new TcpSslConn(hostname, port, waitSeconds, maxBuffSizePool,
-                            systemProperties.sslTrustStore().c_str(),
-                            systemProperties.sslKeyStore().c_str(),
-                            systemProperties.sslKeystorePassword().c_str());
+    if (m_sniProxyHost.empty()) {
+      socket = new TcpSslConn(
+          hostname, static_cast<uint16_t>(port), waitSeconds, maxBuffSizePool,
+          systemProperties.sslTrustStore(), systemProperties.sslKeyStore(),
+          systemProperties.sslKeystorePassword());
+    } else {
+      socket =
+          new TcpSslConn(hostname, waitSeconds, maxBuffSizePool, m_sniProxyHost,
+                         m_sniProxyPort, systemProperties.sslTrustStore(),
+                         systemProperties.sslKeyStore(),
+                         systemProperties.sslKeystorePassword());
+    }
   } else {
     socket = new TcpConn(hostname, port, waitSeconds, maxBuffSizePool);
   }
diff --git a/cppcache/src/ThinClientLocatorHelper.hpp b/cppcache/src/ThinClientLocatorHelper.hpp
index c72f950..df522a3 100644
--- a/cppcache/src/ThinClientLocatorHelper.hpp
+++ b/cppcache/src/ThinClientLocatorHelper.hpp
@@ -44,6 +44,9 @@ class ThinClientLocatorHelper {
  public:
   ThinClientLocatorHelper(const std::vector<std::string>& locatorAddresses,
                           const ThinClientPoolDM* poolDM);
+  ThinClientLocatorHelper(const std::vector<std::string>& locatorAddresses,
+                          const std::string& sniProxyHost, int sniProxyPort,
+                          const ThinClientPoolDM* poolDM);
   GfErrType getEndpointForNewFwdConn(
       ServerLocation& outEndpoint, std::string& additionalLoc,
       const std::set<ServerLocation>& exclEndPts,
@@ -71,6 +74,8 @@ class ThinClientLocatorHelper {
   const ThinClientPoolDM* m_poolDM;
   ThinClientLocatorHelper(const ThinClientLocatorHelper&);
   ThinClientLocatorHelper& operator=(const ThinClientLocatorHelper&);
+  std::string m_sniProxyHost;
+  int m_sniProxyPort;
 };
 }  // namespace client
 }  // namespace geode
diff --git a/cppcache/src/ThinClientPoolDM.cpp b/cppcache/src/ThinClientPoolDM.cpp
index aab3200..8b7022d 100644
--- a/cppcache/src/ThinClientPoolDM.cpp
+++ b/cppcache/src/ThinClientPoolDM.cpp
@@ -186,7 +186,9 @@ ThinClientPoolDM::ThinClientPoolDM(const char* name,
     throw IllegalStateException(msg);
   }
   reset();
-  m_locHelper = new ThinClientLocatorHelper(m_attrs->m_initLocList, this);
+  m_locHelper = new ThinClientLocatorHelper(m_attrs->m_initLocList,
+                                            m_attrs->m_sniProxyHost,
+                                            m_attrs->m_sniProxyPort, this);
 
   m_stats = new PoolStats(
       cacheImpl->getStatisticsManager().getStatisticsFactory(), m_poolName);
diff --git a/cppcache/src/ThinClientPoolDM.hpp b/cppcache/src/ThinClientPoolDM.hpp
index 711e906..ffd3d90 100644
--- a/cppcache/src/ThinClientPoolDM.hpp
+++ b/cppcache/src/ThinClientPoolDM.hpp
@@ -168,6 +168,8 @@ class ThinClientPoolDM
   GfErrType getConnectionToAnEndPoint(std::string epNameStr,
                                       TcrConnection*& conn);
 
+  const std::string getSNIProxyHostname() { return m_attrs->getSniProxyHost(); }
+  uint16_t getSNIPort() { return m_attrs->getSniProxyPort(); }
   virtual inline bool isSticky() { return m_sticky; }
   virtual TcrEndpoint* getEndPoint(
       const std::shared_ptr<BucketServerLocation>& serverLocation,
diff --git a/sni-test-config/docker-compose.yml b/sni-test-config/docker-compose.yml
new file mode 100644
index 0000000..b0a6100
--- /dev/null
+++ b/sni-test-config/docker-compose.yml
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+version: '3'
+services:
+  geode:
+    container_name: 'geode'
+    image: 'apachegeode/geode'
+    expose:
+      - '10334'
+      - '40404'
+    entrypoint: 'sh'
+    command: ["-c", "while true; do sleep 600; done"]
+    networks:
+      geode-sni-test:
+    volumes:
+      - ./geode-config:/geode/config:ro
+      - ./scripts:/geode/scripts
+  haproxy:
+    container_name: 'haproxy'
+    image: 'haproxy:2.1'
+    ports:
+      - "15443"
+    networks:
+      geode-sni-test:
+    volumes:
+      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
+networks:
+  geode-sni-test:
+
diff --git a/sni-test-config/geode-config/gemfire.properties b/sni-test-config/geode-config/gemfire.properties
new file mode 100644
index 0000000..1f13fb0
--- /dev/null
+++ b/sni-test-config/geode-config/gemfire.properties
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+statistic-sampling-enabled=true
+statistic-archive-file=statArchive.gfs
diff --git a/sni-test-config/geode-config/gfsecurity.properties b/sni-test-config/geode-config/gfsecurity.properties
new file mode 100644
index 0000000..813d260
--- /dev/null
+++ b/sni-test-config/geode-config/gfsecurity.properties
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+security-log-level=info
+security-peer-verifymember-timeout=1000
+ssl-keystore-password=geode
+ssl-truststore=/geode/config/truststore.jks
+ssl-truststore-password=geode
+ssl-require-authentication=false
+ssl-web-require-authentication=false
+ssl-enabled-components=all
+ssl-endpoint-identification-enabled=false
+
diff --git a/sni-test-config/geode-config/locator-maeve-keystore.jks b/sni-test-config/geode-config/locator-maeve-keystore.jks
new file mode 100644
index 0000000..a29cf0f
Binary files /dev/null and b/sni-test-config/geode-config/locator-maeve-keystore.jks differ
diff --git a/sni-test-config/geode-config/server-clementine-keystore.jks b/sni-test-config/geode-config/server-clementine-keystore.jks
new file mode 100644
index 0000000..380de6c
Binary files /dev/null and b/sni-test-config/geode-config/server-clementine-keystore.jks differ
diff --git a/sni-test-config/geode-config/server-dolores-keystore.jks b/sni-test-config/geode-config/server-dolores-keystore.jks
new file mode 100644
index 0000000..cb2c4c5
Binary files /dev/null and b/sni-test-config/geode-config/server-dolores-keystore.jks differ
diff --git a/sni-test-config/geode-config/truststore.jks b/sni-test-config/geode-config/truststore.jks
new file mode 100644
index 0000000..ffcdaf3
Binary files /dev/null and b/sni-test-config/geode-config/truststore.jks differ
diff --git a/sni-test-config/geode-config/truststore.p12 b/sni-test-config/geode-config/truststore.p12
new file mode 100644
index 0000000..339d775
Binary files /dev/null and b/sni-test-config/geode-config/truststore.p12 differ
diff --git a/sni-test-config/geode-config/truststore_sni.pem b/sni-test-config/geode-config/truststore_sni.pem
new file mode 100644
index 0000000..b4e8cda
--- /dev/null
+++ b/sni-test-config/geode-config/truststore_sni.pem
@@ -0,0 +1,68 @@
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICtDCCAZygAwIBAgIEXozGnzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFz
+ZXJ2ZXItY2xlbWVudGluZTAeFw0yMDA0MDcxODI5NTFaFw0yNTA0MDcxODI5NTFa
+MBwxGjAYBgNVBAMMEXNlcnZlci1jbGVtZW50aW5lMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6IzshjujS5c58AH8nJHBhlqjfNpacoNxhxykeCVsExa9
+vi0l8ezi35pte06j7gpMWhDYHokrHaw6ymp9iTi7D91yIPGeMMNUli8DnzgAzpeY
+V8SGgkrVBalkVe0GimAHXMrzeZF+8D2BEdvDAsIUbrZRACElPlLUoiO93xZZ8ad+
+fAfLVetH4lDJ54FT7ia+St6L0QxSrDLvrqmc/58ZunkQBnQcd4tMjCD1kX4l+5Q1
+eF+Rc/SbY+/8HfyCZcA98voC3dKF13U+0YAf/0ahin+8Ckm6BL/StUxFNftTtJ7l
+iKf56Y3FbSQ84Q9Te8feb05XidkF74Gifa4Q7gOzjwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQDKvYcnVFryhupo156bB33BU14KN8b5joVyQLeGb2Tx+icZd/jFhqSQ
+c3f8VV+aG9+CtRi/6wesdzf9/CVF+J4ARJ7j3i60NlJi4vQJlZnou+JSBgbBiDkW
+p12ITsw7l1k2zxH8hoMPNbMK1EC/+uwVRJt92L52uShLw9zKtE4MLZxZVa7Amkf4
+zRc78fHwwPXoMjLcQxw+8JRjlciWr/hZccuppXI4qb17l6HAMvW4vCslao0c9pSp
+Opg5Q0PwVXFROIvCANdxNI9ptSrH78Thxh4rggnHs+OZF02D22oTkjquU4Xrar3u
+FXlIS8UmdkqAXGIJf0pqa48aXcqeipRe
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAZagAwIBAgIEXozE5DANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5z
+ZXJ2ZXItZG9sb3JlczAeFw0yMDA0MDcxODIyMjhaFw0yNTA0MDcxODIyMjhaMBkx
+FzAVBgNVBAMMDnNlcnZlci1kb2xvcmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAyRTzsWsih9Boz2/aRFJsgJNDn8/C207kpvJ9lj0uBWNdZGJ86T4i
+CwvIyMFvxeYQB0qO0AHf6FvJfMgunRlCj3fD01s7AHj8kCFoM/akgo04M7iJfSkU
+dDCVuRbrFtz31akNckyxRw/oORiQ6NYGxnuAvtFdjE8jFc77WVXVU5QuqVEueJXs
+HM+t6VGEn+7GwPsSJMIuEERd+05ZlghB1HoQD4Wu4+b/CXU+8aFRad0HRXHInBl0
+0QABETcMtpe3xIotC7H1nsAMipb0jyl3p+1a49FbrAktsiko8Y2iRVv3kZ58xfx9
+2Unmw+ViEb5bVRFytqb5AIgARI/+XX1zBwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
+AQB39QXR3HLEju8B1oNCH1UciZetMxvORC2fwgXhqjbJ2YkHlykaLAAKv6DOSyc2
+HE40F2Q/Y0p0NC41+4YIiujgzKWaDI1Gw22PlceE2B49dO8evmldN2NixkirJbtm
+bEtjINAxHXbhXn8GgUKJxSqtFPTX/fG7OCYvkvGItQAhSrGo9r5ACuDYkTZsBAZp
+9jHc50TZsQ7od4jsPXrtZ6S2doOA0TdQ/+XzNyoadbG0YZbRtUVmhJN7gQfkBcjH
+/AnYeYJL1kg39AuO3PsFhgWCsR2eNizGCh7CnHx7xpJnLYAw/01TGidsku/oYFiI
+5SthBjGC992gTekW54hYtMBU
+-----END CERTIFICATE-----
diff --git a/sni-test-config/haproxy.cfg b/sni-test-config/haproxy.cfg
new file mode 100644
index 0000000..c451ef6
--- /dev/null
+++ b/sni-test-config/haproxy.cfg
@@ -0,0 +1,44 @@
+#
+# 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.
+#
+
+defaults
+  timeout client 1000
+  timeout connect 1000
+  timeout server 1000
+
+frontend sniproxy
+  bind *:15443
+  mode tcp
+  tcp-request inspect-delay 5s
+  tcp-request content accept if { req_ssl_hello_type 1 }
+  use_backend locators-maeve if { req.ssl_sni -i locator-maeve }
+  use_backend servers-dolores if { req.ssl_sni -i server-dolores }
+  use_backend servers-clementine if { req.ssl_sni -i server-clementine }
+  default_backend locators-maeve
+  log stdout format raw  local0  debug
+
+backend locators-maeve
+  mode tcp
+  server locator1 geode:10334
+
+backend servers-dolores
+  mode tcp
+  server server1 geode:40404
+
+backend servers-clementine
+  mode tcp
+  server server1 geode:40405
diff --git a/sni-test-config/scripts/forever b/sni-test-config/scripts/forever
new file mode 100644
index 0000000..4fecfa8
--- /dev/null
+++ b/sni-test-config/scripts/forever
@@ -0,0 +1,20 @@
+#!/usr/bin/env sh
+
+#
+# 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.
+#
+
+while true; do sleep 600; done
diff --git a/sni-test-config/scripts/geode-starter-2.gfsh b/sni-test-config/scripts/geode-starter-2.gfsh
new file mode 100644
index 0000000..38600aa
--- /dev/null
+++ b/sni-test-config/scripts/geode-starter-2.gfsh
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+start locator --name=locator-maeve --hostname-for-clients=locator-maeve --properties-file=/geode/config/gemfire.properties --security-properties-file=/geode/config/gfsecurity.properties --J=-Dgemfire.ssl-keystore=/geode/config/locator-maeve-keystore.jks
+start server --name=server-dolores --group=group-dolores --hostname-for-clients=server-dolores --locators=localhost[10334] --properties-file=/geode/config/gemfire.properties --security-properties-file=/geode/config/gfsecurity.properties --J=-Dgemfire.ssl-keystore=/geode/config/server-dolores-keystore.jks
+start server --name=server-clementine --group=group-clementine --hostname-for-clients=server-clementine --server-port=40405 --locators=localhost[10334] --properties-file=/geode/config/gemfire.properties --security-properties-file=/geode/config/gfsecurity.properties --J=-Dgemfire.ssl-keystore=/geode/config/server-clementine-keystore.jks
+connect --locator=localhost[10334] --use-ssl=true --security-properties-file=/geode/config/gfsecurity.properties
+create region --name=region-dolores --group=group-dolores --type=REPLICATE
+create region --name=region-clementine --group=group-clementine --type=REPLICATE
diff --git a/sni-test-config/scripts/geode-starter.gfsh b/sni-test-config/scripts/geode-starter.gfsh
new file mode 100644
index 0000000..9ceecad
--- /dev/null
+++ b/sni-test-config/scripts/geode-starter.gfsh
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+start locator --name=locator-maeve --hostname-for-clients=locator-maeve --properties-file=/geode/config/gemfire.properties --security-properties-file=/geode/config/gfsecurity.properties --J=-Dgemfire.ssl-keystore=/geode/config/locator-maeve-keystore.jks
+start server --name=server-dolores --max-heap=256m --hostname-for-clients=server-dolores --locators=localhost[10334] --properties-file=/geode/config/gemfire.properties --security-properties-file=/geode/config/gfsecurity.properties --J=-Dgemfire.ssl-keystore=/geode/config/server-dolores-keystore.jks
+connect --locator=localhost[10334] --use-ssl=true --security-properties-file=/geode/config/gfsecurity.properties
+create region --name=jellyfish --type=REPLICATE
+
diff --git a/ssl_keys/client_keys/truststore_sni.pem b/ssl_keys/client_keys/truststore_sni.pem
new file mode 100644
index 0000000..1857ce6
--- /dev/null
+++ b/ssl_keys/client_keys/truststore_sni.pem
@@ -0,0 +1,68 @@
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrDCCAZSgAwIBAgIEXozDxjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1s
+b2NhdG9yLW1hZXZlMB4XDTIwMDQwNzE4MTc0MloXDTI1MDQwNzE4MTc0MlowGDEW
+MBQGA1UEAwwNbG9jYXRvci1tYWV2ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAOJ3jM2Rb50L+1fXyhZbaOHMuVUVGJ5jQV9wH3ijjeCEckaF29LbEtG8
+swMaxSoi4Sp/A4dp/7VI9CFZJKOX3zooZcuHyR7GSta4wH3oO55w0AfyTGeG6KF2
+Ekzj8pDPHyn/141rFAUPmMDnCfbF69Uixfi2XPxEJZw2GDN/YIHndY+X1pJ4ZuXS
+SmrORSEOSmrN9X7pqbL5D2cy15cmTK5449ZqLEfZS72Mv3gve1Ax2JMWCBEwLdob
+xW5utgmEe1/WhlhPzFr5C92znF/5Eucil/Rr+yynp31X+/QYBemYwOxbeZotHBZJ
+tMLMzaInydrZ04wgHRftNeN0TIZkPmcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
+Jj1OSCWoILzWLBU1cAiQK8Gt0DVkqcpO4/vc3CoiU2T/em74cBzTwqmgrBvykWgq
+f05jWQcod2yNg8trHrgx8F9CfyyvTXRIxttyfmbD7DAQk+qn9QBSbRJFfzo8VfNp
+dGcT7KV9UDVyzltiTorqQJHUx3acUgtLYS2XUVlbGclhnNafRO44uobOsteAG01v
+YqFa8ZaZM7qcZ88mbbKLXn6lo203JguM+TM0P7wHnzcww9sLmsP8W2cvsvefwCl4
+O7OYcjhcbEph+mIC3/zN8vF6d8xtLiMSGk6BNCHd003MBEhZHizyquGtAFLaEafX
+V6sLm65i8uF2glnQfwS5JQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICtDCCAZygAwIBAgIEXozGnzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFz
+ZXJ2ZXItY2xlbWVudGluZTAeFw0yMDA0MDcxODI5NTFaFw0yNTA0MDcxODI5NTFa
+MBwxGjAYBgNVBAMMEXNlcnZlci1jbGVtZW50aW5lMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6IzshjujS5c58AH8nJHBhlqjfNpacoNxhxykeCVsExa9
+vi0l8ezi35pte06j7gpMWhDYHokrHaw6ymp9iTi7D91yIPGeMMNUli8DnzgAzpeY
+V8SGgkrVBalkVe0GimAHXMrzeZF+8D2BEdvDAsIUbrZRACElPlLUoiO93xZZ8ad+
+fAfLVetH4lDJ54FT7ia+St6L0QxSrDLvrqmc/58ZunkQBnQcd4tMjCD1kX4l+5Q1
+eF+Rc/SbY+/8HfyCZcA98voC3dKF13U+0YAf/0ahin+8Ckm6BL/StUxFNftTtJ7l
+iKf56Y3FbSQ84Q9Te8feb05XidkF74Gifa4Q7gOzjwIDAQABMA0GCSqGSIb3DQEB
+CwUAA4IBAQDKvYcnVFryhupo156bB33BU14KN8b5joVyQLeGb2Tx+icZd/jFhqSQ
+c3f8VV+aG9+CtRi/6wesdzf9/CVF+J4ARJ7j3i60NlJi4vQJlZnou+JSBgbBiDkW
+p12ITsw7l1k2zxH8hoMPNbMK1EC/+uwVRJt92L52uShLw9zKtE4MLZxZVa7Amkf4
+zRc78fHwwPXoMjLcQxw+8JRjlciWr/hZccuppXI4qb17l6HAMvW4vCslao0c9pSp
+Opg5Q0PwVXFROIvCANdxNI9ptSrH78Thxh4rggnHs+OZF02D22oTkjquU4Xrar3u
+FXlIS8UmdkqAXGIJf0pqa48aXcqeipRe
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICrjCCAZagAwIBAgIEXozE5DANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5z
+ZXJ2ZXItZG9sb3JlczAeFw0yMDA0MDcxODIyMjhaFw0yNTA0MDcxODIyMjhaMBkx
+FzAVBgNVBAMMDnNlcnZlci1kb2xvcmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAyRTzsWsih9Boz2/aRFJsgJNDn8/C207kpvJ9lj0uBWNdZGJ86T4i
+CwvIyMFvxeYQB0qO0AHf6FvJfMgunRlCj3fD01s7AHj8kCFoM/akgo04M7iJfSkU
+dDCVuRbrFtz31akNckyxRw/oORiQ6NYGxnuAvtFdjE8jFc77WVXVU5QuqVEueJXs
+HM+t6VGEn+7GwPsSJMIuEERd+05ZlghB1HoQD4Wu4+b/CXU+8aFRad0HRXHInBl0
+0QABETcMtpe3xIotC7H1nsAMipb0jyl3p+1a49FbrAktsiko8Y2iRVv3kZ58xfx9
+2Unmw+ViEb5bVRFytqb5AIgARI/+XX1zBwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB
+AQB39QXR3HLEju8B1oNCH1UciZetMxvORC2fwgXhqjbJ2YkHlykaLAAKv6DOSyc2
+HE40F2Q/Y0p0NC41+4YIiujgzKWaDI1Gw22PlceE2B49dO8evmldN2NixkirJbtm
+bEtjINAxHXbhXn8GgUKJxSqtFPTX/fG7OCYvkvGItQAhSrGo9r5ACuDYkTZsBAZp
+9jHc50TZsQ7od4jsPXrtZ6S2doOA0TdQ/+XzNyoadbG0YZbRtUVmhJN7gQfkBcjH
+/AnYeYJL1kg39AuO3PsFhgWCsR2eNizGCh7CnHx7xpJnLYAw/01TGidsku/oYFiI
+5SthBjGC992gTekW54hYtMBU
+-----END CERTIFICATE-----