diff --git a/.gitignore b/.gitignore index 586adb73..8c631052 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ target bin .metadata -**/integrationTest.properties \ No newline at end of file +**/integrationTest.properties +/target/ diff --git a/pom.xml b/pom.xml index 19a045e4..7d818974 100755 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ com.openshift openshift-java-client - 2.6.1.Final + 2.6.2.Final jar OpenShift Java Client http://openshift.redhat.com diff --git a/src/main/java/com/openshift/client/OpenShiftConnectionFactory.java b/src/main/java/com/openshift/client/OpenShiftConnectionFactory.java index 451e604b..43a1585a 100755 --- a/src/main/java/com/openshift/client/OpenShiftConnectionFactory.java +++ b/src/main/java/com/openshift/client/OpenShiftConnectionFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2012 Red Hat, Inc. + * Copyright (c) 2012-2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, @@ -14,8 +14,10 @@ import java.io.IOException; import com.openshift.client.IHttpClient.ISSLCertificateCallback; +import com.openshift.client.configuration.AbstractOpenshiftConfiguration.ConfigurationOptions; import com.openshift.client.configuration.IOpenShiftConfiguration; import com.openshift.client.configuration.OpenShiftConfiguration; +import com.openshift.client.utils.SSLUtils; import com.openshift.internal.client.AbstractOpenShiftConnectionFactory; import com.openshift.internal.client.IRestService; import com.openshift.internal.client.RestService; @@ -33,7 +35,7 @@ * */ public class OpenShiftConnectionFactory extends AbstractOpenShiftConnectionFactory { - private IOpenShiftConfiguration configuration = null; + private IOpenShiftConfiguration configuration; /** * Establish a connection with the clientId along with user's password. * User's login and Server URL are retrieved from the local configuration @@ -49,11 +51,7 @@ public class OpenShiftConnectionFactory extends AbstractOpenShiftConnectionFacto * @throws OpenShiftException */ public IOpenShiftConnection getConnection(final String clientId, final String password) throws OpenShiftException { - try { - configuration = new OpenShiftConfiguration(); - } catch (IOException e) { - throw new OpenShiftException(e, "Failed to load OpenShift configuration file."); - } + IOpenShiftConfiguration configuration = getConfiguration(); return getConnection(clientId, configuration.getRhlogin(), password, configuration.getLibraServer()); } @@ -75,12 +73,7 @@ public IOpenShiftConnection getConnection(final String clientId, final String pa */ public IOpenShiftConnection getConnection(final String clientId, final String username, final String password) throws OpenShiftException { - try { - configuration = new OpenShiftConfiguration(); - } catch (IOException e) { - throw new OpenShiftException(e, "Failed to load OpenShift configuration file."); - } - return getConnection(clientId, username, password, configuration.getLibraServer()); + return getConnection(clientId, username, password, getConfiguration().getLibraServer()); } /** @@ -115,6 +108,23 @@ public IOpenShiftConnection getConnection(final String clientId, final String us return getConnection(clientId, username, password, null, null, serverUrl, null); } + public IOpenShiftConnection getConnection(final String clientId, final String username, final String password, + final String authKey, final String authIV, final String serverUrl, + final ISSLCertificateCallback sslCertificateCallback) throws OpenShiftException { + return getConnection(clientId, username, password, authKey, authIV, serverUrl, sslCertificateCallback, createCipherExclusionRegex(getConfiguration())); + } + + protected String createCipherExclusionRegex(IOpenShiftConfiguration configuration) { + if(configuration.getDisableBadSSLCiphers() == ConfigurationOptions.YES + || (configuration.getDisableBadSSLCiphers() == ConfigurationOptions.AUTO) && !SSLUtils.supportsDHECipherKeysOf(1024 + 64)) { + // jdk < 1.8 only support DHE cipher keys <= 1024 bit + // https://issues.jboss.org/browse/JBIDE-18454 + return SSLUtils.CIPHER_DHE_REGEX; + } else { + return null; + } + } + /** * Establish a connection with the clientId along with user's login and * password. @@ -133,31 +143,47 @@ public IOpenShiftConnection getConnection(final String clientId, final String us * @throws OpenShiftException */ public IOpenShiftConnection getConnection(final String clientId, final String username, final String password, - final String authKey, final String authIV, final String serverUrl, - final ISSLCertificateCallback sslCertificateCallback) throws OpenShiftException { - if (configuration == null) { - try { - configuration = new OpenShiftConfiguration(); - } catch (IOException e) { - throw new OpenShiftException(e, "Failed to load OpenShift configuration file."); - } - } + final String authKey, final String authIV, final String serverUrl, + final ISSLCertificateCallback sslCertificateCallback, String exludeSSLCipherRegex) + throws OpenShiftException { Assert.notNull(clientId); Assert.notNull(username); Assert.notNull(password); Assert.notNull(serverUrl); + IHttpClient httpClient = createClient( + clientId, username, password, authKey, authIV, serverUrl, sslCertificateCallback, exludeSSLCipherRegex); try { - IHttpClient httpClient = - new UrlConnectionHttpClientBuilder() + return getConnection(clientId, username, password, serverUrl, httpClient); + } catch (IOException e) { + throw new OpenShiftException(e, "Failed to establish connection for user ''{0}}''", username); + } + } + + protected IHttpClient createClient(final String clientId, final String username, final String password, + final String authKey, final String authIV, final String serverUrl, + final ISSLCertificateCallback sslCertificateCallback, String exludeSSLCipherRegex) { + return new UrlConnectionHttpClientBuilder() .setCredentials(username, password, authKey, authIV) .setSSLCertificateCallback(sslCertificateCallback) - .setConfigTimeout(configuration.getTimeout()) + .setConfigTimeout(getConfiguration().getTimeout()) + .excludeSSLCipher(exludeSSLCipherRegex) .client(); - return getConnection(clientId, username, password, serverUrl, httpClient); + } + + protected IOpenShiftConfiguration getConfiguration() throws OpenShiftException { + if (this.configuration == null) { + this.configuration = createConfiguration(); + } + return this.configuration; + } + + protected IOpenShiftConfiguration createConfiguration() throws OpenShiftException { + try { + return new OpenShiftConfiguration(); } catch (IOException e) { - throw new OpenShiftException(e, "Failed to establish connection for user ''{0}}''", username); + throw new OpenShiftException(e, "Failed to load OpenShift configuration file."); } } diff --git a/src/main/java/com/openshift/client/configuration/AbstractOpenshiftConfiguration.java b/src/main/java/com/openshift/client/configuration/AbstractOpenshiftConfiguration.java index bd0fe743..7fa9d134 100755 --- a/src/main/java/com/openshift/client/configuration/AbstractOpenshiftConfiguration.java +++ b/src/main/java/com/openshift/client/configuration/AbstractOpenshiftConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2011-2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, @@ -33,26 +33,42 @@ public abstract class AbstractOpenshiftConfiguration implements IOpenShiftConfig protected static final String KEY_LIBRA_SERVER = "libra_server"; protected static final String KEY_LIBRA_DOMAIN = "libra_domain"; - protected static final String KEY_PASSWORD = "rhpassword"; protected static final String KEY_CLIENT_ID = "client_id"; protected static final String KEY_TIMEOUT = "timeout"; - protected static final String DEFAULT_OPENSHIFT_TIMEOUT = "180000"; //3 minutes + protected static final String DEFAULT_OPENSHIFT_TIMEOUT = "180000"; // 3mins + + protected static final String KEY_DISABLE_BAD_SSL_CIPHERS = "disable_bad_sslciphers"; private static final Pattern QUOTED_REGEX = Pattern.compile("['\"]*([^'\"]+)['\"]*"); private static final char SINGLEQUOTE = '\''; - + private static final String SYSPROPERTY_PROXY_PORT = "proxyPort"; private static final String SYSPROPERTY_PROXY_HOST = "proxyHost"; private static final String SYSPROPERTY_PROXY_SET = "proxySet"; private Properties properties; private File file; - - // TODO: implement + private boolean doSSLChecks = false; + public enum ConfigurationOptions { + YES, NO, AUTO; + + private static ConfigurationOptions safeValueOf(String string) { + if (string == null) { + return NO; + } + + try { + return valueOf(string.toUpperCase()); + } catch (IllegalArgumentException e) { + return NO; + } + } + } + protected AbstractOpenshiftConfiguration() throws FileNotFoundException, IOException { this(null, null); } @@ -164,7 +180,7 @@ protected String removeQuotes(String value) { return value; } } - + public String getPassword() { return properties.getProperty(KEY_PASSWORD); } @@ -172,26 +188,37 @@ public String getPassword() { public String getClientId() { return properties.getProperty(KEY_CLIENT_ID); } + + public ConfigurationOptions getDisableBadSSLCiphers() { + return ConfigurationOptions.safeValueOf( + removeQuotes(properties.getProperty(KEY_DISABLE_BAD_SSL_CIPHERS))); + } + + public void setDisableBadSSLCiphers(ConfigurationOptions option) { + properties.setProperty(KEY_DISABLE_BAD_SSL_CIPHERS, option.toString()); + } public void setEnableSSLCertChecks(boolean doSSLChecks) { this.doSSLChecks = doSSLChecks; } - + public boolean getProxySet() { - String set = properties.getProperty(SYSPROPERTY_PROXY_SET); - - if (set != null) - return Boolean.parseBoolean(removeQuotes(set)); - else - return false; + return toBoolean(removeQuotes(properties.getProperty(SYSPROPERTY_PROXY_SET))); } - + public String getProxyHost() { return removeQuotes(properties.getProperty(SYSPROPERTY_PROXY_HOST)); } - + public String getProxyPort() { return removeQuotes(properties.getProperty(SYSPROPERTY_PROXY_PORT)); } + private boolean toBoolean(String string) { + if (string != null) { + return Boolean.parseBoolean(string); + } else { + return false; + } + } } diff --git a/src/main/java/com/openshift/client/configuration/DefaultConfiguration.java b/src/main/java/com/openshift/client/configuration/DefaultConfiguration.java index 693b9189..3863da10 100755 --- a/src/main/java/com/openshift/client/configuration/DefaultConfiguration.java +++ b/src/main/java/com/openshift/client/configuration/DefaultConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2011-2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, @@ -14,7 +14,6 @@ import java.io.IOException; import java.util.Properties; -import com.openshift.client.IHttpClient; import com.openshift.client.OpenShiftException; /** @@ -36,6 +35,7 @@ protected Properties getProperties(File file, Properties defaultProperties) { properties.put(KEY_LIBRA_SERVER, LIBRA_SERVER); properties.put(KEY_LIBRA_DOMAIN, LIBRA_DOMAIN); properties.put(KEY_TIMEOUT, DEFAULT_OPENSHIFT_TIMEOUT); + properties.put(KEY_DISABLE_BAD_SSL_CIPHERS, ConfigurationOptions.NO.toString()); return properties; } } diff --git a/src/main/java/com/openshift/client/configuration/IOpenShiftConfiguration.java b/src/main/java/com/openshift/client/configuration/IOpenShiftConfiguration.java index d94304b0..73c8b4a3 100644 --- a/src/main/java/com/openshift/client/configuration/IOpenShiftConfiguration.java +++ b/src/main/java/com/openshift/client/configuration/IOpenShiftConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011 Red Hat, Inc. + * Copyright (c) 2011-2014 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, @@ -12,25 +12,31 @@ import java.util.Properties; +import com.openshift.client.configuration.AbstractOpenshiftConfiguration.ConfigurationOptions; + /** * @author André Dietisheim * @author Corey Daley */ public interface IOpenShiftConfiguration { - public abstract String getRhlogin(); + public String getRhlogin(); - public abstract void setRhlogin(String rhlogin); + public void setRhlogin(String rhlogin); - public abstract String getLibraServer(); + public String getLibraServer(); - public abstract void setLibraServer(String libraServer); + public void setLibraServer(String libraServer); - public abstract String getLibraDomain(); + public String getLibraDomain(); public Integer getTimeout(); - public abstract void setLibraDomain(String libraDomain); + public void setLibraDomain(String libraDomain); + public ConfigurationOptions getDisableBadSSLCiphers(); + + public void setDisableBadSSLCiphers(ConfigurationOptions option); + public Properties getProperties(); } \ No newline at end of file diff --git a/src/main/java/com/openshift/client/configuration/SystemProperties.java b/src/main/java/com/openshift/client/configuration/SystemProperties.java index d123b618..6b38cdbe 100644 --- a/src/main/java/com/openshift/client/configuration/SystemProperties.java +++ b/src/main/java/com/openshift/client/configuration/SystemProperties.java @@ -37,6 +37,7 @@ protected Properties getProperties(File file, Properties defaultProperties) { copySystemProperty(KEY_PASSWORD, properties); copySystemProperty(KEY_CLIENT_ID, properties); copySystemProperty(KEY_OPENSHIFT_TIMEOUT, properties); + copySystemProperty(KEY_DISABLE_BAD_SSL_CIPHERS, properties); return properties; } diff --git a/src/main/java/com/openshift/client/utils/SSLUtils.java b/src/main/java/com/openshift/client/utils/SSLUtils.java new file mode 100644 index 00000000..39ad1450 --- /dev/null +++ b/src/main/java/com/openshift/client/utils/SSLUtils.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2014 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.openshift.client.utils; + +import java.security.AlgorithmParameterGenerator; +import java.security.InvalidParameterException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +/** + * @author Andre Dietisheim + */ +public class SSLUtils { + + private static final String SSL_CONTEXT_NAME = "SSL"; + public static final String CIPHER_DHE_REGEX = ".*_DHE_.*"; + private static final String CIPHER_DHE_NAME = "DiffieHellman"; + private static final int CIPHER_DHE_MINSIZE = 512; + private static final int CIPHER_DHE_MAXSIZE = 16384; + private static final int CIPHER_DHE_MODULO = 64; + + private SSLUtils() { + // inhibit instantiation + } + + /** + * Returns true if the jdk supports DEH cipher keys in the + * given length. + * inspired by http://stackoverflow.com/a/18254095/231357 + * + * @param length + * @return + * + */ + public static boolean supportsDHECipherKeysOf(int length) { + try { + return isMaxKeysize(length, CIPHER_DHE_MINSIZE, CIPHER_DHE_MAXSIZE, CIPHER_DHE_MODULO, + AlgorithmParameterGenerator.getInstance(CIPHER_DHE_NAME)); + } catch (NoSuchAlgorithmException e1) { + return false; + } + } + + private static boolean isMaxKeysize(int length, int minSize, int maxSize, int modulo, + AlgorithmParameterGenerator algorithmParamGen) { + int maxLength = 0; + for (int i = minSize; i <= maxSize; i += modulo) { + try { + algorithmParamGen.init(i); + } catch (InvalidParameterException e) { + break; + } + maxLength = i; + } + return maxLength >= length; + } + + public static final String[] filterCiphers(String excludedCipherRegex, String[] ciphers) { + if (excludedCipherRegex == null + || ciphers == null) { + return ciphers; + } + + List filteredCiphers = new ArrayList(); + for (String cipher : ciphers) { + if (!cipher.matches(excludedCipherRegex)) { + filteredCiphers.add(cipher); + } + } + return filteredCiphers.toArray(new String[filteredCiphers.size()]); + } + + public static SSLContext getSSLContext(TrustManager trustManager) throws NoSuchAlgorithmException, KeyManagementException { + TrustManager[] trustManagers = null; + if (trustManager != null) { + trustManagers = new TrustManager[] { trustManager }; + } + SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_NAME); + sslContext.init(null, trustManagers, null); + return sslContext; + } +} diff --git a/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClient.java b/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClient.java index cd7a00e3..5969ecea 100755 --- a/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClient.java +++ b/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClient.java @@ -12,9 +12,13 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.ProtocolException; +import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; +import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -27,6 +31,7 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -38,6 +43,7 @@ import com.openshift.client.HttpMethod; import com.openshift.client.IHttpClient; import com.openshift.client.utils.Base64Coder; +import com.openshift.client.utils.SSLUtils; import com.openshift.internal.client.httpclient.request.IMediaType; import com.openshift.internal.client.httpclient.request.Parameter; import com.openshift.internal.client.httpclient.request.ParameterValueMap; @@ -62,6 +68,7 @@ public class UrlConnectionHttpClient implements IHttpClient { protected String acceptedVersion; protected ISSLCertificateCallback sslAuthorizationCallback; protected Integer configTimeout; + private String excludedSSLCipherRegex; public UrlConnectionHttpClient( String username, String password, String userAgent, String acceptedMediaType, String version) { @@ -70,11 +77,11 @@ public UrlConnectionHttpClient( public UrlConnectionHttpClient( String username, String password, String userAgent, String acceptedMediaType, String version, String authKey, String authIV) { - this(username, password, userAgent, acceptedMediaType, version, authKey, authIV, null,null); + this(username, password, userAgent, acceptedMediaType, version, authKey, authIV, null,null, null); } public UrlConnectionHttpClient(String username, String password, String userAgent, String acceptedMediaType, - String version, String authKey, String authIV, ISSLCertificateCallback callback, Integer configTimeout) { + String version, String authKey, String authIV, ISSLCertificateCallback callback, Integer configTimeout, String excludedSSLCipherRegex) { this.username = username; this.password = password; this.userAgent = userAgent; @@ -84,6 +91,7 @@ public UrlConnectionHttpClient(String username, String password, String userAgen this.authIV = authIV; this.sslAuthorizationCallback = callback; this.configTimeout = configTimeout; + this.excludedSSLCipherRegex = excludedSSLCipherRegex; } @Override @@ -140,11 +148,7 @@ protected String request(HttpMethod httpMethod, URL url, IMediaType requestMedia connection = createConnection( url, username, password, authKey, authIV, userAgent, acceptedVersion, acceptedMediaType, sslAuthorizationCallback, timeout); // PATCH not yet supported by JVM - if (httpMethod == HttpMethod.PATCH) { - httpMethod = HttpMethod.POST; - connection.setRequestProperty("X-Http-Method-Override", "PATCH"); - } - connection.setRequestMethod(httpMethod.toString()); + setRequestMethod(httpMethod, connection); if (!parameters.isEmpty()) { connection.setDoOutput(true); setRequestMediaType(requestMediaType, connection); @@ -159,6 +163,14 @@ protected String request(HttpMethod httpMethod, URL url, IMediaType requestMedia disconnect(connection); } } + + private void setRequestMethod(HttpMethod httpMethod, HttpURLConnection connection) throws ProtocolException { + if (httpMethod == HttpMethod.PATCH) { + httpMethod = HttpMethod.POST; + connection.setRequestProperty("X-Http-Method-Override", "PATCH"); + } + connection.setRequestMethod(httpMethod.toString()); + } private void disconnect(HttpURLConnection connection) { if (connection != null) { @@ -215,7 +227,11 @@ protected HttpURLConnection createConnection(URL url, String username, String pa "creating connection to {} using username \"{}\" and password \"{}\"", new Object[] { url, username, password }); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - setSSLCallback(url, connection); + if (isHttps(url)) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; + SSLContext sslContext = setSSLCallback(sslAuthorizationCallback, url, httpsConnection); + setFilteredCiphers(excludedSSLCipherRegex, sslContext, httpsConnection); + } setAuthorisation(username, password, authKey, authIV, connection); connection.setUseCaches(false); connection.setDoInput(true); @@ -268,37 +284,71 @@ private void setAuthorisation(String username, String password, String authKey, } } - private void setSSLCallback(URL url, HttpURLConnection connection) { - if (isHttps(url) - && sslAuthorizationCallback != null) { - HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; - httpsConnection.setHostnameVerifier(new CallbackHostnameVerifier()); - setupTrustManagerCallback(httpsConnection);; + private SSLContext setSSLCallback(ISSLCertificateCallback sslAuthorizationCallback, URL url, HttpsURLConnection connection) { + X509TrustManager trustManager = null; + if (sslAuthorizationCallback != null) { + connection.setHostnameVerifier(new CallbackHostnameVerifier()); + trustManager = createCallbackTrustManager(sslAuthorizationCallback, connection); + } + + try { + SSLContext sslContext = SSLUtils.getSSLContext(trustManager); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + return sslContext; + } catch (GeneralSecurityException e) { + LOGGER.warn("Could not install trust manager callback", e);; + return null; } } /** - * Sets the trust manager callbacks to the given connection + * Returns the callback trustmanager or null if it could not be created. * * @see ISSLCertificateCallback */ - private void setupTrustManagerCallback(HttpsURLConnection connection) { + private X509TrustManager createCallbackTrustManager(ISSLCertificateCallback sslAuthorizationCallback,HttpsURLConnection connection) { + X509TrustManager trustManager = null; try { - SSLContext sslContext = SSLContext.getInstance("SSL"); - X509TrustManager trustManager = getCurrentTrustManager(); + trustManager = getCurrentTrustManager(); if (trustManager == null) { LOGGER.warn("Could not install trust manager callback, no trustmanager was found.", trustManager); } else { - sslContext.init(null, new TrustManager[] { - new CallbackTrustManager(trustManager, sslAuthorizationCallback) }, null); - SSLSocketFactory socketFactory = sslContext.getSocketFactory(); - ((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory); + trustManager = new CallbackTrustManager(trustManager, sslAuthorizationCallback); } } catch (GeneralSecurityException e) { - LOGGER.warn("Could not install trust manager callback", e);; + LOGGER.warn("Could not install trust manager callback.", e);; } + return trustManager; } - + + /** + * Sets a ssl socket factory that sets a filtered list of ciphers based on + * the #excludedSSLCipherRegex to the given connection. + * + * @param sslContext + * + * @param sslContext + * the ssl context that shall be used + * @param url + * the url we are connecting to + * @param connection + * the connection that the cipher filter shall be applied to + */ + protected SSLContext setFilteredCiphers(String excludedSSLCipherRegex, SSLContext sslContext, HttpsURLConnection connection) { + if (excludedSSLCipherRegex != null) { + connection.setSSLSocketFactory( + new EnabledCiphersSSLSocketFactory( + SSLUtils.filterCiphers( + excludedSSLCipherRegex, getSupportedCiphers(sslContext)), sslContext + .getSocketFactory())); + } + return sslContext; + } + + protected String[] getSupportedCiphers(SSLContext sslContext) { + return sslContext.getSupportedSSLParameters().getCipherSuites(); + } + private void setConnectTimeout(int timeout, URLConnection connection) { if (getTimeout(timeout) != NO_TIMEOUT) { connection.setConnectTimeout(getTimeout(timeout)); @@ -309,7 +359,6 @@ private void setReadTimeout(int timeout, URLConnection connection) { if (getTimeout(timeout) != NO_TIMEOUT) { connection.setReadTimeout(getTimeout(timeout)); } - } private int getTimeout(int timeout) { @@ -331,14 +380,6 @@ private void setRequestMediaType(IMediaType mediaType, HttpURLConnection connect connection.setRequestProperty(PROPERTY_CONTENT_TYPE, mediaType.getType()); } - private int getSystemPropertyInteger(String key) { - try { - return Integer.parseInt(System.getProperty(key)); - } catch (NumberFormatException e) { - return NO_TIMEOUT; - } - } - private X509TrustManager getCurrentTrustManager() throws NoSuchAlgorithmException, KeyStoreException { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); @@ -374,7 +415,8 @@ public class CallbackTrustManager implements X509TrustManager { private X509TrustManager trustManager; private ISSLCertificateCallback callback; - private CallbackTrustManager(X509TrustManager currentTrustManager, ISSLCertificateCallback callback) throws NoSuchAlgorithmException, KeyStoreException { + private CallbackTrustManager(X509TrustManager currentTrustManager, ISSLCertificateCallback callback) + throws NoSuchAlgorithmException, KeyStoreException { this.trustManager = currentTrustManager; this.callback = callback; } @@ -404,7 +446,71 @@ private class CallbackHostnameVerifier implements HostnameVerifier { public boolean verify(String hostname, SSLSession session) { return sslAuthorizationCallback.allowHostname(hostname, session); } + } + + /** + * SSL socket factory that wraps a given socket factory and sets given ciphers + * to the socket that the wrapped factory creates. + * + * @see http://stackoverflow.com/questions/6851461/java-why-does-ssl-handshake-give-could-not-generate-dh-keypair-exception/16686994#16686994 + */ + private static class EnabledCiphersSSLSocketFactory extends SSLSocketFactory { + + private String[] enabledCiphers; + private SSLSocketFactory socketFactory; + + EnabledCiphersSSLSocketFactory(String[] enabledCiphers, SSLSocketFactory socketFactory) { + this.enabledCiphers = enabledCiphers; + this.socketFactory = socketFactory; + } + + @Override + public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { + return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port, localHost, localPort)); + } + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return setEnabledCiphers((SSLSocket) socketFactory.createSocket(host, port)); + } + + @Override + public String[] getSupportedCipherSuites() { + if (enabledCiphers == null) { + return socketFactory.getSupportedCipherSuites(); + } else { + return enabledCiphers; + } + } + + @Override + public String[] getDefaultCipherSuites() { + return socketFactory.getDefaultCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { + return setEnabledCiphers((SSLSocket) socketFactory.createSocket(socket, host, port, autoClose)); + } + + private SSLSocket setEnabledCiphers(SSLSocket socket) { + if (enabledCiphers == null) { + return socket; + } + socket.setEnabledCipherSuites(enabledCiphers); + return socket; + } } } diff --git a/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClientBuilder.java b/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClientBuilder.java index 13f9a068..cbcd4ca0 100755 --- a/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClientBuilder.java +++ b/src/main/java/com/openshift/internal/client/httpclient/UrlConnectionHttpClientBuilder.java @@ -28,6 +28,7 @@ public class UrlConnectionHttpClientBuilder { private String version; private Integer configTimeout; private ISSLCertificateCallback callback; + private String excludeSSLCipherRegex; public UrlConnectionHttpClientBuilder setUserAgent(String userAgent) { this.userAgent = userAgent; @@ -65,8 +66,13 @@ public UrlConnectionHttpClientBuilder setVersion(String version) { return this; } + public UrlConnectionHttpClientBuilder excludeSSLCipher(String excludeSSLCipherRegex) { + this.excludeSSLCipherRegex = excludeSSLCipherRegex; + return this; + } + public IHttpClient client() { return new UrlConnectionHttpClient( - username, password, userAgent, acceptedMediaType, version, authKey, authIV, callback, configTimeout); + username, password, userAgent, acceptedMediaType, version, authKey, authIV, callback, configTimeout, excludeSSLCipherRegex); } } diff --git a/src/test/java/com/openshift/client/fakes/PayLoadReturningHttpClientFake.java b/src/test/java/com/openshift/client/fakes/PayLoadReturningHttpClientFake.java index 3886990e..237e7d76 100644 --- a/src/test/java/com/openshift/client/fakes/PayLoadReturningHttpClientFake.java +++ b/src/test/java/com/openshift/client/fakes/PayLoadReturningHttpClientFake.java @@ -45,6 +45,7 @@ protected PayLoadReturningHttpClientFake(OpenShiftTestConfiguration configuratio null, null, null, + null, null); } diff --git a/src/test/java/com/openshift/client/fakes/SSLCipherOpenShiftConnectionFactoryFake.java b/src/test/java/com/openshift/client/fakes/SSLCipherOpenShiftConnectionFactoryFake.java new file mode 100644 index 00000000..68caeeba --- /dev/null +++ b/src/test/java/com/openshift/client/fakes/SSLCipherOpenShiftConnectionFactoryFake.java @@ -0,0 +1,103 @@ +package com.openshift.client.fakes; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; + +import com.openshift.client.IHttpClient; +import com.openshift.client.IHttpClient.ISSLCertificateCallback; +import com.openshift.client.IOpenShiftConnection; +import com.openshift.client.OpenShiftException; +import com.openshift.client.configuration.AbstractOpenshiftConfiguration.ConfigurationOptions; +import com.openshift.client.configuration.IOpenShiftConfiguration; +import com.openshift.client.utils.OpenShiftTestConfiguration; +import com.openshift.client.utils.SSLUtils; +import com.openshift.client.utils.TestConnectionFactory; +import com.openshift.internal.client.APIResource; +import com.openshift.internal.client.IRestService; +import com.openshift.internal.client.httpclient.UrlConnectionHttpClient; +import com.openshift.internal.client.response.Link; + +public class SSLCipherOpenShiftConnectionFactoryFake extends TestConnectionFactory { + + private FilteredCiphersClientFake client; + private ConfigurationOptions disableBadCiphers; + + public SSLCipherOpenShiftConnectionFactoryFake(ConfigurationOptions disableBadCiphers) { + this.disableBadCiphers = disableBadCiphers; + } + + public String[] getSupportedCiphers() throws MalformedURLException, IOException, KeyManagementException, NoSuchAlgorithmException { + getConnection(); // create client + junit.framework.Assert.assertNotNull("http client was not created yet", client); + return client.getSupportedCiphers(SSLUtils.getSSLContext(null)); + } + + public String[] getFilteredCiphers() throws MalformedURLException, IOException { + getConnection(); // create client + junit.framework.Assert.assertNotNull("http client was not created yet", client); + return client.getFilteredCiphers(); + } + + @Override + protected IOpenShiftConfiguration createConfiguration() throws OpenShiftException { + try { + return new OpenShiftTestConfiguration() { + + @Override + public ConfigurationOptions getDisableBadSSLCiphers() { + return disableBadCiphers; + } + + }; + } catch (IOException e) { + throw new OpenShiftException(e, "Could not create OpenShift configuration"); + } + } + + @Override + protected IHttpClient createClient(String clientId, String username, String password, String authKey, + String authIV, String serverUrl, ISSLCertificateCallback sslCertificateCallback, + String exludeSSLCipherRegex) { + return this.client = new FilteredCiphersClientFake(clientId, username, password, null, null, null, authKey, authIV, + sslCertificateCallback, IHttpClient.NO_TIMEOUT, exludeSSLCipherRegex); + } + + @Override + protected IOpenShiftConnection getConnection(IRestService service, final String login, final String password) throws IOException, OpenShiftException { + return new APIResource(login, password, service, new HashMap()) {}; + } + + public class FilteredCiphersClientFake extends UrlConnectionHttpClient { + + private FilteredCiphersClientFake(String clientId, String username, String password, String userAgent, String mediaType, + String acceptVersion, String authKey, String authIv, ISSLCertificateCallback callback, int timeout, + String excludedSSLCipherRegex) { + super(username, password, userAgent, mediaType, acceptVersion, authKey, authIv, callback, timeout, + excludedSSLCipherRegex); + + } + + @Override + protected String[] getSupportedCiphers(SSLContext sslContext) { + try { + return SSLUtils.getSSLContext(null).getServerSocketFactory().getSupportedCipherSuites(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + public String[] getFilteredCiphers() throws MalformedURLException, IOException { + HttpsURLConnection connection = (HttpsURLConnection) createConnection(new URL("https://localhost"), username, password, authKey, + authIV, userAgent, acceptedVersion, acceptedMediaType, null, IHttpClient.NO_TIMEOUT); + return connection.getSSLSocketFactory().getSupportedCipherSuites(); + } + } +} diff --git a/src/test/java/com/openshift/client/utils/OpenShiftTestConfiguration.java b/src/test/java/com/openshift/client/utils/OpenShiftTestConfiguration.java index 461dfaa3..e71e48d9 100644 --- a/src/test/java/com/openshift/client/utils/OpenShiftTestConfiguration.java +++ b/src/test/java/com/openshift/client/utils/OpenShiftTestConfiguration.java @@ -20,9 +20,6 @@ import com.openshift.client.configuration.AbstractOpenshiftConfiguration; import com.openshift.client.configuration.DefaultConfiguration; import com.openshift.client.configuration.IOpenShiftConfiguration; -import com.openshift.client.configuration.SystemConfiguration; -import com.openshift.client.configuration.SystemProperties; -import com.openshift.client.configuration.UserConfiguration; import com.openshift.client.fakes.SystemConfigurationFake; import com.openshift.client.fakes.SystemPropertiesFake; import com.openshift.client.fakes.UserConfigurationFake; diff --git a/src/test/java/com/openshift/internal/client/ConfigurationTest.java b/src/test/java/com/openshift/internal/client/ConfigurationTest.java index 7f0e799a..6fe36747 100644 --- a/src/test/java/com/openshift/internal/client/ConfigurationTest.java +++ b/src/test/java/com/openshift/internal/client/ConfigurationTest.java @@ -10,6 +10,7 @@ ******************************************************************************/ package com.openshift.internal.client; +import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -27,6 +28,7 @@ import org.junit.Test; import com.openshift.client.OpenShiftException; +import com.openshift.client.configuration.AbstractOpenshiftConfiguration.ConfigurationOptions; import com.openshift.client.configuration.DefaultConfiguration; import com.openshift.client.configuration.IOpenShiftConfiguration; import com.openshift.client.configuration.SystemConfiguration; @@ -258,4 +260,42 @@ public void fallsBackToDefaultUrl() throws OpenShiftException, IOException { assertTrue(configuration.getLibraServer().contains(DefaultConfiguration.LIBRA_SERVER)); assertNull(configuration.getRhlogin()); } + + @Test + public void disableBadSSLCiphersShouldDefaultToNo() throws OpenShiftException, IOException { + // pre-condition + SystemConfiguration systemConfiguration = new SystemConfigurationFake(new DefaultConfiguration()) { + + @Override + protected void init(Properties properties) { + properties.put(KEY_DISABLE_BAD_SSL_CIPHERS, "bingobongo"); + } + + }; + + // operation + ConfigurationOptions option = systemConfiguration.getDisableBadSSLCiphers(); + + // verification + assertThat(option).isEqualTo(ConfigurationOptions.NO); + } + + @Test + public void disableBadSSLCiphersShouldbeYes() throws OpenShiftException, IOException { + // pre-condition + SystemConfiguration systemConfiguration = new SystemConfigurationFake(new DefaultConfiguration()) { + + @Override + protected void init(Properties properties) { + properties.put(KEY_DISABLE_BAD_SSL_CIPHERS, "\"yes\""); + } + + }; + + // operation + ConfigurationOptions option = systemConfiguration.getDisableBadSSLCiphers(); + + // verification + assertThat(option).isEqualTo(ConfigurationOptions.YES); + } } diff --git a/src/test/java/com/openshift/internal/client/httpclient/HttpClientTest.java b/src/test/java/com/openshift/internal/client/httpclient/HttpClientTest.java index 219d6ac2..997079c9 100755 --- a/src/test/java/com/openshift/internal/client/httpclient/HttpClientTest.java +++ b/src/test/java/com/openshift/internal/client/httpclient/HttpClientTest.java @@ -19,14 +19,12 @@ import java.io.FileNotFoundException; import java.io.IOException; -import java.io.Writer; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; import java.net.URL; import java.security.KeyStoreException; import java.security.cert.X509Certificate; -import java.util.Properties; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -40,9 +38,7 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; -import com.openshift.client.configuration.*; -import com.openshift.client.fakes.*; -import com.openshift.internal.client.TestTimer; +import org.fest.assertions.Condition; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -52,11 +48,20 @@ import com.openshift.client.IHttpClient; import com.openshift.client.IHttpClient.ISSLCertificateCallback; import com.openshift.client.OpenShiftException; +import com.openshift.client.configuration.AbstractOpenshiftConfiguration.ConfigurationOptions; +import com.openshift.client.configuration.IOpenShiftConfiguration; +import com.openshift.client.fakes.HttpServerFake; +import com.openshift.client.fakes.HttpsServerFake; +import com.openshift.client.fakes.OpenShiftConfigurationFake; +import com.openshift.client.fakes.PayLoadReturningHttpClientFake; +import com.openshift.client.fakes.SSLCipherOpenShiftConnectionFactoryFake; +import com.openshift.client.fakes.WaitingHttpServerFake; import com.openshift.client.utils.Base64Coder; import com.openshift.client.utils.ExceptionCauseMatcher; +import com.openshift.client.utils.SSLUtils; +import com.openshift.internal.client.TestTimer; import com.openshift.internal.client.httpclient.request.FormUrlEncodedMediaType; import com.openshift.internal.client.httpclient.request.StringParameter; -import sun.net.www.http.HttpClient; /** * @author Andre Dietisheim @@ -623,6 +628,26 @@ public Long call() throws Exception { } } + @Test + public void shouldFilterBadSSLCiphers() throws Throwable { + // pre-conditions + // operations + SSLCipherOpenShiftConnectionFactoryFake factory = + new SSLCipherOpenShiftConnectionFactoryFake(ConfigurationOptions.YES); + // verification + assertThat(factory.getFilteredCiphers()).satisfies(new NoDHECiphersCondition()); + } + + @Test + public void shouldNotFilterBadSSLCiphers() throws Throwable { + // pre-conditions + // operations + SSLCipherOpenShiftConnectionFactoryFake factory = + new SSLCipherOpenShiftConnectionFactoryFake(ConfigurationOptions.NO); + // verification + assertThat(factory.getSupportedCiphers()).isEqualTo(factory.getFilteredCiphers()); + } + private HttpServerFake startHttpServerFake(String statusLine) throws Exception { int port = new Random().nextInt(9 * 1024) + 1024; HttpServerFake serverFake = null; @@ -653,16 +678,20 @@ private WaitingHttpServerFake startWaitingHttpServerFake(int delay) throws Excep return serverFake; } - private class UserAgentClientFake extends UrlConnectionHttpClientFake { - - public UserAgentClientFake(String userAgent) { - super(userAgent, null); - } - - public String getUserAgent(HttpURLConnection connection) { - return connection.getRequestProperty(PROPERTY_USER_AGENT); + private static final class NoDHECiphersCondition extends Condition { + @Override + public boolean matches(Object[] ciphers) { + for (Object cipher : ciphers) { + if (!(cipher instanceof String)) { + return false; + } + // no DHE ciphers left + if (((String) cipher).matches(SSLUtils.CIPHER_DHE_REGEX)) { + return false; + } + } + return true; } - } private class AcceptVersionClientFake extends UrlConnectionHttpClientFake { @@ -679,12 +708,12 @@ public String getAcceptHeader(HttpURLConnection connection) { private abstract class UrlConnectionHttpClientFake extends UrlConnectionHttpClient { private UrlConnectionHttpClientFake(String userAgent, String acceptVersion) { super("username", "password", userAgent, IHttpClient.MEDIATYPE_APPLICATION_JSON, acceptVersion, - "authkey", "authiv", null,IHttpClient.NO_TIMEOUT); + "authkey", "authiv", null,IHttpClient.NO_TIMEOUT, null); } private UrlConnectionHttpClientFake(String userAgent, String acceptVersion, ISSLCertificateCallback callback) { super("username", "password", userAgent, IHttpClient.MEDIATYPE_APPLICATION_JSON, acceptVersion, - "authkey", "authiv", callback,IHttpClient.NO_TIMEOUT); + "authkey", "authiv", callback,IHttpClient.NO_TIMEOUT, null); } public HttpURLConnection createConnection() throws IOException, KeyStoreException {