org.jivesoftware.openfire.spi.EncryptionArtifactFactory Maven / Gradle / Ivy
The newest version!
package org.jivesoftware.openfire.spi;
import org.apache.mina.filter.ssl.SslFilter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.jivesoftware.openfire.keystore.OpenfireX509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.security.*;
import java.util.*;
/**
* Instances of this class will be able to generate various encryption-related artifacts based on a specific connection
* configuration.
*
* This implementation intends to centralize the implementation for generating the artifacts produced, which in earlier
* versions of the code-base was scattered (and duplicated) over various connection-type-specific implementations.
*
* @author Guus der Kinderen, [email protected]
*/
public class EncryptionArtifactFactory
{
private final Logger Log = LoggerFactory.getLogger( EncryptionArtifactFactory.class );
private final ConnectionConfiguration configuration;
// lazy loaded factory objects. These re-usable objects should be lazy loaded, preventing initialization in situations where they're never going to be used.
private transient KeyManagerFactory keyManagerFactory;
private transient SslContextFactory sslContextFactory;
/**
* Creates a new instance of the factory.
*
* @param configuration the configuration for which this factory generates artifacts (cannot be null).
*/
public EncryptionArtifactFactory( ConnectionConfiguration configuration )
{
if ( configuration == null ) {
throw new IllegalArgumentException( "Argument 'configuration' cannot be null" );
}
this.configuration = configuration;
}
/**
* Generates KeyManager instances suitable for connections that are created based on a particular configuration.
*
* @return KeyManagers applicable to a connection that is established using the provided configuration.
*/
public synchronized KeyManager[] getKeyManagers() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException
{
try
{
if ( keyManagerFactory == null )
{
keyManagerFactory = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() );
keyManagerFactory.init( configuration.getIdentityStore().getStore(), configuration.getIdentityStoreConfiguration().getPassword() );
}
return keyManagerFactory.getKeyManagers();
}
catch ( UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | RuntimeException ex )
{
// Allow initialization to restart upon next iteration.
keyManagerFactory = null;
throw ex;
}
}
/**
* Generates KeyManager instances suitable for connections that are created based on a particular configuration.
*
* @return TrustManagers applicable to a connection that is established using the provided configuration.
*/
public synchronized TrustManager[] getTrustManagers() throws KeyStoreException, NoSuchAlgorithmException
{
return new TrustManager[] {
new OpenfireX509TrustManager( configuration.getTrustStore().getStore(), configuration.isAcceptSelfSignedCertificates(), configuration.isVerifyCertificateValidity() )
};
}
/**
* Generates a new, initialized SSLContext instance that is suitable for connections that are created based on a
* particular configuration.
*
* @return TrustManagers applicable to a connection that is established using the provided configuration.
*/
public synchronized SSLContext getSSLContext() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
{
final SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init( getKeyManagers(), getTrustManagers(), new SecureRandom() );
return sslContext;
}
/**
* A utility method that implements the shared functionality of getClientModeSSLEngine and getServerModeSSLEngine.
*
* This method is used to initialize and pre-configure an instance of SSLEngine for a particular connection type.
* The returned value lacks further configuration. In most cases, developers will want to use getClientModeSSLEngine
* or getServerModeSSLEngine instead of this method.
*
* @return A new pre-configured SSLEngine instance (never null).
*/
private SSLEngine createSSLEngine() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException
{
final SSLContext sslContext = getSSLContext();
final SSLEngine sslEngine = sslContext.createSSLEngine();
// Configure protocol support.
final Set protocols = configuration.getEncryptionProtocols();
if ( !protocols.isEmpty() )
{
// When an explicit list of enabled protocols is defined, use only those (otherwise, an implementation-specific default will be used).
sslEngine.setEnabledProtocols( protocols.toArray( new String[ protocols.size() ] ) );
}
// Configure cipher suite support.
final Set cipherSuites = configuration.getEncryptionCipherSuites();
if ( !cipherSuites.isEmpty() )
{
// When an explicit list of enabled protocols is defined, use only those (otherwise, an implementation-specific default will be used)..
sslEngine.setEnabledCipherSuites( cipherSuites.toArray( new String[ cipherSuites.size() ] ) );
}
return sslEngine;
}
/**
* Creates a new SSL Engine that is configured to use server mode when handshaking.
*
* For Openfire, an engine is of this mode used for most purposes (as Openfire is a server by nature).
*
* @return A new, initialized SSLEngine instance (never null).
*/
public SSLEngine createServerModeSSLEngine() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException
{
final SSLEngine sslEngine = createSSLEngine( );
sslEngine.setUseClientMode( false );
switch ( configuration.getClientAuth() )
{
case needed:
sslEngine.setNeedClientAuth( true );
break;
case wanted:
sslEngine.setWantClientAuth( true );
break;
case disabled:
sslEngine.setWantClientAuth( false );
break;
}
return sslEngine;
}
/**
* Creates an SSL Engine that is configured to use client mode when handshaking.
*
* For Openfire, an engine of this mode is typically used when the server tries to connect to another server.
*
* These SSLEngines never send SSLV2 ClientHello messages.
*
* @return An initialized SSLEngine instance (never null).
*/
public SSLEngine createClientModeSSLEngine() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException
{
final SSLEngine sslEngine = createSSLEngine();
sslEngine.setUseClientMode( true );
final Set protocols = new LinkedHashSet<>( Arrays.asList( sslEngine.getEnabledProtocols() ) );
protocols.remove( "SSLv2Hello" );
sslEngine.setEnabledProtocols( protocols.toArray( new String[ protocols.size() ] ) );
return sslEngine;
}
public synchronized SslContextFactory getSslContextFactory()
{
if ( sslContextFactory != null )
{
return sslContextFactory;
}
Log.info( "Creating new SslContextFactory instance" );
try
{
sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustStore( configuration.getTrustStore().getStore() );
sslContextFactory.setTrustStorePassword( new String( configuration.getTrustStore().getConfiguration().getPassword() ) );
sslContextFactory.setKeyStore( configuration.getIdentityStore().getStore() );
sslContextFactory.setKeyStorePassword( new String( configuration.getIdentityStore().getConfiguration().getPassword() ) );
// Configure protocol support
final Set protocols = configuration.getEncryptionProtocols();
if ( !protocols.isEmpty() )
{
// Note that this is always server-mode, so may support SSLv2Hello.
sslContextFactory.setIncludeProtocols(protocols.toArray(new String[protocols.size()]));
}
// Configure cipher suite support.
final Set cipherSuites = configuration.getEncryptionCipherSuites();
if ( !cipherSuites.isEmpty() )
{
sslContextFactory.setIncludeCipherSuites( cipherSuites.toArray( new String[ cipherSuites.size() ] ) );
}
// Set policy for checking client certificates.
switch ( configuration.getClientAuth() )
{
case disabled:
sslContextFactory.setNeedClientAuth( false );
sslContextFactory.setWantClientAuth( false );
break;
case wanted:
sslContextFactory.setNeedClientAuth( false );
sslContextFactory.setWantClientAuth( true );
break;
case needed:
sslContextFactory.setNeedClientAuth( true );
break;
}
return sslContextFactory;
}
catch ( RuntimeException ex )
{
// Allow initialization to restart upon next iteration.
sslContextFactory = null;
throw ex;
}
}
/**
* Creates an Apache MINA SslFilter that is configured to use server mode when handshaking.
*
* For Openfire, an engine is of this mode used for most purposes (as Openfire is a server by nature).
*
* Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to
* create both SSLContext/SSLEngine as well as SslFilter instances.
*
* @return An initialized SslFilter instance (never null)
*/
public SslFilter createServerModeSslFilter() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException
{
final SSLContext sslContext = getSSLContext();
final SSLEngine sslEngine = createServerModeSSLEngine();
return createSslFilter( sslContext, sslEngine );
}
/**
* Creates an Apache MINA SslFilter that is configured to use client mode when handshaking.
*
* For Openfire, a filter of this mode is typically used when the server tries to connect to another server.
*
* Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to
* create both SSLContext/SSLEngine as well as SslFilter instances.
*
* @return An initialized SslFilter instance (never null)
*/
public SslFilter createClientModeSslFilter() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException
{
final SSLContext sslContext = getSSLContext();
final SSLEngine sslEngine = createClientModeSSLEngine();
return createSslFilter( sslContext, sslEngine );
}
/**
* A utility method that implements the shared functionality of getServerModeSslFilter and getClientModeSslFilter.
*
* This method is used to initialize and configure an instance of SslFilter for a particular pre-configured
* SSLContext and SSLEngine. In most cases, developers will want to use getServerModeSslFilter or
* getClientModeSslFilter instead of this method.
*
* @param sslContext a pre-configured SSL Context instance (cannot be null).
* @param sslEngine a pre-configured SSL Engine instance (cannot be null).
* @return A SslFilter instance (never null).
*/
private static SslFilter createSslFilter( SSLContext sslContext, SSLEngine sslEngine ) {
final SslFilter filter = new SslFilter( sslContext );
// Copy configuration from the SSL Engine into the filter.
filter.setUseClientMode( sslEngine.getUseClientMode() );
filter.setEnabledProtocols( sslEngine.getEnabledProtocols() );
filter.setEnabledCipherSuites( sslEngine.getEnabledCipherSuites() );
// Note that the setters for 'need' and 'want' influence each-other. Invoke only one of them!
if ( sslEngine.getNeedClientAuth() )
{
filter.setNeedClientAuth( true );
}
else if ( sslEngine.getWantClientAuth() )
{
filter.setWantClientAuth( true );
}
return filter;
}
/**
* Returns the names of all encryption protocols that are supported (but not necessarily enabled).
*
* @return An array of protocol names. Not expected to be empty.
*/
public static List getSupportedProtocols() 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().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 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() );
}
/**
* Returns the names of all encryption cipher suites that are supported (but not necessarily enabled).
*
* @return An array of cipher suite names. Not expected to be empty.
*/
public static List getSupportedCipherSuites() 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().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 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() );
}
}