Skip to content

Commit

Permalink
Adding ability to specify custom SSLContext for client
Browse files Browse the repository at this point in the history
  • Loading branch information
arankin-irl committed Dec 3, 2018
1 parent 7ae7485 commit 400839a
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,11 @@
*/
package org.apache.zookeeper.common;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.zookeeper.common.X509Exception.KeyManagerException;
import org.apache.zookeeper.common.X509Exception.SSLContextException;
import org.apache.zookeeper.common.X509Exception.TrustManagerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
Expand All @@ -43,12 +35,19 @@
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import org.apache.zookeeper.common.X509Exception.KeyManagerException;
import org.apache.zookeeper.common.X509Exception.SSLContextException;
import org.apache.zookeeper.common.X509Exception.TrustManagerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

/**
* Utility code for X509 handling
Expand Down Expand Up @@ -85,6 +84,7 @@ public abstract class X509Util {
private String sslTruststoreLocationProperty = getConfigPrefix() + "trustStore.location";
private String sslTruststorePasswdProperty = getConfigPrefix() + "trustStore.password";
private String sslTruststoreTypeProperty = getConfigPrefix() + "trustStore.type";
private String sslClientContextProperty = getConfigPrefix() + "client.context";
private String sslHostnameVerificationEnabledProperty = getConfigPrefix() + "hostnameVerification";
private String sslCrlEnabledProperty = getConfigPrefix() + "crl";
private String sslOcspEnabledProperty = getConfigPrefix() + "ocsp";
Expand Down Expand Up @@ -138,6 +138,10 @@ public String getSslTruststoreTypeProperty() {
return sslTruststoreTypeProperty;
}

public String getSslClientContextProperty() {
return sslClientContextProperty;
}

public String getSslHostnameVerificationEnabledProperty() {
return sslHostnameVerificationEnabledProperty;
}
Expand Down Expand Up @@ -208,62 +212,76 @@ public int getSslHandshakeTimeoutMillis() {
}

public SSLContext createSSLContext(ZKConfig config) throws SSLContextException {
KeyManager[] keyManagers = null;
TrustManager[] trustManagers = null;
if (config.getProperty(sslClientContextProperty) != null) {
LOG.debug("Loading SSLContext from property '" + sslClientContextProperty + "'");
String sslClientContextClass = config.getProperty(sslClientContextProperty);
try {
Class<?> sslContextClass = Class.forName(sslClientContextClass);
ZKClientSSLContext sslContext = (ZKClientSSLContext) sslContextClass.getConstructor().newInstance();
return sslContext.getSSLContext();
} catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | InvocationTargetException |
InstantiationException | IllegalAccessException e) {
throw new SSLContextException("Could not retrieve the SSLContext from source '" + sslClientContextClass +
"' provided in the property '" + sslClientContextProperty + "'", e);
}
} else {
KeyManager[] keyManagers = null;
TrustManager[] trustManagers = null;

String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty, "");
String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty, "");
String keyStoreTypeProp = config.getProperty(sslKeystoreTypeProperty);
String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty, "");
String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty, "");
String keyStoreTypeProp = config.getProperty(sslKeystoreTypeProperty);

// There are legal states in some use cases for null KeyManager or TrustManager.
// But if a user wanna specify one, location is required. Password defaults to empty string if it is not
// specified by the user.
// There are legal states in some use cases for null KeyManager or TrustManager.
// But if a user wanna specify one, location is required. Password defaults to empty string if it is not
// specified by the user.

if (keyStoreLocationProp.isEmpty()) {
LOG.warn(getSslKeystoreLocationProperty() + " not specified");
} else {
try {
keyManagers = new KeyManager[]{
createKeyManager(keyStoreLocationProp, keyStorePasswordProp, keyStoreTypeProp)};
} catch (KeyManagerException keyManagerException) {
throw new SSLContextException("Failed to create KeyManager", keyManagerException);
} catch (IllegalArgumentException e) {
throw new SSLContextException("Bad value for " + sslKeystoreTypeProperty + ": " + keyStoreTypeProp, e);
if (keyStoreLocationProp.isEmpty()) {
LOG.warn(getSslKeystoreLocationProperty() + " not specified");
} else {
try {
keyManagers = new KeyManager[]{
createKeyManager(keyStoreLocationProp, keyStorePasswordProp, keyStoreTypeProp)};
} catch (KeyManagerException keyManagerException) {
throw new SSLContextException("Failed to create KeyManager", keyManagerException);
} catch (IllegalArgumentException e) {
throw new SSLContextException("Bad value for " + sslKeystoreTypeProperty + ": " + keyStoreTypeProp, e);
}
}
}

String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty, "");
String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty, "");
String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty);
String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty, "");
String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty, "");
String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty);

boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty);
boolean sslServerHostnameVerificationEnabled =
config.getBoolean(this.getSslHostnameVerificationEnabledProperty(), true);
boolean sslClientHostnameVerificationEnabled =
sslServerHostnameVerificationEnabled && shouldVerifyClientHostname();
boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty);
boolean sslServerHostnameVerificationEnabled =
config.getBoolean(this.getSslHostnameVerificationEnabledProperty(), true);
boolean sslClientHostnameVerificationEnabled =
sslServerHostnameVerificationEnabled && shouldVerifyClientHostname();

if (trustStoreLocationProp.isEmpty()) {
LOG.warn(getSslTruststoreLocationProperty() + " not specified");
} else {
try {
trustManagers = new TrustManager[]{
createTrustManager(trustStoreLocationProp, trustStorePasswordProp, trustStoreTypeProp, sslCrlEnabled, sslOcspEnabled,
sslServerHostnameVerificationEnabled, sslClientHostnameVerificationEnabled)};
} catch (TrustManagerException trustManagerException) {
throw new SSLContextException("Failed to create TrustManager", trustManagerException);
} catch (IllegalArgumentException e) {
throw new SSLContextException("Bad value for " + sslTruststoreTypeProperty + ": " + trustStoreTypeProp, e);
if (trustStoreLocationProp.isEmpty()) {
LOG.warn(getSslTruststoreLocationProperty() + " not specified");
} else {
try {
trustManagers = new TrustManager[]{
createTrustManager(trustStoreLocationProp, trustStorePasswordProp, trustStoreTypeProp, sslCrlEnabled, sslOcspEnabled,
sslServerHostnameVerificationEnabled, sslClientHostnameVerificationEnabled)};
} catch (TrustManagerException trustManagerException) {
throw new SSLContextException("Failed to create TrustManager", trustManagerException);
} catch (IllegalArgumentException e) {
throw new SSLContextException("Bad value for " + sslTruststoreTypeProperty + ": " + trustStoreTypeProp, e);
}
}
}

String protocol = System.getProperty(sslProtocolProperty, DEFAULT_PROTOCOL);
try {
SSLContext sslContext = SSLContext.getInstance(protocol);
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (NoSuchAlgorithmException|KeyManagementException sslContextInitException) {
throw new SSLContextException(sslContextInitException);
String protocol = System.getProperty(sslProtocolProperty, DEFAULT_PROTOCOL);
try {
SSLContext sslContext = SSLContext.getInstance(protocol);
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException sslContextInitException) {
throw new SSLContextException(sslContextInitException);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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 javax.net.ssl.SSLContext;

/**
* An interface for providing a custom {@link SSLContext} object to {@link X509Util} using {@link X509Util#getSslClientContextProperty()}
*/
public interface ZKClientSSLContext {

/**
* Returns an {@link SSLContext} for use within the {@link X509Util}
*
* @return {@link SSLContext} for use within {@link X509Util}
* @throws X509Exception.SSLContextException if {@link SSLContext} cannot be created
*/
SSLContext getSSLContext() throws X509Exception.SSLContextException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ private void putSSLProperties(X509Util x509Util) {
System.getProperty(x509Util.getSslTruststorePasswdProperty()));
properties.put(x509Util.getSslTruststoreTypeProperty(),
System.getProperty(x509Util.getSslTruststoreTypeProperty()));
properties.put(x509Util.getSslClientContextProperty(),
System.getProperty(x509Util.getSslClientContextProperty()));
properties.put(x509Util.getSslHostnameVerificationEnabledProperty(),
System.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
properties.put(x509Util.getSslCrlEnabledProperty(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
*/
package org.apache.zookeeper.common;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.security.Security;
import java.util.Collection;

Expand Down Expand Up @@ -384,6 +388,27 @@ public void testGetSslHandshakeDetectionTimeoutMillisProperty() {
}
}

@Test
public void testCreateSSLContext_invalidCustomSSLContextClass() {
ZKConfig zkConfig = new ZKConfig();
ClientX509Util clientX509Util = new ClientX509Util();
zkConfig.setProperty(clientX509Util.getSslClientContextProperty(), String.class.getCanonicalName());
try {
clientX509Util.createSSLContext(zkConfig);
fail("SSLContextException expected.");
} catch (X509Exception.SSLContextException e) {
assertTrue(e.getMessage().contains(clientX509Util.getSslClientContextProperty()));
}
}
@Test
public void testCreateSSLContext_validCustomSSLContextClass() throws X509Exception.SSLContextException {
ZKConfig zkConfig = new ZKConfig();
ClientX509Util clientX509Util = new ClientX509Util();
zkConfig.setProperty(clientX509Util.getSslClientContextProperty(), ZKTestClientSSLContext.class.getCanonicalName());
final SSLContext sslContext = clientX509Util.createSSLContext(zkConfig);
assertNull(sslContext);
}

// Warning: this will reset the x509Util
private void setCustomCipherSuites() {
System.setProperty(x509Util.getCipherSuitesProperty(), customCipherSuites[0] + "," + customCipherSuites[1]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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 javax.net.ssl.SSLContext;

public class ZKTestClientSSLContext implements ZKClientSSLContext {

@Override
public SSLContext getSSLContext() {
return null;
}

}

0 comments on commit 400839a

Please sign in to comment.