Commit 1d98c1ec authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1004: Reduce encryption configuration complexity

My original implementation had options to both include as well as exclude specific
encryption protocols and cipher suites. This proved to be to much - Configuration
becomes a nightmare and setups ended up with having no enabled suites at all. This
commit simplifies things: a connection listener now gets a list of protocols and
suites that are enabled - that's it. When no setting is provided, the JVM default
is used (which itself is tweakable through generic Java configuration revolving
around the file JRE_HOME/lib/security/java.security )
parent a1cf99e0
...@@ -34,10 +34,8 @@ public class ConnectionConfiguration ...@@ -34,10 +34,8 @@ public class ConnectionConfiguration
private final CertificateStoreConfiguration trustStoreConfiguration; private final CertificateStoreConfiguration trustStoreConfiguration;
private final boolean acceptSelfSignedCertificates; private final boolean acceptSelfSignedCertificates;
private final boolean verifyCertificateValidity; private final boolean verifyCertificateValidity;
private final Set<String> encryptionProtocolsEnabled; private final Set<String> encryptionProtocols;
private final Set<String> encryptionProtocolsDisabled; private final Set<String> encryptionCipherSuites;
private final Set<String> cipherSuitesEnabled;
private final Set<String> cipherSuitesDisabled;
private final Connection.CompressionPolicy compressionPolicy; private final Connection.CompressionPolicy compressionPolicy;
// derived // derived
...@@ -55,7 +53,7 @@ public class ConnectionConfiguration ...@@ -55,7 +53,7 @@ public class ConnectionConfiguration
* @param tlsPolicy The TLS policy that is applied to connections (cannot be null). * @param tlsPolicy The TLS policy that is applied to connections (cannot be null).
*/ */
// TODO input validation // TODO input validation
public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThreadPoolSize, int maxBufferSize, Connection.ClientAuth clientAuth, InetAddress bindAddress, int port, Connection.TLSPolicy tlsPolicy, CertificateStoreConfiguration identityStoreConfiguration, CertificateStoreConfiguration trustStoreConfiguration, boolean acceptSelfSignedCertificates, boolean verifyCertificateValidity, Set<String> encryptionProtocolsEnabled, Set<String> encryptionProtocolsDisabled, Set<String> cipherSuitesEnabled, Set<String> cipherSuitesDisabled, Connection.CompressionPolicy compressionPolicy ) public ConnectionConfiguration( ConnectionType type, boolean enabled, int maxThreadPoolSize, int maxBufferSize, Connection.ClientAuth clientAuth, InetAddress bindAddress, int port, Connection.TLSPolicy tlsPolicy, CertificateStoreConfiguration identityStoreConfiguration, CertificateStoreConfiguration trustStoreConfiguration, boolean acceptSelfSignedCertificates, boolean verifyCertificateValidity, Set<String> encryptionProtocols, Set<String> encryptionCipherSuites, Connection.CompressionPolicy compressionPolicy )
{ {
if ( maxThreadPoolSize <= 0 ) { if ( maxThreadPoolSize <= 0 ) {
throw new IllegalArgumentException( "Argument 'maxThreadPoolSize' must be equal to or greater than one." ); throw new IllegalArgumentException( "Argument 'maxThreadPoolSize' must be equal to or greater than one." );
...@@ -76,21 +74,8 @@ public class ConnectionConfiguration ...@@ -76,21 +74,8 @@ public class ConnectionConfiguration
this.trustStoreConfiguration = trustStoreConfiguration; this.trustStoreConfiguration = trustStoreConfiguration;
this.acceptSelfSignedCertificates = acceptSelfSignedCertificates; this.acceptSelfSignedCertificates = acceptSelfSignedCertificates;
this.verifyCertificateValidity = verifyCertificateValidity; this.verifyCertificateValidity = verifyCertificateValidity;
this.encryptionProtocols = Collections.unmodifiableSet( encryptionProtocols );
// Remove all disabled protocols from the enabled ones. this.encryptionCipherSuites = Collections.unmodifiableSet( encryptionCipherSuites );
final Set<String> protocolsEnabled = new HashSet<>();
protocolsEnabled.addAll( encryptionProtocolsEnabled );
protocolsEnabled.removeAll( encryptionProtocolsDisabled );
this.encryptionProtocolsEnabled = Collections.unmodifiableSet( protocolsEnabled );
this.encryptionProtocolsDisabled = Collections.unmodifiableSet( encryptionProtocolsDisabled );
// Remove all disabled suites from the enabled ones.
final Set<String> suitesEnabled = new HashSet<>();
suitesEnabled.addAll( cipherSuitesEnabled );
suitesEnabled.removeAll( cipherSuitesDisabled );
this.cipherSuitesEnabled = Collections.unmodifiableSet( suitesEnabled );
this.cipherSuitesDisabled = Collections.unmodifiableSet( cipherSuitesDisabled );
this.compressionPolicy = compressionPolicy; this.compressionPolicy = compressionPolicy;
final CertificateStoreManager certificateStoreManager = XMPPServer.getInstance().getCertificateStoreManager(); final CertificateStoreManager certificateStoreManager = XMPPServer.getInstance().getCertificateStoreManager();
...@@ -175,66 +160,30 @@ public class ConnectionConfiguration ...@@ -175,66 +160,30 @@ public class ConnectionConfiguration
* When non-empty, the list is intended to specify those protocols (from a larger collection of implementation- * When non-empty, the list is intended to specify those protocols (from a larger collection of implementation-
* supported protocols) that can be used to establish encryption. * supported protocols) that can be used to establish encryption.
* *
* Values returned by {@link #getEncryptionProtocolsDisabled()} are not included in the result of this method.
*
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string. This can, but is not guaranteed to, indicate preference. * configuration string. This can, but is not guaranteed to, indicate preference.
* *
* @return An (ordered) set of protocols, never null but possibly empty. * @return An (ordered) set of protocols, never null but possibly empty.
*/ */
public Set<String> getEncryptionProtocolsEnabled() public Set<String> getEncryptionProtocols()
{ {
return encryptionProtocolsEnabled; return encryptionProtocols;
}
/**
* A collection of protocols that must not be used for encryption of connections.
*
* When non-empty, the list is intended to specify those protocols (from a larger collection of implementation-
* supported protocols) that must not be used to establish encryption.
*
* The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string.
*
* @return An (ordered) set of protocols, never null but possibly empty.
*/
public Set<String> getEncryptionProtocolsDisabled()
{
return encryptionProtocolsDisabled;
} }
/** /**
* A collection of cipher suite names that can be used for encryption of connections. * A collection of cipher suite names that can be used for encryption of connections.
* *
* When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation- * When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation-
* supported cipher suties) that can be used to establish encryption. * supported cipher suites) that can be used to establish encryption.
*
* Values returned by {@link #getCipherSuitesDisabled()} are not included in the result of this method.
* *
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string. This can, but is not guaranteed to, indicate preference. * configuration string. This can, but is not guaranteed to, indicate preference.
* *
* @return An (ordered) set of cipher suites, never null but possibly empty. * @return An (ordered) set of cipher suites, never null but possibly empty.
*/ */
public Set<String> getCipherSuitesEnabled() public Set<String> getEncryptionCipherSuites()
{
return cipherSuitesEnabled;
}
/**
* A collection of cipher suites that must not be used for encryption of connections.
*
* When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation-
* supported cipher suites) that must not be used to establish encryption.
*
* The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string.
*
* @return An (ordered) set of cipher suites, never null but possibly empty.
*/
public Set<String> getCipherSuitesDisabled()
{ {
return cipherSuitesDisabled; return encryptionCipherSuites;
} }
public IdentityStore getIdentityStore() public IdentityStore getIdentityStore()
......
...@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; ...@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
...@@ -259,10 +260,8 @@ public class ConnectionListener ...@@ -259,10 +260,8 @@ public class ConnectionListener
trustStoreConfiguration, trustStoreConfiguration,
acceptSelfSignedCertificates(), acceptSelfSignedCertificates(),
verifyCertificateValidity(), verifyCertificateValidity(),
getEncryptionProtocolsEnabled(), getEncryptionProtocols(),
getEncryptionProtocolsDisabled(), getEncryptionCipherSuites(),
getCipherSuitesEnabled(),
getCipherSuitesDisabled(),
getCompressionPolicy() getCompressionPolicy()
); );
} }
...@@ -655,7 +654,7 @@ public class ConnectionListener ...@@ -655,7 +654,7 @@ public class ConnectionListener
* If the listener is currently enabled, this configuration change will be applied immediately (which will cause a * If the listener is currently enabled, this configuration change will be applied immediately (which will cause a
* restart of the underlying connection acceptor). * restart of the underlying connection acceptor).
* *
* @return The configuration of the identity store (not null) * @param configuration The configuration of the identity store (not null)
*/ */
public void setTrustStoreConfiguration( CertificateStoreConfiguration configuration ) public void setTrustStoreConfiguration( CertificateStoreConfiguration configuration )
{ {
...@@ -669,188 +668,6 @@ public class ConnectionListener ...@@ -669,188 +668,6 @@ public class ConnectionListener
restart(); restart();
} }
// /**
// * The KeyStore type (jks, jceks, pkcs12, etc) for the identity and trust store for connections created by this
// * listener.
// *
// * @return a store type (never null).
// * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyStore">Java Cryptography Architecture Standard Algorithm Name Documentation</a>
// */
// public String getKeyStoreType()
// {
// final String propertyName = type.getPrefix() + "storeType";
// final String defaultValue = "jks";
//
// if ( type.getFallback() == null )
// {
// return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
// }
// else
// {
// return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getKeyStoreType() ).trim();
// }
// }
//
// public void setKeyStoreType( String keyStoreType )
// {
// // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
// JiveGlobals.setProperty( type.getPrefix() + "storeType", keyStoreType );
//
// final String oldKeyStoreType = getKeyStoreType();
// if ( oldKeyStoreType.equals( keyStoreType ) )
// {
// Log.debug( "Ignoring KeyStore type change request (to '{}'): listener already in this state.", keyStoreType );
// return;
// }
//
// Log.debug( "Changing KeyStore type from '{}' to '{}'.", oldKeyStoreType, keyStoreType );
// restart();
// }
//
// /**
// * The password of the identity store for connection created by this listener.
// *
// * @return a password (never null).
// */
// public String getIdentityStorePassword()
// {
// final String propertyName = type.getPrefix() + "keypass";
// final String defaultValue = "changeit";
//
// if ( type.getFallback() == null )
// {
// return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
// }
// else
// {
// return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getIdentityStorePassword() ).trim();
// }
// }
//
// public void setIdentityStorePassword( String password )
// {
// // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
// JiveGlobals.setProperty( type.getPrefix() + "keypass", password );
//
// final String oldPassword = getIdentityStorePassword();
// if ( oldPassword.equals( password ) )
// {
// Log.debug( "Ignoring identity store password change request: listener already in this state." ); // Do not put passwords in a logfile.
// return;
// }
//
// Log.debug( "Changing identity store password." ); // Do not put passwords in a logfile.
// restart();
// }
//
// /**
// * The password of the trust store for connections created by this listener.
// *
// * @return a password (never null).
// */
// public String getTrustStorePassword()
// {
// final String propertyName = type.getPrefix() + "trustpass";
// final String defaultValue = "changeit";
//
// if ( type.getFallback() == null )
// {
// return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
// }
// else
// {
// return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getTrustStorePassword() ).trim();
// }
// }
//
// public void setTrustStorePassword( String password )
// {
// // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
// JiveGlobals.setProperty( type.getPrefix() + "trustpass", password );
//
// final String oldPassword = getTrustStorePassword();
// if ( oldPassword.equals( password ) )
// {
// Log.debug( "Ignoring trust store password change request: listener already in this state." ); // Do not put passwords in a logfile.
// return;
// }
//
// Log.debug( "Changing trust store password." ); // Do not put passwords in a logfile.
// restart();
// }
//
// /**
// * The location (relative to OPENFIRE_HOME) of the identity store for connections created by this listener.
// *
// * @return a path (never null).
// */
// public String getIdentityStoreLocation()
// {
// final String propertyName = type.getPrefix() + "keystore";
// final String defaultValue = "resources" + File.separator + "security" + File.separator + "keystore";
//
// if ( type.getFallback() == null )
// {
// return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
// }
// else
// {
// return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getIdentityStoreLocation() ).trim();
// }
// }
//
// public void setIdentityStoreLocation( String location )
// {
// // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
// JiveGlobals.setProperty( type.getPrefix() + "keystore", location );
//
// final String oldLocation = getIdentityStoreLocation();
// if ( oldLocation.equals( location ) )
// {
// Log.debug( "Ignoring identity store location change request (to '{}'): listener already in this state.", location );
// return;
// }
//
// Log.debug( "Changing identity store location from '{}' to '{}'.", oldLocation, location );
// restart();
// }
//
// /**
// * The location (relative to OPENFIRE_HOME) of the trust store for connections created by this listener.
// *
// * @return a path (never null).
// */
// public String getTrustStoreLocation()
// {
// final String propertyName = type.getPrefix() + "truststore";
// final String defaultValue = "resources" + File.separator + "security" + File.separator + "truststore";
//
// if ( type.getFallback() == null )
// {
// return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
// }
// else
// {
// return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getTrustStoreLocation() ).trim();
// }
// }
//
// public void setTrustStoreLocation( String location )
// {
// // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
// JiveGlobals.setProperty( type.getPrefix() + "truststore", location );
//
// final String oldLocation = getTrustStoreLocation();
// if ( oldLocation.equals( location ) )
// {
// Log.debug( "Ignoring trust store location change request (to '{}'): listener already in this state.", location );
// return;
// }
//
// Log.debug( "Changing trust store location from '{}' to '{}'.", oldLocation, location );
// restart();
// }
/** /**
* A boolean that indicates if self-signed peer certificates can be used to establish an encrypted connection. * A boolean that indicates if self-signed peer certificates can be used to establish an encrypted connection.
* *
...@@ -945,27 +762,32 @@ public class ConnectionListener ...@@ -945,27 +762,32 @@ public class ConnectionListener
* When non-empty, the list is intended to specify those protocols (from a larger collection of implementation- * When non-empty, the list is intended to specify those protocols (from a larger collection of implementation-
* supported protocols) that can be used to establish encryption. * supported protocols) that can be used to establish encryption.
* *
* Values returned by {@link #getEncryptionProtocolsDisabled()} are not included in the result of this method.
*
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string. This can, but is not guaranteed to, indicate preference. * configuration string. This can, but is not guaranteed to, indicate preference.
* *
* @return An (ordered) set of protocols, never null but possibly empty. * @return An (ordered) set of protocols, never null but possibly empty.
*/ */
// TODO add setter! // TODO add setter!
public Set<String> getEncryptionProtocolsEnabled() public Set<String> getEncryptionProtocols()
{ {
final Set<String> result = new LinkedHashSet<>(); final Set<String> result = new LinkedHashSet<>();
final String csv = getEncryptionProtocolsEnabledCommaSeparated(); final String csv = getEncryptionProtocolsCommaSeparated();
if ( csv.isEmpty() ) {
try {
result.addAll( EncryptionArtifactFactory.getDefaultProtocols() );
} catch ( Exception ex ) {
Log.error( "An error occurred while obtaining the default encryption protocol setting.", ex );
}
} else {
result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) ); result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) );
result.removeAll( getEncryptionProtocolsDisabled() ); }
return result; return result;
} }
protected String getEncryptionProtocolsEnabledCommaSeparated() protected String getEncryptionProtocolsCommaSeparated()
{ {
final String propertyName = type.getPrefix() + "protocols.enabled"; final String propertyName = type.getPrefix() + "protocols";
final String defaultValue = "TLSv1,TLSv1.1,TLSv1.2"; final String defaultValue = "";
if ( type.getFallback() == null ) if ( type.getFallback() == null )
{ {
...@@ -973,71 +795,97 @@ public class ConnectionListener ...@@ -973,71 +795,97 @@ public class ConnectionListener
} }
else else
{ {
return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getEncryptionProtocolsEnabledCommaSeparated() ).trim(); return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getEncryptionProtocolsCommaSeparated() ).trim();
} }
} }
/** /**
* A collection of protocols that must not be used for encryption of connections. * Defines the collection of protocols (by name) that can be used for encryption of connections.
* *
* When non-empty, the list is intended to specify those protocols (from a larger collection of implementation- * When non-empty, the list is intended to specify those protocols (from a larger collection of implementation-
* supported protocols) that must not be used to establish encryption. * supported protocols) that can be used to establish encryption. An empty list will cause an implementation
* default to be used.
* *
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are presented can, but is not guaranteed to, indicate preference.
* configuration string.
* *
* @return An (ordered) set of protocols, never null but possibly empty. * @param protocols An (ordered) set of protocol names, can be null.
*/ */
// TODO add setter! public void setEncryptionProtocols( Set<String> protocols ) {
public Set<String> getEncryptionProtocolsDisabled() if ( protocols == null ) {
{ setEncryptionProtocols( new String[0] );
final Set<String> result = new LinkedHashSet<>(); } else {
final String csv = getEncryptionProtocolsDisabledCommaSeparated(); setEncryptionProtocols( protocols.toArray( new String[ protocols.size() ] ) );
result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) ); }
return result;
} }
protected String getEncryptionProtocolsDisabledCommaSeparated() /**
* Defines the collection of protocols (by name) that can be used for encryption of connections.
*
* When non-empty, the list is intended to specify those protocols (from a larger collection of implementation-
* supported protocols) that can be used to establish encryption. An empty list will cause an implementation
* default to be used.
*
* The order over which values are presented can, but is not guaranteed to, indicate preference.
*
* @param protocols An array of protocol names, can be null.
*/
public void setEncryptionProtocols( String[] protocols )
{ {
final String propertyName = type.getPrefix() + "protocols.disabled"; if ( protocols == null) {
final String defaultValue = "SSLv1,SSLv2,SSLv2Hello,SSLv3"; protocols = new String[0];
}
final String oldValue = getEncryptionProtocolsCommaSeparated();
if ( type.getFallback() == null ) // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
final StringBuilder csv = new StringBuilder();
for( String protocol : protocols )
{ {
return JiveGlobals.getProperty( propertyName, defaultValue ).trim(); csv.append( protocol );
csv.append( ',' );
} }
else final String newValue = csv.length() > 0 ? csv.substring( 0, csv.length() - 1 ) : "";
JiveGlobals.setProperty( type.getPrefix() + "protocols", newValue );
if ( oldValue.equals( newValue ) )
{ {
return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getEncryptionProtocolsDisabledCommaSeparated() ).trim(); Log.debug( "Ignoring protocol configuration change request (to '{}'): listener already in this state.", newValue );
return;
} }
Log.debug( "Changing protocol configuration from '{}' to '{}'.", oldValue, newValue );
restart();
} }
/** /**
* A collection of cipher suite names that can be used for encryption of connections. * A collection of cipher suite names that can be used for encryption of connections.
* *
* When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation- * When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation-
* supported cipher suties) that can be used to establish encryption. * supported cipher suites) that can be used to establish encryption.
*
* Values returned by {@link #getCipherSuitesDisabled()} are not included in the result of this method.
* *
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are iterated in the result is equal to the order of values in the comma-separated
* configuration string. This can, but is not guaranteed to, indicate preference. * configuration string. This can, but is not guaranteed to, indicate preference.
* *
* @return An (ordered) set of cipher suites, never null but possibly empty. * @return An (ordered) set of cipher suite names, never null but possibly empty.
*/ */
// TODO add setter! public Set<String> getEncryptionCipherSuites()
public Set<String> getCipherSuitesEnabled()
{ {
final Set<String> result = new LinkedHashSet<>(); final Set<String> result = new LinkedHashSet<>();
final String csv = getCipherSuitesEnabledCommaSeparated(); final String csv = getEncryptionCipherSuitesCommaSeparated();
if ( csv.isEmpty() ) {
try {
result.addAll( EncryptionArtifactFactory.getDefaultCipherSuites() );
} catch ( Exception ex ) {
Log.error( "An error occurred while obtaining the default encryption cipher suite setting.", ex );
}
} else {
result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) ); result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) );
result.removeAll( getCipherSuitesDisabled() ); }
return result; return result;
} }
protected String getCipherSuitesEnabledCommaSeparated() protected String getEncryptionCipherSuitesCommaSeparated()
{ {
final String propertyName = type.getPrefix() + "ciphersuites.enabled"; final String propertyName = type.getPrefix() + "ciphersuites";
final String defaultValue = ""; final String defaultValue = "";
if ( type.getFallback() == null ) if ( type.getFallback() == null )
...@@ -1046,43 +894,65 @@ public class ConnectionListener ...@@ -1046,43 +894,65 @@ public class ConnectionListener
} }
else else
{ {
return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getCipherSuitesEnabledCommaSeparated() ); return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getEncryptionCipherSuitesCommaSeparated() );
} }
} }
/** /**
* A collection of cipher suites that must not be used for encryption of connections. * Defines the collection of cipher suite (by name) that can be used for encryption of connections.
* *
* When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation- * When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation-
* supported cipher suites) that must not be used to establish encryption. * supported cipher suites) that can be used to establish encryption. An empty list will cause an implementation
* default to be used.
* *
* The order over which values are iterated in the result is equal to the order of values in the comma-separated * The order over which values are presented can, but is not guaranteed to, indicate preference.
* configuration string.
* *
* @return An (ordered) set of cipher suites, never null but possibly empty. * @param cipherSuites An (ordered) set of cipher suite names, can be null.
*/ */
// TODO add setter! public void setEncryptionCipherSuites( Set<String> cipherSuites ) {
public Set<String> getCipherSuitesDisabled() if ( cipherSuites == null ) {
{ setEncryptionCipherSuites( new String[0] );
final Set<String> result = new LinkedHashSet<>(); } else {
final String csv = getCipherSuitesDisabledCommaSeparated(); setEncryptionCipherSuites( cipherSuites.toArray( new String[ cipherSuites.size() ] ) );
result.addAll( Arrays.asList( csv.split( "\\s*,\\s*" ) ) ); }
return result;
} }
protected String getCipherSuitesDisabledCommaSeparated() /**
* Defines the collection of cipher suite (by name) that can be used for encryption of connections.
*
* When non-empty, the list is intended to specify those cipher suites (from a larger collection of implementation-
* supported cipher suites) that can be used to establish encryption. An empty list will cause an implementation
* default to be used.
*
* The order over which values are presented can, but is not guaranteed to, indicate preference.
*
* @param cipherSuites An array of cipher suite names, can be null.
*/
public void setEncryptionCipherSuites( String[] cipherSuites )
{ {
final String propertyName = type.getPrefix() + "ciphersuites.disabled"; if ( cipherSuites == null) {
final String defaultValue = ""; cipherSuites = new String[0];
}
final String oldValue = getEncryptionCipherSuitesCommaSeparated();
if ( type.getFallback() == null ) // Always set the property explicitly even if it appears the equal to the old value (the old value might be a fallback value).
final StringBuilder csv = new StringBuilder();
for( String cipherSuite : cipherSuites )
{ {
return JiveGlobals.getProperty( propertyName, defaultValue ).trim(); csv.append( cipherSuite );
csv.append( ',' );
} }
else final String newValue = csv.length() > 0 ? csv.substring( 0, csv.length() - 1 ) : "";
JiveGlobals.setProperty( type.getPrefix() + "ciphersuites", newValue );
if ( oldValue.equals( newValue ) )
{ {
return JiveGlobals.getProperty( propertyName, getConnectionListener( type.getFallback() ).getCipherSuitesDisabledCommaSeparated() ).trim(); Log.debug( "Ignoring cipher suite configuration change request (to '{}'): listener already in this state.", newValue );
return;
} }
Log.debug( "Changing cipher suite configuration from '{}' to '{}'.", oldValue, newValue );
restart();
} }
/** /**
......
...@@ -9,6 +9,8 @@ import org.slf4j.LoggerFactory; ...@@ -9,6 +9,8 @@ import org.slf4j.LoggerFactory;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.security.*; import java.security.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
...@@ -109,49 +111,19 @@ public class EncryptionArtifactFactory ...@@ -109,49 +111,19 @@ public class EncryptionArtifactFactory
final SSLEngine sslEngine = sslContext.createSSLEngine(); final SSLEngine sslEngine = sslContext.createSSLEngine();
// Configure protocol support. // Configure protocol support.
final Set<String> protocolsEnabled = configuration.getEncryptionProtocolsEnabled(); final Set<String> protocols = configuration.getEncryptionProtocols();
if ( !protocolsEnabled.isEmpty() ) if ( !protocols.isEmpty() )
{ {
// When an explicit list of enabled protocols is defined, use only those. // When an explicit list of enabled protocols is defined, use only those (otherwise, an implementation-specific default will be used).
sslEngine.setEnabledProtocols( protocolsEnabled.toArray( new String[ protocolsEnabled.size() ] ) ); sslEngine.setEnabledProtocols( protocols.toArray( new String[ protocols.size() ] ) );
}
else
{
// Otherwise, use all supported protocols (except for the ones that are explicitly disabled).
final Set<String> disabled = configuration.getEncryptionProtocolsDisabled();
final ArrayList<String> supported = new ArrayList<>();
for ( final String candidate : sslEngine.getSupportedProtocols() )
{
if ( !disabled.contains( candidate ) )
{
supported.add( candidate );
}
}
sslEngine.setEnabledProtocols( supported.toArray( new String[ supported.size()] ) );
} }
// Configure cipher suite support. // Configure cipher suite support.
final Set<String> cipherSuitesEnabled = configuration.getCipherSuitesEnabled(); final Set<String> cipherSuites = configuration.getEncryptionCipherSuites();
if ( !cipherSuitesEnabled.isEmpty() ) if ( !cipherSuites.isEmpty() )
{
// When an explicit list of enabled protocols is defined, use only those.
sslEngine.setEnabledCipherSuites( cipherSuitesEnabled.toArray( new String[ cipherSuitesEnabled.size() ] ) );
}
else
{ {
// Otherwise, use all supported cipher suites (except for the ones that are explicitly disabled). // When an explicit list of enabled protocols is defined, use only those (otherwise, an implementation-specific default will be used)..
final Set<String> disabled = configuration.getCipherSuitesDisabled(); sslEngine.setEnabledCipherSuites( cipherSuites.toArray( new String[ cipherSuites.size() ] ) );
final ArrayList<String> supported = new ArrayList<>();
for ( final String candidate : sslEngine.getSupportedCipherSuites() )
{
if ( !disabled.contains( candidate ) )
{
supported.add( candidate );
}
}
sslEngine.setEnabledCipherSuites( supported.toArray( new String[ supported.size() ] ) );
} }
return sslEngine; return sslEngine;
...@@ -221,20 +193,20 @@ public class EncryptionArtifactFactory ...@@ -221,20 +193,20 @@ public class EncryptionArtifactFactory
sslContextFactory.setKeyStorePassword( new String( configuration.getIdentityStore().getConfiguration().getPassword() ) ); sslContextFactory.setKeyStorePassword( new String( configuration.getIdentityStore().getConfiguration().getPassword() ) );
// Configure protocol support // Configure protocol support
if ( configuration.getEncryptionProtocolsEnabled() != null && !configuration.getEncryptionProtocolsEnabled().isEmpty() ) final Set<String> protocols = configuration.getEncryptionProtocols();
if ( !protocols.isEmpty() )
{ {
sslContextFactory.setIncludeProtocols( configuration.getEncryptionProtocolsEnabled().toArray( new String[ configuration.getEncryptionProtocolsEnabled().size() ] ) ); sslContextFactory.setIncludeProtocols( protocols.toArray( new String[ protocols.size() ] ) );
} }
sslContextFactory.setExcludeProtocols( configuration.getEncryptionProtocolsDisabled().toArray( new String[ configuration.getEncryptionProtocolsDisabled().size() ] ) );
// Configure cipher suite support. // Configure cipher suite support.
if ( configuration.getCipherSuitesEnabled() != null && !configuration.getCipherSuitesEnabled().isEmpty() ) final Set<String> cipherSuites = configuration.getEncryptionCipherSuites();
if ( !cipherSuites.isEmpty() )
{ {
sslContextFactory.setIncludeCipherSuites( configuration.getCipherSuitesEnabled().toArray( new String[ configuration.getCipherSuitesEnabled().size() ] ) ); sslContextFactory.setIncludeCipherSuites( cipherSuites.toArray( new String[ cipherSuites.size() ] ) );
} }
sslContextFactory.setExcludeCipherSuites( configuration.getCipherSuitesDisabled().toArray( new String[ configuration.getCipherSuitesDisabled().size() ] ) );
//Set policy for checking client certificates // Set policy for checking client certificates.
switch ( configuration.getClientAuth() ) switch ( configuration.getClientAuth() )
{ {
case disabled: case disabled:
...@@ -331,12 +303,25 @@ public class EncryptionArtifactFactory ...@@ -331,12 +303,25 @@ public class EncryptionArtifactFactory
* *
* @return An array of protocol names. Not expected to be empty. * @return An array of protocol names. Not expected to be empty.
*/ */
public static String[] getSupportedProtocols() throws NoSuchAlgorithmException, KeyManagementException public static List<String> getSupportedProtocols() throws NoSuchAlgorithmException, KeyManagementException
{ {
// TODO Might want to cache the result. It's unlikely to change at runtime. // TODO Might want to cache the result. It's unlikely to change at runtime.
final SSLContext context = SSLContext.getInstance( "TLSv1" ); final SSLContext context = SSLContext.getInstance( "TLSv1" );
context.init( null, null, null ); context.init( null, null, null );
return context.createSSLEngine().getSupportedProtocols(); return Arrays.asList( context.createSSLEngine().getSupportedProtocols() );
}
/**
* Returns the names of all encryption protocols that are enabled by default.
*
* @return An array of protocol names. Not expected to be empty.
*/
public static List<String> getDefaultProtocols() throws NoSuchAlgorithmException, KeyManagementException
{
// TODO Might want to cache the result. It's unlikely to change at runtime.
final SSLContext context = SSLContext.getInstance( "TLSv1" );
context.init( null, null, null );
return Arrays.asList( context.createSSLEngine().getEnabledProtocols() );
} }
/** /**
...@@ -344,11 +329,25 @@ public class EncryptionArtifactFactory ...@@ -344,11 +329,25 @@ public class EncryptionArtifactFactory
* *
* @return An array of cipher suite names. Not expected to be empty. * @return An array of cipher suite names. Not expected to be empty.
*/ */
public static String[] getSupportedCipherSuites() throws NoSuchAlgorithmException, KeyManagementException public static List<String> getSupportedCipherSuites() throws NoSuchAlgorithmException, KeyManagementException
{ {
// TODO Might want to cache the result. It's unlikely to change at runtime. // TODO Might want to cache the result. It's unlikely to change at runtime.
final SSLContext context = SSLContext.getInstance( "TLSv1" ); final SSLContext context = SSLContext.getInstance( "TLSv1" );
context.init( null, null, null ); context.init( null, null, null );
return context.createSSLEngine().getSupportedCipherSuites(); return Arrays.asList( context.createSSLEngine().getSupportedCipherSuites() );
} }
/**
* Returns the names of all encryption cipher suites that are enabled by default.
*
* @return An array of cipher suite names. Not expected to be empty.
*/
public static List<String> getDefaultCipherSuites() throws NoSuchAlgorithmException, KeyManagementException
{
// TODO Might want to cache the result. It's unlikely to change at runtime.
final SSLContext context = SSLContext.getInstance( "TLSv1" );
context.init( null, null, null );
return Arrays.asList( context.createSSLEngine().getEnabledCipherSuites() );
}
} }
...@@ -3,6 +3,8 @@ package org.jivesoftware.openfire.spi; ...@@ -3,6 +3,8 @@ package org.jivesoftware.openfire.spi;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.Collection;
/** /**
* Unit tests that verify the functionality of {@link EncryptionArtifactFactory}. * Unit tests that verify the functionality of {@link EncryptionArtifactFactory}.
* *
...@@ -20,10 +22,26 @@ public class EncryptionArtifactFactoryTest ...@@ -20,10 +22,26 @@ public class EncryptionArtifactFactoryTest
// (not needed) // (not needed)
// Execute system under test. // Execute system under test.
final String[] result = EncryptionArtifactFactory.getSupportedProtocols(); final Collection<String> result = EncryptionArtifactFactory.getSupportedProtocols();
// Verify results.
Assert.assertFalse( result.isEmpty() );
}
/**
* Verifies that the collection of default encryption protocols is not empty.
*/
@Test
public void testHasDefaultProtocols() throws Exception
{
// Setup fixture.
// (not needed)
// Execute system under test.
final Collection<String> result = EncryptionArtifactFactory.getDefaultProtocols();
// Verify results. // Verify results.
Assert.assertTrue( result.length > 0 ); Assert.assertFalse( result.isEmpty() );
} }
/** /**
...@@ -36,9 +54,25 @@ public class EncryptionArtifactFactoryTest ...@@ -36,9 +54,25 @@ public class EncryptionArtifactFactoryTest
// (not needed) // (not needed)
// Execute system under test. // Execute system under test.
final String[] result = EncryptionArtifactFactory.getSupportedCipherSuites(); final Collection<String> result = EncryptionArtifactFactory.getSupportedCipherSuites();
// Verify results.
Assert.assertFalse( result.isEmpty() );
}
/**
* Verifies that the collection of default cipher suites is not empty.
*/
@Test
public void testHasDefaultCipherSuites() throws Exception
{
// Setup fixture.
// (not needed)
// Execute system under test.
final Collection<String> result = EncryptionArtifactFactory.getDefaultCipherSuites();
// Verify results. // Verify results.
Assert.assertTrue( result.length > 0 ); Assert.assertFalse( result.isEmpty() );
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment