Commit 97f7cf3f authored by Guus der Kinderen's avatar Guus der Kinderen

OF-946: Openfire should allow for more than one set of keystores.

This commit:
- Refactors SSL-related code to reduce complexity and deprecate unused code
- Adds factory methods to obtain SSL/TLS engine / contexts, to replace all
  code duplication that was going on before.
parent bd2c086b
......@@ -354,7 +354,7 @@ public interface Connection extends Closeable {
* otherwise a {@link org.jivesoftware.openfire.net.ServerTrustManager} will be used.
* @param authentication policy to use for authenticating the remote peer.
* @throws Exception if an error occured while securing the connection.
* @deprecated Use {@link #startTLS(boolean, boolean, ClientAuth)} instead, with isClientToServer = (remoteServer == null)
* @deprecated Use {@link #startTLS(boolean, boolean, ClientAuth)} instead, with isPeerClient = (remoteServer == null)
*/
@Deprecated
void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception;
......@@ -373,13 +373,13 @@ public interface Connection extends Closeable {
* <tt>remoteServer</tt> parameter will always be <tt>null</tt>.
*
* @param clientMode boolean indicating if this entity is a client or a server in the TLS negotiation.
* @param isClientToServer indicates if the remote party is a server. When false a
* @param isPeerClient indicates if the remote party is a client. When true a
* {@link org.jivesoftware.openfire.net.ClientTrustManager} will be used for verifying certificates
* otherwise a {@link org.jivesoftware.openfire.net.ServerTrustManager} will be used.
* @param authentication policy to use for authenticating the remote peer.
* @throws Exception if an error occured while securing the connection.
*/
void startTLS(boolean clientMode, boolean isClientToServer, ClientAuth authentication) throws Exception;
void startTLS(boolean clientMode, boolean isPeerClient, ClientAuth authentication) throws Exception;
/**
* Adds the compression filter to the connection but only filter incoming traffic. Do not filter
......
......@@ -63,7 +63,7 @@ public class SSLProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext createSSLContext(String host) {
try {
final SSLContext context = SSLConfig.getSSLContext();
final SSLContext context = SSLConfig.getSSLContext( SSLConfig.Type.ADMIN );
context.init(
null,
new TrustManager[] {
......
......@@ -151,7 +151,7 @@ public class IdentityStoreConfig extends CertificateStoreConfig
}
// Note that PKCS#7 does not require a specific order for the certificates in the file - ordering is needed.
final List<X509Certificate> ordered = CertificateManager.order( certificates );
final List<X509Certificate> ordered = CertificateUtils.order( certificates );
// Of the ordered chain, the first certificate should be for our domain.
if ( !isForThisDomain( ordered.get( 0 ) ) )
......@@ -254,7 +254,7 @@ public class IdentityStoreConfig extends CertificateStoreConfig
}
// Note that PKCS#7 does not require a specific order for the certificates in the file - ordering is needed.
final List<X509Certificate> ordered = CertificateManager.order( certificates );
final List<X509Certificate> ordered = CertificateUtils.order( certificates );
// Of the ordered chain, the first certificate should be for our domain.
if ( !isForThisDomain( ordered.get( 0 ) ) )
......
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jivesoftware.util.Log;
import javax.net.ssl.*;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.*;
/**
* A Trust Manager implementation that adds Openfire-proprietary functionality.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
// TODO re-enable optional OCSP checking.
// TODO re-enable CRL checking.
public class OpenfireX509ExtendedTrustManager implements X509TrustManager
{
private static final Provider PROVIDER = new BouncyCastleProvider();
static
{
// Add the BC provider to the list of security providers
Security.addProvider( PROVIDER );
}
/**
* A boolean that indicates if this trust manager will allow self-signed certificates to be trusted.
*/
protected final boolean acceptSelfSigned;
/**
* A boolean that indicates if this trust manager will check if all certificates in the chain (including the root
* certificates) are currently valid (notBefore/notAfter check).
*/
private final boolean checkValidity;
/**
* The set of trusted issuers from the trust store. Note that these certificates are not validated. It is assumed
* that this set can be long-lived. Time-based validation should occur close to the actual usage / invocation.
*/
protected final Set<X509Certificate> trustedIssuers;
public OpenfireX509ExtendedTrustManager( KeyStore trustStore, boolean acceptSelfSigned, boolean checkValidity ) throws NoSuchAlgorithmException, KeyStoreException
{
this.acceptSelfSigned = acceptSelfSigned;
this.checkValidity = checkValidity;
// Retrieve all trusted certificates from the store, but don't validate them just yet!
final Set<X509Certificate> trusted = new HashSet<>();
final Enumeration<String> aliases = trustStore.aliases();
while ( aliases.hasMoreElements() )
{
final String alias = aliases.nextElement();
if ( trustStore.isCertificateEntry( alias ) )
{
final Certificate certificate = trustStore.getCertificate( alias );
if ( certificate instanceof X509Certificate )
{
trusted.add( (X509Certificate) certificate );
}
}
}
trustedIssuers = Collections.unmodifiableSet( trusted );
}
@Override
public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException
{
// Find and use the end entity as the selector for verification.
final X509Certificate endEntityCert = CertificateUtils.identifyEndEntityCertificate( Arrays.asList( chain ) );
final X509CertSelector selector = new X509CertSelector();
selector.setCertificate( endEntityCert );
try
{
checkChainTrusted( selector, chain );
}
catch ( InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertPathBuilderException ex )
{
throw new CertificateException( ex );
}
}
@Override
public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException
{
// Find and use the end entity as the selector for verification.
final X509Certificate endEntityCert = CertificateUtils.identifyEndEntityCertificate( Arrays.asList( chain ) );
final X509CertSelector selector = new X509CertSelector();
selector.setCertificate( endEntityCert );
try
{
checkChainTrusted( selector, chain );
}
catch ( InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertPathBuilderException ex )
{
throw new CertificateException( ex );
}
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
final Set<X509Certificate> result;
if ( checkValidity )
{
// Filter the set of issuers to see what certificates are currently valid. Note that this might result in a
// different result as compared with the last verification.
result = CertificateUtils.filterValid( this.trustedIssuers );
}
else
{
result = this.trustedIssuers;
}
return result.toArray( new X509Certificate[ result.size() ] );
}
/**
* Determine if the given partial or complete certificate chain can be trusted to represent the entity that is
* defined by the criteria specified by the 'selector' argument.
*
* A (valid) partial chain is a chain that, combined with certificates from the trust store in this manager, can be
* completed to a full chain.
*
* Chains provided to this method do not need to be in any particular order.
*
* This implementation uses the trust anchors as represented by {@link #getAcceptedIssuers()} to verify that the
* chain that is provided either includes a certificate from an accepted issuer, or is directly issued by one.
*
* Depending on the configuration of this class, other verification is done:
* <ul>
* <li>{@link #acceptSelfSigned}: when <tt>true</tt>, any chain that has a length of one and is self-signed is
* considered as a 'trust anchor' (but is still subject to other checks, such as
* expiration checks).</li>
* </ul>
*
* This method will either return a value, which indicates that the chain is trusted, or will throw an exception.
*
* @param selector Characteristics of the entity to be represented by the chain (cannot be null).
* @param chain The certificate chain that is to be verified (cannot be null or empty).
* @return A trusted certificate path (never null).
*
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws CertPathBuilderException
*/
protected CertPath checkChainTrusted( CertSelector selector, X509Certificate... chain ) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, CertPathBuilderException
{
if ( selector == null )
{
throw new IllegalArgumentException( "Argument 'selector' cannot be null");
}
if ( chain == null || chain.length == 0 )
{
throw new IllegalArgumentException( "Argument 'chain' cannot be null or an empty array.");
}
// The set of trusted issuers (for this invocation), based on the issuers from the truststore.
final Set<X509Certificate> trustedIssuers = new HashSet<>();
trustedIssuers.addAll( this.trustedIssuers );
// When accepting self-signed certificates, and the chain is a self-signed certificate, add it to the collection
// of trusted issuers. Blindly accepting this issuer is undesirable, as that would circumvent other checks, such
// as expiration checking.
if ( acceptSelfSigned && chain.length == 1 )
{
final X509Certificate cert = chain[0];
if ( cert.getSubjectDN().equals( cert.getIssuerDN() ) )
{
trustedIssuers.add( cert );
}
}
// Turn trusted into accepted issuers.
final Set<X509Certificate> acceptedIssuers;
if ( checkValidity )
{
// See what certificates are currently valid.
acceptedIssuers = CertificateUtils.filterValid( trustedIssuers );
}
else
{
acceptedIssuers = trustedIssuers;
}
// Transform all accepted issuers into a set of unique trustAnchors.
final Set<TrustAnchor> trustAnchors = CertificateUtils.toTrustAnchors( acceptedIssuers );
// All certificates that are part of the (possibly incomplete) chain.
final CertStore certificates = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Arrays.asList( chain ) ) );
// Build the configuration for the path builder. It is based on the collection of accepted issuers / trust anchors
final PKIXBuilderParameters parameters = new PKIXBuilderParameters( trustAnchors, selector );
// Validity checks are enabled by default in the CertPathBuilder implementation.
if ( !checkValidity )
{
// There is no way to configure the pathBuilder to ignore date validity. When validity checks are to be
// ignored, try to find a point in time where all certificates in the chain are valid.
final Date validPointInTime = CertificateUtils.findValidPointInTime( chain );
// This strategy to 'disable' validity checks won't work if there's no overlap of validity periods of all
// certificates. TODO improve the implementation.
if ( validPointInTime == null )
{
Log.warn( "The existing implementation is unable to fully ignore certificate validity periods for this chain, even though it is configured to do so. Certificate checks might fail because of expiration for end entity: " + chain[0] );
}
else
{
parameters.setDate( validPointInTime );
}
}
// Add all certificates that are part of the chain to the configuration. Together with the trust anchors, the
// entire chain should now be in the store.
parameters.addCertStore( certificates );
// Finally, construct (and implicitly validate) the certificate path.
final CertPathBuilder pathBuilder = CertPathBuilder.getInstance( "PKIX" );
final CertPathBuilderResult result = pathBuilder.build( parameters );
return result.getCertPath();
}
}
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jivesoftware.openfire.net.ClientTrustManager;
import org.jivesoftware.openfire.net.ServerTrustManager;
import org.jivesoftware.util.CertificateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -28,142 +30,67 @@ public class TrustStoreConfig extends CertificateStoreConfig
{
private static final Logger Log = LoggerFactory.getLogger( TrustStoreConfig.class );
private final TrustManagerFactory trustFactory;
private transient TrustManager[] trustManagers;
private final CertPathValidator certPathValidator; // not thread safe
private final CertificateFactory certificateFactory; // not thread safe.
private boolean acceptSelfSigned;
private boolean checkValidity;
public TrustStoreConfig( String path, String password, String type, boolean createIfAbsent ) throws CertificateStoreConfigException
public TrustStoreConfig( String path, String password, String type, boolean createIfAbsent, boolean acceptSelfSigned, boolean checkValidity ) throws CertificateStoreConfigException
{
super( path, password, type, createIfAbsent );
try
{
certPathValidator = CertPathValidator.getInstance( "PKIX" );
certificateFactory = CertificateFactory.getInstance( "X.509" );
trustFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
trustFactory.init( store );
}
catch ( CertificateException | NoSuchAlgorithmException | KeyStoreException ex )
{
throw new CertificateStoreConfigException( "Unable to load store of type '" + type + "' from location '" + path + "'", ex );
}
this.acceptSelfSigned = acceptSelfSigned;
this.checkValidity = checkValidity;
}
public TrustManager[] getTrustManagers()
{
return trustFactory.getTrustManagers();
}
/**
* Returns all valid certificates from the store.
*
* @return A collection of certificates (possibly empty, but never null).
*/
protected Set<TrustAnchor> getAllValidTrustAnchors() throws KeyStoreException
public synchronized TrustManager[] getTrustManagers() throws KeyStoreException, NoSuchAlgorithmException
{
final Set<TrustAnchor> results = new HashSet<>();
for ( X509Certificate certificate : getAllCertificates().values() )
{
try
{
certificate.checkValidity();
}
catch ( CertificateExpiredException | CertificateNotYetValidException e )
{
// Not yet or no longer valid. Don't include in result.
continue;
}
final TrustAnchor trustAnchor = new TrustAnchor( certificate, null );
results.add( trustAnchor );
if ( trustManagers == null ) {
trustManagers = new TrustManager[] { new OpenfireX509ExtendedTrustManager( this.getStore(), acceptSelfSigned, checkValidity ) };
}
return results;
return trustManagers;
}
/**
* Validates the provided certificate chain, by verifying (among others):
* <ul>
* <li>The validity of each certificate in the chain</li>
* <li>chain integrity (matching issuer/subject)</li>
* <li>the root of the chain is validated by a trust anchor that is in this store.</li>
* </ul>
*
* @param chain A chain of certificates (cannot be null)
* @return true when the validity of the chain could be verified, otherwise false.
*/
public synchronized boolean canTrust( Collection<X509Certificate> chain )
public synchronized void reconfigure( boolean acceptSelfSigned, boolean checkValidity ) throws CertificateStoreConfigException
{
// Input validation
if ( chain == null )
boolean needsReload = false;
if ( this.acceptSelfSigned != acceptSelfSigned )
{
throw new IllegalArgumentException( "Argument 'chain' cannot be null." );
this.acceptSelfSigned = acceptSelfSigned;
needsReload = true;
}
if (chain.isEmpty() )
if ( this.checkValidity != checkValidity )
{
return false;
this.checkValidity = checkValidity;
needsReload = true;
}
// For some reason, the default validation fails to iterate over all providers and will fail if the default
// provider does not support the algorithm of the chain. To work around this issue, this code iterates over
// each provider explicitly, returning success when at least one provider validates the chain successfully.
Log.debug( "Iterating over all available security providers in order to validate a certificate chain." );
for (Provider p : Security.getProviders())
{
try
{
final Set<TrustAnchor> trustAnchors = getAllValidTrustAnchors();
final CertPath certPath = getCertPath( chain );
final PKIXParameters parameters = new PKIXParameters( trustAnchors );
parameters.setRevocationEnabled( false ); // TODO: enable revocation list validation.
parameters.setSigProvider( p.getName() ); // Explicitly iterate over each signature provider. See comment above.
certPathValidator.validate( certPath, parameters );
Log.debug( "Provider "+p.getName()+": Able to validate certificate chain." );
return true;
}
catch ( Exception ex )
{
Log.debug( "Provider "+p.getName()+": Unable to validate certificate chain.", ex );
}
if ( needsReload ) {
reload();
}
return false;
}
/**
* Creates a CertPath instance from the provided certificate chain.
*
* This implementation can process unordered input (ordering will by applied).
*
* @param chain A certificate chain (cannot be null or an empty collection).
* @return A CertPath instance (never null).
* @throws CertificateException When no CertPath instance could be created.
*/
protected synchronized CertPath getCertPath( Collection<X509Certificate> chain ) throws CertificateException
public boolean isAcceptSelfSigned()
{
// Input validation
if ( chain == null || chain.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'chain' cannot be null or empty." );
}
return acceptSelfSigned;
}
// Note that PKCS#7 does not require a specific order for the certificates in the file - ordering is needed.
final List<X509Certificate> ordered = CertificateManager.order( chain );
public boolean isCheckValidity()
{
return checkValidity;
}
return certificateFactory.generateCertPath( ordered );
@Override
public synchronized void reload() throws CertificateStoreConfigException
{
super.reload();
trustManagers = null;
}
/**
* Imports one certificate as a trust anchor into this store.
*
* Note that this method explicitly allows one to add invalid certificates. Other methods in this class might ignore
* such a certificate ({@link #canTrust(Collection)} being a prime example).
* Note that this method explicitly allows one to add invalid certificates.
*
* As this store is intended to contain certificates for "most-trusted" / root Certificate Authorities, this method
* will fail when the PEM representation contains more than one certificate.
......@@ -209,9 +136,12 @@ public class TrustStoreConfig extends CertificateStoreConfig
}
catch ( CertificateException | KeyStoreException | IOException e )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to install a certificate into a trust store.", e );
}
finally
{
reload(); // re-initialize store.
}
// TODO Notify listeners that a new certificate has been added.
}
......
......@@ -168,16 +168,24 @@ public class SocketConnection implements Connection {
@Deprecated
public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception {
final boolean isClientToServer = ( remoteServer == null );
startTLS( clientMode, isClientToServer, authentication );
final boolean isPeerClient = ( remoteServer == null );
startTLS( clientMode, isPeerClient, authentication );
}
public void startTLS(boolean clientMode, boolean isClientToServer, ClientAuth authentication) throws IOException {
public void startTLS(boolean clientMode, boolean isPeerClient, ClientAuth authentication) throws IOException {
if (!secure) {
secure = true;
// Prepare for TLS
tlsStreamHandler = new TLSStreamHandler(this, socket, clientMode, remoteServer,
session instanceof IncomingServerSession);
final ClientAuth clientAuth;
if (session instanceof IncomingServerSession)
{
clientAuth = ClientAuth.needed;
}
else
{
clientAuth = ClientAuth.wanted;
}
tlsStreamHandler = new TLSStreamHandler(socket, clientMode, isPeerClient, clientAuth);
if (!clientMode) {
// Indicate the client that the server is ready to negotiate TLS
deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
......@@ -574,7 +582,7 @@ public class SocketConnection implements Connection {
private void release() {
writeStarted = -1;
instances.remove(this);
}
}
private void closeConnection() {
release();
......
......@@ -94,7 +94,12 @@ public class TLSStreamHandler {
public TLSStreamHandler(Connection connection, Socket socket, boolean clientMode, String remoteServer,
boolean needClientAuth) throws IOException
{
this(socket,clientMode,(remoteServer==null),needClientAuth);
this(
socket,
clientMode,
remoteServer==null,
needClientAuth?Connection.ClientAuth.needed: Connection.ClientAuth.wanted
);
}
/**
......@@ -105,14 +110,12 @@ public class TLSStreamHandler {
*
* @param socket the plain socket connection to secure
* @param clientMode boolean indicating if this entity is a client or a server.
* @param isClientToServer indicates if the remote party is a server.
* @param needClientAuth boolean that indicates if client should authenticate during the TLS
* negotiation. This option is only required when the client is a server since
* EXTERNAL SASL is going to be used.
* @param isPeerClient indicates if the remote party is a client (or server).
* @param clientAuth indicates if client should authenticate during the TLS negotiation.
* @throws java.io.IOException
*/
public TLSStreamHandler(Socket socket, boolean clientMode, boolean isClientToServer, boolean needClientAuth) throws IOException {
wrapper = new TLSWrapper(clientMode, needClientAuth, isClientToServer);
public TLSStreamHandler(Socket socket, boolean clientMode, boolean isPeerClient, Connection.ClientAuth clientAuth) throws IOException {
wrapper = new TLSWrapper(clientMode, clientAuth, isPeerClient);
tlsEngine = wrapper.getTlsEngine();
reader = new TLSStreamReader(wrapper, socket);
writer = new TLSStreamWriter(wrapper, socket);
......@@ -145,7 +148,7 @@ public class TLSStreamHandler {
initialHSStatus = HandshakeStatus.NEED_WRAP;
tlsEngine.beginHandshake();
}
else if (needClientAuth) {
else if (clientAuth == Connection.ClientAuth.needed) {
// Only REQUIRE client authentication if we are fully verifying certificates
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true) &&
JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true) &&
......
......@@ -23,21 +23,14 @@ package org.jivesoftware.openfire.net;
import java.nio.ByteBuffer;
import java.security.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -70,59 +63,34 @@ public class TLSWrapper {
@Deprecated
public TLSWrapper(Connection connection, boolean clientMode, boolean needClientAuth, String remoteServer)
{
this(clientMode,needClientAuth,(remoteServer == null));
this(
clientMode,
needClientAuth?Connection.ClientAuth.needed:Connection.ClientAuth.wanted,
remoteServer == null
);
}
public TLSWrapper(boolean clientMode, boolean needClientAuth, boolean isClientToServer) {
public TLSWrapper(boolean clientMode, Connection.ClientAuth clientAuth, boolean isPeerClient) {
// Create/initialize the SSLContext with key material
try {
// First initialize the key and trust material.
final SSLConfig sslConfig = SSLConfig.getInstance();
final Purpose purpose = (isClientToServer ? Purpose.SOCKETBASED_C2S_TRUSTSTORE : Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) sslConfig.getStoreConfig( purpose );
// TrustManager's decide whether to allow connections.
final TrustManager[] tm;
if (clientMode || needClientAuth)
try
{
final SSLEngine sslEngine;
if ( clientMode )
{
final KeyStore ksTrust = trustStoreConfig.getStore();
if (isClientToServer)
{
// Check if we can trust certificates presented by the client
tm = new TrustManager[]{new ClientTrustManager(ksTrust)};
}
else
{
// Check if we can trust certificates presented by the server
tm = new TrustManager[]{new ServerTrustManager(ksTrust)};
}
sslEngine = SSLConfig.getClientModeSSLEngine( SSLConfig.Type.SOCKET_S2S );
}
else
{
tm = trustStoreConfig.getTrustManagers();
final SSLConfig.Type type = isPeerClient ? SSLConfig.Type.SOCKET_C2S : SSLConfig.Type.SOCKET_S2S;
sslEngine = SSLConfig.getServerModeSSLEngine( type, clientAuth );
}
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final SSLContext tlsContext = SSLConfig.getSSLContext();
tlsContext.init( identityStoreConfig.getKeyManagers(), tm, null);
/*
* Configure the tlsEngine to act as a server in the SSL/TLS handshake. We're a server,
* so no need to use host/port variant.
*
* The first call for a server is a NEED_UNWRAP.
*/
tlsEngine = tlsContext.createSSLEngine();
tlsEngine.setUseClientMode(clientMode);
SSLSession sslSession = tlsEngine.getSession();
final SSLSession sslSession = sslEngine.getSession();
netBuffSize = sslSession.getPacketBufferSize();
appBuffSize = sslSession.getApplicationBufferSize();
}
catch ( NoSuchAlgorithmException | KeyManagementException ex )
catch ( NoSuchAlgorithmException | KeyManagementException | KeyStoreException ex )
{
Log.error("TLSHandler startup problem. SSLContext initialisation failed.", ex );
}
......
......@@ -160,6 +160,7 @@ public abstract class VirtualConnection implements Connection {
}
@Override
public void addCompression() {
//Ignore
}
......
......@@ -28,6 +28,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
......@@ -46,6 +47,7 @@ import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.compression.CompressionFilter;
import org.apache.mina.filter.ssl.SslFilter;
import org.dom4j.io.OutputFormat;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionCloseListener;
import org.jivesoftware.openfire.PacketDeliverer;
......@@ -58,6 +60,7 @@ import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.Packet;
......@@ -74,6 +77,12 @@ public class NIOConnection implements Connection {
private static final Logger Log = LoggerFactory.getLogger(NIOConnection.class);
public enum State { RUNNING, CLOSING, CLOSED }
/**
* The utf-8 charset for decoding and encoding XMPP packet streams.
*/
public static final String CHARSET = "UTF-8";
private LocalSession session;
private IoSession ioSession;
......@@ -125,6 +134,7 @@ public class NIOConnection implements Connection {
public NIOConnection(IoSession session, PacketDeliverer packetDeliverer) {
this.ioSession = session;
this.backupDeliverer = packetDeliverer;
state = State.RUNNING;
}
@Override
......@@ -368,7 +378,7 @@ public class NIOConnection implements Connection {
@Deprecated
@Override
public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception {
final boolean isClientToServer = (remoteServer == null);
final boolean isClientToServer = ( remoteServer == null );
startTLS( clientMode, isClientToServer, authentication );
}
......
......@@ -427,7 +427,6 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
// Start clients SSL unless it's been disabled.
if (isClientSSLListenerEnabled()) {
int port = getClientSSLListenerPort();
String algorithm = JiveGlobals.getProperty(ConnectionSettings.Client.TLS_ALGORITHM, "TLS");
try {
// Customize Executor that will be used by processors to process incoming stanzas
int maxPoolSize = JiveGlobals.getIntProperty(ConnectionSettings.Client.MAX_THREADS_SSL, 16);
......@@ -453,19 +452,14 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
sslSocketAcceptor, maxBufferSize);
// Add the SSL filter now since sockets are "borned" encrypted in the old ssl method
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final SSLContext sslContext = SSLConfig.getSSLContext();
sslContext.init( identityStoreConfig.getKeyManagers(), trustStoreConfig.getTrustManagers(), new java.security.SecureRandom());
SslFilter sslFilter = new SslFilter(sslContext);
if (JiveGlobals.getProperty(ConnectionSettings.Client.AUTH_PER_CLIENTCERT_POLICY,"disabled").equals("needed")) {
sslFilter.setNeedClientAuth(true);
}
else if(JiveGlobals.getProperty(ConnectionSettings.Client.AUTH_PER_CLIENTCERT_POLICY,"disabled").equals("wanted")) {
sslFilter.setWantClientAuth(true);
Connection.ClientAuth clientAuth;
try {
clientAuth = Connection.ClientAuth.valueOf(JiveGlobals.getProperty(ConnectionSettings.Client.AUTH_PER_CLIENTCERT_POLICY, "disabled"));
} catch (IllegalArgumentException e) {
clientAuth = Connection.ClientAuth.disabled;
}
final SslFilter sslFilter = SSLConfig.getServerModeSslFilter( SSLConfig.Type.SOCKET_C2S, clientAuth );
sslSocketAcceptor.getFilterChain().addAfter(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, sslFilter);
}
......@@ -496,7 +490,7 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
ports.add(new ServerPort(port, serverName, localIPAddress, true, null, ServerPort.Type.client));
List<String> params = new ArrayList<>();
List<String> params = new ArrayList<String>();
params.add(Integer.toString(port));
Log.info(LocaleUtils.getLocalizedString("startup.ssl", params));
}
......
......@@ -63,6 +63,7 @@ import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.jivesoftware.openfire.keystore.CertificateStoreConfig;
import org.jivesoftware.openfire.keystore.CertificateStoreConfigException;
import org.jivesoftware.openfire.keystore.CertificateUtils;
import org.jivesoftware.util.cert.CertificateIdentityMapping;
import org.jivesoftware.util.cert.CNCertificateIdentityMapping;
import org.jivesoftware.util.cert.SANCertificateIdentityMapping;
......@@ -851,69 +852,12 @@ public class CertificateManager {
*
* @param certificates an unordered collection of certificates (cannot be null).
* @return An ordered list of certificates (possibly empty, but never null).
* @deprecated Moved to CertificateUtils
*/
@Deprecated
public static List<X509Certificate> order( Collection<X509Certificate> certificates ) throws CertificateException
{
final LinkedList<X509Certificate> orderedResult = new LinkedList<>();
if ( certificates.isEmpty() ) {
return orderedResult;
}
if (certificates.size() == 1) {
orderedResult.addAll( certificates );
return orderedResult;
}
final Map<Principal, X509Certificate> byIssuer = new HashMap<>();
final Map<Principal, X509Certificate> bySubject = new HashMap<>();
for ( final X509Certificate certificate : certificates ) {
final Principal issuer = certificate.getIssuerDN();
final Principal subject = certificate.getSubjectDN();
if ( byIssuer.put( issuer, certificate ) != null ) {
throw new CertificateException( "The provided input should not contain multiple certificates with identical issuerDN values." );
}
if ( bySubject.put( subject, certificate ) != null ) {
throw new CertificateException( "The provided input should not contain multiple certificates with identical subjectDN values." );
}
}
// The first certificate will have a 'subject' value that's not an 'issuer' of any other chain.
X509Certificate first = null;
for ( Map.Entry<Principal, X509Certificate> entry : bySubject.entrySet() ) {
final Principal subject = entry.getKey();
final X509Certificate certificate = entry.getValue();
if ( ! byIssuer.containsKey( subject ) ) {
if (first == null) {
first = certificate;
} else {
throw new CertificateException( "The provided input should not contain more than one certificates that has a subjectDN value that's not equal to the issuerDN value of another certificate." );
}
}
}
if (first == null) {
throw new CertificateException( "The provided input should contain a certificates that has a subjectDN value that's not equal to the issuerDN value of any other certificate." );
}
orderedResult.add( first );
// With the first certificate in hand, every following certificate should have a subject that's equal to the previous issuer value.
X509Certificate next = bySubject.get( first.getIssuerDN() );
while (next != null) {
orderedResult.add( next );
next = bySubject.get( next.getIssuerDN() );
}
// final check
if (orderedResult.size() != certificates.size()) {
throw new CertificateException( "Unable to recreate a certificate chain from the provided input." );
}
return orderedResult;
return CertificateUtils.order( certificates );
}
/**
......
......@@ -58,7 +58,7 @@ public class SimpleSSLSocketFactory extends SSLSocketFactory implements Comparat
public SimpleSSLSocketFactory() {
try {
final SSLContext sslContext = SSLConfig.getSSLContext();
final SSLContext sslContext = SSLContext.getDefault();
sslContext.init(null, // KeyManager not required
new TrustManager[] { new DummyTrustManager() },
new java.security.SecureRandom());
......
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.Test;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
/**
* Unit tests that verify the functionality of {@link CertificateUtils}.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class CertificateUtilsTest
{
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* is null returns an empty collection.
*/
@Test
public void testFilterValidNull() throws Exception
{
// Setup fixture.
final Collection<X509Certificate> input = null;
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertTrue( result.isEmpty() );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* is an empty (not-null) collection returns an empty collection.
*/
@Test
public void testFilterValidEmpty() throws Exception
{
// Setup fixture.
final Collection<X509Certificate> input = new ArrayList<>();
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertTrue( result.isEmpty() );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains one valid certificate returns an collection that contains that certificate.
*/
@Test
public void testFilterValidWithOneValidCert() throws Exception
{
// Setup fixture.
final X509Certificate valid = KeystoreTestUtils.generateValidCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( valid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertEquals( 1, result.size() );
Assert.assertTrue( result.contains( valid ) );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains one invalid certificate returns an collection that is empty.
*/
@Test
public void testFilterValidWithOneInvalidCert() throws Exception
{
// Setup fixture.
final X509Certificate invalid = KeystoreTestUtils.generateExpiredCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( invalid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertTrue( result.isEmpty() );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains two duplicate, valid certificates returns an collection that contains that certificate once.
*/
@Test
public void testFilterValidWithTwoDuplicateValidCerts() throws Exception
{
// Setup fixture.
final X509Certificate valid = KeystoreTestUtils.generateValidCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( valid );
input.add( valid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertEquals( 1, result.size() );
Assert.assertTrue( result.contains( valid ) );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains two distinct, valid certificates returns an collection that contains both certificates.
*/
@Test
public void testFilterValidWithTwoDistinctValidCerts() throws Exception
{
// Setup fixture.
final X509Certificate validA = KeystoreTestUtils.generateValidCertificate();
final X509Certificate validB = KeystoreTestUtils.generateValidCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( validA );
input.add( validB );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertEquals( 2, result.size() );
Assert.assertTrue( result.contains( validA ) );
Assert.assertTrue( result.contains( validB ) );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains two duplicate, invalid certificate returns an collection that is empty.
*/
@Test
public void testFilterValidWithTwoDuplicateInvalidCerts() throws Exception
{
// Setup fixture.
final X509Certificate invalid = KeystoreTestUtils.generateExpiredCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( invalid );
input.add( invalid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertTrue( result.isEmpty() );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains two distinct, invalid certificate returns an collection that is empty.
*/
@Test
public void testFilterValidWithTwoDistinctInvalidCerts() throws Exception
{
// Setup fixture.
final X509Certificate invalidA = KeystoreTestUtils.generateExpiredCertificate();
final X509Certificate invalidB = KeystoreTestUtils.generateExpiredCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( invalidA );
input.add( invalidB );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertTrue( result.isEmpty() );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains one valid and one invalid valid certificatereturns an collection that contains one valid certificate.
*/
@Test
public void testFilterValidWithValidAndInvalidCerts() throws Exception
{
// Setup fixture.
final X509Certificate valid = KeystoreTestUtils.generateValidCertificate();
final X509Certificate invalid = KeystoreTestUtils.generateExpiredCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( valid );
input.add( invalid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertEquals( 1, result.size() );
Assert.assertTrue( result.contains( valid ) );
}
/**
* Test for {@link CertificateUtils#filterValid(Collection)}. Verifies that an input argument that
* contains:
* - one valid
* - another valid (duplicated from the first)
* - a third valid (no duplicate)
* - and one invalid valid certificate
* returns an collection that contains the two distinc valid certificates.
*/
@Test
public void testFilterValidWithMixOfValidityAndDuplicates() throws Exception
{
// Setup fixture.
final X509Certificate validA = KeystoreTestUtils.generateValidCertificate();
final X509Certificate validB = KeystoreTestUtils.generateValidCertificate();
final X509Certificate invalid = KeystoreTestUtils.generateExpiredCertificate();
final Collection<X509Certificate> input = new ArrayList<>();
input.add( validA );
input.add( validA );
input.add( validB );
input.add( invalid );
// Execute system under test.
final Collection<X509Certificate> result = CertificateUtils.filterValid( input );
// Verify results.
Assert.assertEquals( 2, result.size() );
Assert.assertTrue( result.contains( validA ) );
Assert.assertTrue( result.contains( validB ) );
}
}
package org.jivesoftware.openfire.keystore;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.cert.*;
import java.util.*;
/**
* Unit tests that verify the functionality of {@link OpenfireX509ExtendedTrustManager}.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class OpenfireX509ExtendedTrustManagerTest
{
/**
* An instance that is freshly recreated before each test.
*/
private OpenfireX509ExtendedTrustManager systemUnderTest;
/**
* The keystore that contains the certificates used by the system under test (refreshed before every test invocation).
*/
private KeyStore trustStore;
private String location; // location on disc for the keystore. Used to clean-up after each test.
private X509Certificate[] validChain;
private X509Certificate[] expiredIntChain;
private X509Certificate[] expiredRootChain;
private X509Certificate[] untrustedCAChain;
@Before
public void createFixture() throws Exception
{
// Create a fresh store in a location that holds only temporary files.
final String tempDir = System.getProperty("java.io.tmpdir");
location = tempDir + ( tempDir.endsWith( File.separator ) ? "" : File.separator ) + UUID.randomUUID();
trustStore = KeyStore.getInstance( KeyStore.getDefaultType());
final String password = "TS%WV@# aSG 4";
trustStore.load( null, password.toCharArray() );
// Populate the store with a valid CA certificate.
validChain = KeystoreTestUtils.generateValidCertificateChain();
expiredIntChain = KeystoreTestUtils.generateCertificateChainWithExpiredIntermediateCert();
expiredRootChain = KeystoreTestUtils.generateCertificateChainWithExpiredRootCert();
untrustedCAChain = KeystoreTestUtils.generateValidCertificateChain();
trustStore.setCertificateEntry( getLast( validChain ).getSubjectDN().getName(), getLast( validChain ) );
trustStore.setCertificateEntry( getLast( expiredIntChain ).getSubjectDN().getName(), getLast( expiredIntChain ) );
trustStore.setCertificateEntry( getLast( expiredRootChain ).getSubjectDN().getName(), getLast( expiredRootChain ) );
// Persist the key store file
try ( FileOutputStream fos = new FileOutputStream( location ) ) {
trustStore.store( fos, password.toCharArray() );
}
// Create the Trust Manager that is subject of these tests.
systemUnderTest = null; // TODO FIXME // new OpenfireX509ExtendedTrustManager( trustStore );
}
/**
* Returns the last element from the provided array.
* @param chain An array (cannot be null).
* @return The last element of the provided array.
*/
private static <X extends Object> X getLast(X[]chain) {
return chain[ chain.length - 1 ];
}
@After
public void tearDown() throws Exception
{
// Attempt to delete any left-overs from the test.
validChain = null;
if ( trustStore != null)
{
trustStore = null;
Files.deleteIfExists( Paths.get( location ) );
}
systemUnderTest = null;
}
/**
* This test verifies that {@link OpenfireX509ExtendedTrustManager#getAcceptedIssuers()} does not return expired
* certificates.
*/
@Test
public void testAcceptedIssuersAreAllValid() throws Exception
{
// Setup fixture.
// Execute system under test.
final X509Certificate[] result = systemUnderTest.getAcceptedIssuers();
// Verify results.
Assert.assertEquals( 2, result.length );
Assert.assertTrue( Arrays.asList( result ).contains( getLast( validChain ) ) );
Assert.assertTrue( Arrays.asList( result ).contains( getLast( expiredIntChain ) ) );
Assert.assertFalse( Arrays.asList( result ).contains( getLast( expiredRootChain ) ) );
}
/**
* Verifies that a valid chain is not rejected.
*/
@Test
public void testValidChain() throws Exception
{
// Setup fixture.
// Execute system under test.
systemUnderTest.checkClientTrusted( validChain, "RSA" );
// Verify result
// (getting here without an exception being thrown is enough).
}
/**
* Verifies that a chain that has an intermediate certificate that is expired is rejected.
*/
@Test(expected = CertificateException.class)
public void testInvalidChainExpiredIntermediate() throws Exception
{
// Setup fixture.
// Execute system under test.
systemUnderTest.checkClientTrusted( expiredIntChain, "RSA" );
}
/**
* Verifies that a chain that has an root certificate (trust anchor) that is expired is rejected.
*/
@Test(expected = CertificateException.class)
public void testInvalidChainExpiredTrustAnchor() throws Exception
{
// Setup fixture.
// Execute system under test.
systemUnderTest.checkClientTrusted( expiredRootChain, "RSA" );
}
/**
* Verifies that a chain that is missing an intermediate certificate is rejected.
*/
@Test(expected = CertificateException.class)
public void testInvalidChainMissingIntermediate() throws Exception
{
// Setup fixture.
assert validChain.length == 4;
final X509Certificate[] input = new X509Certificate[ 3 ];
input[ 0 ] = validChain[ 0 ];
input[ 1 ] = validChain[ 2 ];
input[ 2 ] = validChain[ 3 ];
// Execute system under test.
systemUnderTest.checkClientTrusted( input, "RSA" );
}
/**
* Verifies that a chain that is valid, but does not have its root CA certificate in the trust store, is rejected.
*/
@Test(expected = CertificateException.class)
public void testInvalidChainCAnotTrusted() throws Exception
{
// Setup fixture.
// Execute system under test.
systemUnderTest.checkClientTrusted( untrustedCAChain, "RSA" );
}
}
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