X509UtilTest.java
/*
* 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.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
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.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import org.apache.zookeeper.PortAssignment;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.server.ServerCnxnFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class X509UtilTest extends BaseX509ParameterizedTestCase {
private X509Util x509Util;
private static final String[] customCipherSuites = new String[]{
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"
};
public void init(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
super.init(caKeyType, certKeyType, keyPassword, paramIndex);
try (X509Util x509util = new ClientX509Util()) {
x509TestContext.setSystemProperties(x509util, KeyStoreFileType.JKS, KeyStoreFileType.JKS);
}
System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory");
System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty");
x509Util = new ClientX509Util();
}
@AfterEach
public void cleanUp() {
x509TestContext.clearSystemProperties(x509Util);
System.clearProperty(x509Util.getSslOcspEnabledProperty());
System.clearProperty(x509Util.getSslCrlEnabledProperty());
System.clearProperty(x509Util.getCipherSuitesProperty());
System.clearProperty(x509Util.getSslProtocolProperty());
System.clearProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty());
System.clearProperty("com.sun.net.ssl.checkRevocation");
System.clearProperty("com.sun.security.enableCRLDP");
Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString());
System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET);
x509Util.close();
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContextWithoutCustomProtocol(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
SSLContext sslContext = x509Util.getDefaultSSLContext();
assertEquals(X509Util.DEFAULT_PROTOCOL, sslContext.getProtocol());
// Check that TLSv1.3 is selected in JDKs that support it (OpenJDK 8u272 and later).
List<String> supported = Arrays.asList(SSLContext.getDefault().getSupportedSSLParameters().getProtocols());
if (supported.contains(X509Util.TLS_1_3)) {
// SSLContext protocol.
assertEquals(X509Util.TLS_1_3, sslContext.getProtocol());
// Enabled protocols.
List<String> protos = Arrays.asList(sslContext.getDefaultSSLParameters().getProtocols());
assertTrue(protos.contains(X509Util.TLS_1_2));
assertTrue(protos.contains(X509Util.TLS_1_3));
} else {
assertEquals(X509Util.TLS_1_2, sslContext.getProtocol());
assertArrayEquals(new String[]{X509Util.TLS_1_2}, sslContext.getDefaultSSLParameters().getProtocols());
}
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContextWithCustomProtocol(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
final String protocol = X509Util.TLS_1_1;
init(caKeyType, certKeyType, keyPassword, paramIndex);
System.setProperty(x509Util.getSslProtocolProperty(), protocol);
SSLContext sslContext = x509Util.getDefaultSSLContext();
assertEquals(protocol, sslContext.getProtocol());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContextWithoutKeyStoreLocation(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
System.clearProperty(x509Util.getSslKeystoreLocationProperty());
x509Util.getDefaultSSLContext();
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContextWithoutKeyStorePassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.SSLContextException.class, () -> {
if (!x509TestContext.isKeyStoreEncrypted()) {
throw new X509Exception.SSLContextException("");
}
System.clearProperty(x509Util.getSslKeystorePasswdProperty());
x509Util.getDefaultSSLContext();
});
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContext_withKeyStorePasswordFromFile(final X509KeyType caKeyType,
final X509KeyType certKeyType,
final String keyPassword,
final Integer paramIndex) throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
testCreateSSLContext_withPasswordFromFile(keyPassword,
x509Util.getSslKeystorePasswdProperty(),
x509Util.getSslKeystorePasswdPathProperty());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContext_withTrustStorePasswordFromFile(final X509KeyType caKeyType,
final X509KeyType certKeyType,
final String keyPassword,
final Integer paramIndex) throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
testCreateSSLContext_withPasswordFromFile(keyPassword,
x509Util.getSslTruststorePasswdProperty(),
x509Util.getSslTruststorePasswdPathProperty());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContext_withWrongKeyStorePasswordFromFile(final X509KeyType caKeyType,
final X509KeyType certKeyType,
final String keyPassword,
final Integer paramIndex) throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
testCreateSSLContext_withWrongPasswordFromFile(keyPassword, x509Util.getSslKeystorePasswdPathProperty());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContext_withWrongTrustStorePasswordFromFile(final X509KeyType caKeyType,
final X509KeyType certKeyType,
final String keyPassword,
final Integer paramIndex) throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
testCreateSSLContext_withWrongPasswordFromFile(keyPassword, x509Util.getSslTruststorePasswdPathProperty());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLContextWithCustomCipherSuites(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
setCustomCipherSuites();
SSLSocket sslSocket = x509Util.createSSLSocket();
assertArrayEquals(customCipherSuites, sslSocket.getEnabledCipherSuites());
}
// It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled but it does not appear to be
// possible
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCRLEnabled(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
System.setProperty(x509Util.getSslCrlEnabledProperty(), "true");
x509Util.getDefaultSSLContext();
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCRLDisabled(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
x509Util.getDefaultSSLContext();
assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testOCSPEnabled(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
System.setProperty(x509Util.getSslOcspEnabledProperty(), "true");
x509Util.getDefaultSSLContext();
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
assertTrue(Boolean.valueOf(Security.getProperty("ocsp.enable")));
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLSocket(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
setCustomCipherSuites();
SSLSocket sslSocket = x509Util.createSSLSocket();
assertArrayEquals(customCipherSuites, sslSocket.getEnabledCipherSuites());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLServerSocketWithoutPort(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
setCustomCipherSuites();
SSLServerSocket sslServerSocket = x509Util.createSSLServerSocket();
assertArrayEquals(customCipherSuites, sslServerSocket.getEnabledCipherSuites());
assertTrue(sslServerSocket.getNeedClientAuth());
}
@ParameterizedTest
@MethodSource("data")
@Timeout(value = 5)
public void testCreateSSLServerSocketWithPort(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
setCustomCipherSuites();
int port = PortAssignment.unique();
SSLServerSocket sslServerSocket = x509Util.createSSLServerSocket(port);
assertEquals(sslServerSocket.getLocalPort(), port);
assertArrayEquals(customCipherSuites, sslServerSocket.getEnabledCipherSuites());
assertTrue(sslServerSocket.getNeedClientAuth());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMKeyStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the PEM file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
KeyStoreFileType.PEM.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMKeyStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getKeyStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
null,
KeyStoreFileType.PEM.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMKeyStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the PEM file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
null /* null StoreFileType means 'autodetect from file extension' */);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMKeyStoreWithWrongPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.KeyManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
"wrong password", // intentionally use the wrong password
KeyStoreFileType.PEM.getPropertyValue());
});
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMTrustStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the PEM file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
x509TestContext.getTrustStorePassword(), KeyStoreFileType.PEM.getPropertyValue(),
false,
false,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMTrustStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getTrustStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
null,
KeyStoreFileType.PEM.getPropertyValue(),
false,
false,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPEMTrustStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the PEM file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PEM).getAbsolutePath(),
x509TestContext.getTrustStorePassword(),
null, // null StoreFileType means 'autodetect from file extension'
false,
false,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSKeyStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the JKS file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
KeyStoreFileType.JKS.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSKeyStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getKeyStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
null,
KeyStoreFileType.JKS.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSKeyStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the JKS file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
null /* null StoreFileType means 'autodetect from file extension' */);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSKeyStoreWithWrongPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.KeyManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
"wrong password",
KeyStoreFileType.JKS.getPropertyValue());
});
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSTrustStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the JKS file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
x509TestContext.getTrustStorePassword(),
KeyStoreFileType.JKS.getPropertyValue(),
true,
true,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSTrustStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getTrustStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
null,
KeyStoreFileType.JKS.getPropertyValue(),
false,
false,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSTrustStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the JKS file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
x509TestContext.getTrustStorePassword(),
null, // null StoreFileType means 'autodetect from file extension'
true,
true,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadJKSTrustStoreWithWrongPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.TrustManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
"wrong password",
KeyStoreFileType.JKS.getPropertyValue(),
true,
true,
true,
true,
false);
});
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12KeyStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the PKCS12 file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
KeyStoreFileType.PKCS12.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12KeyStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getKeyStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
null,
KeyStoreFileType.PKCS12.getPropertyValue());
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12KeyStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a key manager from the PKCS12 file on disk
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
x509TestContext.getKeyStorePassword(),
null /* null StoreFileType means 'autodetect from file extension' */);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12KeyStoreWithWrongPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.KeyManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509KeyManager km = X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
"wrong password",
KeyStoreFileType.PKCS12.getPropertyValue());
});
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12TrustStore(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the PKCS12 file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
x509TestContext.getTrustStorePassword(), KeyStoreFileType.PKCS12.getPropertyValue(),
true,
true,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12TrustStoreNullPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
if (!x509TestContext.getTrustStorePassword().isEmpty()) {
return;
}
// Make sure that empty password and null password are treated the same
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
null,
KeyStoreFileType.PKCS12.getPropertyValue(),
false,
false,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12TrustStoreAutodetectStoreFileType(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
// Make sure we can instantiate a trust manager from the PKCS12 file on disk
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
x509TestContext.getTrustStorePassword(),
null, // null StoreFileType means 'autodetect from file extension'
true,
true,
true,
true,
false);
}
@ParameterizedTest
@MethodSource("data")
public void testLoadPKCS12TrustStoreWithWrongPassword(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.TrustManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509TrustManager tm = X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
"wrong password",
KeyStoreFileType.PKCS12.getPropertyValue(),
true,
true,
true,
true,
false);
});
}
@ParameterizedTest
@MethodSource("data")
public void testGetSslHandshakeDetectionTimeoutMillisProperty(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertEquals(X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS, x509Util.getSslHandshakeTimeoutMillis());
// Note: need to create a new ClientX509Util each time to pick up modified property value
String newPropertyString = Integer.toString(X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS + 1);
System.setProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty(), newPropertyString);
try (X509Util tempX509Util = new ClientX509Util()) {
assertEquals(X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS
+ 1, tempX509Util.getSslHandshakeTimeoutMillis());
}
// 0 value not allowed, will return the default
System.setProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty(), "0");
try (X509Util tempX509Util = new ClientX509Util()) {
assertEquals(X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS, tempX509Util.getSslHandshakeTimeoutMillis());
}
// Negative value not allowed, will return the default
System.setProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty(), "-1");
try (X509Util tempX509Util = new ClientX509Util()) {
assertEquals(X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS, tempX509Util.getSslHandshakeTimeoutMillis());
}
}
@ParameterizedTest
@MethodSource("data")
public void testCreateSSLContext_invalidCustomSSLContextClass(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(X509Exception.SSLContextException.class, () -> {
ZKConfig zkConfig = new ZKConfig();
ClientX509Util clientX509Util = new ClientX509Util();
zkConfig.setProperty(clientX509Util.getSslContextSupplierClassProperty(), String.class.getCanonicalName());
clientX509Util.createSSLContext(zkConfig);
});
}
@ParameterizedTest
@MethodSource("data")
public void testCreateSSLContext_validCustomSSLContextClass(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Exception {
init(caKeyType, certKeyType, keyPassword, paramIndex);
ZKConfig zkConfig = new ZKConfig();
ClientX509Util clientX509Util = new ClientX509Util();
zkConfig.setProperty(clientX509Util.getSslContextSupplierClassProperty(), SslContextSupplier.class.getName());
final SSLContext sslContext = clientX509Util.createSSLContext(zkConfig);
assertEquals(SSLContext.getDefault(), sslContext);
}
private static void forceClose(Socket s) {
if (s == null || s.isClosed()) {
return;
}
try {
s.close();
} catch (IOException e) {
}
}
private static void forceClose(ServerSocket s) {
if (s == null || s.isClosed()) {
return;
}
try {
s.close();
} catch (IOException e) {
}
}
// This test makes sure that client-initiated TLS renegotiation does not
// succeed when using TLSv1.2. We explicitly disable it at the top of X509Util.java.
// Force TLSv1.2 since the renegotiation feature is not supported anymore in TLSv1.3 and the test becomes invalid.
@ParameterizedTest
@MethodSource("data")
public void testClientRenegotiationFails(
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
throws Throwable {
init(caKeyType, certKeyType, keyPassword, paramIndex);
assertThrows(SSLHandshakeException.class, () -> {
int port = PortAssignment.unique();
ExecutorService workerPool = Executors.newCachedThreadPool();
final SSLServerSocket listeningSocket = x509Util.createSSLServerSocket();
SSLSocket clientSocket = null;
SSLSocket serverSocket = null;
final AtomicInteger handshakesCompleted = new AtomicInteger(0);
final CountDownLatch handshakeCompleted = new CountDownLatch(1);
try {
InetSocketAddress localServerAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
listeningSocket.bind(localServerAddress);
Future<SSLSocket> acceptFuture;
acceptFuture = workerPool.submit(new Callable<SSLSocket>() {
@Override
public SSLSocket call() throws Exception {
SSLSocket sslSocket = (SSLSocket) listeningSocket.accept();
sslSocket.setEnabledProtocols(new String[]{X509Util.TLS_1_2});
sslSocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
@Override
public void handshakeCompleted(HandshakeCompletedEvent handshakeCompletedEvent) {
handshakesCompleted.getAndIncrement();
handshakeCompleted.countDown();
}
});
assertEquals(1, sslSocket.getInputStream().read());
try {
// 2nd read is after the renegotiation attempt and will fail
sslSocket.getInputStream().read();
return sslSocket;
} catch (Exception e) {
forceClose(sslSocket);
throw e;
}
}
});
clientSocket = x509Util.createSSLSocket();
clientSocket.connect(localServerAddress);
clientSocket.getOutputStream().write(1);
// Attempt to renegotiate after establishing the connection
clientSocket.startHandshake();
clientSocket.getOutputStream().write(1);
// The exception is thrown on the server side, we need to unwrap it
try {
serverSocket = acceptFuture.get();
} catch (ExecutionException e) {
throw e.getCause();
}
} finally {
forceClose(serverSocket);
forceClose(clientSocket);
forceClose(listeningSocket);
workerPool.shutdown();
// Make sure the first handshake completed and only the second
// one failed.
handshakeCompleted.await(5, TimeUnit.SECONDS);
assertEquals(1, handshakesCompleted.get());
}
});
}
// Warning: this will reset the x509Util
private void setCustomCipherSuites() {
System.setProperty(x509Util.getCipherSuitesProperty(), customCipherSuites[0] + "," + customCipherSuites[1]);
x509Util.close(); // remember to close old instance before replacing it
x509Util = new ClientX509Util();
}
public static class SslContextSupplier implements Supplier<SSLContext> {
@Override
public SSLContext get() {
try {
return SSLContext.getDefault();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
private void testCreateSSLContext_withPasswordFromFile(final String keyPassword,
final String propertyName,
final String pathPropertyName) throws Exception {
final Path secretFile = SecretUtilsTest.createSecretFile(keyPassword);
System.clearProperty(propertyName);
System.setProperty(pathPropertyName, secretFile.toString());
x509Util.getDefaultSSLContext();
}
private void testCreateSSLContext_withWrongPasswordFromFile(final String keyPassword,
final String pathPropertyName) throws Exception {
final Path secretFile = SecretUtilsTest.createSecretFile(keyPassword + "_wrong");
assertThrows(X509Exception.SSLContextException.class, () -> {
System.setProperty(pathPropertyName, secretFile.toString());
x509Util.getDefaultSSLContext();
});
}
}