/** * 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.camel.util.jsse; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.KeyManager; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import org.apache.camel.util.jsse.FilterParameters.Patterns; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.camel.util.CollectionHelper.collectionAsCommaDelimitedString; /** * Represents configuration options that can be applied in the client-side * or server-side context depending on what they are applied to. */ public abstract class BaseSSLContextParameters extends JsseParameters { protected static final List<String> DEFAULT_CIPHER_SUITES_FILTER_INCLUDE = Collections.unmodifiableList(Arrays.asList(".*")); protected static final List<String> DEFAULT_CIPHER_SUITES_FILTER_EXCLUDE = Collections.unmodifiableList(Arrays.asList(".*_NULL_.*", ".*_anon_.*", ".*_EXPORT_.*", ".*_DES_.*")); protected static final List<String> DEFAULT_SECURE_SOCKET_PROTOCOLS_FILTER_INCLUDE = Collections.unmodifiableList(Arrays.asList(".*")); protected static final List<String> DEFAULT_SECURE_SOCKET_PROTOCOLS_FILTER_EXCLUDE = Collections.unmodifiableList(Arrays.asList("SSL.*")); private static final Logger LOG = LoggerFactory.getLogger(BaseSSLContextParameters.class); private static final String LS = System.getProperty("line.separator"); private static final String SSL_ENGINE_CIPHER_SUITE_LOG_MSG = createCipherSuiteLogMessage("SSLEngine"); private static final String SSL_SOCKET_CIPHER_SUITE_LOG_MSG = createCipherSuiteLogMessage("SSLSocket"); private static final String SSL_SERVER_SOCKET_CIPHER_SUITE_LOG_MSG = createCipherSuiteLogMessage("SSLServerSocket"); private static final String SSL_ENGINE_PROTOCOL_LOG_MSG = createProtocolLogMessage("SSLEngine"); private static final String SSL_SOCKET_PROTOCOL_LOG_MSG = createProtocolLogMessage("SSLSocket"); private static final String SSL_SERVER_SOCKET_PROTOCOL_LOG_MSG = createProtocolLogMessage("SSLServerSocket"); /** * The optional explicitly configured cipher suites for this configuration. */ private CipherSuitesParameters cipherSuites; /** * The optional cipher suite filter configuration for this configuration. */ private FilterParameters cipherSuitesFilter; /** * The optional explicitly configured secure socket protocol names for this configuration. */ private SecureSocketProtocolsParameters secureSocketProtocols; /** * The option secure socket protocol name filter configuration for this configuration. */ private FilterParameters secureSocketProtocolsFilter; /** * The optional {@link SSLSessionContext} timeout time for {@link javax.net.ssl.SSLSession}s in seconds. */ private String sessionTimeout; protected List<SNIServerName> getSNIHostNames() { return Collections.emptyList(); } /** * Returns the optional explicitly configured cipher suites for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values override any filters supplied in {@link #setCipherSuitesFilter(FilterParameters)} */ public CipherSuitesParameters getCipherSuites() { return cipherSuites; } /** * Sets the optional explicitly configured cipher suites for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values override any filters supplied in {@link #setCipherSuitesFilter(FilterParameters)} * * @param cipherSuites the suite configuration */ public void setCipherSuites(CipherSuitesParameters cipherSuites) { this.cipherSuites = cipherSuites; } /** * Returns the optional cipher suite filter for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values are ignored if {@link #setCipherSuites(CipherSuitesParameters)} is * called with a non {@code null} argument. */ public FilterParameters getCipherSuitesFilter() { return cipherSuitesFilter; } /** * Sets the optional cipher suite filter for this JSSE configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values are ignored if {@link #setCipherSuites(CipherSuitesParameters)} is * called with a non {@code null} argument. * * @param cipherSuitesFilter the filter configuration */ public void setCipherSuitesFilter(FilterParameters cipherSuitesFilter) { this.cipherSuitesFilter = cipherSuitesFilter; } /** * Returns the explicitly configured secure socket protocol names for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values override any filters supplied in {@link #setSecureSocketProtocolsFilter(FilterParameters)} */ public SecureSocketProtocolsParameters getSecureSocketProtocols() { return secureSocketProtocols; } /** * Sets the explicitly configured secure socket protocol names for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values override any filters supplied in {@link #setSecureSocketProtocolsFilter(FilterParameters)} */ public void setSecureSocketProtocols(SecureSocketProtocolsParameters secureSocketProtocols) { this.secureSocketProtocols = secureSocketProtocols; } /** * Returns the optional secure socket protocol filter for this configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values are ignored if {@link #setSecureSocketProtocols(SecureSocketProtocolsParameters)} is * called with a non-{@code null} argument. */ public FilterParameters getSecureSocketProtocolsFilter() { return secureSocketProtocolsFilter; } /** * Sets the optional secure socket protocol filter for this JSSE configuration. * These options are used in the configuration of {@link SSLEngine}, * {@link SSLSocketFactory} and {@link SSLServerSocketFactory} depending * on the context in which they are applied. * <p/> * These values are ignored if {@link #setSecureSocketProtocols(SecureSocketProtocolsParameters)} is * called with a non-{@code null} argument. * * @param secureSocketProtocolsFilter the filter configuration */ public void setSecureSocketProtocolsFilter(FilterParameters secureSocketProtocolsFilter) { this.secureSocketProtocolsFilter = secureSocketProtocolsFilter; } /** * Returns the optional {@link SSLSessionContext} timeout time for {@link javax.net.ssl.SSLSession}s * in seconds. */ public String getSessionTimeout() { return sessionTimeout; } /** * Sets the optional {@link SSLSessionContext} timeout time for {@link javax.net.ssl.SSLSession}s * in seconds. * * @param sessionTimeout the timeout value or {@code null} to use the default */ public void setSessionTimeout(String sessionTimeout) { this.sessionTimeout = sessionTimeout; } /** * Returns a flag indicating if default values should be applied in the event that no other property * of the instance configures a particular aspect of the entity produced by the instance. * This flag is used to allow instances of this class to produce a configurer that simply * passes through the current configuration of a configured entity when the instance of this * class would otherwise only apply some default configuration. * * @see SSLContextClientParameters * @see SSLContextServerParameters */ protected boolean getAllowPassthrough() { return false; } /** * Configures the actual {@link SSLContext} itself with direct setter calls. This method differs from * configuration options that are handled by a configurer instance in that the options are part of the * context itself and are not part of some factory or instance object returned by the context. * * @param context the context to configure * * @throws GeneralSecurityException if there is an error configuring the context */ protected void configureSSLContext(SSLContext context) throws GeneralSecurityException { LOG.trace("Configuring client and server side SSLContext parameters on SSLContext [{}]...", context); if (this.getSessionTimeout() != null) { LOG.debug("Configuring client and server side SSLContext session timeout on SSLContext [{}] to [{}]", context, this.getSessionTimeout()); this.configureSessionContext(context.getClientSessionContext(), this.getSessionTimeout()); this.configureSessionContext(context.getServerSessionContext(), this.getSessionTimeout()); } LOG.trace("Configured client and server side SSLContext parameters on SSLContext [{}].", context); } protected FilterParameters getDefaultCipherSuitesFilter() { FilterParameters filter = new FilterParameters(); filter.getInclude().addAll(DEFAULT_CIPHER_SUITES_FILTER_INCLUDE); filter.getExclude().addAll(DEFAULT_CIPHER_SUITES_FILTER_EXCLUDE); return filter; } protected FilterParameters getDefaultSecureSocketProcotolFilter() { FilterParameters filter = new FilterParameters(); filter.getInclude().addAll(DEFAULT_SECURE_SOCKET_PROTOCOLS_FILTER_INCLUDE); filter.getExclude().addAll(DEFAULT_SECURE_SOCKET_PROTOCOLS_FILTER_EXCLUDE); return filter; } /** * Returns the list of configurers to apply to an {@link SSLEngine} in order * to fully configure it in compliance with the provided configuration options. * The configurers are to be applied in the order in which they appear in the list. * * @param context the context that serves as the factory for {@code SSLEngine} instances * * @return the needed configurers */ protected List<Configurer<SSLEngine>> getSSLEngineConfigurers(SSLContext context) { final List<String> enabledCipherSuites = this.getCipherSuites() == null ? null : this.parsePropertyValues(this.getCipherSuites().getCipherSuite()); final Patterns enabledCipherSuitePatterns; final Patterns defaultEnabledCipherSuitePatterns = this.getDefaultCipherSuitesFilter().getPatterns(); if (this.getCipherSuitesFilter() != null) { enabledCipherSuitePatterns = this.getCipherSuitesFilter().getPatterns(); } else { enabledCipherSuitePatterns = null; } /// final List<String> enabledSecureSocketProtocols = this.getSecureSocketProtocols() == null ? null : this.parsePropertyValues(this.getSecureSocketProtocols().getSecureSocketProtocol()); final Patterns enabledSecureSocketProtocolsPatterns; final Patterns defaultEnabledSecureSocketProtocolsPatterns = this.getDefaultSecureSocketProcotolFilter().getPatterns(); if (this.getSecureSocketProtocolsFilter() != null) { enabledSecureSocketProtocolsPatterns = this.getSecureSocketProtocolsFilter().getPatterns(); } else { enabledSecureSocketProtocolsPatterns = null; } // final boolean allowPassthrough = getAllowPassthrough(); ////// Configurer<SSLEngine> sslEngineConfigurer = new Configurer<SSLEngine>() { @Override public SSLEngine configure(SSLEngine engine) { Collection<String> filteredCipherSuites = BaseSSLContextParameters.this .filter(enabledCipherSuites, Arrays.asList(engine.getSSLParameters().getCipherSuites()), Arrays.asList(engine.getEnabledCipherSuites()), enabledCipherSuitePatterns, defaultEnabledCipherSuitePatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_ENGINE_CIPHER_SUITE_LOG_MSG, new Object[] {engine, enabledCipherSuites, enabledCipherSuitePatterns, engine.getSSLParameters().getCipherSuites(), engine.getEnabledCipherSuites(), defaultEnabledCipherSuitePatterns, filteredCipherSuites}); } engine.setEnabledCipherSuites(filteredCipherSuites.toArray(new String[filteredCipherSuites.size()])); Collection<String> filteredSecureSocketProtocols = BaseSSLContextParameters.this .filter(enabledSecureSocketProtocols, Arrays.asList(engine.getSSLParameters().getProtocols()), Arrays.asList(engine.getEnabledProtocols()), enabledSecureSocketProtocolsPatterns, defaultEnabledSecureSocketProtocolsPatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_ENGINE_PROTOCOL_LOG_MSG, new Object[] {engine, enabledSecureSocketProtocols, enabledSecureSocketProtocolsPatterns, engine.getSSLParameters().getProtocols(), engine.getEnabledProtocols(), defaultEnabledSecureSocketProtocolsPatterns, filteredSecureSocketProtocols}); } engine.setEnabledProtocols(filteredSecureSocketProtocols.toArray(new String[filteredSecureSocketProtocols.size()])); return engine; } }; List<Configurer<SSLEngine>> sslEngineConfigurers = new LinkedList<Configurer<SSLEngine>>(); sslEngineConfigurers.add(sslEngineConfigurer); return sslEngineConfigurers; } /** * Returns the list of configurers to apply to an {@link SSLSocketFactory} in order * to fully configure it in compliance with the provided configuration options. * The configurers are to be applied in the order in which they appear in the list. * <p/> * It is preferred to use {@link #getSSLSocketFactorySSLSocketConfigurers(SSLContext)} instead * of this method as {@code SSLSocketFactory} does not contain any configuration options that * are non-proprietary. * * @param context the context that serves as the factory for {@code SSLSocketFactory} instances * * @return the needed configurers * * @see #getSSLSocketFactorySSLSocketConfigurers(SSLContext) */ protected List<Configurer<SSLSocketFactory>> getSSLSocketFactoryConfigurers(SSLContext context) { final List<Configurer<SSLSocket>> sslSocketConfigurers = this.getSSLSocketFactorySSLSocketConfigurers(context); Configurer<SSLSocketFactory> sslSocketFactoryConfigurer = new Configurer<SSLSocketFactory>() { @Override public SSLSocketFactory configure(SSLSocketFactory factory) { return new SSLSocketFactoryDecorator( factory, sslSocketConfigurers); } }; List<Configurer<SSLSocketFactory>> sslSocketFactoryConfigurers = new LinkedList<Configurer<SSLSocketFactory>>(); sslSocketFactoryConfigurers.add(sslSocketFactoryConfigurer); return sslSocketFactoryConfigurers; } /** * Returns the list of configurers to apply to an {@link SSLServerSocketFactory} in order * to fully configure it in compliance with the provided configuration options. * The configurers are to be applied in the order in which they appear in the list. * <p/> * It is preferred to use {@link #getSSLServerSocketFactorySSLServerSocketConfigurers(SSLContext)} instead * of this method as {@code SSLServerSocketFactory} does not contain any configuration options that * are non-proprietary. * * @param context the context that serves as the factory for {@code SSLServerSocketFactory} instances * * @return the needed configurers * * @see #getSSLServerSocketFactorySSLServerSocketConfigurers(SSLContext) */ protected List<Configurer<SSLServerSocketFactory>> getSSLServerSocketFactoryConfigurers(SSLContext context) { final List<Configurer<SSLServerSocket>> sslServerSocketConfigurers = this.getSSLServerSocketFactorySSLServerSocketConfigurers(context); Configurer<SSLServerSocketFactory> sslServerSocketFactoryConfigurer = new Configurer<SSLServerSocketFactory>() { @Override public SSLServerSocketFactory configure(SSLServerSocketFactory factory) { return new SSLServerSocketFactoryDecorator( factory, sslServerSocketConfigurers); } }; List<Configurer<SSLServerSocketFactory>> sslServerSocketFactoryConfigurers = new LinkedList<Configurer<SSLServerSocketFactory>>(); sslServerSocketFactoryConfigurers.add(sslServerSocketFactoryConfigurer); return sslServerSocketFactoryConfigurers; } /** * Returns the list of configurers to apply to an {@link SSLSocket} in order * to fully configure it in compliance with the provided configuration * options. These configurers are intended for sockets produced by a * {@link SSLSocketFactory}, see * {@link #getSSLServerSocketFactorySSLServerSocketConfigurers(SSLContext)} for * configurers related to sockets produced by a * {@link SSLServerSocketFactory}. The configurers are to be applied in * the order in which they appear in the list. * * @param context the context that serves as the factory for * {@code SSLSocketFactory} instances * * @return the needed configurers */ protected List<Configurer<SSLSocket>> getSSLSocketFactorySSLSocketConfigurers(SSLContext context) { final List<String> enabledCipherSuites = this.getCipherSuites() == null ? null : this.parsePropertyValues(this.getCipherSuites().getCipherSuite()); final Patterns enabledCipherSuitePatterns; final Patterns defaultEnabledCipherSuitePatterns = this.getDefaultCipherSuitesFilter().getPatterns(); if (this.getCipherSuitesFilter() != null) { enabledCipherSuitePatterns = this.getCipherSuitesFilter().getPatterns(); } else { enabledCipherSuitePatterns = null; } /// final List<String> enabledSecureSocketProtocols = this.getSecureSocketProtocols() == null ? null : this.parsePropertyValues(this.getSecureSocketProtocols().getSecureSocketProtocol()); final Patterns enabledSecureSocketProtocolsPatterns; final Patterns defaultEnabledSecureSocketProtocolsPatterns = this.getDefaultSecureSocketProcotolFilter().getPatterns(); if (this.getSecureSocketProtocolsFilter() != null) { enabledSecureSocketProtocolsPatterns = this.getSecureSocketProtocolsFilter().getPatterns(); } else { enabledSecureSocketProtocolsPatterns = null; } // final boolean allowPassthrough = getAllowPassthrough(); ////// Configurer<SSLSocket> sslSocketConfigurer = new Configurer<SSLSocket>() { @Override public SSLSocket configure(SSLSocket socket) { if (!getSNIHostNames().isEmpty()) { SSLParameters sslParameters = socket.getSSLParameters(); sslParameters.setServerNames(getSNIHostNames()); socket.setSSLParameters(sslParameters); } Collection<String> filteredCipherSuites = BaseSSLContextParameters.this .filter(enabledCipherSuites, Arrays.asList(socket.getSSLParameters().getCipherSuites()), Arrays.asList(socket.getEnabledCipherSuites()), enabledCipherSuitePatterns, defaultEnabledCipherSuitePatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_SOCKET_CIPHER_SUITE_LOG_MSG, new Object[] {socket, enabledCipherSuites, enabledCipherSuitePatterns, socket.getSSLParameters().getCipherSuites(), socket.getEnabledCipherSuites(), defaultEnabledCipherSuitePatterns, filteredCipherSuites}); } socket.setEnabledCipherSuites(filteredCipherSuites.toArray(new String[filteredCipherSuites.size()])); Collection<String> filteredSecureSocketProtocols = BaseSSLContextParameters.this .filter(enabledSecureSocketProtocols, Arrays.asList(socket.getSSLParameters().getProtocols()), Arrays.asList(socket.getEnabledProtocols()), enabledSecureSocketProtocolsPatterns, defaultEnabledSecureSocketProtocolsPatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_SOCKET_PROTOCOL_LOG_MSG, new Object[] {socket, enabledSecureSocketProtocols, enabledSecureSocketProtocolsPatterns, socket.getSSLParameters().getProtocols(), socket.getEnabledProtocols(), defaultEnabledSecureSocketProtocolsPatterns, filteredSecureSocketProtocols}); } socket.setEnabledProtocols(filteredSecureSocketProtocols.toArray(new String[filteredSecureSocketProtocols.size()])); return socket; } }; List<Configurer<SSLSocket>> sslSocketConfigurers = new LinkedList<Configurer<SSLSocket>>(); sslSocketConfigurers.add(sslSocketConfigurer); return sslSocketConfigurers; } /** * Returns the list of configurers to apply to an {@link SSLServerSocket} in order * to fully configure it in compliance with the provided configuration * options. These configurers are intended for sockets produced by a * {@link SSLServerSocketFactory}, see * {@link #getSSLSocketFactorySSLSocketConfigurers(SSLContext)} for * configurers related to sockets produced by a * {@link SSLSocketFactory}. The configurers are to be applied in * the order in which they appear in the list. * * @param context the context that serves as the factory for * {@code SSLServerSocketFactory} instances * @return the needed configurers */ protected List<Configurer<SSLServerSocket>> getSSLServerSocketFactorySSLServerSocketConfigurers(SSLContext context) { final List<String> enabledCipherSuites = this.getCipherSuites() == null ? null : this.parsePropertyValues(this.getCipherSuites().getCipherSuite()); final Patterns enabledCipherSuitePatterns; final Patterns defaultEnabledCipherSuitePatterns = this.getDefaultCipherSuitesFilter().getPatterns(); if (this.getCipherSuitesFilter() != null) { enabledCipherSuitePatterns = this.getCipherSuitesFilter().getPatterns(); } else { enabledCipherSuitePatterns = null; } /// final List<String> enabledSecureSocketProtocols = this.getSecureSocketProtocols() == null ? null : this.parsePropertyValues(this.getSecureSocketProtocols().getSecureSocketProtocol()); final Patterns enabledSecureSocketProtocolsPatterns; final Patterns defaultEnabledSecureSocketProtocolsPatterns = this.getDefaultSecureSocketProcotolFilter().getPatterns(); if (this.getSecureSocketProtocolsFilter() != null) { enabledSecureSocketProtocolsPatterns = this.getSecureSocketProtocolsFilter().getPatterns(); } else { enabledSecureSocketProtocolsPatterns = null; } // final boolean allowPassthrough = getAllowPassthrough(); ////// Configurer<SSLServerSocket> sslServerSocketConfigurer = new Configurer<SSLServerSocket>() { @Override public SSLServerSocket configure(SSLServerSocket socket) { Collection<String> filteredCipherSuites = BaseSSLContextParameters.this .filter(enabledCipherSuites, Arrays.asList(socket.getSupportedCipherSuites()), Arrays.asList(socket.getEnabledCipherSuites()), enabledCipherSuitePatterns, defaultEnabledCipherSuitePatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_SERVER_SOCKET_CIPHER_SUITE_LOG_MSG, new Object[] {socket, enabledCipherSuites, enabledCipherSuitePatterns, socket.getSupportedCipherSuites(), socket.getEnabledCipherSuites(), defaultEnabledCipherSuitePatterns, filteredCipherSuites}); } socket.setEnabledCipherSuites(filteredCipherSuites.toArray(new String[filteredCipherSuites.size()])); Collection<String> filteredSecureSocketProtocols = BaseSSLContextParameters.this .filter(enabledSecureSocketProtocols, Arrays.asList(socket.getSupportedProtocols()), Arrays.asList(socket.getEnabledProtocols()), enabledSecureSocketProtocolsPatterns, defaultEnabledSecureSocketProtocolsPatterns, !allowPassthrough); if (LOG.isDebugEnabled()) { LOG.debug(SSL_SERVER_SOCKET_PROTOCOL_LOG_MSG, new Object[] {socket, enabledSecureSocketProtocols, enabledSecureSocketProtocolsPatterns, socket.getSupportedProtocols(), socket.getEnabledProtocols(), defaultEnabledSecureSocketProtocolsPatterns, filteredSecureSocketProtocols}); } socket.setEnabledProtocols(filteredSecureSocketProtocols.toArray(new String[filteredSecureSocketProtocols.size()])); return socket; } }; List<Configurer<SSLServerSocket>> sslServerSocketConfigurers = new LinkedList<Configurer<SSLServerSocket>>(); sslServerSocketConfigurers.add(sslServerSocketConfigurer); return sslServerSocketConfigurers; } /** * Configures a {@link SSLSessionContext}, client or server, with the supplied session timeout. * * @param sessionContext the context to configure * @param sessionTimeout the timeout time period * @throws GeneralSecurityException if {@code sessionContext} is {@code null} */ protected void configureSessionContext( SSLSessionContext sessionContext, String sessionTimeout) throws GeneralSecurityException { int sessionTimeoutInt = Integer.parseInt(this.parsePropertyValue(sessionTimeout)); if (sessionContext != null) { sessionContext.setSessionTimeout(sessionTimeoutInt); } else { throw new GeneralSecurityException( "The SSLContext does not support SSLSessionContext, " + "but a session timeout is configured. Set sessionTimeout to null " + "to avoid this error."); } } /** * Filters the values in {@code availableValues} returning only the values that * are explicitly listed in {@code explicitValues} (returns them regardless * of if they appear in {@code availableValues} or not) if {@code explicitValues} is not * {@code null} or according to the following rules: * <ol> * <li>Match the include patterns in {@code patterns} and don't match the exclude patterns in {@code patterns} * if patterns is not {@code null}.</li> * <li>Match the include patterns in {@code defaultPatterns} and don't match the exclude patterns in {@code defaultPatterns} * if patterns is {@code null} and {@code applyDefaults} is true.</li> * <li>Are provided in currentValues if if patterns is {@code null} and {@code applyDefaults} is false.</li> * </ol> * * @param explicitValues the optional explicit values to use * @param availableValues the available values to filter from * @param patterns the optional patterns to use when {@code explicitValues} is not used * @param defaultPatterns the required patterns to use when {@code explicitValues} and {@code patterns} are not used * @param applyDefaults flag indicating whether or not to apply defaults in the event that no explicit values and no * patterns apply * * @return the filtered values * * @see #filter(Collection, Collection, List, List) */ protected Collection<String> filter( Collection<String> explicitValues, Collection<String> availableValues, Collection<String> currentValues, Patterns patterns, Patterns defaultPatterns, boolean applyDefaults) { final List<Pattern> enabledIncludePatterns; final List<Pattern> enabledExcludePatterns; if (explicitValues == null && patterns == null && !applyDefaults) { return currentValues; } if (patterns != null) { enabledIncludePatterns = patterns.getIncludes(); enabledExcludePatterns = patterns.getExcludes(); } else { enabledIncludePatterns = defaultPatterns.getIncludes(); enabledExcludePatterns = defaultPatterns.getExcludes(); } return this.filter( explicitValues, availableValues, enabledIncludePatterns, enabledExcludePatterns); } /** * Filters the values in {@code availableValues} returning only the values that * are explicitly listed in {@code explicitValues} (returns them regardless * of if they appear in {@code availableValues} or not) if {@code explicitValues} is not * {@code null} or as match the patterns in {@code includePatterns} and do * not match the patterns in {@code excludePatterns} if {@code explicitValues} is {@code null}. * * @param explicitValues the optional explicit values to use * @param availableValues the available values to filter from if {@code explicitValues} is {@code null} * @param includePatterns the patterns to use for inclusion filtering, required if {@code explicitValues} is {@code null} * @param excludePatterns the patterns to use for exclusion filtering, required if {@code explicitValues} is {@code null} * * @return the filtered values */ protected Collection<String> filter(Collection<String> explicitValues, Collection<String> availableValues, List<Pattern> includePatterns, List<Pattern> excludePatterns) { Collection<String> returnValues; // Explicit list has precedence over filters, even when the list is // empty. if (explicitValues != null) { returnValues = new ArrayList<String>(explicitValues); } else { returnValues = new LinkedList<String>(); for (String value : availableValues) { if (this.matchesOneOf(value, includePatterns) && !this.matchesOneOf(value, excludePatterns)) { returnValues.add(value); } } } return returnValues; } /** * Returns true if and only if the value is matched by one or more of the supplied patterns. * * @param value the value to match * @param patterns the patterns to try to match against */ protected boolean matchesOneOf(String value, List<Pattern> patterns) { boolean matches = false; for (Pattern pattern : patterns) { Matcher matcher = pattern.matcher(value); if (matcher.matches()) { matches = true; break; } } return matches; } /** * Configures a {@code T} based on the related configuration options. */ interface Configurer<T> { /** * Configures a {@code T} based on the related configuration options. * The return value from this method may be {@code object} or it * may be a decorated instance there of. Consequently, any subsequent * actions on {@code object} must be performed using the returned value. * * @param object the object to configure * @return {@code object} or a decorated instance there of */ T configure(T object); } /** * Makes a decorated {@link SSLContext} appear as a normal {@code SSLContext}. */ protected static final class SSLContextDecorator extends SSLContext { public SSLContextDecorator(SSLContextSpiDecorator decorator) { super(decorator, decorator.getDelegate().getProvider(), decorator.getDelegate().getProtocol()); LOG.debug("SSLContextDecorator [{}] decorating SSLContext [{}].", this, decorator.getDelegate()); } @Override public String toString() { return String.format("SSLContext[hash=%h, provider=%s, protocol=%s, needClientAuth=%s, " + "wantClientAuth=%s\n\tdefaultProtocols=%s\n\tdefaultChiperSuites=%s\n\tsupportedProtocols=%s\n\tsupportedChiperSuites=%s\n]", hashCode(), getProvider(), getProtocol(), getDefaultSSLParameters().getNeedClientAuth(), getDefaultSSLParameters().getWantClientAuth(), collectionAsCommaDelimitedString(getDefaultSSLParameters().getProtocols()), collectionAsCommaDelimitedString(getDefaultSSLParameters().getCipherSuites()), collectionAsCommaDelimitedString(getSupportedSSLParameters().getProtocols()), collectionAsCommaDelimitedString(getSupportedSSLParameters().getCipherSuites())); } } /** * Class needed to provide decoration of an existing {@link SSLContext}. * Since {@code SSLContext} is an abstract class and requires an instance of * {@link SSLContextSpi}, this class effectively wraps an * {@code SSLContext} as if it were an {@code SSLContextSpi}, allowing us to * achieve decoration. */ protected static final class SSLContextSpiDecorator extends SSLContextSpi { private final SSLContext context; private final List<Configurer<SSLEngine>> sslEngineConfigurers; private final List<Configurer<SSLSocketFactory>> sslSocketFactoryConfigurers; private final List<Configurer<SSLServerSocketFactory>> sslServerSocketFactoryConfigurers; public SSLContextSpiDecorator(SSLContext context, List<Configurer<SSLEngine>> sslEngineConfigurers, List<Configurer<SSLSocketFactory>> sslSocketFactoryConfigurers, List<Configurer<SSLServerSocketFactory>> sslServerSocketFactoryConfigurers) { this.context = context; this.sslEngineConfigurers = sslEngineConfigurers; this.sslSocketFactoryConfigurers = sslSocketFactoryConfigurers; this.sslServerSocketFactoryConfigurers = sslServerSocketFactoryConfigurers; } @Override protected SSLEngine engineCreateSSLEngine() { SSLEngine engine = this.context.createSSLEngine(); LOG.debug("SSLEngine [{}] created from SSLContext [{}].", engine, context); this.configureSSLEngine(engine); return engine; } @Override protected SSLEngine engineCreateSSLEngine(String peerHost, int peerPort) { SSLEngine engine = this.context.createSSLEngine(peerHost, peerPort); LOG.debug("SSLEngine [{}] created from SSLContext [{}].", engine, context); return this.configureSSLEngine(engine); } @Override protected SSLSessionContext engineGetClientSessionContext() { return this.context.getClientSessionContext(); } @Override protected SSLSessionContext engineGetServerSessionContext() { return this.context.getServerSessionContext(); } @Override protected SSLServerSocketFactory engineGetServerSocketFactory() { SSLServerSocketFactory factory = this.context.getServerSocketFactory(); LOG.debug("SSLServerSocketFactoryEngine [{}] created from SSLContext [{}].", factory, context); return this.configureSSLServerSocketFactory(factory); } @Override protected SSLSocketFactory engineGetSocketFactory() { SSLSocketFactory factory = this.context.getSocketFactory(); LOG.debug("SSLSocketFactory [{}] created from SSLContext [{}].", factory, context); return this.configureSSLSocketFactory(factory); } @Override protected void engineInit(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws KeyManagementException { this.context.init(km, tm, random); } protected SSLContext getDelegate() { return this.context; } /** * Configures an {@link SSLEngine} based on the configurers in instance. * The return value from this method may be {@code engine} or it may be * a decorated instance there of. Consequently, any subsequent actions * on {@code engine} must be performed using the returned value. * * @param engine the engine to configure * @return {@code engine} or a decorated instance there of */ protected SSLEngine configureSSLEngine(SSLEngine engine) { SSLEngine workingEngine = engine; for (Configurer<SSLEngine> configurer : this.sslEngineConfigurers) { workingEngine = configurer.configure(workingEngine); } return workingEngine; } /** * Configures an {@link SSLSocketFactory} based on the configurers in * this instance. The return value from this method may be * {@code factory} or it may be a decorated instance there of. * Consequently, any subsequent actions on {@code factory} must be * performed using the returned value. * * @param factory the factory to configure * @return {@code factory} or a decorated instance there of */ protected SSLSocketFactory configureSSLSocketFactory(SSLSocketFactory factory) { SSLSocketFactory workingFactory = factory; for (Configurer<SSLSocketFactory> configurer : this.sslSocketFactoryConfigurers) { workingFactory = configurer.configure(workingFactory); } return workingFactory; } /** * Configures an {@link SSLServerSocketFactory} based on the * configurers in this instance. The return value from this method may be * {@code factory} or it may be a decorated instance there of. * Consequently, any subsequent actions on {@code factory} must be * performed using the returned value. * * @param factory the factory to configure * @return {@code factory} or a decorated instance there of */ protected SSLServerSocketFactory configureSSLServerSocketFactory( SSLServerSocketFactory factory) { SSLServerSocketFactory workingFactory = factory; for (Configurer<SSLServerSocketFactory> configurer : this.sslServerSocketFactoryConfigurers) { workingFactory = configurer.configure(workingFactory); } return workingFactory; } } /** * A decorator that enables the application of configuration options to be * applied to created sockets even after this factory has been created and * turned over to client code. */ protected static final class SSLServerSocketFactoryDecorator extends SSLServerSocketFactory { private final SSLServerSocketFactory sslServerSocketFactory; private final List<Configurer<SSLServerSocket>> sslServerSocketConfigurers; public SSLServerSocketFactoryDecorator(SSLServerSocketFactory sslServerSocketFactory, List<Configurer<SSLServerSocket>> sslServerSocketConfigurers) { this.sslServerSocketFactory = sslServerSocketFactory; this.sslServerSocketConfigurers = sslServerSocketConfigurers; } @Override public String[] getDefaultCipherSuites() { return this.sslServerSocketFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return this.sslServerSocketFactory.getSupportedCipherSuites(); } @Override public ServerSocket createServerSocket() throws IOException { return this.configureSocket(this.sslServerSocketFactory.createServerSocket()); } @Override public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { return this.configureSocket(this.sslServerSocketFactory.createServerSocket(port, backlog, ifAddress)); } @Override public ServerSocket createServerSocket(int port, int backlog) throws IOException { return this.configureSocket(this.sslServerSocketFactory.createServerSocket(port, backlog)); } @Override public ServerSocket createServerSocket(int port) throws IOException { return this.configureSocket(this.sslServerSocketFactory.createServerSocket(port)); } public SSLServerSocketFactory getDelegate() { return this.sslServerSocketFactory; } private ServerSocket configureSocket(ServerSocket s) { SSLServerSocket workingSocket = (SSLServerSocket) s; LOG.debug("Created ServerSocket [{}] from SslServerSocketFactory [{}].", s, sslServerSocketFactory); for (Configurer<SSLServerSocket> configurer : this.sslServerSocketConfigurers) { workingSocket = configurer.configure(workingSocket); } return workingSocket; } } /** * A decorator that enables the application of configuration options to be * applied to created sockets even after this factory has been created and * turned over to client code. */ protected static final class SSLSocketFactoryDecorator extends SSLSocketFactory { private final SSLSocketFactory sslSocketFactory; private final List<Configurer<SSLSocket>> sslSocketConfigurers; public SSLSocketFactoryDecorator(SSLSocketFactory sslSocketFactory, List<Configurer<SSLSocket>> sslSocketConfigurers) { this.sslSocketFactory = sslSocketFactory; this.sslSocketConfigurers = sslSocketConfigurers; } @Override public String[] getDefaultCipherSuites() { return sslSocketFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return sslSocketFactory.getSupportedCipherSuites(); } @Override public Socket createSocket() throws IOException { return configureSocket(sslSocketFactory.createSocket()); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return configureSocket(sslSocketFactory.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return configureSocket(sslSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return configureSocket(sslSocketFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return configureSocket(sslSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return configureSocket(sslSocketFactory.createSocket(address, port, localAddress, localPort)); } public SSLSocketFactory getDelegate() { return this.sslSocketFactory; } private Socket configureSocket(Socket s) { SSLSocket workingSocket = (SSLSocket) s; LOG.debug("Created Socket [{}] from SocketFactory [{}].", s, sslSocketFactory); for (Configurer<SSLSocket> configurer : this.sslSocketConfigurers) { workingSocket = configurer.configure(workingSocket); } return workingSocket; } } private static String createCipherSuiteLogMessage(String entityName) { return "Configuring " + entityName + " [{}] with " + LS + "\t explicitly set cipher suites [{}]," + LS + "\t cipher suite patterns [{}]," + LS + "\t available cipher suites [{}]," + LS + "\t currently enabled cipher suites [{}]," + LS + "\t and default cipher suite patterns [{}]." + LS + "\t Resulting enabled cipher suites are [{}]."; } private static String createProtocolLogMessage(String entityName) { return "Configuring " + entityName + " [{}] with " + LS + "\t explicitly set protocols [{}]," + LS + "\t protocol patterns [{}]," + LS + "\t available protocols [{}]," + LS + "\t currently enabled protocols [{}]," + LS + "\t and default protocol patterns [{}]." + LS + "\t Resulting enabled protocols are [{}]."; } }