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

kezhuw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new a7fe813aa ZOOKEEPER-4958: Fix client hostname verification ignored in 
server if ssl.authProvider configured
a7fe813aa is described below

commit a7fe813aa3fee50c1a653b92e7f91e4c67f977ba
Author: Kezhu Wang <[email protected]>
AuthorDate: Sun Dec 14 21:46:58 2025 +0800

    ZOOKEEPER-4958: Fix client hostname verification ignored in server if 
ssl.authProvider configured
    
    Reviewers: anmolnar
    Author: kezhuw
    Closes #2303 from kezhuw/ZOOKEEPER-4958-client-server-hostname-verification
---
 .../server/auth/KeyAuthenticationProvider.java     |   2 +-
 .../server/auth/X509AuthenticationProvider.java    |   3 +-
 .../server/SSLHostnameVerificationTest.java        | 529 +++++++++++++++++++++
 3 files changed, 532 insertions(+), 2 deletions(-)

diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
index 92bb380fb..cf252b9b0 100644
--- 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
@@ -37,7 +37,7 @@
  * See the "Pluggable ZooKeeper authentication" section of the
  * "Zookeeper Programmer's Guide" for general details of implementing an
  * authentication plugin. e.g.
- * 
http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication
+ * 
http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication
  *
  * This class looks for a numeric "key" under the /key node.
  * Authorization is granted if the user passes in as authorization a number
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
index befdecd10..8392db6bc 100644
--- 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
@@ -89,6 +89,7 @@ public X509AuthenticationProvider() throws X509Exception {
             boolean crlEnabled = 
config.getBoolean(x509Util.getSslCrlEnabledProperty(), 
Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
             boolean ocspEnabled = 
config.getBoolean(x509Util.getSslOcspEnabledProperty(), 
Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
             boolean hostnameVerificationEnabled = 
Boolean.parseBoolean(config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
+            boolean clientHostnameVerificationEnabled = 
x509Util.isClientHostnameVerificationEnabled(config);
             boolean allowReverseDnsLookup = 
Boolean.parseBoolean(config.getProperty(x509Util.getSslAllowReverseDnsLookupProperty()));
 
             X509KeyManager km = null;
@@ -121,7 +122,7 @@ public X509AuthenticationProvider() throws X509Exception {
                         crlEnabled,
                         ocspEnabled,
                         hostnameVerificationEnabled,
-                        false,
+                        clientHostnameVerificationEnabled,
                         allowReverseDnsLookup,
                         fipsMode);
                 } catch (TrustManagerException e) {
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
new file mode 100644
index 000000000..bee6abb4b
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/SSLHostnameVerificationTest.java
@@ -0,0 +1,529 @@
+/*
+ * 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.zookeeper.server;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Security;
+import java.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.client.ZKClientConfig;
+import org.apache.zookeeper.common.ssl.Ca;
+import org.apache.zookeeper.common.ssl.Cert;
+import org.apache.zookeeper.server.embedded.ExitHandler;
+import org.apache.zookeeper.server.embedded.ZooKeeperServerEmbedded;
+import org.apache.zookeeper.test.ClientBase;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.burningwave.tools.net.HostResolutionRequestInterceptor;
+import org.burningwave.tools.net.MappedHostResolver;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class SSLHostnameVerificationTest {
+    @BeforeAll
+    public static void setupDNSMocks() {
+        Map<String, String> hostAliases = new LinkedHashMap<>();
+
+        // avoid resolving "localhost" to ipv6 address "::1"
+        hostAliases.put("localhost", "127.0.0.1");
+        HostResolutionRequestInterceptor.INSTANCE.install(new 
MappedHostResolver(hostAliases));
+        HostResolutionRequestInterceptor.INSTANCE.clearCache();
+    }
+
+    @AfterAll
+    public static void clearDNSMocks() {
+        HostResolutionRequestInterceptor.INSTANCE.uninstall();
+    }
+
+    @BeforeAll
+    public static void setup() {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    @AfterAll
+    public static void cleanup() {
+        Security.removeProvider("BC");
+    }
+
+    Watcher.Event.KeeperState checkConnectState(String connectString, 
ZKClientConfig clientConfig) throws Exception {
+        Duration timeout = Duration.ofSeconds(1);
+        Watcher.Event.KeeperState state;
+        CompletableFuture<WatchedEvent> future = new CompletableFuture<>();
+        try (ZooKeeper zk = new ZooKeeper(connectString, (int) 
timeout.toMillis(), future::complete, clientConfig)) {
+            try {
+                WatchedEvent event = future.get(timeout.toMillis() * 2, 
TimeUnit.MILLISECONDS);
+                state = event.getState();
+            } catch (TimeoutException ignored) {
+                // See: ZOOKEEPER-4508, ZOOKEEPER-4921, ZOOKEEPER-4923
+                state = Watcher.Event.KeeperState.Expired;
+            }
+        }
+        return state;
+    }
+
+    @ParameterizedTest(name = "{0}, fips-mode: {1}")
+    @CsvSource({
+            "localhost, true",
+            "localhost, false",
+            "127.0.0.1, true",
+            "127.0.0.1, false",
+    })
+    public void testClientHostnameVerificationWithMismatchNames(String 
serverHost, boolean fipsEnabled, @TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server with cert mismatching cn/dns/ip
+            Cert server1Cert = 
ca.signer("abc0").withDnsName("abc1").withIpAddress("192.168.0.10").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+            config.put("secureClientPortAddress", serverHost);
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.fips-mode", 
Boolean.toString(fipsEnabled));
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+
+                // when: connect using mismatched dns/ip
+                String connectionString =  server.getSecureConnectionString();
+                // then: connection rejected by us as no matching name
+                assertEquals(Watcher.Event.KeeperState.Expired, 
checkConnectState(server.getSecureConnectionString(), clientConfig));
+            }
+        }
+    }
+
+    @Test
+    public void testClientHostnameVerificationWithMatchingCnName(@TempDir Path 
tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server cert with cn name "localhost"
+            Cert server1Cert = ca.signer("localhost").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+                // when: connect using matching dns
+                String connectionString =  "localhost:" + 
server.getSecureClientPort();
+                // then: connected as there is no other sans
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @Test
+    public void 
testClientHostnameVerificationWithMatchingReversedDnsName(@TempDir Path tmpDir) 
throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server cert with cn name "localhost"
+            Cert server1Cert = ca.signer("localhost").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+                // when: connect using matching reversed dns
+                String connectionString =  "127.0.0.1:" + 
server.getSecureClientPort();
+                
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+                // then: connected as there is no other sans
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+
+    @Test
+    public void 
testClientHostnameVerificationWithMatchingDisabledReversedDnsName(@TempDir Path 
tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server cert with cn name "localhost"
+            Cert server1Cert = ca.signer("localhost").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+                // when: connect using matching reversed dns
+                String connectionString =  "127.0.0.1:" + 
server.getSecureClientPort();
+                
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "false");
+                // then: connected as there is no other sans
+                assertEquals(Watcher.Event.KeeperState.Expired, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+    @ParameterizedTest
+    @ValueSource(strings = {"localhost", "127.0.0.1"})
+    public void 
testClientHostnameVerificationWithMatchingCnNameButMismatchingSan(String 
serverHost, @TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server with cert matching cn
+            Cert server1Cert = 
ca.signer("localhost").withDnsName("abc1").withIpAddress("192.168.0.10").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+                // when: connect with dns or ip resolved to cn name
+                String connectionString = String.format("%s:%d", serverHost, 
server.getSecureClientPort());
+
+                // then: fail to connect
+                //
+                // CN matching has been deprecated by rfc2818 and can be used
+                // as fallback only when no subjectAlts are available
+                assertEquals(Watcher.Event.KeeperState.Expired, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"localhost", "127.0.0.1"})
+    public void testClientHostnameVerificationWithMatchingIpAddress(String 
serverHost, @TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server with cert mismatching ip
+            Cert server1Cert = 
ca.signer("abc0").withDnsName("abc1").withIpAddress("127.0.0.1").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+
+                // when: connect with matching ip or its dns
+                String connectionString = String.format("%s:%d", serverHost, 
server.getSecureClientPort());
+
+                // then: connected
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @Test
+    public void testClientHostnameVerificationFipsModeWithIpAddress(@TempDir 
Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server with cert mismatching ip
+            Cert server1Cert = 
ca.signer("abc0").withDnsName("abc1").withIpAddress("127.0.0.1").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.fips-mode", "true");
+
+                // when: connect with ip's dns
+                String connectionString = String.format("localhost:%d", 
server.getSecureClientPort());
+                // then: rejected as fips-mode don't do dns lookup
+                assertEquals(Watcher.Event.KeeperState.Expired, 
checkConnectState(connectionString, clientConfig));
+
+                // when: connect with ip address
+                connectionString = String.format("127.0.0.1:%d", 
server.getSecureClientPort());
+                // then: connected
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @Test
+    public void testClientHostnameVerificationFipsModeWithDns(@TempDir Path 
tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server cert with dns "localhost"
+            Cert server1Cert = 
ca.signer("abc0").withDnsName("localhost").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.fips-mode", "true");
+
+                // when: connect with ip address
+                String connectionString = String.format("127.0.0.1:%d", 
server.getSecureClientPort());
+                // then: fail as fips-mode won't do reverse dns lookup
+                assertEquals(Watcher.Event.KeeperState.Expired, 
checkConnectState(connectionString, clientConfig));
+
+                // when: connect with "localhost"
+                connectionString = String.format("localhost:%d", 
server.getSecureClientPort());
+                // then: succeed
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @Test
+    public void testClientHostnameVerificationWithMatchingDnsName(@TempDir 
Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            // given: server cert with dns name "localhost"
+            Cert server1Cert = 
ca.signer("abc0").withDnsName("localhost").withIpAddress("192.168.0.10").sign();
+            Properties config = server1Cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "false");
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign("client");
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
+                clientConfig.setProperty("zookeeper.sasl.client", "false");
+                clientConfig.setProperty("zookeeper.fips-mode", "false");
+                clientConfig.setProperty("zookeeper.ssl.hostnameVerification", 
"true");
+                
clientConfig.setProperty("zookeeper.ssl.allowReverseDnsLookup", "true");
+
+                // when: connect to "127.0.0.1"
+                String connectionString = String.format("127.0.0.1:%d", 
server.getSecureClientPort());
+                // then: connected as ZKHostnameVerifier will do reverse dns 
lookup
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+
+                // when: connect to "localhost"
+                connectionString = String.format("localhost:%d", 
server.getSecureClientPort());
+                // then: connected as dns match
+                assertEquals(Watcher.Event.KeeperState.SyncConnected, 
checkConnectState(connectionString, clientConfig));
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"x509", ""})
+    public void testServerHostnameVerification(String authProvider, @TempDir 
Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            Cert cert = ca.sign("server");
+
+            // given: server with client hostname verification enabled
+            Properties config = cert.buildServerProperties(ca);
+            config.put("ssl.hostnameVerification", "true");
+            config.put("ssl.clientHostnameVerification", "true");
+            config.put("ssl.allowReverseDnsLookup", "false");
+            config.put("fips-mode", "false");
+            if (!authProvider.isEmpty()) {
+                config.put("ssl.authProvider", "x509");
+            }
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                // // when: connect with matching dns name
+                Cert client1Cert = 
ca.signer("client1").withDnsName("localhost").sign();
+                ZKClientConfig client1Config = 
client1Cert.buildClientConfig(ca);
+                
client1Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+                // then: connected
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
+
+                // when: connect with matching ip address
+                Cert client2Cert = 
ca.signer("client2").withIpAddress("127.0.0.1").sign();
+                ZKClientConfig client2Config = 
client2Cert.buildClientConfig(ca);
+                
client2Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+                // then: connected
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client2Config));
+
+                // when: connect with matching cn name
+                Cert client3Cert = ca.signer("localhost").sign();
+                ZKClientConfig client3Config = 
client3Cert.buildClientConfig(ca);
+                
client3Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+                // then: connected
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client3Config));
+
+                // when: connect with mismatching cert name
+                Cert client4Cert = 
ca.signer("client4").withDnsName("abc").sign();
+                ZKClientConfig client4Config = 
client4Cert.buildClientConfig(ca);
+                
client4Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+                // then: fail to connect
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, client4Config));
+            }
+        }
+    }
+
+    /**
+     * FIPS mode disallow custom trust manager so server has no way to 
validate against client's endpoint.
+     */
+    @ParameterizedTest
+    @ValueSource(strings = {"x509", ""})
+    public void testServerHostnameVerificationFipsMode(String authProvider, 
@TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            Cert cert = ca.sign("server");
+
+            Properties config = cert.buildServerProperties(ca);
+
+            // given: server in fips mode with client hostname verification 
enabled
+            config.put("ssl.hostnameVerification", "true");
+            config.put("ssl.clientHostnameVerification", "true");
+            config.put("fips-mode", "true");
+
+            if (!authProvider.isEmpty()) {
+                config.put("ssl.authProvider", "x509");
+            }
+
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                // server ready
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                // // when: connect with matching dns name
+                Cert client1Cert = 
ca.signer("localhost").withResolvedDns("localhost").sign();
+                ZKClientConfig client1Config = 
client1Cert.buildClientConfig(ca);
+                
client1Config.setProperty("zookeeper.ssl.hostnameVerification", "false");
+
+                // then: fail to connect
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, client1Config));
+            }
+        }
+    }
+}

Reply via email to