diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509KeyManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509KeyManager.java
new file mode 100644
index 00000000000..9c0f1c66652
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509KeyManager.java
@@ -0,0 +1,131 @@
+/*
+ * 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.common;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+/**
+ * An {@link X509ExtendedKeyManager} that delegates client-mode and server-mode
+ * key selection to separate underlying key managers. This allows a ZooKeeper
+ * node to use different keystores (and therefore different certificates) for
+ * its client role (outgoing connections) and server role (incoming connections),
+ * enabling the use of certificates with a single Extended Key Usage (EKU).
+ */
+public class ClientServerX509KeyManager extends X509ExtendedKeyManager {
+
+ private static final String CLIENT_PREFIX = "client:";
+ private static final String SERVER_PREFIX = "server:";
+
+ private final X509KeyManager clientKeyManager;
+ private final X509KeyManager serverKeyManager;
+
+ public ClientServerX509KeyManager(X509KeyManager clientKeyManager, X509KeyManager serverKeyManager) {
+ this.clientKeyManager = clientKeyManager;
+ this.serverKeyManager = serverKeyManager;
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+ String alias = clientKeyManager.chooseClientAlias(keyType, issuers, socket);
+ return alias != null ? CLIENT_PREFIX + alias : null;
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ String alias = serverKeyManager.chooseServerAlias(keyType, issuers, socket);
+ return alias != null ? SERVER_PREFIX + alias : null;
+ }
+
+ @Override
+ public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
+ if (clientKeyManager instanceof X509ExtendedKeyManager) {
+ String alias = ((X509ExtendedKeyManager) clientKeyManager)
+ .chooseEngineClientAlias(keyType, issuers, engine);
+ return alias != null ? CLIENT_PREFIX + alias : null;
+ }
+ return chooseClientAlias(keyType, issuers, null);
+ }
+
+ @Override
+ public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
+ if (serverKeyManager instanceof X509ExtendedKeyManager) {
+ String alias = ((X509ExtendedKeyManager) serverKeyManager)
+ .chooseEngineServerAlias(keyType, issuers, engine);
+ return alias != null ? SERVER_PREFIX + alias : null;
+ }
+ return chooseServerAlias(keyType, issuers, null);
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ if (alias == null) {
+ return null;
+ }
+ if (alias.startsWith(CLIENT_PREFIX)) {
+ return clientKeyManager.getCertificateChain(alias.substring(CLIENT_PREFIX.length()));
+ }
+ if (alias.startsWith(SERVER_PREFIX)) {
+ return serverKeyManager.getCertificateChain(alias.substring(SERVER_PREFIX.length()));
+ }
+ return serverKeyManager.getCertificateChain(alias);
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ if (alias == null) {
+ return null;
+ }
+ if (alias.startsWith(CLIENT_PREFIX)) {
+ return clientKeyManager.getPrivateKey(alias.substring(CLIENT_PREFIX.length()));
+ }
+ if (alias.startsWith(SERVER_PREFIX)) {
+ return serverKeyManager.getPrivateKey(alias.substring(SERVER_PREFIX.length()));
+ }
+ return serverKeyManager.getPrivateKey(alias);
+ }
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ String[] aliases = clientKeyManager.getClientAliases(keyType, issuers);
+ return prefixAliases(aliases, CLIENT_PREFIX);
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ String[] aliases = serverKeyManager.getServerAliases(keyType, issuers);
+ return prefixAliases(aliases, SERVER_PREFIX);
+ }
+
+ private static String[] prefixAliases(String[] aliases, String prefix) {
+ if (aliases == null) {
+ return null;
+ }
+ String[] prefixed = new String[aliases.length];
+ for (int i = 0; i < aliases.length; i++) {
+ prefixed[i] = prefix + aliases[i];
+ }
+ return prefixed;
+ }
+}
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509TrustManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509TrustManager.java
new file mode 100644
index 00000000000..81a60b13f9c
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientServerX509TrustManager.java
@@ -0,0 +1,97 @@
+/*
+ * 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.common;
+
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+
+/**
+ * An {@link X509ExtendedTrustManager} that delegates to separate trust managers for
+ * client and server certificate validation. This allows configuring different truststores
+ * for validating client certificates (when acting as a server) vs. server certificates
+ * (when acting as a client).
+ *
+ *
+ * - {@code checkServerTrusted} — delegates to the client trust manager
+ * (validates server certs when this node connects as a client)
+ * - {@code checkClientTrusted} — delegates to the server trust manager
+ * (validates client certs when this node accepts connections as a server)
+ *
+ */
+public class ClientServerX509TrustManager extends X509ExtendedTrustManager {
+
+ private final X509ExtendedTrustManager clientTrustManager;
+ private final X509ExtendedTrustManager serverTrustManager;
+
+ /**
+ * @param clientTrustManager used to validate server certificates (when acting as TLS client)
+ * @param serverTrustManager used to validate client certificates (when acting as TLS server)
+ */
+ public ClientServerX509TrustManager(X509ExtendedTrustManager clientTrustManager,
+ X509ExtendedTrustManager serverTrustManager) {
+ this.clientTrustManager = clientTrustManager;
+ this.serverTrustManager = serverTrustManager;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ serverTrustManager.checkClientTrusted(chain, authType);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ serverTrustManager.checkClientTrusted(chain, authType, socket);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ serverTrustManager.checkClientTrusted(chain, authType, engine);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ clientTrustManager.checkServerTrusted(chain, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ clientTrustManager.checkServerTrusted(chain, authType, socket);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ clientTrustManager.checkServerTrusted(chain, authType, engine);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ X509Certificate[] clientIssuers = clientTrustManager.getAcceptedIssuers();
+ X509Certificate[] serverIssuers = serverTrustManager.getAcceptedIssuers();
+ List combined = new ArrayList<>(clientIssuers.length + serverIssuers.length);
+ combined.addAll(Arrays.asList(clientIssuers));
+ combined.addAll(Arrays.asList(serverIssuers));
+ return combined.toArray(new X509Certificate[0]);
+ }
+}
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
index 1e50b84257c..0306848e833 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
@@ -65,7 +65,7 @@ public SslContext createNettySslContextForClient(ZKConfig config)
throws X509Exception.KeyManagerException, X509Exception.TrustManagerException, SSLException {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
- KeyManager km = buildKeyManager(config);
+ KeyManager km = buildClientKeyManager(config);
if (km != null) {
sslContextBuilder.keyManager(km);
}
@@ -102,7 +102,7 @@ public SslContext createNettySslContextForServer(ZKConfig config)
throw new X509Exception.SSLContextException(
"Keystore is required for SSL server: " + getSslKeystoreLocationProperty());
}
- return createNettySslContextForServer(config, km, buildTrustManager(config));
+ return createNettySslContextForServer(config, km, buildServerTrustManager(config));
}
public SslContext createNettySslContextForServer(ZKConfig config, KeyManager keyManager, TrustManager trustManager) throws SSLException {
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
index 460a6f3ac55..8a0dd5373c5 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
@@ -169,10 +169,18 @@ public io.netty.handler.ssl.ClientAuth toNettyClientAuth() {
private final String sslKeystorePasswdProperty = getConfigPrefix() + "keyStore.password";
private final String sslKeystorePasswdPathProperty = getConfigPrefix() + "keyStore.passwordPath";
private final String sslKeystoreTypeProperty = getConfigPrefix() + "keyStore.type";
+ private final String sslClientKeystoreLocationProperty = getConfigPrefix() + "client.keyStore.location";
+ private final String sslClientKeystorePasswdProperty = getConfigPrefix() + "client.keyStore.password";
+ private final String sslClientKeystorePasswdPathProperty = getConfigPrefix() + "client.keyStore.passwordPath";
+ private final String sslClientKeystoreTypeProperty = getConfigPrefix() + "client.keyStore.type";
private final String sslTruststoreLocationProperty = getConfigPrefix() + "trustStore.location";
private final String sslTruststorePasswdProperty = getConfigPrefix() + "trustStore.password";
private final String sslTruststorePasswdPathProperty = getConfigPrefix() + "trustStore.passwordPath";
private final String sslTruststoreTypeProperty = getConfigPrefix() + "trustStore.type";
+ private final String sslServerTruststoreLocationProperty = getConfigPrefix() + "server.trustStore.location";
+ private final String sslServerTruststorePasswdProperty = getConfigPrefix() + "server.trustStore.password";
+ private final String sslServerTruststorePasswdPathProperty = getConfigPrefix() + "server.trustStore.passwordPath";
+ private final String sslServerTruststoreTypeProperty = getConfigPrefix() + "server.trustStore.type";
private final String sslContextSupplierClassProperty = getConfigPrefix() + "context.supplier.class";
private final String sslHostnameVerificationEnabledProperty = getConfigPrefix() + "hostnameVerification";
private final String sslClientHostnameVerificationEnabledProperty = getConfigPrefix() + "clientHostnameVerification";
@@ -185,10 +193,15 @@ public io.netty.handler.ssl.ClientAuth toNettyClientAuth() {
private final AtomicReference defaultSSLContextAndOptions = new AtomicReference<>(null);
private FileChangeWatcher keyStoreFileWatcher;
+ private FileChangeWatcher clientKeyStoreFileWatcher;
private FileChangeWatcher trustStoreFileWatcher;
+ private FileChangeWatcher serverTrustStoreFileWatcher;
public X509Util() {
- keyStoreFileWatcher = trustStoreFileWatcher = null;
+ keyStoreFileWatcher = null;
+ clientKeyStoreFileWatcher = null;
+ trustStoreFileWatcher = null;
+ serverTrustStoreFileWatcher = null;
}
protected abstract String getConfigPrefix();
@@ -227,6 +240,22 @@ public String getSslKeystoreTypeProperty() {
return sslKeystoreTypeProperty;
}
+ public String getSslClientKeystoreLocationProperty() {
+ return sslClientKeystoreLocationProperty;
+ }
+
+ public String getSslClientKeystorePasswdProperty() {
+ return sslClientKeystorePasswdProperty;
+ }
+
+ public String getSslClientKeystorePasswdPathProperty() {
+ return sslClientKeystorePasswdPathProperty;
+ }
+
+ public String getSslClientKeystoreTypeProperty() {
+ return sslClientKeystoreTypeProperty;
+ }
+
public String getSslTruststoreLocationProperty() {
return sslTruststoreLocationProperty;
}
@@ -243,6 +272,22 @@ public String getSslTruststoreTypeProperty() {
return sslTruststoreTypeProperty;
}
+ public String getSslServerTruststoreLocationProperty() {
+ return sslServerTruststoreLocationProperty;
+ }
+
+ public String getSslServerTruststorePasswdProperty() {
+ return sslServerTruststorePasswdProperty;
+ }
+
+ public String getSslServerTruststorePasswdPathProperty() {
+ return sslServerTruststorePasswdPathProperty;
+ }
+
+ public String getSslServerTruststoreTypeProperty() {
+ return sslServerTruststoreTypeProperty;
+ }
+
public String getSslContextSupplierClassProperty() {
return sslContextSupplierClassProperty;
}
@@ -395,10 +440,7 @@ public SSLContextAndOptions createSSLContextAndOptionsFromConfig(ZKConfig config
TrustManager[] trustManagers = null;
try {
- KeyManager km = buildKeyManager(config);
- if (km != null) {
- keyManagers = new KeyManager[]{km};
- }
+ keyManagers = buildKeyManagers(config, keyManagers);
} catch (KeyManagerException keyManagerException) {
throw new SSLContextException("Failed to create KeyManager", keyManagerException);
} catch (IllegalArgumentException e) {
@@ -407,10 +449,7 @@ public SSLContextAndOptions createSSLContextAndOptionsFromConfig(ZKConfig config
}
try {
- TrustManager tm = buildTrustManager(config);
- if (tm != null) {
- trustManagers = new TrustManager[]{tm};
- }
+ trustManagers = buildTrustManagers(config);
} catch (TrustManagerException trustManagerException) {
throw new SSLContextException("Failed to create TrustManager", trustManagerException);
} catch (IllegalArgumentException e) {
@@ -431,6 +470,24 @@ public SSLContextAndOptions createSSLContextAndOptionsFromConfig(ZKConfig config
}
}
+ private KeyManager[] buildKeyManagers(ZKConfig config, KeyManager[] keyManagers) throws KeyManagerException {
+ String clientKeyStoreLocation = config.getProperty(sslClientKeystoreLocationProperty, "");
+ X509KeyManager serverKm = buildKeyManager(config);
+ if (!clientKeyStoreLocation.isEmpty()) {
+ X509KeyManager clientKm = buildClientKeyManager(config);
+ if (serverKm != null && clientKm != null) {
+ keyManagers = new KeyManager[]{new ClientServerX509KeyManager(clientKm, serverKm)};
+ } else if (clientKm != null) {
+ keyManagers = new KeyManager[]{clientKm};
+ } else if (serverKm != null) {
+ keyManagers = new KeyManager[]{serverKm};
+ }
+ } else if (serverKm != null) {
+ keyManagers = new KeyManager[]{serverKm};
+ }
+ return keyManagers;
+ }
+
public static KeyStore loadKeyStore(
String keyStoreLocation,
String keyStorePassword,
@@ -489,6 +546,22 @@ public X509KeyManager buildKeyManager(ZKConfig config) throws KeyManagerExceptio
return createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType);
}
+ /**
+ * Builds a key manager for the client role. If a dedicated client keystore
+ * is configured ({@code client.keyStore.location}), it is loaded and returned.
+ * Otherwise, falls back to the shared keystore via {@link #buildKeyManager(ZKConfig)}.
+ */
+ public X509KeyManager buildClientKeyManager(ZKConfig config) throws KeyManagerException {
+ String clientKeyStoreLocation = config.getProperty(getSslClientKeystoreLocationProperty(), "");
+ if (!clientKeyStoreLocation.isEmpty()) {
+ String clientKeyStorePassword = getPasswordFromConfigPropertyOrFile(config,
+ getSslClientKeystorePasswdProperty(), getSslClientKeystorePasswdPathProperty());
+ String clientKeyStoreType = config.getProperty(getSslClientKeystoreTypeProperty());
+ return createKeyManager(clientKeyStoreLocation, clientKeyStorePassword, clientKeyStoreType);
+ }
+ return buildKeyManager(config);
+ }
+
/**
* Creates a key manager by loading the key store from the given file of
* the given type, optionally decrypting it using the given password.
@@ -531,6 +604,22 @@ public X509TrustManager buildTrustManager(ZKConfig config) throws TrustManagerEx
return null;
}
+ return buildX509TrustManager(config, trustStoreLocationProp, sslTruststorePasswdProperty, sslTruststorePasswdPathProperty, sslTruststoreTypeProperty);
+ }
+
+ public X509TrustManager buildServerTrustManager(ZKConfig config) throws TrustManagerException {
+ String serverTrustStoreLocation = config.getProperty(sslServerTruststoreLocationProperty, "");
+ if (serverTrustStoreLocation.isEmpty()) {
+ return buildTrustManager(config);
+ }
+
+ return buildX509TrustManager(config, serverTrustStoreLocation, sslServerTruststorePasswdProperty, sslServerTruststorePasswdPathProperty, sslServerTruststoreTypeProperty);
+ }
+
+ private X509TrustManager buildX509TrustManager(ZKConfig config, String trustStoreLocationProp,
+ String sslTruststorePasswdProperty,
+ String sslTruststorePasswdPathProperty,
+ String sslTruststoreTypeProperty) throws TrustManagerException {
String trustStorePasswordProp = getPasswordFromConfigPropertyOrFile(config, sslTruststorePasswdProperty, sslTruststorePasswdPathProperty);
String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty);
@@ -554,6 +643,25 @@ public X509TrustManager buildTrustManager(ZKConfig config) throws TrustManagerEx
fipsMode);
}
+ private TrustManager[] buildTrustManagers(ZKConfig config) throws TrustManagerException {
+ String serverTrustStoreLocation = config.getProperty(sslServerTruststoreLocationProperty, "");
+ X509TrustManager clientTm = buildTrustManager(config);
+ if (!serverTrustStoreLocation.isEmpty()) {
+ X509TrustManager serverTm = buildServerTrustManager(config);
+ if (clientTm != null && serverTm != null) {
+ return new TrustManager[]{new ClientServerX509TrustManager(
+ (X509ExtendedTrustManager) clientTm, (X509ExtendedTrustManager) serverTm)};
+ } else if (serverTm != null) {
+ return new TrustManager[]{serverTm};
+ } else if (clientTm != null) {
+ return new TrustManager[]{clientTm};
+ }
+ } else if (clientTm != null) {
+ return new TrustManager[]{clientTm};
+ }
+ return null;
+ }
+
// @VisibleForTesting
protected X509TrustManager createTrustManagerInternal(
String trustStoreLocation,
@@ -726,6 +834,15 @@ public void enableCertFileReloading() throws IOException {
keyStoreFileWatcher = newKeyStoreFileWatcher;
keyStoreFileWatcher.start();
}
+ FileChangeWatcher newClientKeyStoreFileWatcher = newFileChangeWatcher(config.getProperty(sslClientKeystoreLocationProperty));
+ if (newClientKeyStoreFileWatcher != null) {
+ // stop old watcher if there is one
+ if (clientKeyStoreFileWatcher != null) {
+ clientKeyStoreFileWatcher.stop();
+ }
+ clientKeyStoreFileWatcher = newClientKeyStoreFileWatcher;
+ clientKeyStoreFileWatcher.start();
+ }
FileChangeWatcher newTrustStoreFileWatcher = newFileChangeWatcher(config.getProperty(sslTruststoreLocationProperty));
if (newTrustStoreFileWatcher != null) {
// stop old watcher if there is one
@@ -735,6 +852,15 @@ public void enableCertFileReloading() throws IOException {
trustStoreFileWatcher = newTrustStoreFileWatcher;
trustStoreFileWatcher.start();
}
+ FileChangeWatcher newServerTrustStoreFileWatcher = newFileChangeWatcher(config.getProperty(sslServerTruststoreLocationProperty));
+ if (newServerTrustStoreFileWatcher != null) {
+ // stop old watcher if there is one
+ if (serverTrustStoreFileWatcher != null) {
+ serverTrustStoreFileWatcher.stop();
+ }
+ serverTrustStoreFileWatcher = newServerTrustStoreFileWatcher;
+ serverTrustStoreFileWatcher.start();
+ }
}
/**
@@ -748,10 +874,18 @@ public void close() {
keyStoreFileWatcher.stop();
keyStoreFileWatcher = null;
}
+ if (clientKeyStoreFileWatcher != null) {
+ clientKeyStoreFileWatcher.stop();
+ clientKeyStoreFileWatcher = null;
+ }
if (trustStoreFileWatcher != null) {
trustStoreFileWatcher.stop();
trustStoreFileWatcher = null;
}
+ if (serverTrustStoreFileWatcher != null) {
+ serverTrustStoreFileWatcher.stop();
+ serverTrustStoreFileWatcher = null;
+ }
}
/**
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
index 442d8e7221d..a40bb3c533c 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKConfig.java
@@ -146,10 +146,18 @@ private void putSSLProperties(X509Util x509Util) {
properties.put(x509Util.getSslKeystorePasswdProperty(), System.getProperty(x509Util.getSslKeystorePasswdProperty()));
properties.put(x509Util.getSslKeystorePasswdPathProperty(), System.getProperty(x509Util.getSslKeystorePasswdPathProperty()));
properties.put(x509Util.getSslKeystoreTypeProperty(), System.getProperty(x509Util.getSslKeystoreTypeProperty()));
+ properties.put(x509Util.getSslClientKeystoreLocationProperty(), System.getProperty(x509Util.getSslClientKeystoreLocationProperty()));
+ properties.put(x509Util.getSslClientKeystorePasswdProperty(), System.getProperty(x509Util.getSslClientKeystorePasswdProperty()));
+ properties.put(x509Util.getSslClientKeystorePasswdPathProperty(), System.getProperty(x509Util.getSslClientKeystorePasswdPathProperty()));
+ properties.put(x509Util.getSslClientKeystoreTypeProperty(), System.getProperty(x509Util.getSslClientKeystoreTypeProperty()));
properties.put(x509Util.getSslTruststoreLocationProperty(), System.getProperty(x509Util.getSslTruststoreLocationProperty()));
properties.put(x509Util.getSslTruststorePasswdProperty(), System.getProperty(x509Util.getSslTruststorePasswdProperty()));
properties.put(x509Util.getSslTruststorePasswdPathProperty(), System.getProperty(x509Util.getSslTruststorePasswdPathProperty()));
properties.put(x509Util.getSslTruststoreTypeProperty(), System.getProperty(x509Util.getSslTruststoreTypeProperty()));
+ properties.put(x509Util.getSslServerTruststoreLocationProperty(), System.getProperty(x509Util.getSslServerTruststoreLocationProperty()));
+ properties.put(x509Util.getSslServerTruststorePasswdProperty(), System.getProperty(x509Util.getSslServerTruststorePasswdProperty()));
+ properties.put(x509Util.getSslServerTruststorePasswdPathProperty(), System.getProperty(x509Util.getSslServerTruststorePasswdPathProperty()));
+ properties.put(x509Util.getSslServerTruststoreTypeProperty(), System.getProperty(x509Util.getSslServerTruststoreTypeProperty()));
properties.put(x509Util.getSslContextSupplierClassProperty(), System.getProperty(x509Util.getSslContextSupplierClassProperty()));
properties.put(x509Util.getSslClientHostnameVerificationEnabledProperty(), System.getProperty(x509Util.getSslClientHostnameVerificationEnabledProperty()));
properties.put(x509Util.getSslHostnameVerificationEnabledProperty(), System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509KeyManagerTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509KeyManagerTest.java
new file mode 100644
index 00000000000..5e04b0b8a42
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509KeyManagerTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.common;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import java.net.Socket;
+import java.security.KeyPair;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class ClientServerX509KeyManagerTest {
+
+ private static KeyPair clientKeyPair;
+ private static X509Certificate clientCert;
+ private static KeyPair serverKeyPair;
+ private static X509Certificate serverCert;
+
+ @BeforeAll
+ public static void setUp() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ KeyPair caKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ X509Certificate caCert = X509TestHelpers.newSelfSignedCACert(
+ new org.bouncycastle.asn1.x500.X500NameBuilder(org.bouncycastle.asn1.x500.style.BCStyle.INSTANCE)
+ .addRDN(org.bouncycastle.asn1.x500.style.BCStyle.CN, "Test CA")
+ .build(),
+ caKeyPair, 86400000L);
+ clientKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ clientCert = X509TestHelpers.newClientOnlyCert(caCert, caKeyPair, "client", clientKeyPair.getPublic());
+ serverKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ serverCert = X509TestHelpers.newServerOnlyCert(caCert, caKeyPair, "server", serverKeyPair.getPublic());
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ }
+
+ @Test
+ public void testChooseClientAliasDelegatesToClientKeyManager() {
+ X509KeyManager clientKm = new StubKeyManager("clientAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("serverAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String alias = keyManager.chooseClientAlias(new String[]{"RSA"}, null, null);
+ assertNotNull(alias);
+ assertEquals("client:clientAlias", alias);
+ }
+
+ @Test
+ public void testChooseServerAliasDelegatesToServerKeyManager() {
+ X509KeyManager clientKm = new StubKeyManager("clientAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("serverAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String alias = keyManager.chooseServerAlias("RSA", null, null);
+ assertNotNull(alias);
+ assertEquals("server:serverAlias", alias);
+ }
+
+ @Test
+ public void testGetCertificateChainRoutesToClientManager() {
+ X509KeyManager clientKm = new StubKeyManager("myAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("myAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ X509Certificate[] chain = keyManager.getCertificateChain("client:myAlias");
+ assertNotNull(chain);
+ assertEquals(1, chain.length);
+ assertEquals(clientCert, chain[0]);
+ }
+
+ @Test
+ public void testGetCertificateChainRoutesToServerManager() {
+ X509KeyManager clientKm = new StubKeyManager("myAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("myAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ X509Certificate[] chain = keyManager.getCertificateChain("server:myAlias");
+ assertNotNull(chain);
+ assertEquals(1, chain.length);
+ assertEquals(serverCert, chain[0]);
+ }
+
+ @Test
+ public void testGetPrivateKeyRoutesToClientManager() {
+ X509KeyManager clientKm = new StubKeyManager("myAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("myAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ PrivateKey key = keyManager.getPrivateKey("client:myAlias");
+ assertEquals(clientKeyPair.getPrivate(), key);
+ }
+
+ @Test
+ public void testGetPrivateKeyRoutesToServerManager() {
+ X509KeyManager clientKm = new StubKeyManager("myAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("myAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ PrivateKey key = keyManager.getPrivateKey("server:myAlias");
+ assertEquals(serverKeyPair.getPrivate(), key);
+ }
+
+ @Test
+ public void testGetClientAliasesPrefixed() {
+ X509KeyManager clientKm = new StubKeyManager("a1", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("a2", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String[] aliases = keyManager.getClientAliases("RSA", null);
+ assertNotNull(aliases);
+ assertEquals(1, aliases.length);
+ assertEquals("client:a1", aliases[0]);
+ }
+
+ @Test
+ public void testGetServerAliasesPrefixed() {
+ X509KeyManager clientKm = new StubKeyManager("a1", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("a2", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String[] aliases = keyManager.getServerAliases("RSA", null);
+ assertNotNull(aliases);
+ assertEquals(1, aliases.length);
+ assertEquals("server:a2", aliases[0]);
+ }
+
+ @Test
+ public void testNullAliasReturnsNull() {
+ X509KeyManager clientKm = new StubKeyManager(null, clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager(null, serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ assertNull(keyManager.chooseClientAlias(new String[]{"RSA"}, null, null));
+ assertNull(keyManager.chooseServerAlias("RSA", null, null));
+ assertNull(keyManager.getCertificateChain(null));
+ assertNull(keyManager.getPrivateKey(null));
+ }
+
+ @Test
+ public void testUnprefixedAliasFallsBackToServerManager() {
+ X509KeyManager clientKm = new StubKeyManager("myAlias", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("myAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ X509Certificate[] chain = keyManager.getCertificateChain("myAlias");
+ assertNotNull(chain);
+ assertEquals(serverCert, chain[0]);
+
+ PrivateKey key = keyManager.getPrivateKey("myAlias");
+ assertEquals(serverKeyPair.getPrivate(), key);
+ }
+
+ @Test
+ public void testChooseEngineClientAliasDelegatesToExtendedKeyManager() {
+ ExtendedStubKeyManager clientKm = new ExtendedStubKeyManager("engineClient", clientCert, clientKeyPair.getPrivate());
+ X509KeyManager serverKm = new StubKeyManager("serverAlias", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String alias = keyManager.chooseEngineClientAlias(new String[]{"RSA"}, null, null);
+ assertNotNull(alias);
+ assertEquals("client:engineClient", alias);
+ }
+
+ @Test
+ public void testChooseEngineServerAliasDelegatesToExtendedKeyManager() {
+ X509KeyManager clientKm = new StubKeyManager("clientAlias", clientCert, clientKeyPair.getPrivate());
+ ExtendedStubKeyManager serverKm = new ExtendedStubKeyManager("engineServer", serverCert, serverKeyPair.getPrivate());
+ ClientServerX509KeyManager keyManager = new ClientServerX509KeyManager(clientKm, serverKm);
+
+ String alias = keyManager.chooseEngineServerAlias("RSA", null, null);
+ assertNotNull(alias);
+ assertEquals("server:engineServer", alias);
+ }
+
+ private static class StubKeyManager implements X509KeyManager {
+ private final String alias;
+ private final X509Certificate cert;
+ private final PrivateKey key;
+
+ StubKeyManager(String alias, X509Certificate cert, PrivateKey key) {
+ this.alias = alias;
+ this.cert = cert;
+ this.key = key;
+ }
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return alias != null ? new String[]{alias} : null;
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+ return alias;
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return alias != null ? new String[]{alias} : null;
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return alias;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ if (this.alias != null && this.alias.equals(alias)) {
+ return new X509Certificate[]{cert};
+ }
+ return null;
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ if (this.alias != null && this.alias.equals(alias)) {
+ return key;
+ }
+ return null;
+ }
+ }
+
+ private static class ExtendedStubKeyManager extends X509ExtendedKeyManager {
+ private final String alias;
+ private final X509Certificate cert;
+ private final PrivateKey key;
+
+ ExtendedStubKeyManager(String alias, X509Certificate cert, PrivateKey key) {
+ this.alias = alias;
+ this.cert = cert;
+ this.key = key;
+ }
+
+ @Override
+ public String[] getClientAliases(String keyType, Principal[] issuers) {
+ return alias != null ? new String[]{alias} : null;
+ }
+
+ @Override
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
+ return alias;
+ }
+
+ @Override
+ public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
+ return alias;
+ }
+
+ @Override
+ public String[] getServerAliases(String keyType, Principal[] issuers) {
+ return alias != null ? new String[]{alias} : null;
+ }
+
+ @Override
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
+ return alias;
+ }
+
+ @Override
+ public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
+ return alias;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain(String alias) {
+ if (this.alias != null && this.alias.equals(alias)) {
+ return new X509Certificate[]{cert};
+ }
+ return null;
+ }
+
+ @Override
+ public PrivateKey getPrivateKey(String alias) {
+ if (this.alias != null && this.alias.equals(alias)) {
+ return key;
+ }
+ return null;
+ }
+ }
+}
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509TrustManagerTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509TrustManagerTest.java
new file mode 100644
index 00000000000..24d903aef19
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ClientServerX509TrustManagerTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.common;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.net.Socket;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedTrustManager;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class ClientServerX509TrustManagerTest {
+
+ private static X509Certificate clientCaCert;
+ private static X509Certificate clientCert;
+
+ private static X509Certificate serverCaCert;
+ private static X509Certificate serverCert;
+
+ @BeforeAll
+ public static void setUp() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+
+ KeyPair clientCaKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ clientCaCert = X509TestHelpers.newSelfSignedCACert(
+ new X500NameBuilder(BCStyle.INSTANCE).addRDN(BCStyle.CN, "Client CA").build(),
+ clientCaKeyPair, 86400000L);
+
+ KeyPair clientKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ clientCert = X509TestHelpers.newClientOnlyCert(clientCaCert, clientCaKeyPair, "client", clientKeyPair.getPublic());
+
+ KeyPair serverCaKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ serverCaCert = X509TestHelpers.newSelfSignedCACert(
+ new X500NameBuilder(BCStyle.INSTANCE).addRDN(BCStyle.CN, "Server CA").build(),
+ serverCaKeyPair, 86400000L);
+
+ KeyPair serverKeyPair = X509TestHelpers.generateKeyPair(X509KeyType.RSA);
+ serverCert = X509TestHelpers.newServerOnlyCert(serverCaCert, serverCaKeyPair, "server", serverKeyPair.getPublic());
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+ }
+
+ @Test
+ public void testCheckServerTrustedDelegatesToClientTrustManager() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkServerTrusted(
+ new X509Certificate[]{serverCert, serverCaCert}, "RSA"));
+ assertTrue(clientTm.checkServerTrustedCalled);
+ }
+
+ @Test
+ public void testCheckClientTrustedDelegatesToServerTrustManager() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkClientTrusted(
+ new X509Certificate[]{clientCert, clientCaCert}, "RSA"));
+ assertTrue(serverTm.checkClientTrustedCalled);
+ }
+
+ @Test
+ public void testCheckServerTrustedFailsWithWrongTrustManager() {
+ StubTrustManager clientTm = new StubTrustManager(clientCaCert);
+ StubTrustManager serverTm = new StubTrustManager(serverCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertThrows(CertificateException.class, () -> trustManager.checkServerTrusted(
+ new X509Certificate[]{serverCert, serverCaCert}, "RSA"));
+ }
+
+ @Test
+ public void testCheckClientTrustedFailsWithWrongTrustManager() {
+ StubTrustManager clientTm = new StubTrustManager(clientCaCert);
+ StubTrustManager serverTm = new StubTrustManager(serverCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertThrows(CertificateException.class, () -> trustManager.checkClientTrusted(
+ new X509Certificate[]{clientCert, clientCaCert}, "RSA"));
+ }
+
+ @Test
+ public void testCheckServerTrustedWithSocket() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkServerTrusted(
+ new X509Certificate[]{serverCert, serverCaCert}, "RSA", (Socket) null));
+ assertTrue(clientTm.checkServerTrustedSocketCalled);
+ }
+
+ @Test
+ public void testCheckClientTrustedWithSocket() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkClientTrusted(
+ new X509Certificate[]{clientCert, clientCaCert}, "RSA", (Socket) null));
+ assertTrue(serverTm.checkClientTrustedSocketCalled);
+ }
+
+ @Test
+ public void testCheckServerTrustedWithEngine() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkServerTrusted(
+ new X509Certificate[]{serverCert, serverCaCert}, "RSA", (SSLEngine) null));
+ assertTrue(clientTm.checkServerTrustedEngineCalled);
+ }
+
+ @Test
+ public void testCheckClientTrustedWithEngine() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ assertDoesNotThrow(() -> trustManager.checkClientTrusted(
+ new X509Certificate[]{clientCert, clientCaCert}, "RSA", (SSLEngine) null));
+ assertTrue(serverTm.checkClientTrustedEngineCalled);
+ }
+
+ @Test
+ public void testGetAcceptedIssuersReturnsCombined() {
+ StubTrustManager clientTm = new StubTrustManager(serverCaCert);
+ StubTrustManager serverTm = new StubTrustManager(clientCaCert);
+ ClientServerX509TrustManager trustManager = new ClientServerX509TrustManager(clientTm, serverTm);
+
+ X509Certificate[] issuers = trustManager.getAcceptedIssuers();
+ assertEquals(2, issuers.length);
+ assertEquals(serverCaCert, issuers[0]);
+ assertEquals(clientCaCert, issuers[1]);
+ }
+
+ private static class StubTrustManager extends X509ExtendedTrustManager {
+ private final X509Certificate trustedCa;
+ boolean checkClientTrustedCalled;
+ boolean checkServerTrustedCalled;
+ boolean checkClientTrustedSocketCalled;
+ boolean checkServerTrustedSocketCalled;
+ boolean checkClientTrustedEngineCalled;
+ boolean checkServerTrustedEngineCalled;
+
+ StubTrustManager(X509Certificate trustedCa) {
+ this.trustedCa = trustedCa;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ checkClientTrustedCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ checkServerTrustedCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ checkClientTrustedSocketCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
+ checkServerTrustedSocketCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ checkClientTrustedEngineCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
+ checkServerTrustedEngineCalled = true;
+ verifyChain(chain);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[]{trustedCa};
+ }
+
+ private void verifyChain(X509Certificate[] chain) throws CertificateException {
+ for (X509Certificate cert : chain) {
+ if (cert.equals(trustedCa)) {
+ return;
+ }
+ try {
+ cert.verify(trustedCa.getPublicKey());
+ return;
+ } catch (Exception e) {
+ // continue checking
+ }
+ }
+ throw new CertificateException("Certificate chain not trusted");
+ }
+ }
+}
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
index 80632e45c01..45fad846d3d 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
@@ -393,10 +393,18 @@ public void clearSystemProperties(X509Util x509Util) {
System.clearProperty(x509Util.getSslKeystorePasswdProperty());
System.clearProperty(x509Util.getSslKeystorePasswdPathProperty());
System.clearProperty(x509Util.getSslKeystoreTypeProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdPathProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreTypeProperty());
System.clearProperty(x509Util.getSslTruststoreLocationProperty());
System.clearProperty(x509Util.getSslTruststorePasswdProperty());
System.clearProperty(x509Util.getSslTruststorePasswdPathProperty());
System.clearProperty(x509Util.getSslTruststoreTypeProperty());
+ System.clearProperty(x509Util.getSslServerTruststoreLocationProperty());
+ System.clearProperty(x509Util.getSslServerTruststorePasswdProperty());
+ System.clearProperty(x509Util.getSslServerTruststorePasswdPathProperty());
+ System.clearProperty(x509Util.getSslServerTruststoreTypeProperty());
System.clearProperty(x509Util.getSslHostnameVerificationEnabledProperty());
}
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
index 761c718b865..192ad7ae5db 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
@@ -165,6 +165,13 @@ public static X509Certificate newCert(
public static X509Certificate newCert(
X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis, CertificateCustomization customization) throws Exception {
+ return newCert(caCert, caKeyPair, certSubject, certPublicKey, expirationMillis,
+ new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}, customization);
+ }
+
+ public static X509Certificate newCert(
+ X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey,
+ long expirationMillis, KeyPurposeId[] keyPurposes, CertificateCustomization customization) throws Exception {
if (!caKeyPair.getPublic().equals(caCert.getPublicKey())) {
throw new IllegalArgumentException("CA private key does not match the public key in the CA cert");
}
@@ -175,7 +182,7 @@ public static X509Certificate newCert(
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); // not a CA
builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature
| KeyUsage.keyEncipherment));
- builder.addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}));
+ builder.addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(keyPurposes));
builder.addExtension(Extension.subjectAlternativeName, false, getLocalhostSubjectAltNames());
if (customization != null) {
@@ -184,6 +191,25 @@ public static X509Certificate newCert(
return buildAndSignCertificate(caKeyPair.getPrivate(), builder);
}
+ public static X509Certificate newServerOnlyCert(X509Certificate caCert, KeyPair caKeyPair,
+ String name, PublicKey certPublicKey) throws Exception {
+ return createSinglePurposeCert(name, caCert, caKeyPair, certPublicKey, KeyPurposeId.id_kp_serverAuth);
+ }
+
+ public static X509Certificate newClientOnlyCert(X509Certificate caCert, KeyPair caKeyPair,
+ String name, PublicKey certPublicKey) throws Exception {
+ return createSinglePurposeCert(name, caCert, caKeyPair, certPublicKey, KeyPurposeId.id_kp_clientAuth);
+ }
+
+ private static X509Certificate createSinglePurposeCert(
+ String name, X509Certificate caCert, KeyPair caKeyPair, PublicKey certPublicKey, KeyPurposeId keyPurposeId)
+ throws Exception {
+ X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+ nameBuilder.addRDN(BCStyle.CN, name);
+ return newCert(caCert, caKeyPair, nameBuilder.build(), certPublicKey, Duration.ofDays(1).toMillis(),
+ new KeyPurposeId[]{keyPurposeId}, null);
+ }
+
/**
* Returns subject alternative names for "localhost".
* @return the subject alternative names for "localhost".
diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
index 660ca64bfdb..ca22afd91d9 100644
--- a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
@@ -23,17 +23,22 @@
import static org.apache.zookeeper.common.X509Util.TLS_1_3;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.handler.ssl.SslContext;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Path;
+import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
@@ -892,4 +897,355 @@ private void testCreateSSLContext_withWrongPasswordFromFile(final String keyPass
x509Util.getDefaultSSLContext();
});
}
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateClientKeyStore_backwardCompat(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ // No clientKeyStore properties set — should behave identically to existing code
+ SSLContext ctx = x509Util.getDefaultSSLContext();
+ assertNotNull(ctx);
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 15)
+ public void testSeparateClientKeyStore_singleEkuCertsHandshake(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ // Generate separate server-only and client-only certs
+ KeyPair serverKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate serverCert = X509TestHelpers.newServerOnlyCert(
+ x509TestContext.getTrustStoreCertificates().get(0),
+ x509TestContext.getTrustStoreKeyPair(),
+ "server", serverKeyPair.getPublic());
+
+ KeyPair clientKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate clientCert = X509TestHelpers.newClientOnlyCert(
+ x509TestContext.getTrustStoreCertificates().get(0),
+ x509TestContext.getTrustStoreKeyPair(),
+ "client", clientKeyPair.getPublic());
+
+ // Write server keystore
+ File serverKsFile = File.createTempFile("server_ks", ".jks", x509TestContext.getTempDir());
+ serverKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(serverKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ serverCert, serverKeyPair.getPrivate(), keyPassword));
+ }
+
+ // Write client keystore
+ File clientKsFile = File.createTempFile("client_ks", ".jks", x509TestContext.getTempDir());
+ clientKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(clientKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ clientCert, clientKeyPair.getPrivate(), keyPassword));
+ }
+
+ // Set properties: server keystore as keyStore, client keystore as clientKeyStore
+ System.setProperty(x509Util.getSslKeystoreLocationProperty(), serverKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslKeystoreTypeProperty(), "JKS");
+ System.setProperty(x509Util.getSslClientKeystoreLocationProperty(), clientKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslClientKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslClientKeystoreTypeProperty(), "JKS");
+
+ x509Util.close(); // reset cached context
+ x509Util = new ClientX509Util();
+
+ // Create server socket and client socket, perform handshake
+ int port = PortAssignment.unique();
+ SSLServerSocket serverSocket = x509Util.createSSLServerSocket(port);
+ serverSocket.setSoTimeout(5000);
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ Future serverFuture = executor.submit(() -> {
+ try (SSLSocket accepted = (SSLSocket) serverSocket.accept()) {
+ accepted.startHandshake();
+ }
+ return null;
+ });
+
+ SSLSocket clientSocket = x509Util.createSSLSocket();
+ clientSocket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 3000);
+ clientSocket.startHandshake();
+ clientSocket.close();
+
+ serverFuture.get(5, TimeUnit.SECONDS);
+ } finally {
+ serverSocket.close();
+ executor.shutdownNow();
+ System.clearProperty(x509Util.getSslClientKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreTypeProperty());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateClientKeyStore_nettyClientUsesClientKeyStore(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ // Generate separate keystores
+ KeyPair clientKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate clientCert = X509TestHelpers.newClientOnlyCert(
+ x509TestContext.getTrustStoreCertificates().get(0),
+ x509TestContext.getTrustStoreKeyPair(),
+ "client", clientKeyPair.getPublic());
+
+ File clientKsFile = File.createTempFile("client_ks", ".jks", x509TestContext.getTempDir());
+ clientKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(clientKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ clientCert, clientKeyPair.getPrivate(), keyPassword));
+ }
+
+ System.setProperty(x509Util.getSslClientKeystoreLocationProperty(), clientKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslClientKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslClientKeystoreTypeProperty(), "JKS");
+
+ try {
+ // Netty client context should load from clientKeyStore
+ SslContext clientCtx = ((ClientX509Util) x509Util).createNettySslContextForClient(new ZKConfig());
+ assertNotNull(clientCtx);
+
+ // Netty server context should still load from keyStore (the main one)
+ SslContext serverCtx = ((ClientX509Util) x509Util).createNettySslContextForServer(new ZKConfig());
+ assertNotNull(serverCtx);
+ } finally {
+ System.clearProperty(x509Util.getSslClientKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreTypeProperty());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateClientKeyStore_clientServerKeyManagerUsed(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ // Generate a separate client keystore
+ KeyPair clientKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate clientCert = X509TestHelpers.newClientOnlyCert(
+ x509TestContext.getTrustStoreCertificates().get(0),
+ x509TestContext.getTrustStoreKeyPair(),
+ "client", clientKeyPair.getPublic());
+
+ File clientKsFile = File.createTempFile("client_ks", ".jks", x509TestContext.getTempDir());
+ clientKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(clientKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ clientCert, clientKeyPair.getPrivate(), keyPassword));
+ }
+
+ System.setProperty(x509Util.getSslClientKeystoreLocationProperty(), clientKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslClientKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslClientKeystoreTypeProperty(), "JKS");
+
+ x509Util.close();
+ x509Util = new ClientX509Util();
+
+ try {
+ SSLContextAndOptions ctxAndOpts = x509Util.getDefaultSSLContextAndOptions();
+ assertNotNull(ctxAndOpts);
+ // The SSLContext should be functional
+ assertNotNull(ctxAndOpts.getSSLContext());
+ } finally {
+ System.clearProperty(x509Util.getSslClientKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreTypeProperty());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateServerTrustStore_backwardCompat(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+ // Without server.trustStore configured, existing trustStore is used for both roles
+ x509Util.close();
+ x509Util = new ClientX509Util();
+
+ int port = PortAssignment.unique();
+ SSLServerSocket serverSocket = x509Util.createSSLServerSocket(port);
+ serverSocket.setSoTimeout(5000);
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ Future serverFuture = executor.submit(() -> {
+ try (SSLSocket accepted = (SSLSocket) serverSocket.accept()) {
+ accepted.startHandshake();
+ }
+ return null;
+ });
+
+ SSLSocket clientSocket = x509Util.createSSLSocket();
+ clientSocket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 3000);
+ clientSocket.startHandshake();
+ clientSocket.close();
+
+ serverFuture.get(5, TimeUnit.SECONDS);
+ } finally {
+ serverSocket.close();
+ executor.shutdownNow();
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateServerTrustStore_separateCAs(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+
+ // Create a separate CA for client certs
+ KeyPair clientCaKeyPair = X509TestHelpers.generateKeyPair(caKeyType);
+ X509Certificate clientCaCert = X509TestHelpers.newSelfSignedCACert(
+ new org.bouncycastle.asn1.x500.X500NameBuilder(org.bouncycastle.asn1.x500.style.BCStyle.INSTANCE)
+ .addRDN(org.bouncycastle.asn1.x500.style.BCStyle.CN, "Client CA")
+ .build(),
+ clientCaKeyPair, 86400000L);
+
+ // The existing CA is used for server certs
+ KeyPair serverCaKeyPair = x509TestContext.getTrustStoreKeyPair();
+ X509Certificate serverCaCert = x509TestContext.getTrustStoreCertificates().get(0);
+
+ // Generate server cert signed by server CA
+ KeyPair serverKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate serverCert = X509TestHelpers.newServerOnlyCert(
+ serverCaCert, serverCaKeyPair, "server", serverKeyPair.getPublic());
+
+ // Generate client cert signed by client CA
+ KeyPair clientKeyPair = X509TestHelpers.generateKeyPair(certKeyType);
+ X509Certificate clientCert = X509TestHelpers.newClientOnlyCert(
+ clientCaCert, clientCaKeyPair, "client", clientKeyPair.getPublic());
+
+ // Server keystore with server cert
+ File serverKsFile = File.createTempFile("server_ks", ".jks", x509TestContext.getTempDir());
+ serverKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(serverKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ serverCert, serverKeyPair.getPrivate(), keyPassword));
+ }
+
+ // Client keystore with client cert
+ File clientKsFile = File.createTempFile("client_ks", ".jks", x509TestContext.getTempDir());
+ clientKsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(clientKsFile)) {
+ fos.write(X509TestHelpers.certAndPrivateKeyToJavaKeyStoreBytes(
+ clientCert, clientKeyPair.getPrivate(), keyPassword));
+ }
+
+ // Client truststore: trusts server CA (validates servers we connect to)
+ File clientTsFile = File.createTempFile("client_ts", ".jks", x509TestContext.getTempDir());
+ clientTsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(clientTsFile)) {
+ fos.write(X509TestHelpers.certToJavaTrustStoreBytes(serverCaCert, keyPassword));
+ }
+
+ // Server truststore: trusts client CA (validates clients connecting to us)
+ File serverTsFile = File.createTempFile("server_ts", ".jks", x509TestContext.getTempDir());
+ serverTsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(serverTsFile)) {
+ fos.write(X509TestHelpers.certToJavaTrustStoreBytes(clientCaCert, keyPassword));
+ }
+
+ System.setProperty(x509Util.getSslKeystoreLocationProperty(), serverKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslKeystoreTypeProperty(), "JKS");
+ System.setProperty(x509Util.getSslClientKeystoreLocationProperty(), clientKsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslClientKeystorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslClientKeystoreTypeProperty(), "JKS");
+ System.setProperty(x509Util.getSslTruststoreLocationProperty(), clientTsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslTruststorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslTruststoreTypeProperty(), "JKS");
+ System.setProperty(x509Util.getSslServerTruststoreLocationProperty(), serverTsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslServerTruststorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslServerTruststoreTypeProperty(), "JKS");
+
+ x509Util.close();
+ x509Util = new ClientX509Util();
+
+ int port = PortAssignment.unique();
+ SSLServerSocket serverSocket = x509Util.createSSLServerSocket(port);
+ serverSocket.setSoTimeout(5000);
+
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ Future serverFuture = executor.submit(() -> {
+ try (SSLSocket accepted = (SSLSocket) serverSocket.accept()) {
+ accepted.startHandshake();
+ }
+ return null;
+ });
+
+ SSLSocket clientSocket = x509Util.createSSLSocket();
+ clientSocket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 3000);
+ clientSocket.startHandshake();
+ clientSocket.close();
+
+ serverFuture.get(5, TimeUnit.SECONDS);
+ } finally {
+ serverSocket.close();
+ executor.shutdownNow();
+ System.clearProperty(x509Util.getSslClientKeystoreLocationProperty());
+ System.clearProperty(x509Util.getSslClientKeystorePasswdProperty());
+ System.clearProperty(x509Util.getSslClientKeystoreTypeProperty());
+ System.clearProperty(x509Util.getSslServerTruststoreLocationProperty());
+ System.clearProperty(x509Util.getSslServerTruststorePasswdProperty());
+ System.clearProperty(x509Util.getSslServerTruststoreTypeProperty());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ @Timeout(value = 5)
+ public void testSeparateServerTrustStore_clientServerTrustManagerUsed(
+ X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
+ throws Exception {
+ init(caKeyType, certKeyType, keyPassword, paramIndex);
+
+ // Create a separate truststore for server role
+ KeyPair serverCaKeyPair = X509TestHelpers.generateKeyPair(caKeyType);
+ X509Certificate serverCaCert = X509TestHelpers.newSelfSignedCACert(
+ new org.bouncycastle.asn1.x500.X500NameBuilder(org.bouncycastle.asn1.x500.style.BCStyle.INSTANCE)
+ .addRDN(org.bouncycastle.asn1.x500.style.BCStyle.CN, "Server Trust CA")
+ .build(),
+ serverCaKeyPair, 86400000L);
+
+ File serverTsFile = File.createTempFile("server_ts", ".jks", x509TestContext.getTempDir());
+ serverTsFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(serverTsFile)) {
+ fos.write(X509TestHelpers.certToJavaTrustStoreBytes(serverCaCert, keyPassword));
+ }
+
+ System.setProperty(x509Util.getSslServerTruststoreLocationProperty(), serverTsFile.getAbsolutePath());
+ System.setProperty(x509Util.getSslServerTruststorePasswdProperty(), keyPassword);
+ System.setProperty(x509Util.getSslServerTruststoreTypeProperty(), "JKS");
+
+ x509Util.close();
+ x509Util = new ClientX509Util();
+
+ try {
+ SSLContextAndOptions ctxAndOpts = x509Util.getDefaultSSLContextAndOptions();
+ assertNotNull(ctxAndOpts);
+ assertNotNull(ctxAndOpts.getSSLContext());
+ } finally {
+ System.clearProperty(x509Util.getSslServerTruststoreLocationProperty());
+ System.clearProperty(x509Util.getSslServerTruststorePasswdProperty());
+ System.clearProperty(x509Util.getSslServerTruststoreTypeProperty());
+ }
+ }
}