Commit fc648e2b authored by Guus der Kinderen's avatar Guus der Kinderen

Merge pull request #325 from guusdk/OF-946

OF-946: Openfire should allow for more than one set of key stores.
parents 06876d60 25c09adb
......@@ -606,12 +606,26 @@ tab.server.descr=Click to manage server settings
sidebar.transfer-proxy=File Transfer Settings
sidebar.transfer-proxy.descr=Click to view file tranfer settings
sidebar.sidebar-certificates=TLS/SSL Certificates
sidebar.security-keystore=Openfire Certificates
sidebar.security-keystore.descr=Click to manage certificates for this Openfire server.
sidebar.security-truststore-c2s=Client Truststore
sidebar.security-truststore-c2s.descr=Click to manage certificates used in client-to-server communication
sidebar.security-truststore-s2s=Server Truststore
sidebar.security-truststore-s2s.descr=Click to manage certificates used in server-to-server communication
sidebar.sidebar-certificates-keys=Openfire Certificates
sidebar.sidebar-certificates-keys-submenu=Connectivity Type
sidebar.security-keystore-socket=Socket
sidebar.security-keystore-socket.descr=Click to manage certificates used for socket-based connections on this Openfire server.
sidebar.security-keystore-bosh=BOSH
sidebar.security-keystore-bosh.descr=Click to manage certificates used for BOSH-based (HTTP binding) on this Openfire server.
sidebar.security-keystore-administrative=Administrative
sidebar.security-keystore-administrative.descr=Click to manage certificates used for administrative interfaces on this Openfire server.
sidebar.security-truststore-socket-c2s=Client (socket) Truststore
sidebar.security-truststore-socket-c2s.descr=Click to manage certificates used for socket-based, client-to-server communication.
sidebar.security-truststore-socket-s2s=Server (socket) Truststore
sidebar.security-truststore-socket-s2s.descr=Click to manage certificates used for socket-based, server-to-server communication.
sidebar.security-truststore-bosh-c2s=Client (BOSH) Truststore
sidebar.security-truststore-bosh-c2s.descr=Click to manage certificates used for BOSH-based (HTTP binding), client-to-server communication.
sidebar.security-truststore-bosh-s2s=Server (BOSH) Truststore
sidebar.security-truststore-bosh-s2s.descr=Click to manage certificates used for BOSH-based (HTTP binding), server-to-server communication.
sidebar.security-truststore-administrative-c2s=Client (administrative) Truststore
sidebar.security-truststore-administrative-c2s.descr=Click to manage certificates used for administrative, client-to-server communication.
sidebar.security-truststore-administrative-s2s=Server (administrative) Truststore
sidebar.security-truststore-administrative-s2s.descr=Click to manage certificates used for administrative, server-to-server communication.
sidebar.sidebar-media-services=Media Services
sidebar.media-proxy=Media Proxy
sidebar.media-proxy.descr=Click to view media proxy settings.
......@@ -2291,7 +2305,6 @@ ssl.settings.server.dialback=Server Dialback:
ssl.settings.server.customTLS=TLS method:
# Generic certificate-related messages
ssl.certificates.added_updated=Certificate added or modified successfully.
ssl.certificates.deleted=Certificate deleted successfully.
ssl.certificates.generated=Certificates generated successfully.
......@@ -2409,6 +2422,27 @@ ssl.signing-request.requests_info=Below you will find the signing requests gener
ssl.signing-request.alias=Alias
ssl.signing-request.signing-request=Signing Request
# Certificate management
certificate-management.purpose.SOCKETBASED_IDENTITYSTORE.title=Identity Store (socket)
certificate-management.purpose.SOCKETBASED_IDENTITYSTORE.description=This store contains certificates that identify this Openfire instance, used for plain socket-based connections.
certificate-management.purpose.SOCKETBASED_S2S_TRUSTSTORE.title=Server-to-Server Trust Store (socket)
certificate-management.purpose.SOCKETBASED_S2S_TRUSTSTORE.description=This store contains certificates of security authorities that are trusted to identify other XMPP servers. These certificates are used during server-to-server federation via plain socket-based connections.
certificate-management.purpose.SOCKETBASED_C2S_TRUSTSTORE.title=Client-to-Server Trust Store (socket)
certificate-management.purpose.SOCKETBASED_C2S_TRUSTSTORE.description=This store contains certificates of security authorities that are trusted to identify XMPP clients. These certificates are used during mutual authentication via plain socket-based connections.
certificate-management.purpose.BOSHBASED_IDENTITYSTORE.title=Identity Store (BOSH/HTTP-bind)
certificate-management.purpose.BOSHBASED_IDENTITYSTORE.description=This store contains certificates that identify this Openfire instance, used for BOSH (HTTP-bind) connections.
certificate-management.purpose.BOSHBASED_C2S_TRUSTSTORE.title=Client-to-Server Trust Store (BOSH/HTTP-bind)
certificate-management.purpose.BOSHBASED_C2S_TRUSTSTORE.description=This store contains certificates of security authorities that are trusted to identify XMPP clients. These certificates are used during mutual authentication via BOSH (HTTP-bind) connections.
certificate-management.purpose.ADMINISTRATIVE_IDENTITYSTORE.title=Administrative Identity Store
certificate-management.purpose.ADMINISTRATIVE_IDENTITYSTORE.description=This store contains certificates that identify this Openfire instance, used for connections to administrative services (eg: user providers).
certificate-management.purpose.ADMINISTRATIVE_TRUSTSTORE.title=Administrative Trust Store
certificate-management.purpose.ADMINISTRATIVE_TRUSTSTORE.description=This store contains certificates of security authorities that are trusted to identify applications/servers that provide administrative functionality (eg: user providers).
certificate-management.purpose.WEBADMIN_IDENTITYSTORE.title=Admin Panel Identity Store
certificate-management.purpose.WEBADMIN_IDENTITYSTORE.description=This store contains certificates that identify this Openfire instance, used by the Web-Admin panel (when accessed via HTTPS).
certificate-management.purpose.WEBADMIN_TRUSTSTORE.title=Admin Panel Trust Store
certificate-management.purpose.WEBADMIN_TRUSTSTORE.description=This store contains certificates of security authorities that are trusted to identify parties that wish to interact with the Openfire Web-Admin.
# Restart HTTP server
server-restart.title=HTTP Server Restart
......@@ -3071,7 +3105,9 @@ mediaproxy.summary.stopbutton = Stop Active Sessions
# Import keystore certificate page
ssl.import.certificate.keystore.title=Import Signed Certificate
ssl.import.certificate.keystore.socket.title=Import Signed Certificate for Socket-based Communication
ssl.import.certificate.keystore.bosh.title=Import Signed Certificate for BOSH-based Communication
ssl.import.certificate.keystore.administrative.title=Import Signed Certificate for Administrative Purposes
ssl.import.certificate.keystore.info=Use the form below to import a private key and certificate that was provided by a \
Certificate Authority. Make sure that root certificates of the CA signing the certificate are present in the \
truststore. Otherwise you will need to manually import them using the "keytool" command line tool. If you are \
......
......@@ -4,6 +4,7 @@ import org.bouncycastle.asn1.*;
import org.jivesoftware.util.Log;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
......
......@@ -17,30 +17,6 @@
package org.jivesoftware.openfire;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
......@@ -50,7 +26,6 @@ import org.jivesoftware.openfire.admin.AdminManager;
import org.jivesoftware.openfire.audit.AuditManager;
import org.jivesoftware.openfire.audit.spi.AuditManagerImpl;
import org.jivesoftware.openfire.auth.ScramUtils;
import org.jivesoftware.openfire.clearspace.ClearspaceManager;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.commands.AdHocCommandHandler;
......@@ -58,69 +33,43 @@ import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.container.AdminConsolePlugin;
import org.jivesoftware.openfire.container.Module;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.disco.IQDiscoInfoHandler;
import org.jivesoftware.openfire.disco.IQDiscoItemsHandler;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.disco.ServerIdentitiesProvider;
import org.jivesoftware.openfire.disco.ServerItemsProvider;
import org.jivesoftware.openfire.disco.UserIdentitiesProvider;
import org.jivesoftware.openfire.disco.UserItemsProvider;
import org.jivesoftware.openfire.disco.*;
import org.jivesoftware.openfire.filetransfer.DefaultFileTransferManager;
import org.jivesoftware.openfire.filetransfer.FileTransferManager;
import org.jivesoftware.openfire.filetransfer.proxy.FileTransferProxy;
import org.jivesoftware.openfire.handler.IQAuthHandler;
import org.jivesoftware.openfire.handler.IQBindHandler;
import org.jivesoftware.openfire.handler.IQEntityTimeHandler;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.handler.IQLastActivityHandler;
import org.jivesoftware.openfire.handler.IQMessageCarbonsHandler;
import org.jivesoftware.openfire.handler.IQOfflineMessagesHandler;
import org.jivesoftware.openfire.handler.IQPingHandler;
import org.jivesoftware.openfire.handler.IQPrivacyHandler;
import org.jivesoftware.openfire.handler.IQPrivateHandler;
import org.jivesoftware.openfire.handler.IQRegisterHandler;
import org.jivesoftware.openfire.handler.IQRosterHandler;
import org.jivesoftware.openfire.handler.IQSessionEstablishmentHandler;
import org.jivesoftware.openfire.handler.IQSharedGroupHandler;
import org.jivesoftware.openfire.handler.IQTimeHandler;
import org.jivesoftware.openfire.handler.IQVersionHandler;
import org.jivesoftware.openfire.handler.IQvCardHandler;
import org.jivesoftware.openfire.handler.PresenceSubscribeHandler;
import org.jivesoftware.openfire.handler.PresenceUpdateHandler;
import org.jivesoftware.openfire.handler.*;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.mediaproxy.MediaProxyService;
import org.jivesoftware.openfire.muc.MultiUserChatManager;
import org.jivesoftware.openfire.net.MulticastDNSService;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.ServerTrafficCounter;
import org.jivesoftware.openfire.pep.IQPEPHandler;
import org.jivesoftware.openfire.pep.IQPEPOwnerHandler;
import org.jivesoftware.openfire.pubsub.PubSubModule;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.session.RemoteSessionLocator;
import org.jivesoftware.openfire.spi.ConnectionManagerImpl;
import org.jivesoftware.openfire.spi.PacketDelivererImpl;
import org.jivesoftware.openfire.spi.PacketRouterImpl;
import org.jivesoftware.openfire.spi.PacketTransporterImpl;
import org.jivesoftware.openfire.spi.PresenceManagerImpl;
import org.jivesoftware.openfire.spi.RoutingTableImpl;
import org.jivesoftware.openfire.spi.XMPPServerInfoImpl;
import org.jivesoftware.openfire.transport.TransportHandler;
import org.jivesoftware.openfire.update.UpdateManager;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.InitializationException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.util.Version;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import java.io.*;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The main XMPP server that will load, initialize and start all the server's
* modules. The server is unique in the JVM and could be obtained by using the
......@@ -421,31 +370,14 @@ public class XMPPServer {
JiveGlobals.setProperty(propName, JiveGlobals.getXMLProperty(propName));
}
}
// Set default SASL SCRAM-SHA-1 iteration count
JiveGlobals.setProperty("sasl.scram-sha-1.iteration-count", Integer.toString(ScramUtils.DEFAULT_ITERATION_COUNT));
// Update certificates (if required)
try {
// Check if keystore already has certificates for current domain
KeyStore ksKeys = SSLConfig.getKeyStore();
boolean dsaFound = CertificateManager.isDSACertificate(ksKeys, name);
boolean rsaFound = CertificateManager.isRSACertificate(ksKeys, name);
// No certificates were found so create new self-signed certificates
if (!dsaFound) {
CertificateManager.createDSACert(ksKeys, SSLConfig.getKeyPassword(),
name + "_dsa", "cn=" + name, "cn=" + name, "*." + name);
}
if (!rsaFound) {
CertificateManager.createRSACert(ksKeys, SSLConfig.getKeyPassword(),
name + "_rsa", "cn=" + name, "cn=" + name, "*." + name);
}
// Save new certificates into the key store
if (!dsaFound || !rsaFound) {
SSLConfig.saveStores();
}
final IdentityStoreConfig storeConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
storeConfig.ensureDomainCertificates( "DSA", "RSA" );
} catch (Exception e) {
logger.error("Error generating self-signed certificates", e);
}
......@@ -580,7 +512,7 @@ public class XMPPServer {
/**
* Loads a module.
*
* @param module the name of the class that implements the Module interface.
* @param moduleName the name of the class that implements the Module interface.
*/
@SuppressWarnings("unchecked")
private void loadModule(String moduleName, String moduleImpl) {
......
......@@ -35,6 +35,7 @@ import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.HttpClientError;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.net.SSLConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -65,7 +66,12 @@ public class SSLProtocolSocketFactory implements SecureProtocolSocketFactory {
SSLContext context = SSLContext.getInstance("SSL");
context.init(
null,
new TrustManager[]{new ClearspaceX509TrustManager(host, manager.getProperties(), SSLConfig.gets2sTrustStore())},
new TrustManager[] {
new ClearspaceX509TrustManager(
host,
manager.getProperties(),
SSLConfig.getStore( Purpose.ADMINISTRATIVE_TRUSTSTORE ) )
},
null);
return context;
} catch (Exception e) {
......
......@@ -42,6 +42,9 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.CertificateStoreConfig;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.CertificateManager;
......@@ -113,18 +116,14 @@ public class AdminConsolePlugin implements Plugin {
adminServer.addBean(jmx.getContainer());
}
ServerConnector httpConnector = null;
ServerConnector httpsConnector = null;
HttpConfiguration httpConfig = null;
// Create connector for http traffic if it's enabled.
if (adminPort > 0) {
httpConfig = new HttpConfiguration();
final HttpConfiguration httpConfig = new HttpConfiguration();
// Do not send Jetty info in HTTP headers
httpConfig.setSendServerVersion( false );
httpConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads,
new HttpConnectionFactory(httpConfig));
final ServerConnector httpConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads, new HttpConnectionFactory(httpConfig));
// Listen on a specific network interface if it has been set.
String bindInterface = getBindInterface();
......@@ -136,23 +135,28 @@ public class AdminConsolePlugin implements Plugin {
// Create a connector for https traffic if it's enabled.
sslEnabled = false;
try {
if (adminSecurePort > 0 && CertificateManager.isRSACertificate(SSLConfig.getKeyStore(), "*"))
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.WEBADMIN_IDENTITYSTORE );
if (adminSecurePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() )
{
if (!CertificateManager.isRSACertificate(SSLConfig.getKeyStore(),
XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
if ( !identityStoreConfig.containsDomainCertificate( "RSA" )) {
Log.warn("Admin console: Using RSA certificates but they are not valid for the hosted domain");
}
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.addExcludeProtocols("SSLv3");
sslContextFactory.setTrustStorePassword(SSLConfig.gets2sTrustPassword());
sslContextFactory.setTrustStoreType(SSLConfig.getStoreType());
sslContextFactory.setKeyStorePath(SSLConfig.getKeystoreLocation());
sslContextFactory.setNeedClientAuth(false);
sslContextFactory.setWantClientAuth(false);
sslContextFactory.setKeyStorePassword(SSLConfig.getKeyPassword());
sslContextFactory.setKeyStoreType(SSLConfig.getStoreType());
final CertificateStoreConfig trustStoreConfig = SSLConfig.getInstance().getStoreConfig( Purpose.WEBADMIN_TRUSTSTORE );
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() );
sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() );
sslContextFactory.setTrustStoreType( trustStoreConfig.getType() );
sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() );
sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() );
sslContextFactory.addExcludeProtocols( "SSLv3" );
sslContextFactory.setNeedClientAuth( false );
sslContextFactory.setWantClientAuth( false );
final ServerConnector httpsConnector;
if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", "")))
{
httpsConnector = new HTTPSPDYServerConnector(adminServer, sslContextFactory);
......
......@@ -57,6 +57,9 @@ import org.eclipse.jetty.webapp.WebAppContext;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.CertificateStoreConfig;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.util.CertificateEventListener;
......@@ -243,21 +246,26 @@ public final class HttpBindManager {
private void createSSLConnector(int securePort, int bindThreads) {
httpsConnector = null;
try {
if (securePort > 0 && CertificateManager.isRSACertificate(SSLConfig.getKeyStore(), "*")) {
if (!CertificateManager.isRSACertificate(SSLConfig.getKeyStore(),
XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.BOSHBASED_IDENTITYSTORE );
final KeyStore keyStore = identityStoreConfig.getStore();
if (securePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() ) {
if ( !identityStoreConfig.containsDomainCertificate( "RSA" ) ) {
Log.warn("HTTP binding: Using RSA certificates but they are not valid for " +
"the hosted domain");
}
final CertificateStoreConfig trustStoreConfig = SSLConfig.getInstance().getStoreConfig( Purpose.BOSHBASED_C2S_TRUSTSTORE );
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.addExcludeProtocols("SSLv3");
sslContextFactory.setTrustStorePath(SSLConfig.getc2sTruststoreLocation());
sslContextFactory.setTrustStorePassword(SSLConfig.getc2sTrustPassword());
sslContextFactory.setTrustStoreType(SSLConfig.getStoreType());
sslContextFactory.setKeyStorePath(SSLConfig.getKeystoreLocation());
sslContextFactory.setKeyStorePassword(SSLConfig.getKeyPassword());
sslContextFactory.setKeyStoreType(SSLConfig.getStoreType());
sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() );
sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() );
sslContextFactory.setTrustStoreType( trustStoreConfig.getType() );
sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() );
sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() );
sslContextFactory.addExcludeProtocols( "SSLv3" );
// Set policy for checking client certificates
String certPol = JiveGlobals.getProperty(HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY, "disabled");
......
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
/**
* A wrapper class for a Java store of certificates, its metadata (password, location) and related functionality.
*
* A subclass of this class exists for each of the two distinct types of key store.
* <ul>
* <li>one that is used to provide credentials, an <em>identity store</em>, in {@link IdentityStoreConfig}</li>
* <li>one that is used to verify credentials, a <em>trust store</em>, in {@link TrustStoreConfig}</li>
* </ul>
*
* Note that in Java terminology, an identity store is commonly referred to as a 'key store', while the same name is
* also used to identify the generic certificate store. To have clear distinction between common denominator and each of
* the specific types, this implementation uses the terms "certificate store", "identity store" and "trust store".
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public abstract class CertificateStoreConfig
{
private static final Logger Log = LoggerFactory.getLogger( CertificateStoreConfig.class );
protected static final Provider PROVIDER = new BouncyCastleProvider();
static
{
// Add the BC provider to the list of security providers
Security.addProvider( PROVIDER );
}
protected final KeyStore store;
protected final char[] password;
protected final String canonicalPath;
public CertificateStoreConfig( String path, String password, String type, boolean createIfAbsent ) throws CertificateStoreConfigException
{
try
{
this.canonicalPath = SSLConfig.canonicalize( path );
final File file = new File( canonicalPath );
if ( createIfAbsent && !file.exists() )
{
try ( final FileOutputStream os = new FileOutputStream( canonicalPath ) )
{
store = KeyStore.getInstance( type );
store.load( null, password.toCharArray() );
store.store( os, password.toCharArray() );
this.password = password.toCharArray();
}
}
else
{
try ( final FileInputStream is = new FileInputStream( canonicalPath ) )
{
store = KeyStore.getInstance( type );
store.load( is, password.toCharArray() );
this.password = password.toCharArray();
}
}
}
catch ( IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException ex )
{
throw new CertificateStoreConfigException( "Unable to load store of type '" + type + "' from location '" + path + "'", ex );
}
}
/**
* Reloads the content of the store from disk. Useful when the store content has been modified outside of the
* Openfire process, or when changes that have not been persisted need to be undone.
*/
public void reload() throws CertificateStoreConfigException
{
try ( final FileInputStream is = new FileInputStream( canonicalPath ) )
{
store.load( is, password );
}
catch ( IOException | NoSuchAlgorithmException | CertificateException ex )
{
throw new CertificateStoreConfigException( "Unable to reload store in location '" + canonicalPath + "'", ex );
}
}
/**
* Saves the current state of the store to disk. Useful when certificates have been added or removed from the
* store.
*/
public void persist() throws CertificateStoreConfigException
{
try ( final FileOutputStream os = new FileOutputStream( canonicalPath ) )
{
store.store( os, password );
}
catch ( NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException ex )
{
throw new CertificateStoreConfigException( "Unable to save changes to store in location '" + canonicalPath + "'", ex );
}
}
/**
* Returns a collection of all x.509 certificates in this store. Certificates returned by this method can be of any
* state (eg: invalid, on a revocation list, etc).
*
* @return A collection (possibly empty, never null) of all certificates in this store, mapped by their alias.
*/
public Map<String, X509Certificate> getAllCertificates() throws KeyStoreException
{
final Map<String, X509Certificate> results = new HashMap<>();
for ( final String alias : Collections.list( store.aliases() ) )
{
final Certificate certificate = store.getCertificate( alias );
if ( !( certificate instanceof X509Certificate ) )
{
continue;
}
results.put( alias, (X509Certificate) certificate );
}
return results;
}
/**
* Deletes an entry (by entry) in this store. All information related to this entry will be removed, including
* certificates and keys.
*
* When the store does not contain an entry that matches the provided alias, this method does nothing.
*
* @param alias The alias for which to delete an entry (cannot be null or empty).
* @throws CertificateStoreConfigException
*/
public void delete( String alias ) throws CertificateStoreConfigException
{
// Input validation
if ( alias == null || alias.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'alias' cannot be null or an empty String." );
}
try
{
if ( !store.containsAlias( alias ) )
{
Log.info( "Unable to delete certificate for alias '" + alias + "' from store, as the store does not contain a certificate for that alias." );
return;
}
store.deleteEntry( alias );
persist();
}
catch ( CertificateStoreConfigException | KeyStoreException e )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to install a certificate into an identity store.", e );
}
// TODO: Notify listeners that a new certificate has been removed.
}
public String getType()
{
return store.getType();
}
public KeyStore getStore()
{
return store;
}
public String getPassword()
{
return String.valueOf( password );
}
public String getCanonicalPath()
{
return canonicalPath;
}
public String getPath()
{
final Path path = Paths.get( canonicalPath );
final Path home = Paths.get( JiveGlobals.getHomeDirectory() );
final Path corrected = path.startsWith( home ) ? home.relativize( path ) : path;
return corrected.toString();
}
}
package org.jivesoftware.openfire.keystore;
/**
* A checked exception that indicates problems related to Certificate Store functionality.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class CertificateStoreConfigException extends Exception
{
public CertificateStoreConfigException()
{
}
public CertificateStoreConfigException( String message )
{
super( message );
}
public CertificateStoreConfigException( String message, Throwable cause )
{
super( message, cause );
}
public CertificateStoreConfigException( Throwable cause )
{
super( cause );
}
public CertificateStoreConfigException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace )
{
super( message, cause, enableSuppression, writableStackTrace );
}
}
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.net.DNSUtil;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import java.io.IOException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* A wrapper class for a store of certificates, its metadata (password, location) and related functionality that is
* used to <em>provide</em> credentials (that represent this Openfire instance), an <em>identity store</em>
*
* An identity store should contain private keys, each associated with its certificate chain.
*
* Having the root certificate of the Certificate Authority that signed the certificates in this identity store should
* be in a corresponding trust store, although this is not strictly required. The reasoning here is that when you trust
* a Certificate Authority to verify your identity, you're likely to trust the same Certificate Authority to verify the
* identities of others.
*
* Note that in Java terminology, an identity store is commonly referred to as a 'key store', while the same name is
* also used to identify the generic certificate store. To have clear distinction between common denominator and each of
* the specific types, this implementation uses the terms "certificate store", "identity store" and "trust store".
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class IdentityStoreConfig extends CertificateStoreConfig
{
private static final Logger Log = LoggerFactory.getLogger( IdentityStoreConfig.class );
protected final KeyManagerFactory keyFactory;
public IdentityStoreConfig( String path, String password, String type, boolean createIfAbsent ) throws CertificateStoreConfigException
{
super( path, password, type, createIfAbsent );
try
{
keyFactory = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm(), PROVIDER );
keyFactory.init( store, password.toCharArray() );
}
catch ( UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException ex )
{
throw new CertificateStoreConfigException( "Unable to load store of type '" + type + "' from location '" + path + "'", ex );
}
}
public KeyManager[] getKeyManagers()
{
return keyFactory.getKeyManagers();
}
/**
* Creates a Certificate Signing Request based on the private key and certificate identified by the provided alias.
*
* When the alias does not identify a private key and/or certificate, this method will throw an exception.
*
* The certificate that is identified by the provided alias can be an unsigned certificate, but also a certificate
* that is already signed. The latter implies that the generated request is a request for certificate renewal.
*
* An invocation of this method does not change the state of the underlying store.
*
* @param alias An identifier for a private key / certificate in this store (cannot be null).
* @return A PEM-encoded Certificate Signing Request (never null).
*/
public String generateCSR( String alias ) throws CertificateStoreConfigException
{
// Input validation
if ( alias == null || alias.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'alias' cannot be null or an empty String." );
}
alias = alias.trim();
try
{
if ( !store.containsAlias( alias ) ) {
throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"': the alias does not exist in the store." );
}
final Certificate certificate = store.getCertificate( alias );
if ( certificate == null || (!(certificate instanceof X509Certificate)))
{
throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"': there is no corresponding certificate in the store, or it is not an X509 certificate." );
}
final Key key = store.getKey( alias, password );
if ( key == null || (!(key instanceof PrivateKey) ) )
{
throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"': there is no corresponding key in the store, or it is not a private key." );
}
final String pemCSR = CertificateManager.createSigningRequest( (X509Certificate) certificate, (PrivateKey) key );
return pemCSR;
}
catch ( IOException | NoSuchProviderException | SignatureException | InvalidKeyException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e )
{
throw new CertificateStoreConfigException( "Cannot generate CSR for alias '"+ alias +"'", e );
}
}
/**
* Imports a certificate (and its chain) in this store.
*
* This method will fail when the provided certificate chain:
* <ul>
* <li>does not match the domain of this XMPP service.</li>
* <li>is not a proper chain</li>
* </ul>
*
* This method will also fail when a corresponding private key is not already in this store (it is assumed that the
* CA reply follows a signing request based on a private key that was added to the store earlier).
*
* @param pemCertificates a PEM representation of the certificate or certificate chain (cannot be null or empty).
*/
public void installCSRReply( String alias, String pemCertificates ) throws CertificateStoreConfigException
{
// Input validation
if ( alias == null || alias.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'alias' cannot be null or an empty String." );
}
if ( pemCertificates == null || pemCertificates.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'pemCertificates' cannot be null or an empty String." );
}
alias = alias.trim();
pemCertificates = pemCertificates.trim();
try
{
// From its PEM representation, parse the certificates.
final Collection<X509Certificate> certificates = CertificateManager.parseCertificates( pemCertificates );
if ( certificates.isEmpty() )
{
throw new CertificateStoreConfigException( "No certificate was found in the input." );
}
// 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 );
// Of the ordered chain, the first certificate should be for our domain.
if ( !isForThisDomain( ordered.get( 0 ) ) )
{
throw new CertificateStoreConfigException( "The supplied certificate chain does not cover the domain of this XMPP service." );
}
// This method is used to update a pre-existing entry in the store. Find out if this entry corresponds with the provided certificate chain.
if ( !corresponds( alias, ordered ) ) {
throw new IllegalArgumentException( "The provided CSR reply does not match an existing certificate in the store under the provided alias '" + alias + "'." );
}
// All appears to be in order. Update the existing entry in the store.
store.setKeyEntry( alias, store.getKey( alias, password ), password, ordered.toArray( new X509Certificate[ ordered.size() ] ) );
}
catch ( RuntimeException | IOException | CertificateException | UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to install a singing reply into an identity store.", e );
}
// TODO notifiy listneers.
}
protected boolean corresponds( String alias, List<X509Certificate> certificates ) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException
{
if ( !store.containsAlias( alias ) ) {
return false;
}
final Key key = store.getKey( alias, password );
if ( key == null ) {
return false;
}
if ( !(key instanceof PrivateKey)) {
return false;
}
final Certificate certificate = store.getCertificate( alias );
if ( certificate == null ) {
return false;
}
if ( !(certificate instanceof X509Certificate) ) {
return false;
}
final X509Certificate x509Certificate = (X509Certificate) certificate;
// First certificate in the chain should correspond with the certificate in the store
if ( !x509Certificate.getSerialNumber().equals( certificates.get( 0 ).getSerialNumber() ) )
{
return false;
}
return true;
}
/**
* Imports a certificate and the private key that was used to generate the certificate.
*
* This method will fail when the provided certificate does not match the domain of this XMPP service.
*
* @param alias the name (key) under which the certificate is to be stored in the store (cannot be null or empty).
* @param pemCertificates a PEM representation of the certificate or certificate chain (cannot be null or empty).
* @param pemPrivateKey a PEM representation of the private key (cannot be null or empty).
* @param passPhrase optional pass phrase (must be present if the private key is encrypted).
*/
public void installCertificate( String alias, String pemCertificates, String pemPrivateKey, String passPhrase ) throws CertificateStoreConfigException
{
// Input validation
if ( alias == null || alias.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'alias' cannot be null or an empty String." );
}
if ( pemCertificates == null || pemCertificates.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'pemCertificates' cannot be null or an empty String." );
}
if ( pemPrivateKey == null || pemPrivateKey.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'pemPrivateKey' cannot be null or an empty String." );
}
alias = alias.trim();
pemCertificates = pemCertificates.trim();
// Check that there is a certificate for the specified alias
try
{
if ( store.containsAlias( alias ) )
{
throw new CertificateStoreConfigException( "Certificate already exists for alias: " + alias );
}
// From its PEM representation, parse the certificates.
final Collection<X509Certificate> certificates = CertificateManager.parseCertificates( pemCertificates );
if ( certificates.isEmpty() )
{
throw new CertificateStoreConfigException( "No certificate was found in the input." );
}
// 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 );
// Of the ordered chain, the first certificate should be for our domain.
if ( !isForThisDomain( ordered.get( 0 ) ) )
{
throw new CertificateStoreConfigException( "The supplied certificate chain does not cover the domain of this XMPP service." );
}
// From its PEM representation (and pass phrase), parse the private key.
final PrivateKey privateKey = CertificateManager.parsePrivateKey( pemPrivateKey, passPhrase );
// All appears to be in order. Install in the store.
store.setKeyEntry( alias, privateKey, password, ordered.toArray( new X509Certificate[ ordered.size() ] ) );
persist();
}
catch ( CertificateException | KeyStoreException | IOException e )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to install a certificate into an identity store.", e );
}
// TODO Notify listeners that a new certificate has been added.
}
/**
* Adds a self-signed certificate for the domain of this XMPP service when no certificate for the domain (of the
* provided algorithm) was found.
*
* This method is a thread-safe equivalent of:
* <pre>
* for ( String algorithm : algorithms ) {
* if ( !containsDomainCertificate( algorithm ) ) {
* addSelfSignedDomainCertificate( algorithm );
* }
* }
* </pre>
*
* @param algorithms The algorithms for which to verify / add a domain certificate.
*/
public synchronized void ensureDomainCertificates( String... algorithms ) throws CertificateStoreConfigException
{
for ( String algorithm : algorithms )
{
if ( !containsDomainCertificate( algorithm ) )
{
addSelfSignedDomainCertificate( algorithm );
}
}
}
/**
* Checks if the store contains a certificate of a particular algorithm that matches the domain of this
* XMPP service. This method will not distinguish between self-signed and non-self-signed certificates.
*/
public synchronized boolean containsDomainCertificate( String algorithm ) throws CertificateStoreConfigException
{
final String domainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
try
{
for ( final String alias : Collections.list( store.aliases() ) )
{
final Certificate certificate = store.getCertificate( alias );
if ( !( certificate instanceof X509Certificate ) )
{
continue;
}
if ( !certificate.getPublicKey().getAlgorithm().equalsIgnoreCase( algorithm ) )
{
continue;
}
for ( String identity : CertificateManager.getServerIdentities( (X509Certificate) certificate ) )
{
if ( DNSUtil.isNameCoveredByPattern( domainName, identity ) )
{
return true;
}
}
}
return false;
}
catch ( KeyStoreException e )
{
throw new CertificateStoreConfigException( "An exception occurred while searching for " + algorithm + " certificates that match the Openfire domain.", e );
}
}
/**
* Populates the key store with a self-signed certificate for the domain of this XMPP service.
*/
public synchronized void addSelfSignedDomainCertificate( String algorithm ) throws CertificateStoreConfigException
{
final int keySize;
final String signAlgorithm;
switch ( algorithm.toUpperCase() )
{
case "RSA":
keySize = JiveGlobals.getIntProperty( "cert.rsa.keysize", 2048 );
signAlgorithm = "SHA1WITHRSAENCRYPTION";
break;
case "DSA":
keySize = JiveGlobals.getIntProperty( "cert.dsa.keysize", 1024 );
signAlgorithm = "SHA1withDSA";
break;
default:
throw new IllegalArgumentException( "Unsupported algorithm '" + algorithm + "'. Use 'RSA' or 'DSA'." );
}
final String name = JiveGlobals.getProperty( "xmpp.domain" ).toLowerCase();
final String alias = name + "_" + algorithm.toLowerCase();
final String distinctName = "cn=" + name;
final String domain = "*." + name;
final int validityInDays = 60;
// Generate public and private keys
try
{
final KeyPair keyPair = generateKeyPair( algorithm.toUpperCase(), keySize );
// Create X509 certificate with keys and specified domain
final X509Certificate cert = CertificateManager.createX509V3Certificate( keyPair, validityInDays, distinctName, distinctName, domain, signAlgorithm );
// Store new certificate and private key in the key store
store.setKeyEntry( alias, keyPair.getPrivate(), password, new X509Certificate[]{cert} );
// Persist the changes in the store to disk.
persist();
}
catch ( CertificateStoreConfigException | IOException | GeneralSecurityException ex )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to generate new self-signed " + algorithm + " certificate.", ex );
}
// TODO Notify listeners that a new certificate has been created
}
/**
* Returns a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
*
* @param algorithm DSA, RSA, etc.
* @param keySize the desired key size. This is an algorithm-specific metric, such as modulus length, specified in number of bits.
* @return a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
*/
protected static synchronized KeyPair generateKeyPair( String algorithm, int keySize ) throws GeneralSecurityException
{
final KeyPairGenerator generator;
if ( PROVIDER == null )
{
generator = KeyPairGenerator.getInstance( algorithm );
}
else
{
generator = KeyPairGenerator.getInstance( algorithm, PROVIDER );
}
generator.initialize( keySize, new SecureRandom() );
return generator.generateKeyPair();
}
/**
* Verifies that the subject of the certificate matches the domain of this XMPP service.
*
* @param certificate The certificate to verify (cannot be null)
* @return true when the certificate subject is this domain, otherwise false.
*/
public static boolean isForThisDomain( X509Certificate certificate )
{
final String domainName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
final List<String> serverIdentities = CertificateManager.getServerIdentities( certificate );
for ( String identity : serverIdentities )
{
if ( DNSUtil.isNameCoveredByPattern( domainName, identity ) )
{
return true;
}
}
Log.info( "The supplied certificate chain does not cover the domain of this XMPP service ('" + domainName + "'). Instead, it covers " + Arrays.toString( serverIdentities.toArray( new String[ serverIdentities.size() ] ) ) );
return false;
}
}
package org.jivesoftware.openfire.keystore;
/**
* Potential intended usages for keystores
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public enum Purpose
{
/**
* Identification of this Openfire instance used by regular socket-based connections.
*/
SOCKETBASED_IDENTITYSTORE( false ),
/**
* Identification of remote servers that you choose to trust, applies to server-to-server federation via regular socket-based connections.
*/
SOCKETBASED_S2S_TRUSTSTORE( true ),
/**
* Identification of clients that you choose to trust, applies to mutual authentication via regular socket-based connections.
*/
SOCKETBASED_C2S_TRUSTSTORE( true ),
/**
* Identification of this Openfire instance used by regular BOSH (HTTP-bind) connections.
*/
BOSHBASED_IDENTITYSTORE( false ),
/**
* Identification of clients that you choose to trust, applies to mutual authentication via BOSH (HTTP-bind) connections.
*/
BOSHBASED_C2S_TRUSTSTORE( true ),
/**
* Identification of this Openfire instance used by connections to administrative services (eg: user providers).
*/
ADMINISTRATIVE_IDENTITYSTORE( false ),
/**
* Identification of remote applications/servers that provide administrative functionality (eg: user providers).
*/
ADMINISTRATIVE_TRUSTSTORE( true ),
/**
* Openfire web-admin console.
*/
WEBADMIN_IDENTITYSTORE( false ),
/**
* Openfire web-admin console.
*/
WEBADMIN_TRUSTSTORE( true );
private final boolean isTrustStore;
Purpose( boolean isTrustStore )
{
this.isTrustStore = isTrustStore;
}
public boolean isIdentityStore()
{
return !isTrustStore;
}
public boolean isTrustStore()
{
return isTrustStore;
}
}
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jivesoftware.util.CertificateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.cert.*;
import java.util.*;
/**
* A wrapper class for a store of certificates, its metadata (password, location) and related functionality that is
* used to <em>verify</em> credentials, a <em>trust store</em>
*
* The trust store should only contain certificates for the "most-trusted" Certificate Authorities (the store should not
* contain Intermediates"). These certificates are referred to as "Trust Anchors".
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class TrustStoreConfig extends CertificateStoreConfig
{
private static final Logger Log = LoggerFactory.getLogger( TrustStoreConfig.class );
private final TrustManagerFactory trustFactory;
private final CertPathValidator certPathValidator; // not thread safe
private final CertificateFactory certificateFactory; // not thread safe.
public TrustStoreConfig( String path, String password, String type, boolean createIfAbsent ) throws CertificateStoreConfigException
{
super( path, password, type, createIfAbsent );
try
{
certPathValidator = CertPathValidator.getInstance( "PKIX", PROVIDER );
certificateFactory = CertificateFactory.getInstance( "X.509", PROVIDER );
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 );
}
}
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
{
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 );
}
return results;
}
/**
* 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 )
{
// Input validation
if ( chain == null )
{
throw new IllegalArgumentException( "Argument 'chain' cannot be null." );
}
if (chain.isEmpty() )
{
return false;
}
try
{
final Set<TrustAnchor> trustAnchors = getAllValidTrustAnchors();
final CertPath certPath = getCertPath( chain );
final PKIXParameters pkixp = new PKIXParameters( trustAnchors );
pkixp.setRevocationEnabled( false ); // TODO: enable revocation list validation.
certPathValidator.validate( certPath, pkixp );
}
catch ( Exception ex )
{
Log.info( "Unable to trust certificate chain.", ex );
return false;
}
return true;
}
/**
* 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
{
// Input validation
if ( chain == null || chain.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'chain' cannot be null or empty." );
}
// 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 );
return certificateFactory.generateCertPath( ordered );
}
/**
* 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).
*
* 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.
*
* @param alias the name (key) under which the certificate is to be stored in the store (cannot be null or empty).
* @param pemRepresentation The PEM representation of the certificate to add (cannot be null or empty).
*/
public void installCertificate( String alias, String pemRepresentation ) throws CertificateStoreConfigException
{
// Input validation
if ( alias == null || alias.trim().isEmpty() )
{
throw new IllegalArgumentException( "Argument 'alias' cannot be null or an empty String." );
}
if ( pemRepresentation == null )
{
throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null." );
}
alias = alias.trim();
// Check that there is a certificate for the specified alias
try
{
if ( store.containsAlias( alias ) )
{
throw new CertificateStoreConfigException( "Certificate already exists for alias: " + alias );
}
// From their PEM representation, parse the certificates.
final Collection<X509Certificate> certificates = CertificateManager.parseCertificates( pemRepresentation );
if ( certificates.isEmpty() ) {
throw new CertificateStoreConfigException( "No certificate was found in the input.");
}
if ( certificates.size() != 1 ) {
throw new CertificateStoreConfigException( "More than one certificate was found in the input." );
}
final X509Certificate certificate = certificates.iterator().next();
store.setCertificateEntry(alias, certificate);
persist();
}
catch ( CertificateException | KeyStoreException | IOException e )
{
reload(); // reset state of the store.
throw new CertificateStoreConfigException( "Unable to install a certificate into a trust store.", e );
}
// TODO Notify listeners that a new certificate has been added.
}
}
......@@ -212,6 +212,37 @@ public class DNSUtil {
return new ArrayList<HostAddress>();
}
/**
* Checks if the provided DNS pattern matches the provided name. For example, this method will:
* return <em>true</em> for name: <tt>xmpp.example.org</tt>, pattern: <tt>*.example.org</tt>
* return <em>false</em> for name: <tt>xmpp.example.org</tt>, pattern: <tt>example.org</tt>
*
* This method is not case sensitive.
*
* @param name The name to check against a pattern (cannot be null or empty).
* @param pattern the pattern (cannot be null or empty).
* @return true when the name is covered by the pattern, otherwise false.
*/
public static boolean isNameCoveredByPattern( String name, String pattern )
{
if ( name == null || name.isEmpty() || pattern == null || pattern.isEmpty() )
{
throw new IllegalArgumentException( "Arguments cannot be null or empty." );
}
final String needle = name.toLowerCase();
final String hayStack = pattern.toLowerCase();
if ( needle.equals( hayStack )) {
return true;
}
if ( hayStack.startsWith( "*." ) ) {
return needle.endsWith( hayStack.substring( 2 ) );
}
return false;
}
/**
* Encapsulates a hostname and port.
*/
......
......@@ -20,10 +20,9 @@
package org.jivesoftware.openfire.net;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.security.KeyStoreException;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
......@@ -49,6 +48,7 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.ConnectionSettings;
......@@ -192,22 +192,20 @@ public class SASLAuthentication {
return null;
}
Element mechs = DocumentHelper.createElement(new QName("mechanisms",
new Namespace("", "urn:ietf:params:xml:ns:xmpp-sasl")));
Element mechs = DocumentHelper.createElement( new QName( "mechanisms",
new Namespace( "", "urn:ietf:params:xml:ns:xmpp-sasl" ) ) );
if (session instanceof LocalIncomingServerSession) {
// Server connections don't follow the same rules as clients
if (session.isSecure()) {
boolean haveTrustedCertificate = false;
try {
LocalIncomingServerSession svr = (LocalIncomingServerSession)session;
X509Certificate trusted = CertificateManager.getEndEntityCertificate(svr.getConnection().getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
haveTrustedCertificate = trusted != null;
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE );
final KeyStore trustStore = SSLConfig.getStore( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( svr.getConnection().getPeerCertificates(), keyStore, trustStore );
boolean haveTrustedCertificate = trusted != null;
if (trusted != null && svr.getDefaultIdentity() != null) {
haveTrustedCertificate = verifyCertificate(trusted, svr.getDefaultIdentity());
}
} catch (IOException ex) {
Log.warn("Exception occurred while trying to determine whether remote certificate is trusted. Treating as untrusted.", ex);
}
if (haveTrustedCertificate) {
// Offer SASL EXTERNAL only if TLS has already been negotiated and the peer has a trusted cert.
Element mechanism = mechs.addElement("mechanism");
......@@ -471,7 +469,7 @@ public class SASLAuthentication {
return false;
}
String sharedSecert = getSharedSecret();
return StringUtils.hash(sharedSecert).equals(digest);
return StringUtils.hash(sharedSecert).equals( digest );
}
......@@ -557,7 +555,7 @@ public class SASLAuthentication {
if (!verify) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
} else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname)) {
} else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname, true)) {
authenticationSuccessful(session, hostname, null);
LocalIncomingServerSession s = (LocalIncomingServerSession)session;
if (s != null) {
......@@ -567,7 +565,7 @@ public class SASLAuthentication {
}
}
else if (session instanceof LocalClientSession) {
// Client EXTERNALL login
// Client EXTERNAL login
Log.debug("SASLAuthentication: EXTERNAL authentication via SSL certs for c2s connection");
// This may be null, we will deal with that later
......@@ -581,12 +579,10 @@ public class SASLAuthentication {
return Status.failed;
}
X509Certificate trusted;
try {
trusted = CertificateManager.getEndEntityCertificate(connection.getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
} catch (IOException e) {
trusted = null;
}
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE );
final KeyStore trustStore = SSLConfig.getStore( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( connection.getPeerCertificates(), keyStore, trustStore );
if (trusted == null) {
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but EE cert untrusted.");
authenticationFailed(session, Failure.NOT_AUTHORIZED);
......@@ -655,16 +651,21 @@ public class SASLAuthentication {
return false;
}
/**
* @deprecated Use {@link #verifyCertificates(Certificate[], String, boolean)} instead.
*/
@Deprecated
public static boolean verifyCertificates(Certificate[] chain, String hostname) {
try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(chain, SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
return verifyCertificates( chain, hostname, true );
}
public static boolean verifyCertificates(Certificate[] chain, String hostname, boolean isS2S) {
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE );
final KeyStore trustStore = SSLConfig.getStore( isS2S ? Purpose.SOCKETBASED_S2S_TRUSTSTORE : Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( chain, keyStore, trustStore );
if (trusted != null) {
return verifyCertificate(trusted, hostname);
}
} catch(IOException e) {
Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage());
}
return false;
}
......@@ -726,7 +727,7 @@ public class SASLAuthentication {
else {
reply.append("/>");
}
session.deliverRawText(reply.toString());
session.deliverRawText( reply.toString() );
// We only support SASL for c2s
if (session instanceof ClientSession) {
((LocalClientSession) session).setAuthToken(new AuthToken(username));
......
......@@ -2,15 +2,15 @@
* $RCSfile$
* $Revision: 1217 $
* $Date: 2005-04-11 18:11:06 -0300 (Mon, 11 Apr 2005) $
*
* <p/>
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
*
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -20,437 +20,377 @@
package org.jivesoftware.openfire.net;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.openfire.keystore.*;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Configuration of Openfire's SSL settings.
*
* Openfire distinguishes up to three distinct sets of certificate stores.
*
* <ul>
* <li>"Socket" - TCP-based XMPP communication (examples: desktop XMPP clients, server-to-server federation);</li>
* <li>"BOSH" - HTTP-based XMPP communication (examples: most mobile clients, web-based clients);</li>
* <li>"Administrative" - non-XMPP based communication (example: the web-based admin panel)</li>
* </ul>
*
* By default, the same set of stores is reused for all three purposes.
*
* A set consists of three stores: one key store and two trust stores.
*
* <em>key store</em>
* Contains certificates that identify this instance of Openfire. On request, these certificates are transmitted to
* other parties which use these certificates to identify your server,
*
* <em>server-to-server trust store</em>
* Contains certificates that identify remote servers that you choose to trust (applies to server-to-server federation).
*
* <em>client-to-server trust store</em>
* Contains certificates that identify clients that you choose to trust (applies to mutual authentication). By default,
* the client-to-server trust store that ships with Openfire is empty.
*
* @author Iain Shigeoka
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class SSLConfig {
public class SSLConfig
{
private static final Logger Log = LoggerFactory.getLogger( SSLConfig.class );
private static final Logger Log = LoggerFactory.getLogger(SSLConfig.class);
private static SSLConfig INSTANCE;
private static SSLServerSocketFactory s2sFactory;
private static SSLServerSocketFactory c2sFactory;
public static synchronized SSLConfig getInstance()
{
if (INSTANCE == null) {
try
{
INSTANCE = new SSLConfig();
}
catch ( CertificateStoreConfigException | NoSuchAlgorithmException | IOException ex )
{
Log.error( "Unable to instantiate SSL Configuration!", ex );
}
}
private static String storeType;
private static SSLContext s2sContext;
private static SSLContext c2sContext;
return INSTANCE;
}
private static KeyStore keyStore;
private static String keyStoreLocation;
private static String keypass;
/**
* An utility method that is short-hand for getInstance().getStoreConfig(purpose).getStore();
* @param purpose The purpose for which to return a store.
* @return a store (never null).
*/
public static synchronized KeyStore getStore( Purpose purpose )
{
return getInstance().getStoreConfig( purpose ).getStore();
}
private static KeyStore s2sTrustStore;
private static String s2sTrustStoreLocation;
private static String s2sTrustpass;
/**
* Openfire allows a store to be re-used for multiple purposes. This method will find the store used for the
* provided purpose, and based on that will return all <em>other</em> purposes for which the same store is used.
*
* @param purpose The purpose for which to find a store (cannot be null).
* @return all <em>other</em> purposes for which the store is used (never null, but possibly an empty collection).
* @throws IOException
*/
public Set<Purpose> getOtherPurposesForSameStore( Purpose purpose ) throws IOException
{
if ( purpose == null )
{
throw new IllegalArgumentException( "Argument 'purpose' cannot be null." );
}
private static KeyStore c2sTrustStore;
private static String c2sTrustStoreLocation;
private static String c2sTrustpass;
final Set<Purpose> results = new HashSet<>();
final String location = getLocation( purpose );
for ( Map.Entry<Purpose, String> entry : locationByPurpose.entrySet() )
{
if ( entry.getValue().equalsIgnoreCase( location ) ) {
results.add( entry.getKey() );
}
}
private SSLConfig() {
return results;
}
static {
storeType = JiveGlobals.getProperty("xmpp.socket.ssl.storeType", "jks");
private final ConcurrentMap<Purpose, String> locationByPurpose = new ConcurrentHashMap<>();
private final ConcurrentMap<String, CertificateStoreConfig> storesByLocation = new ConcurrentHashMap<>();
// Get the keystore location. The default location is security/keystore
keyStoreLocation = JiveGlobals.getProperty("xmpp.socket.ssl.keystore",
"resources" + File.separator + "security" + File.separator + "keystore");
keyStoreLocation = JiveGlobals.getHomeDirectory() + File.separator + keyStoreLocation;
public static String getNonCanonicalizedLocation(Purpose purpose)
{
final String path;
switch ( purpose )
{
// Identity store for socket-based IO (this is the default identity store)
case SOCKETBASED_IDENTITYSTORE:
path = JiveGlobals.getProperty( "xmpp.socket.ssl.keystore", "resources" + File.separator + "security" + File.separator + "keystore" );
break;
// Get the keystore password. The default password is "changeit".
keypass = JiveGlobals.getProperty("xmpp.socket.ssl.keypass", "changeit");
keypass = keypass.trim();
// Identity store for BOSH-based IO (falls back to the default identity store when not configured)
case BOSHBASED_IDENTITYSTORE:
path = JiveGlobals.getProperty( "xmpp.bosh.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) );
break;
// Get the truststore location for c2s connections
c2sTrustStoreLocation = JiveGlobals.getProperty("xmpp.socket.ssl.client.truststore",
"resources" + File.separator + "security" + File.separator + "client.truststore");
c2sTrustStoreLocation = JiveGlobals.getHomeDirectory() + File.separator + c2sTrustStoreLocation;
// Identity store for administrative IO (falls back to the default identity store when not configured)
case ADMINISTRATIVE_IDENTITYSTORE:
path = JiveGlobals.getProperty( "admin.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) );
break;
c2sTrustpass = JiveGlobals.getProperty("xmpp.socket.ssl.client.trustpass", "changeit");
c2sTrustpass = c2sTrustpass.trim();
// Identity store for admin panel (falls back to the administrative identity store when not configured)
case WEBADMIN_IDENTITYSTORE:
path = JiveGlobals.getProperty( "admin.web.ssl.keystore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) );
break;
// Get the truststore location for s2s connections
s2sTrustStoreLocation = JiveGlobals.getProperty("xmpp.socket.ssl.truststore",
"resources" + File.separator + "security" + File.separator + "truststore");
s2sTrustStoreLocation = JiveGlobals.getHomeDirectory() + File.separator + s2sTrustStoreLocation;
// server-to-server trust store (This is the only / default S2S trust store. S2S over BOSH is unsupported by Openfire).
case SOCKETBASED_S2S_TRUSTSTORE:
path = JiveGlobals.getProperty( "xmpp.socket.ssl.truststore", "resources" + File.separator + "security" + File.separator + "truststore" );
break;
// Get the truststore password; default is "changeit".
s2sTrustpass = JiveGlobals.getProperty("xmpp.socket.ssl.trustpass", "changeit");
s2sTrustpass = s2sTrustpass.trim();
// client-to-server trust store for socket-based IO (This is the default C2S trust store).
case SOCKETBASED_C2S_TRUSTSTORE:
path = JiveGlobals.getProperty( "xmpp.socket.ssl.client.truststore", "resources" + File.separator + "security" + File.separator + "client.truststore" );
break;
// Load s2s keystore
try {
keyStore = KeyStore.getInstance(storeType);
keyStore.load(new FileInputStream(keyStoreLocation), keypass.toCharArray());
}
catch (Exception e) {
Log.error("SSLConfig startup problem.\n" +
" storeType: [" + storeType + "]\n" +
" keyStoreLocation: [" + keyStoreLocation + "]\n" +
" keypass: [" + keypass + "]\n", e);
keyStore = null;
s2sFactory = null;
}
// Load s2s truststore
try {
s2sTrustStore = KeyStore.getInstance(storeType);
s2sTrustStore.load(new FileInputStream(s2sTrustStoreLocation), s2sTrustpass.toCharArray());
}
catch (Exception e) {
Log.error("SSLConfig startup problem.\n" +
" storeType: [" + storeType + "]\n" +
" s2sTrustStoreLocation: [" + s2sTrustStoreLocation + "]\n" +
" s2sTrustpass: [" + s2sTrustpass + "]\n", e);
s2sTrustStore = null;
s2sFactory = null;
}
// Load c2s truststore
try {
if (s2sTrustStoreLocation.equals(c2sTrustStoreLocation)) {
c2sTrustStore = s2sTrustStore;
c2sTrustpass = s2sTrustpass;
}
else {
c2sTrustStore = KeyStore.getInstance(storeType);
c2sTrustStore.load(new FileInputStream(c2sTrustStoreLocation), c2sTrustpass.toCharArray());
}
}
catch (Exception e) {
try {
c2sTrustStore = KeyStore.getInstance(storeType);
c2sTrustStore.load(null, c2sTrustpass.toCharArray());
}
catch (Exception ex) {
Log.error("SSLConfig startup problem.\n" +
" storeType: [" + storeType + "]\n" +
" c2sTrustStoreLocation: [" + c2sTrustStoreLocation + "]\n" +
" c2sTrustPass: [" + c2sTrustpass + "]", e);
c2sTrustStore = null;
c2sFactory = null;
}
}
resetFactory();
// client-to-server trust store for BOSH-based IO (falls back to the default C2S trust store when not configured).
case BOSHBASED_C2S_TRUSTSTORE:
path = JiveGlobals.getProperty( "xmpp.bosh.ssl.client.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) );
break;
// Reset SSL factory when certificates are modified
CertificateManager.addListener(new CertificateEventListener() {
// Reset SSL factory since keystores have changed
public void certificateCreated(KeyStore keyStore, String alias, X509Certificate cert) {
resetFactory();
}
// Administrative trust store (falls back to the default trust store when not configured)
case ADMINISTRATIVE_TRUSTSTORE:
path = JiveGlobals.getProperty( "admin.ssl.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) );
break;
public void certificateDeleted(KeyStore keyStore, String alias) {
resetFactory();
}
// Trust store for admin panel (falls back to the administrative trust store when not configured)
case WEBADMIN_TRUSTSTORE:
path = JiveGlobals.getProperty( "admin.web.ssl.truststore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_TRUSTSTORE ) );
break;
public void certificateSigned(KeyStore keyStore, String alias, List<X509Certificate> certificates) {
resetFactory();
default:
throw new IllegalStateException( "Unrecognized purpose: " + purpose );
}
});
return path;
}
private static void resetFactory() {
try {
String algorithm = JiveGlobals.getProperty("xmpp.socket.ssl.algorithm", "TLS");
public static String getLocation(Purpose purpose) throws IOException
{
return canonicalize( getNonCanonicalizedLocation( purpose ) );
}
s2sContext = SSLContext.getInstance(algorithm);
c2sContext = SSLContext.getInstance(algorithm);
protected String getPassword(Purpose purpose){
switch ( purpose ) {
case SOCKETBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.keypass", "changeit" ).trim();
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, SSLConfig.getKeyPassword().toCharArray());
case BOSHBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
TrustManagerFactory s2sTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
s2sTrustFactory.init(s2sTrustStore);
case ADMINISTRATIVE_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
s2sContext.init(keyFactory.getKeyManagers(),
s2sTrustFactory.getTrustManagers(),
new java.security.SecureRandom());
case WEBADMIN_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.keypass", getPassword( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
s2sFactory = s2sContext.getServerSocketFactory();
case SOCKETBASED_S2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.trustpass", "changeit" ).trim();
if (s2sTrustStore == c2sTrustStore) {
c2sContext = s2sContext;
c2sFactory = s2sFactory;
}
else {
TrustManagerFactory c2sTrustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
c2sTrustFactory.init(c2sTrustStore);
case SOCKETBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.client.trustpass", "changeit" ).trim();
c2sContext.init(keyFactory.getKeyManagers(),
c2sTrustFactory.getTrustManagers(),
new java.security.SecureRandom());
case BOSHBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.trustpass", getPassword( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
c2sFactory = c2sContext.getServerSocketFactory();
}
case ADMINISTRATIVE_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.ssl.trustpass", getPassword( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
}
catch (Exception e) {
Log.error("SSLConfig factory setup problem.\n" +
" storeType: [" + storeType + "]\n" +
" keyStoreLocation: [" + keyStoreLocation + "]\n" +
" keypass: [" + keypass + "]\n" +
" s2sTrustStoreLocation: [" + s2sTrustStoreLocation+ "]\n" +
" s2sTrustpass: [" + s2sTrustpass + "]" +
" c2sTrustStoreLocation: [" + c2sTrustStoreLocation + "]\n" +
" c2sTrustpass: [" + c2sTrustpass + "]", e);
keyStore = null;
s2sTrustStore = null;
c2sTrustStore = null;
s2sFactory = null;
c2sFactory = null;
case WEBADMIN_TRUSTSTORE:
return JiveGlobals.getProperty( "admin..web.ssl.trustpass", getPassword( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
default:
throw new IllegalStateException( "Unrecognized purpose: " + purpose );
}
}
/**
* Get the Key Store password
*
* @return the key store password
*/
public static String getKeyPassword() {
return keypass;
}
protected String getStoreType(Purpose purpose) {
/**
* Return the Trust Store password for s2s connections.
*
* @return the s2s trust store password.
*/
public static String gets2sTrustPassword() {
return s2sTrustpass;
}
// FIXME All properties should be unique, instead of being re-used by different stores.
switch ( purpose )
{
case SOCKETBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim();
case BOSHBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
/**
* Return the Trust Store password for c2s connections.
*
* @return the c2s trust store password.
*/
public static String getc2sTrustPassword() {
return c2sTrustpass;
}
case ADMINISTRATIVE_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
public static String[] getDefaultCipherSuites() {
String[] suites;
if (s2sFactory == null) {
suites = new String[]{};
}
else {
suites = s2sFactory.getDefaultCipherSuites();
}
return suites;
}
case WEBADMIN_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
public static String[] getSupportedCipherSuites() {
String[] suites;
if (s2sFactory == null) {
suites = new String[]{};
}
else {
suites = s2sFactory.getSupportedCipherSuites();
}
return suites;
}
case SOCKETBASED_S2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim();
/**
* Get the Key Store
*
* @return the Key Store
*/
public static KeyStore getKeyStore() throws IOException {
if (keyStore == null) {
throw new IOException();
}
return keyStore;
}
case SOCKETBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.client.storeType", "jks" ).trim();
/**
* Get the Trust Store for s2s connections
*
* @return the s2s Trust Store
*/
public static KeyStore gets2sTrustStore() throws IOException {
if (s2sTrustStore == null) {
throw new IOException();
}
return s2sTrustStore;
}
case BOSHBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.storeType", getStoreType( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
/**
* Get the Trust Store for c2s connections
*
* @return the c2s Trust Store
*/
public static KeyStore getc2sTrustStore() throws IOException {
if (c2sTrustStore == null) {
throw new IOException();
}
return c2sTrustStore;
}
case ADMINISTRATIVE_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
/**
* Initializes (wipes and recreates) the keystore, and returns the new keystore.
*
* @return Newly initialized keystore.
*/
public static KeyStore initializeKeyStore() {
try {
keyStore = KeyStore.getInstance(storeType);
keyStore.load(null, keypass.toCharArray());
}
catch (Exception e) {
Log.error("Unable to initialize keystore: ", e);
case WEBADMIN_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
default:
throw new IllegalStateException( "Unrecognized purpose: " + purpose );
}
return keyStore;
}
/**
* Save all key and trust stores.
*/
public static void saveStores() throws IOException {
try {
File keyStoreDirectory = new File(keyStoreLocation).getParentFile();
if (!keyStoreDirectory.exists())
keyStoreDirectory.mkdirs();
keyStore.store(new FileOutputStream(keyStoreLocation), keypass.toCharArray());
if (s2sTrustStore != null) {
File s2sTrustStoreDirectory = new File(s2sTrustStoreLocation).getParentFile();
if (!s2sTrustStoreDirectory.exists())
s2sTrustStoreDirectory.mkdirs();
s2sTrustStore.store(new FileOutputStream(s2sTrustStoreLocation), s2sTrustpass.toCharArray());
private SSLConfig() throws CertificateStoreConfigException, IOException, NoSuchAlgorithmException
{
for (Purpose purpose : Purpose.values()) {
final String location = getLocation( purpose );
if ( !storesByLocation.containsKey( location )) {
final CertificateStoreConfig storeConfig;
if (purpose.isTrustStore()) {
storeConfig = new TrustStoreConfig( getLocation( purpose ), getPassword( purpose ), getStoreType( purpose ), false );
} else {
storeConfig = new IdentityStoreConfig( getLocation( purpose ), getPassword( purpose ), getStoreType( purpose ), false );
}
if (c2sTrustStore != null && c2sTrustStore != s2sTrustStore) {
File c2sTrustStoreDirectory = new File(c2sTrustStoreLocation).getParentFile();
if (!c2sTrustStoreDirectory.exists())
c2sTrustStoreDirectory.mkdirs();
c2sTrustStore.store(new FileOutputStream(c2sTrustStoreLocation), c2sTrustpass.toCharArray());
}
storesByLocation.put( location, storeConfig );
}
catch (IOException e) {
throw e;
}
catch (Exception e) {
throw new IOException(e.getMessage());
locationByPurpose.put(purpose, location);
}
}
/**
* Create a ServerSocket for s2s connections
*
* @return the ServerSocket for an s2s connection
*/
public static ServerSocket createServerSocket(int port, InetAddress ifAddress) throws
IOException {
if (s2sFactory == null) {
throw new IOException();
public CertificateStoreConfig getStoreConfig( Purpose purpose ) {
if ( purpose == null ) {
throw new IllegalArgumentException( "Argument 'purpose' cannot be null.");
}
else {
return s2sFactory.createServerSocket(port, -1, ifAddress);
return storesByLocation.get( locationByPurpose.get( purpose ) );
}
public void useStoreForPurpose( Purpose purpose, String location, String password, String storeType, boolean createIfAbsent ) throws IOException, CertificateStoreConfigException
{
final String newPath = canonicalize( location );
final String oldPath = locationByPurpose.get( purpose );
final CertificateStoreConfig oldConfig = storesByLocation.get( oldPath );
// When this invocation does not change the current state, only trigger a reload.
if (oldPath.equalsIgnoreCase( newPath ) && oldConfig.getPassword().equals( password ) && oldConfig.getType().equals( storeType ))
{
oldConfig.reload();
return;
}
/**
* Create a ServerSocket for c2s connections
*
* @return the ServerSocket for an c2s connection
*/
public static ServerSocket createc2sServerSocket(int port, InetAddress ifAddress) throws
IOException {
if (c2sFactory == null) {
throw new IOException();
// Has a store already been loaded from this location?
final boolean isKnown = storesByLocation.containsKey( newPath );
final CertificateStoreConfig newConfig;
if ( isKnown )
{
newConfig = storesByLocation.get( newPath );
}
else {
return c2sFactory.createServerSocket(port, -1, ifAddress);
else
{
if (purpose.isTrustStore()) {
newConfig = new TrustStoreConfig( newPath, password, storeType, createIfAbsent );
} else {
newConfig = new IdentityStoreConfig( newPath, password, storeType, createIfAbsent );
}
}
/**
* Get the Key Store location
*
* @return the keystore location
*/
public static String getKeystoreLocation() {
return keyStoreLocation;
}
locationByPurpose.replace( purpose, newConfig.getCanonicalPath() );
storesByLocation.replace( newConfig.getCanonicalPath(), newConfig );
/**
* Get the s2s Trust Store location
*
* @return the s2s Trust Store location
*/
public static String gets2sTruststoreLocation() {
return s2sTrustStoreLocation;
}
// Persist changes by modifying the Openfire properties.
final Path locationToStore = Paths.get( newConfig.getPath() );
/**
* Get the c2s Trust Store location
*
* @return the c2s Trust Store location
*/
public static String getc2sTruststoreLocation() {
return c2sTrustStoreLocation;
}
switch ( purpose )
{
case SOCKETBASED_IDENTITYSTORE:
JiveGlobals.setProperty( "xmpp.socket.ssl.keystore", locationToStore.toString() );
JiveGlobals.setProperty( "xmpp.socket.ssl.keypass", password );
JiveGlobals.setProperty( "xmpp.socket.ssl.storeType", storeType ); // FIXME also in use by SOCKETBASED_S2S_TRUSTSTORE
break;
public static String getStoreType() {
return storeType;
}
case BOSHBASED_IDENTITYSTORE:
JiveGlobals.setProperty( "xmpp.bosh.ssl.keystore", locationToStore.toString() );
JiveGlobals.setProperty( "xmpp.bosh.ssl.keypass", password );
JiveGlobals.setProperty( "xmpp.bosh.ssl.storeType", storeType );
break;
/**
* Get the SSLContext for s2s connections
*
* @return the SSLContext for s2s connections
*/
public static SSLContext getSSLContext() {
return s2sContext;
case ADMINISTRATIVE_IDENTITYSTORE:
JiveGlobals.setProperty( "admin.ssl.keystore", locationToStore.toString() );
JiveGlobals.setProperty( "admin.ssl.keypass", password );
JiveGlobals.setProperty( "admin.ssl.storeType", storeType ); // FIXME also in use by ADMINISTRATIVE_TRUSTSTORE
break;
case WEBADMIN_IDENTITYSTORE:
JiveGlobals.setProperty( "admin.web.ssl.keystore", locationToStore.toString() );
JiveGlobals.setProperty( "admin.web.ssl.keypass", password );
JiveGlobals.setProperty( "admin.web.ssl.storeType", storeType ); // FIXME also in use by WEBADMIN_TRUSTSTORE
break;
case SOCKETBASED_S2S_TRUSTSTORE:
JiveGlobals.setProperty( "xmpp.socket.ssl.truststore", locationToStore.toString() );
JiveGlobals.setProperty( "xmpp.socket.ssl.trustpass", password );
JiveGlobals.setProperty( "xmpp.socket.ssl.storeType", storeType ); // FIXME also in use by SOCKETBASED_IDENTITYSTORE
break;
case SOCKETBASED_C2S_TRUSTSTORE:
JiveGlobals.setProperty( "xmpp.socket.ssl.client.truststore", locationToStore.toString() );
JiveGlobals.setProperty( "xmpp.socket.ssl.client.trustpass", password );
JiveGlobals.setProperty( "xmpp.socket.ssl.client.storeType", storeType );
break;
case BOSHBASED_C2S_TRUSTSTORE:
JiveGlobals.setProperty( "xmpp.bosh.ssl.client.truststore", locationToStore.toString() );
JiveGlobals.setProperty( "xmpp.bosh.ssl.client.trustpass", password );
JiveGlobals.setProperty( "xmpp.bosh.ssl.storeType", storeType );
break;
case ADMINISTRATIVE_TRUSTSTORE:
JiveGlobals.setProperty( "admin.ssl.truststore", locationToStore.toString() );
JiveGlobals.setProperty( "admin.ssl.trustpass", password );
JiveGlobals.setProperty( "admin.ssl.storeType", storeType ); // FIXME also in use by ADMINISTRATIVE_IDENTITYSTORE
case WEBADMIN_TRUSTSTORE:
JiveGlobals.setProperty( "admin.web.ssl.truststore", locationToStore.toString() );
JiveGlobals.setProperty( "admin.web.ssl.trustpass", password );
JiveGlobals.setProperty( "admin.web.ssl.storeType", storeType ); // FIXME also in use by WEBADMIN_IDENTITYSTORE
default:
throw new IllegalStateException( "Unrecognized purpose: " + purpose );
}
/**
* Get the SSLContext for c2s connections
*
* @return the SSLContext for c2s connections
*/
public static SSLContext getc2sSSLContext() {
return c2sContext;
// TODO notify listeners
}
/**
* Get the SSLServerSocketFactory for s2s connections
*
* @return the SSLServerSocketFactory for s2s connections
*/
public static SSLServerSocketFactory getServerSocketFactory() {
return s2sFactory;
public static String canonicalize( String path ) throws IOException
{
File file = new File( path );
if (!file.isAbsolute()) {
file = new File( JiveGlobals.getHomeDirectory() + File.separator + path );
}
/**
* Get the SSLServerSocketFactory for c2s connections
*
* @return the SSLServerSocketFactory for c2s connections
*/
public static SSLServerSocketFactory getc2sServerSocketFactory() {
return c2sFactory;
return file.getCanonicalPath();
}
}
package org.jivesoftware.openfire.net;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* A factory object for creating server sockets based on Openfire's SSL configuration.
*
* This implementation distinguishes between server sockets created for server-to-server ('s2s') and for
* client-to-server ('c2s') communication. The primary difference between the two is the set of key and trust stores
* that are used.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
// TODO: This code was split off from SSLConfig, but does not appear to be used! Remove?
public class SSLConfigSocketFactory
{
private static final Logger Log = LoggerFactory.getLogger( SSLConfigSocketFactory.class );
/**
* The factory used for server-to-server connections.
*/
private static SSLServerSocketFactory s2sFactory;
/**
* The factory used for client-to-server connections.
*/
private static SSLServerSocketFactory c2sFactory;
static
{
// Initial instantiation
resetFactory();
// Reset SSL factory when certificates are modified
CertificateManager.addListener( new CertificateEventListener()
{
// Reset SSL factory since key stores have changed
public void certificateCreated( KeyStore keyStore, String alias, X509Certificate cert )
{
resetFactory();
}
public void certificateDeleted( KeyStore keyStore, String alias )
{
resetFactory();
}
public void certificateSigned( KeyStore keyStore, String alias, List<X509Certificate> certificates )
{
resetFactory();
}
} );
}
private static void resetFactory()
{
Log.debug( "(Re)setting the SSL-based socket factories." );
try
{
final KeyStore s2sTrustStore = SSLConfig.getStore( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final KeyStore c2sTrustStore = SSLConfig.getStore( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
s2sFactory = createS2SServerSocketFactory();
if ( s2sTrustStore == c2sTrustStore )
{
c2sFactory = s2sFactory;
}
else
{
c2sFactory = createC2SServerSocketFactory();
}
}
catch ( Exception e )
{
Log.error( "An exception occurred while (re)setting the SSL-based socket factories. Factories will be unavailable.", e );
s2sFactory = null;
c2sFactory = null;
}
}
static SSLServerSocketFactory createC2SServerSocketFactory() throws NoSuchAlgorithmException, KeyManagementException
{
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final String algorithm = JiveGlobals.getProperty( "xmpp.socket.ssl.algorithm", "TLS" );
final SSLContext context = SSLContext.getInstance( algorithm );
context.init( identityStoreConfig.getKeyManagers(), trustStoreConfig.getTrustManagers(), new java.security.SecureRandom() );
return context.getServerSocketFactory();
}
static SSLServerSocketFactory createS2SServerSocketFactory() throws NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, KeyStoreException, IOException
{
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final String algorithm = JiveGlobals.getProperty( "xmpp.socket.ssl.algorithm", "TLS" );
final SSLContext context = SSLContext.getInstance( algorithm );
context.init( identityStoreConfig.getKeyManagers(), trustStoreConfig.getTrustManagers(), new java.security.SecureRandom() );
return context.getServerSocketFactory();
}
/**
* Create a ServerSocket for server-to-server connections. This method will throw an IOException if it fails to
* create a socket.
*
* @return the ServerSocket for a server-to-server connection (never null).
* @throws IOException Failed to create a socket.
*/
public static ServerSocket createS2SServerSocket( int port, InetAddress ifAddress ) throws IOException
{
if ( s2sFactory == null )
{
throw new IOException( "S2S server socket factory has not been initialized successfully." );
}
return s2sFactory.createServerSocket( port, -1, ifAddress );
}
/**
* Create a ServerSocket for client-to-server connections. This method will throw an IOException if it fails to
* create a socket.
*
* @return the ServerSocket for a client-to-server connection (never null).
* @throws IOException Failed to create a socket.
*/
public static ServerSocket createC2SServerSocket( int port, InetAddress ifAddress ) throws IOException
{
if ( c2sFactory == null )
{
throw new IOException( "C2S server socket factory has not been initialized successfully." );
}
return c2sFactory.createServerSocket( port, -1, ifAddress );
}
/**
* Get the SSLServerSocketFactory for server-to-server connections. When the factory has not been initialized
* successfully, this method returns null.
*
* @return the SSLServerSocketFactory for server-to-server connections (possibly null).
*/
public static SSLServerSocketFactory getS2SServerSocketFactory()
{
return s2sFactory;
}
/**
* Get the SSLServerSocketFactory for client-to-server connections. When the factory has not been initialized
* successfully, this method returns null.
*
* @return the SSLServerSocketFactory for client-to-server connections (possibly null).
*/
public static SSLServerSocketFactory getC2SServerSocketFactory()
{
return c2sFactory;
}
/**
* Returns an array of cipher suite names that are enabled by default for server-to-server communication. When no
* cipher suite names cannot be determined (ie: when the socket factory has not been initialized) this method
* returns an empty array.
*
* @return array of cipher suites names, possibly empty, but never null.
* @see SSLServerSocketFactory#getDefaultCipherSuites()
*/
public static String[] getS2SDefaultCipherSuites()
{
if ( s2sFactory == null )
{
return new String[ 0 ];
}
return s2sFactory.getDefaultCipherSuites();
}
/**
* Returns an array of cipher suite names that are available (but not necessarily enabled) for server-to-server
* communication. When cipher suite names cannot be determined (ie: when the socket factory has not been
* initialized) this method returns an empty array.
*
* @return array of cipher suites names, possibly empty, but never null.
* @see SSLServerSocketFactory#getSupportedCipherSuites()
*/
public static String[] getS2SSupportedCipherSuites()
{
if ( s2sFactory == null )
{
return new String[ 0 ];
}
return s2sFactory.getSupportedCipherSuites();
}
/**
* Returns an array of cipher suite names that are enabled by default for client-to-server communication. When
* cipher suite names cannot be determined (ie: when the socket factory has not been initialized) this method
* returns an empty array.
*
* @return array of cipher suites names, possibly empty, but never null.
* @see SSLServerSocketFactory#getDefaultCipherSuites()
*/
public static String[] getC2SDefaultCipherSuites()
{
if ( c2sFactory == null )
{
return new String[ 0 ];
}
return c2sFactory.getDefaultCipherSuites();
}
/**
* Returns an array of cipher suite names that are available (but not necessarily enabled) for client-to-server
* communication. When cipher suite names cannot be determined (ie: when the socket factory has not been
* initialized) this method returns an empty array.
*
* @return array of cipher suites names, possibly empty, but never null.
* @see SSLServerSocketFactory#getSupportedCipherSuites()
*/
public static String[] getC2SSupportedCipherSuites()
{
if ( c2sFactory == null )
{
return new String[ 0 ];
}
return c2sFactory.getSupportedCipherSuites();
}
}
/**
* $RCSfile$
* $Revision: 128 $
* $Date: 2004-10-25 20:42:00 -0300 (Mon, 25 Oct 2004) $
*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.net;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.X509KeyManager;
/**
* A skeleton placeholder for developers wishing to implement their own custom
* key manager. In future revisions we may expand the skeleton code if customers
* request assistance in creating custom key managers.
* <p>
* The key manager is an essential part of server SSL support. Typically you
* will implement a custom key manager to retrieve certificates from repositories
* that are not of standard Java types (e.g. obtaining them from LDAP or a JDBC database).
* </p>
*
* @author Iain Shigeoka
*/
public class SSLJiveKeyManager implements X509KeyManager {
private static final Logger Log = LoggerFactory.getLogger(SSLJiveKeyManager.class);
public String[] getClientAliases(String s, Principal[] principals) {
return new String[0];
}
public String chooseClientAlias(String s, Principal[] principals) {
return null;
}
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
return null;
}
public String[] getServerAliases(String s, Principal[] principals) {
return new String[0];
}
public String chooseServerAlias(String s, Principal[] principals) {
return null;
}
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
return null;
}
public X509Certificate[] getCertificateChain(String s) {
return new X509Certificate[0];
}
public PrivateKey getPrivateKey(String s) {
return null;
}
}
/**
* $RCSfile$
* $Revision: 2774 $
* $Date: 2005-09-05 01:53:16 -0300 (Mon, 05 Sep 2005) $
*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.net;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A custom KeyManagerFactory that creates a key manager list using the
* default key manager or a standard keystore as specified in openfire.xml.
* The default keystore provided with the Jive distribution uses the Sun Java
* Keystore (JKS) and that takes a single password which must apply to both the
* keystore and the key itself. Users may specify another keystore type and keystore
* location. Alternatively, don't set a keystore type to use the JVM defaults and
* configure your JVMs security files (see your JVM documentation) to plug in
* any KeyManagerFactory provider.
*
* @author Iain Shigeoka
*/
public class SSLJiveKeyManagerFactory {
private static final Logger Log = LoggerFactory.getLogger(SSLJiveKeyManagerFactory.class);
/**
* Creates a KeyManager list which is null if the storeType is null, or
* is a standard KeyManager that uses a KeyStore of type storeType,
* located at 'keystore' location under home, and uses 'keypass' as
* the password for the keystore password and key password. The default
* Jive keystore contains a self-signed X509 certificate pair under the
* alias '127.0.0.1' in a Java KeyStore (JKS) with initial password 'changeit'.
* This is sufficient for local host testing but should be using standard
* key management tools for any significant testing or deployment. See
* the Jive XMPP server security documentation for more information.
*
* @param storeType The type of keystore (e.g. "JKS") to use or null to indicate no keystore should be used
* @param keystore The relative location of the keystore under home
* @param keypass The password for the keystore and key
* @return An array of relevant KeyManagers (may be null indicating a default KeyManager should be created)
* @throws NoSuchAlgorithmException If the keystore type doesn't exist (not provided or configured with your JVM)
* @throws KeyStoreException If the keystore is corrupt
* @throws IOException If the keystore could not be located or loaded
* @throws CertificateException If there were no certificates to be loaded or they are invalid
* @throws UnrecoverableKeyException If they keystore coud not be opened (typically the password is bad)
*/
public static KeyManager[] getKeyManagers(String storeType, String keystore, String keypass) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException {
KeyManager[] keyManagers;
if (keystore == null) {
keyManagers = null;
}
else {
if (keypass == null) {
keypass = "";
}
KeyStore keyStore = KeyStore.getInstance(storeType);
keyStore.load(new FileInputStream(keystore), keypass.toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, keypass.toCharArray());
keyManagers = keyFactory.getKeyManagers();
}
return keyManagers;
}
public static KeyManager[] getKeyManagers(KeyStore keystore, String keypass) {
KeyManager[] keyManagers;
try {
if (keystore == null) {
keyManagers = null;
} else {
KeyManagerFactory keyFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (keypass == null) {
keypass = SSLConfig.getKeyPassword();
}
keyFactory.init(keystore, keypass.toCharArray());
keyManagers = keyFactory.getKeyManagers();
}
} catch (KeyStoreException e) {
keyManagers = null;
Log.error("SSLJiveKeyManagerFactory startup problem.\n" +
" the keystore is corrupt", e);
} catch (NoSuchAlgorithmException e) {
keyManagers = null;
Log.error("SSLJiveKeyManagerFactory startup problem.\n" +
" the keystore type doesn't exist (not provided or configured with your JVM)", e);
} catch (UnrecoverableKeyException e) {
keyManagers = null;
Log.error("SSLJiveKeyManagerFactory startup problem.\n" +
" the keystore could not be opened (typically the password is bad)", e);
}
return keyManagers;
}
}
/**
* $RCSfile$
* $Revision: 2774 $
* $Date: 2005-09-05 01:53:16 -0300 (Mon, 05 Sep 2005) $
*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.net;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A custom TrustManagerFactory that creates a trust manager list using the
* default trust manager or a standard keystore as specified in openfire.xml.
* There is no default trust keystore provided with the Jive distribution as most
* clients will not need to be authenticated with the server.
* <p>
* The Java Keystore (JKS) takes a single password which must apply to both the
* keystore and the key itself. Users may specify another keystore type and keystore
* location. Alternatively, don't set a keystore type to use the JVM defaults and
* configure your JVMs security files (see your JVM documentation) to plug in
* any TrustManagerFactory provider.</p>
*
* @author Iain Shigeoka
*/
public class SSLJiveTrustManagerFactory {
private static final Logger Log = LoggerFactory.getLogger(SSLJiveTrustManagerFactory.class);
/**
* Creates a TrustManager list which is null if the storeType is null, or
* is a standard TrustManager that uses a KeyStore of type storeType,
* located at 'keystore' location under home, and uses 'keypass' as
* the password for the keystore password and key password (note that
* trust managers typically don't need a key password as public keys
* are stored in the clear and can be obtained without a key password).
* The default Jive distribution doesn't ship with a trust keystore
* as it is not needed (the server does not require client authentication).
*
* @param storeType The type of keystore (e.g. "JKS") to use or null to indicate no keystore should be used
* @param truststore The relative location of the keystore under home
* @param trustpass The password for the keystore and key
* @return An array of relevant KeyManagers (may be null indicating a default KeyManager should be created)
* @throws NoSuchAlgorithmException If the keystore type doesn't exist (not provided or configured with your JVM)
* @throws KeyStoreException If the keystore is corrupt
* @throws IOException If the keystore could not be located or loaded
* @throws CertificateException If there were no certificates to be loaded or they are invalid
*/
public static TrustManager[] getTrustManagers(String storeType, String truststore, String trustpass) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
TrustManager[] trustManagers;
if (truststore == null) {
trustManagers = null;
}
else {
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
if (trustpass == null) {
trustpass = "";
}
KeyStore keyStore = KeyStore.getInstance(storeType);
keyStore.load(new FileInputStream(truststore), trustpass.toCharArray());
trustFactory.init(keyStore);
trustManagers = trustFactory.getTrustManagers();
}
return trustManagers;
}
//TODO: Is this for c2s or s2s connections? Or both?
public static TrustManager[] getTrustManagers(KeyStore truststore,
String trustpass) {
TrustManager[] trustManagers;
try {
if (truststore == null) {
trustManagers = null;
} else {
TrustManagerFactory trustFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
if (trustpass == null) {
trustpass = SSLConfig.gets2sTrustPassword();
}
trustFactory.init(truststore);
trustManagers = trustFactory.getTrustManagers();
}
} catch (KeyStoreException e) {
trustManagers = null;
Log.error("SSLJiveTrustManagerFactory startup problem.\n" +
" the keystore is corrupt", e);
} catch (NoSuchAlgorithmException e) {
trustManagers = null;
Log.error("SSLJiveTrustManagerFactory startup problem.\n" +
" the keystore type doesn't exist (not provided or configured with your JVM)", e);
}
return trustManagers;
}
}
......@@ -20,13 +20,9 @@
package org.jivesoftware.openfire.net;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.*;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
......@@ -37,6 +33,9 @@ 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.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -58,17 +57,6 @@ public class TLSWrapper {
*/
private boolean logging = false;
/*
* Enables the JSSE system debugging system property:
*
* -Djavax.net.debug=all
*
* This gives a lot of low-level information about operations underway, including specific
* handshake messages, and might be best examined after gaining some familiarity with this
* application.
*/
private static boolean debug = false;
private SSLEngine tlsEngine;
private SSLEngineResult tlsEngineResult;
......@@ -76,42 +64,41 @@ public class TLSWrapper {
private int appBuffSize;
public TLSWrapper(Connection connection, boolean clientMode, boolean needClientAuth, String remoteServer) {
boolean c2sConnection = (remoteServer == null);
if (debug) {
System.setProperty("javax.net.debug", "all");
}
String algorithm = JiveGlobals.getProperty("xmpp.socket.ssl.algorithm", "TLS");
final boolean isClientToServer = (remoteServer == null);
// Create/initialize the SSLContext with key material
try {
// First initialize the key and trust material.
KeyStore ksKeys = SSLConfig.getKeyStore();
String keypass = SSLConfig.getKeyPassword();
KeyStore ksTrust = (c2sConnection ? SSLConfig.getc2sTrustStore() : SSLConfig.gets2sTrustStore());
String trustpass = (c2sConnection ? SSLConfig.getc2sTrustPassword() : SSLConfig.gets2sTrustPassword());
// KeyManager's decide which key material to use.
KeyManager[] km = SSLJiveKeyManagerFactory.getKeyManagers(ksKeys, keypass);
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.
TrustManager[] tm = SSLJiveTrustManagerFactory.getTrustManagers(ksTrust, trustpass);
if (clientMode || needClientAuth) {
if (c2sConnection) {
final TrustManager[] tm;
if (clientMode || needClientAuth)
{
final KeyStore ksTrust = trustStoreConfig.getStore();
if (isClientToServer)
{
// Check if we can trust certificates presented by the client
tm = new TrustManager[]{new ClientTrustManager(ksTrust)};
}
else {
else
{
// Check if we can trust certificates presented by the server
tm = new TrustManager[]{new ServerTrustManager(remoteServer, ksTrust, connection)};
}
}
else
{
tm = trustStoreConfig.getTrustManagers();
}
SSLContext tlsContext = SSLContext.getInstance(algorithm);
tlsContext.init(km, tm, null);
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final String algorithm = JiveGlobals.getProperty("xmpp.socket.ssl.algorithm", "TLS");
final SSLContext tlsContext = SSLContext.getInstance(algorithm);
tlsContext.init( identityStoreConfig.getKeyManagers(), tm, null);
/*
* Configure the tlsEngine to act as a server in the SSL/TLS handshake. We're a server,
......@@ -126,13 +113,10 @@ public class TLSWrapper {
netBuffSize = sslSession.getPacketBufferSize();
appBuffSize = sslSession.getApplicationBufferSize();
} catch (KeyManagementException e) {
Log.error("TLSHandler startup problem.\n" + " SSLContext initialisation failed.", e);
} catch (NoSuchAlgorithmException e) {
Log.error("TLSHandler startup problem.\n" + " The " + algorithm + " does not exist", e);
} catch (IOException e) {
Log.error("TLSHandler startup problem.\n"
+ " the KeyStore or TrustStore does not exist", e);
}
catch ( NoSuchAlgorithmException | KeyManagementException ex )
{
Log.error("TLSHandler startup problem. SSLContext initialisation failed.", ex );
}
}
......
......@@ -35,7 +35,6 @@ import java.security.KeyStore;
import java.security.cert.Certificate;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
......@@ -51,11 +50,10 @@ import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionCloseListener;
import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.net.ClientTrustManager;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SSLJiveKeyManagerFactory;
import org.jivesoftware.openfire.net.SSLJiveTrustManagerFactory;
import org.jivesoftware.openfire.net.ServerTrustManager;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session;
......@@ -393,35 +391,38 @@ public class NIOConnection implements Connection {
}
public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception {
boolean c2s = (remoteServer == null);
KeyStore ksKeys = SSLConfig.getKeyStore();
String keypass = SSLConfig.getKeyPassword();
final boolean isClientToServer = (remoteServer == null);
KeyStore ksTrust = (c2s ? SSLConfig.getc2sTrustStore() : SSLConfig.gets2sTrustStore() );
String trustpass = (c2s ? SSLConfig.getc2sTrustPassword() : SSLConfig.gets2sTrustPassword() );
if (c2s) Log.debug("NIOConnection: startTLS: using c2s");
else Log.debug("NIOConnection: startTLS: using s2s");
// KeyManager's decide which key material to use.
KeyManager[] km = SSLJiveKeyManagerFactory.getKeyManagers(ksKeys, keypass);
Log.debug( "StartTLS: using {}", isClientToServer ? "c2s" : "s2s" );
// TrustManager's decide whether to allow connections.
TrustManager[] tm = SSLJiveTrustManagerFactory.getTrustManagers(ksTrust, trustpass);
final SSLConfig sslConfig = SSLConfig.getInstance();
final TrustStoreConfig storeConfig;
if (isClientToServer) {
storeConfig = (TrustStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
} else {
storeConfig = (TrustStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
}
final TrustManager[] tm;
if (clientMode || authentication == ClientAuth.needed || authentication == ClientAuth.wanted) {
// We might need to verify a certificate from our peer, so get different TrustManager[]'s
if(c2s) {
final KeyStore ksTrust = storeConfig.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(remoteServer, ksTrust, this)};
}
} else {
tm = storeConfig.getTrustManagers();
}
String algorithm = JiveGlobals.getProperty(ConnectionSettings.Client.TLS_ALGORITHM, "TLS");
SSLContext tlsContext = SSLContext.getInstance(algorithm);
SSLContext tlsContext = SSLContext.getInstance( algorithm );
tlsContext.init(km, tm, null);
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
tlsContext.init( identityStoreConfig.getKeyManagers(), tm, null);
SslFilter filter = new SslFilter(tlsContext);
filter.setUseClientMode(clientMode);
......
......@@ -541,7 +541,7 @@ public class ServerDialback {
return false;
}
else {
if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname)) {
if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) {
// If the remote host passes strong auth, just skip the dialback.
Log.debug("ServerDialback: RS - Sending key verification result to OS: " + hostname);
sb = new StringBuilder();
......
......@@ -34,6 +34,7 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SocketConnection;
......@@ -255,7 +256,7 @@ public class LocalClientSession extends LocalSession implements ClientSession {
if (!connection.isSecure()) {
boolean hasCertificates = false;
try {
hasCertificates = SSLConfig.getKeyStore().size() > 0;
hasCertificates = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0;
}
catch (Exception e) {
Log.error(e.getMessage(), e);
......
......@@ -20,6 +20,7 @@
package org.jivesoftware.openfire.session;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
......@@ -34,6 +35,7 @@ import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SocketConnection;
......@@ -151,7 +153,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
Connection.TLSPolicy.required;
boolean hasCertificates = false;
try {
hasCertificates = SSLConfig.getKeyStore().size() > 0;
hasCertificates = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0;
}
catch (Exception e) {
Log.error(e.getMessage(), e);
......@@ -371,13 +373,11 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
usingSelfSigned = true;
} else {
try {
usingSelfSigned = CertificateManager.isSelfSignedCertificate(SSLConfig.getKeyStore(), (X509Certificate) chain[0]);
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE );
usingSelfSigned = CertificateManager.isSelfSignedCertificate(keyStore, (X509Certificate) chain[0]);
} catch (KeyStoreException ex) {
Log.warn("Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex);
usingSelfSigned = true;
} catch (IOException ex) {
Log.warn("Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex);
usingSelfSigned = true;
}
}
......
......@@ -387,7 +387,7 @@ public class LocalOutgoingServerSession extends LocalServerSession implements Ou
throw e;
}
log.debug("TLS negotiation was successful.");
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname)) {
if (!SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), hostname, true)) {
log.debug("X.509/PKIX failure on outbound session");
if (ServerDialback.isEnabled() || ServerDialback.isEnabledForSelfSigned()) {
log.debug("Will continue with dialback.");
......
......@@ -41,9 +41,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.buffer.SimpleBufferAllocator;
......@@ -70,13 +68,10 @@ import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.container.PluginManagerListener;
import org.jivesoftware.openfire.http.HttpBindManager;
import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.ServerSocketReader;
import org.jivesoftware.openfire.net.SocketAcceptThread;
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.net.SocketReader;
import org.jivesoftware.openfire.net.SocketSendingTracker;
import org.jivesoftware.openfire.net.StalledSessionsFilter;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.nio.ClientConnectionHandler;
import org.jivesoftware.openfire.nio.ComponentConnectionHandler;
import org.jivesoftware.openfire.nio.MultiplexerConnectionHandler;
......@@ -457,15 +452,11 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
sslSocketAcceptor, maxBufferSize);
// Add the SSL filter now since sockets are "borned" encrypted in the old ssl method
SSLContext sslContext = SSLContext.getInstance(algorithm);
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(SSLConfig.getKeyStore(), SSLConfig.getKeyPassword().toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(SSLConfig.getc2sTrustStore());
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
sslContext.init(keyFactory.getKeyManagers(),
trustFactory.getTrustManagers(),
new java.security.SecureRandom());
final SSLContext sslContext = SSLContext.getInstance( algorithm );
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")) {
......@@ -623,11 +614,9 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
public boolean isClientSSLListenerEnabled() {
try {
return JiveGlobals.getBooleanProperty(ConnectionSettings.Client.ENABLE_OLD_SSLPORT, false) && SSLConfig.getKeyStore().size() > 0;
return JiveGlobals.getBooleanProperty(ConnectionSettings.Client.ENABLE_OLD_SSLPORT, false) && SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0;
} catch (KeyStoreException e) {
return false;
} catch (IOException e) {
return false;
}
}
......
......@@ -20,23 +20,9 @@
package org.jivesoftware.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.io.*;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.*;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
......@@ -46,31 +32,18 @@ import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
......@@ -81,7 +54,6 @@ import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
......@@ -89,6 +61,8 @@ import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
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.util.cert.CertificateIdentityMapping;
import org.jivesoftware.util.cert.CNCertificateIdentityMapping;
import org.jivesoftware.util.cert.SANCertificateIdentityMapping;
......@@ -103,11 +77,10 @@ import org.slf4j.LoggerFactory;
*/
public class CertificateManager {
private static final Logger Log = LoggerFactory.getLogger(CertificateManager.class);
private static final Logger Log = LoggerFactory.getLogger( CertificateManager.class );
private static Pattern valuesPattern = Pattern.compile("(?i)(=)([^,]*)");
private static Provider provider = new BouncyCastleProvider();
/**
* The maximum length of lines in certification requests
......@@ -121,8 +94,6 @@ public class CertificateManager {
private static List<CertificateIdentityMapping> clientCertMapping = new ArrayList<CertificateIdentityMapping>();
static {
// Add the BC provider to the list of security providers
Security.addProvider(provider);
String serverCertIdentityMapList = JiveGlobals.getProperty("provider.serverCertIdentityMap.classList");
if (serverCertIdentityMapList != null) {
......@@ -173,98 +144,28 @@ public class CertificateManager {
}
/**
* Creates a new X509 certificate using the DSA algorithm. The new certificate together with its private
* key are stored in the specified key store. However, the key store is not saved to the disk. This means
* that it is up to the "caller" to save the key store to disk after new certificates have been added
* to the store.
*
* @param ksKeys key store where the new certificate and private key are going to be stored.
* @param keyPassword password of the keystore.
* @param alias name to use when storing the certificate in the key store.
* @param issuerDN Issuer string e.g "O=Grid,OU=OGSA,CN=ACME"
* @param subjectDN Subject string e.g "O=Grid,OU=OGSA,CN=John Doe"
* @param domain domain of the server to store in the subject alternative name extension.
* @return the new X509 V3 Certificate.
* @throws GeneralSecurityException
* @throws IOException
* @Deprecated Use {@link CertificateStoreConfig#delete(String)} instead.
*/
public static X509Certificate createDSACert(KeyStore ksKeys, String keyPassword, String alias, String issuerDN,
String subjectDN, String domain)
throws GeneralSecurityException, IOException {
// Generate public and private keys
KeyPair keyPair = generateKeyPair("DSA", JiveGlobals.getIntProperty("cert.dsa.keysize", 1024));
// Create X509 certificate with keys and specified domain
X509Certificate cert = createX509V3Certificate(keyPair, 60, issuerDN, subjectDN, domain, "SHA1withDSA");
// Store new certificate and private key in the keystore
ksKeys.setKeyEntry(alias, keyPair.getPrivate(), keyPassword.toCharArray(), new X509Certificate[]{cert});
// Notify listeners that a new certificate has been created
for (CertificateEventListener listener : listeners) {
try {
listener.certificateCreated(ksKeys, alias, cert);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
// Return new certificate
return cert;
@Deprecated
public static void deleteCertificate(CertificateStoreConfig storeConfig, String alias) throws GeneralSecurityException, IOException, CertificateStoreConfigException
{
final KeyStore store = storeConfig.getStore();
if (!store.containsAlias( alias ) )
{
Log.info( "Unable to delete certificate for alias '"+alias+"' from store, as the store does not contain a certificate for that alias." );
return;
}
/**
* Creates a new X509 certificate using the RSA algorithm. The new certificate together with its private
* key are stored in the specified key store. However, the key store is not saved to the disk. This means
* that it is up to the "caller" to save the key store to disk after new certificates have been added
* to the store.
*
* @param ksKeys key store where the new certificate and private key are going to be stored.
* @param keyPassword password of the keystore.
* @param alias name to use when storing the certificate in the key store.
* @param issuerDN Issuer string e.g "O=Grid,OU=OGSA,CN=ACME"
* @param subjectDN Subject string e.g "O=Grid,OU=OGSA,CN=John Doe"
* @param domain domain of the server to store in the subject alternative name extension.
* @return the new X509 V3 Certificate.
* @throws GeneralSecurityException
* @throws IOException
*/
public static X509Certificate createRSACert(KeyStore ksKeys, String keyPassword, String alias, String issuerDN,
String subjectDN, String domain)
throws GeneralSecurityException, IOException {
// Generate public and private keys
KeyPair keyPair = generateKeyPair("RSA", JiveGlobals.getIntProperty("cert.rsa.keysize", 2048));
// Create X509 certificate with keys and specified domain
X509Certificate cert = createX509V3Certificate(keyPair, 60, issuerDN, subjectDN, domain, "SHA1WITHRSAENCRYPTION");
// Store new certificate and private key in the keystore
ksKeys.setKeyEntry(alias, keyPair.getPrivate(), keyPassword.toCharArray(), new X509Certificate[]{cert});
// Notify listeners that a new certificate has been created
for (CertificateEventListener listener : listeners) {
try {
listener.certificateCreated(ksKeys, alias, cert);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
// Return new certificate
return cert;
}
storeConfig.getStore().deleteEntry( alias );
storeConfig.persist();
/**
* Deletes the specified certificate from the
*
* @param ksKeys key store where the certificate is stored.
* @param alias alias of the certificate to delete.
* @throws GeneralSecurityException
* @throws IOException
*/
public static void deleteCertificate(KeyStore ksKeys, String alias) throws GeneralSecurityException, IOException {
ksKeys.deleteEntry(alias);
// Notify listeners that a new certificate has been created
// Notify listeners that a new certificate has been removed.
for (CertificateEventListener listener : listeners) {
try {
listener.certificateDeleted(ksKeys, alias);
listener.certificateDeleted(store, alias);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
Log.warn( "An exception occurred while notifying CertificateEventListener " + listener, e );
}
}
}
......@@ -435,25 +336,25 @@ public class CertificateManager {
/**
* Returns true if an RSA certificate was found in the specified keystore for the specified domain.
*
* @param ksKeys the keystore that contains the certificates.
* @param storeConfig the store to use for searching the certificate.
* @param domain domain of the server signed by the certificate.
* @return true if an RSA certificate was found in the specified keystore for the specified domain.
* @throws KeyStoreException
*/
public static boolean isRSACertificate(KeyStore ksKeys, String domain) throws KeyStoreException {
return isCertificate(ksKeys, domain, "RSA");
public static boolean isRSACertificate(CertificateStoreConfig storeConfig, String domain) throws KeyStoreException {
return isCertificate(storeConfig, domain, "RSA");
}
/**
* Returns true if an DSA certificate was found in the specified keystore for the specified domain.
*
* @param ksKeys the keystore that contains the certificates.
* @param storeConfig the store to use for searching the certificate.
* @param domain domain of the server signed by the certificate.
* @return true if an DSA certificate was found in the specified keystore for the specified domain.
* @throws KeyStoreException
*/
public static boolean isDSACertificate(KeyStore ksKeys, String domain) throws KeyStoreException {
return isCertificate(ksKeys, domain, "DSA");
public static boolean isDSACertificate(CertificateStoreConfig storeConfig, String domain) throws KeyStoreException {
return isCertificate( storeConfig, domain, "DSA" );
}
/**
......@@ -466,40 +367,42 @@ public class CertificateManager {
* @throws KeyStoreException
*/
public static boolean isDSACertificate(X509Certificate certificate) throws KeyStoreException {
return certificate.getPublicKey().getAlgorithm().equals("DSA");
return certificate.getPublicKey().getAlgorithm().equals( "DSA" );
}
/**
* Returns true if a certificate with the specified configuration was found in the key store.
* Returns true if a certificate with the specified configuration was found in a certificate store.
*
* @param ksKeys the keystore to use for searching the certificate.
* @param storeConfig the store to use for searching the certificate.
* @param domain the domain present in the subjectAltName or "*" if anything is accepted.
* @param algorithm the DSA or RSA algorithm used by the certificate.
* @return true if a certificate with the specifed configuration was found in the key store.
* @return true if a certificate with the specified configuration was found in the key store.
* @throws KeyStoreException
*/
private static boolean isCertificate(KeyStore ksKeys, String domain, String algorithm) throws KeyStoreException {
boolean result = false;
for (Enumeration<String> aliases = ksKeys.aliases(); aliases.hasMoreElements();) {
X509Certificate certificate = (X509Certificate) ksKeys.getCertificate(aliases.nextElement());
private static boolean isCertificate(CertificateStoreConfig storeConfig, String domain, String algorithm) throws KeyStoreException {
for (Enumeration<String> aliases = storeConfig.getStore().aliases(); aliases.hasMoreElements();) {
X509Certificate certificate = (X509Certificate) storeConfig.getStore().getCertificate(aliases.nextElement());
if ( !certificate.getPublicKey().getAlgorithm().equalsIgnoreCase( algorithm ) ) {
continue;
}
if ("*".equals(domain)) {
// Any domain certified by the certificate is accepted
if (certificate.getPublicKey().getAlgorithm().equals(algorithm)) {
result = true;
}
return true;
}
else {
// Only accept certified domains that match the specified domain
for (String identity : getServerIdentities(certificate)) {
if (identity.endsWith(domain) && certificate.getPublicKey().getAlgorithm().equals(algorithm)) {
result = true;
// TODO check that domain=foo.bar does not match identitiy "a.longerfoo.bar"
for (String identity : getServerIdentities( certificate ) ) {
if (identity.endsWith(domain) ) {
return true;
}
}
}
}
Log.debug("Check for certificate for '{}' using algorithm {} returned: {}", new Object[] { domain, algorithm, result} );
return result;
return false;
}
/**
......@@ -512,7 +415,7 @@ public class CertificateManager {
*/
public static boolean isSelfSignedCertificate(KeyStore keyStore, String alias) throws KeyStoreException {
// Get certificate chain
java.security.cert.Certificate[] certificateChain = keyStore.getCertificateChain(alias);
java.security.cert.Certificate[] certificateChain = keyStore.getCertificateChain( alias );
// Verify that the chain is empty or was signed by himself
return certificateChain == null || certificateChain.length == 1;
}
......@@ -527,7 +430,7 @@ public class CertificateManager {
* @throws KeyStoreException if an error happens while usign the keystore
*/
public static boolean isSelfSignedCertificate(KeyStore keyStore, X509Certificate certificate) throws KeyStoreException {
String alias = keyStore.getCertificateAlias(certificate);
String alias = keyStore.getCertificateAlias( certificate );
if (alias == null) {
throw new KeyStoreException("Certificate not found in store: " + certificate);
}
......@@ -551,7 +454,7 @@ public class CertificateManager {
}
// Verify that the issuer information has been entered
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
Matcher matcher = valuesPattern.matcher(certificate.getIssuerDN().toString());
Matcher matcher = valuesPattern.matcher( certificate.getIssuerDN().toString() );
return matcher.find() && matcher.find();
}
......@@ -560,15 +463,15 @@ public class CertificateManager {
* requests are required by Certificate Authorities as part of their signing process. The signing request
* contains information about the certificate issuer, subject DN, subject alternative names and public key.
* Private keys are not included. After the Certificate Authority verified and signed the certificate a new
* certificate is going to be returned. Use {@link #installReply(java.security.KeyStore, java.security.KeyStore, String, String, java.io.InputStream, boolean, boolean)}
* certificate is going to be returned. Use {@link #installReply(java.security.KeyStore, java.security.KeyStore, String, String, java.io.InputStream)}
* to import the CA reply.
*
* @param cert the certificate to create a signing request.
* @param privKey the private key of the certificate.
* @return the content of a new singing request for the specified certificate.
* @throws Exception
*/
public static String createSigningRequest(X509Certificate cert, PrivateKey privKey) throws Exception {
public static String createSigningRequest(X509Certificate cert, PrivateKey privKey) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, IOException
{
StringBuilder sb = new StringBuilder();
String subject = cert.getSubjectDN().getName();
......@@ -611,29 +514,24 @@ public class CertificateManager {
* Installs the Certificate Authority reply returned as part of the signing request. The certificate
* being signed will get its certificate chain updated with the imported certificate(s). An exception
* will be thrown if the replied certificate does not match a local certificate or if the signing
* authority is not known by the server (i.e. keystore and truststore files). When <tt>trustCACerts</tt>
* is set to <tt>true</tt> then certificates present in the truststore file will be used to verify the
* identity of the entity signing the certificate. In case the reply is composed of more than one
* certificate then you can also specify if you want to verify that the root certificate in the chain
* can be trusted.
* authority is not known by the server (i.e. keystore and truststore files)
*
* The identity of the entity that has signed the reply is verified against the provided trust store.
*
* The
*
* @param keyStore key store where the certificate is stored.
* @param trustStore key store where ca certificates are stored.
* @param keyPassword password of the keystore.
* @param alias the alias of the existing certificate being signed.
* @param inputStream the stream containing the CA reply.
* @param trustCACerts true if certificates present in the truststore file will be used to verify the
* identity of the entity signing the certificate.
* @param validateRoot true if you want to verify that the root certificate in the chain can be trusted
* based on the truststore.
* @return true if the CA reply was successfully processed.
* @throws Exception
*/
public static boolean installReply(KeyStore keyStore, KeyStore trustStore, String keyPassword, String alias, InputStream inputStream, boolean trustCACerts,
boolean validateRoot) throws Exception {
public static boolean installReply(KeyStore keyStore, KeyStore trustStore, String keyPassword, String alias, InputStream inputStream) throws Exception {
// Check that there is a certificate for the specified alias
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
X509Certificate certificate = (X509Certificate) keyStore.getCertificate( alias );
if (certificate == null) {
Log.warn("Certificate not found for alias: " + alias);
return false;
......@@ -641,29 +539,28 @@ public class CertificateManager {
// Retrieve the private key of the stored certificate
PrivateKey privKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
// Load certificates found in the PEM input stream
List<X509Certificate> certs = new ArrayList<X509Certificate>();
for (Certificate cert : CertificateFactory.getInstance("X509").generateCertificates(inputStream)) {
certs.add((X509Certificate) cert);
}
Collection<X509Certificate> certs = parseCertificates( inputStream );
if (certs.isEmpty()) {
throw new Exception("Reply has no certificates");
}
List<X509Certificate> newCerts;
if (certs.size() == 1) {
// Reply has only one certificate
newCerts = establishCertChain(keyStore, trustStore, null, certs.get(0), trustCACerts);
newCerts = establishCertChain(keyStore, trustStore, null, certs.iterator().next());
} else {
// Reply has a chain of certificates
newCerts = validateReply(keyStore, trustStore, alias, null, certs, trustCACerts, validateRoot);
newCerts = validateReply(keyStore, trustStore, alias, null, certs);
}
if (newCerts == null)
{
return false;
}
if (newCerts != null) {
keyStore.setKeyEntry(alias, privKey, keyPassword.toCharArray(),
newCerts.toArray(new X509Certificate[newCerts.size()]));
keyStore.setKeyEntry(alias, privKey, keyPassword.toCharArray(), newCerts.toArray(new X509Certificate[newCerts.size()]));
// Notify listeners that a new certificate has been created
for (CertificateEventListener listener : listeners) {
try {
listener.certificateSigned(keyStore, alias, newCerts);
listener.certificateSigned( keyStore, alias, newCerts );
}
catch (Exception e) {
Log.error(e.getMessage(), e);
......@@ -671,61 +568,8 @@ public class CertificateManager {
}
return true;
} else {
return false;
}
}
/**
* Imports one certificate into a truststore.
*
* This method will fail when more than one certificate is being provided.
*
* @param trustStore store where certificates are stored.
* @param alias the name (key) under which the certificate is to be stored in the store.
* @param inputStream a stream containing the certificate.
*/
public static void installCertsInTrustStore(KeyStore trustStore, String alias, InputStream inputStream) throws Exception
{
// Input validation
if (trustStore == null) {
throw new IllegalArgumentException("Argument 'trustStore' cannot be null.");
}
if (alias == null || alias.trim().isEmpty()) {
throw new IllegalArgumentException("Argument 'alias' cannot be null or an empty String.");
}
if (inputStream == null) {
throw new IllegalArgumentException("Argument 'inputStream' cannot be null.");
}
alias = alias.trim();
// Check that there is a certificate for the specified alias
if (trustStore.containsAlias(alias)) {
throw new IllegalArgumentException("Certificate already exists for alias: " + alias);
}
// Load certificate found in the PEM input stream
final Collection<? extends Certificate> certificates = CertificateFactory.getInstance("X509").generateCertificates(inputStream);
if (certificates.isEmpty()) {
throw new Exception("No certificate was found in the input.");
}
if (certificates.size() != 1) {
throw new Exception("More than one certificate was found in the input.");
}
final X509Certificate certificate = (X509Certificate) certificates.iterator().next();
trustStore.setCertificateEntry(alias, certificate);
// Notify listeners that a new certificate has been added.
for (CertificateEventListener listener : listeners) {
try {
listener.certificateCreated(trustStore, alias, certificate);
} catch (Throwable e) {
Log.warn("An exception occurred during the invocation of a CertificateEventListener.", e);
}
}
}
/**
* Imports a new signed certificate and its private key into the keystore. The certificate input
......@@ -738,16 +582,11 @@ public class CertificateManager {
* @param pkInputStream the stream containing the private key.
* @param passPhrase is the password phrased used when creating the private key.
* @param inputStream the stream containing the signed certificate.
* @param trustCACerts true if certificates present in the truststore file will be used to verify the
* identity of the entity signing the certificate.
* @param validateRoot true if you want to verify that the root certificate in the chain can be trusted
* based on the truststore.
* @return true if the certificate was successfully imported.
* @throws Exception if no certificates were found in the inputStream.
*/
public static boolean installCert(KeyStore keyStore, KeyStore trustStore, String keyPassword, String alias,
InputStream pkInputStream, final String passPhrase, InputStream inputStream,
boolean trustCACerts, boolean validateRoot) throws Exception {
InputStream pkInputStream, final String passPhrase, InputStream inputStream) throws Exception {
// Check that there is a certificate for the specified alias
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
if (certificate != null) {
......@@ -755,47 +594,33 @@ public class CertificateManager {
return false;
}
PEMParser pemParser = new PEMParser(new InputStreamReader(pkInputStream));
Object object = pemParser.readObject();
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passPhrase.toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
if (object instanceof PEMEncryptedKeyPair) {
Log.debug("Encrypted key - we will use provided password");
kp = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
} else {
Log.debug("Unencrypted key - no password needed");
kp = converter.getKeyPair((PEMKeyPair) object);
}
PrivateKey privKey = kp.getPrivate();
// Load certificates found in the PEM input stream
List<X509Certificate> certs = new ArrayList<X509Certificate>();
for (Certificate cert : CertificateFactory.getInstance("X509").generateCertificates(inputStream)) {
certs.add((X509Certificate) cert);
}
PrivateKey privKey = parsePrivateKey( pkInputStream, passPhrase );
Collection<X509Certificate> certs = parseCertificates( inputStream );
if (certs.isEmpty()) {
throw new Exception("No certificates were found");
}
List<X509Certificate> newCerts;
if (certs.size() == 1) {
if (certs.size() == 1)
{
// Reply has only one certificate
newCerts = establishCertChain(keyStore, trustStore, certificate, certs.get(0), trustCACerts);
} else {
newCerts = establishCertChain(keyStore, trustStore, certificate, certs.iterator().next() );
}
else
{
// Reply has a chain of certificates
newCerts = validateReply(keyStore, trustStore, alias, certificate, certs, trustCACerts, validateRoot);
newCerts = validateReply(keyStore, trustStore, alias, certificate, certs);
}
if (newCerts != null) {
keyStore.setKeyEntry(alias, privKey, keyPassword.toCharArray(),
newCerts.toArray(new X509Certificate[newCerts.size()]));
if (newCerts == null)
{
return false;
}
keyStore.setKeyEntry( alias, privKey, keyPassword.toCharArray(), newCerts.toArray( new X509Certificate[ newCerts.size() ] ) );
// Notify listeners that a new certificate has been created (and signed)
for (CertificateEventListener listener : listeners) {
try {
listener.certificateCreated(keyStore, alias, certs.get(0));
listener.certificateCreated( keyStore, alias, newCerts.get( 0 ) );
if (newCerts.size() > 1) {
listener.certificateSigned(keyStore, alias, newCerts);
}
......@@ -806,8 +631,83 @@ public class CertificateManager {
}
return true;
} else {
return false;
}
/**
* @deprecated Use {@link #parsePrivateKey(String, String)} instead.
*/
@Deprecated
public static PrivateKey parsePrivateKey( InputStream pemRepresentation, String passPhrase ) throws IOException
{
// see http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
final java.util.Scanner s = new java.util.Scanner( pemRepresentation ).useDelimiter("\\A");
return parsePrivateKey( s.hasNext() ? s.next() : "", passPhrase );
}
/**
* Parses a PrivateKey instance from a PEM representation.
*
* When the provided key is encrypted, the provided pass phrase is applied.
*
* @param pemRepresentation a PEM representation of a private key (cannot be null or empty)
* @param passPhrase optional pass phrase (must be present if the private key is encrypted).
* @return a PrivateKey instance (never null)
*/
public static PrivateKey parsePrivateKey( String pemRepresentation, String passPhrase ) throws IOException
{
if ( pemRepresentation == null || pemRepresentation.trim().isEmpty() ) {
throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null or an empty String.");
}
try ( Reader reader = new StringReader( pemRepresentation.trim() ))
{
final Object object = new PEMParser( reader ).readObject();
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider( "BC" );
final KeyPair kp;
if ( object instanceof PEMEncryptedKeyPair )
{
// Encrypted key - we will use provided password
final PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build( passPhrase.toCharArray() );
kp = converter.getKeyPair( ( (PEMEncryptedKeyPair) object ).decryptKeyPair( decProv ) );
}
else
{
// Unencrypted key - no password needed
kp = converter.getKeyPair( (PEMKeyPair) object );
}
return kp.getPrivate();
}
}
/**
* @deprecated Use {@link #parseCertificates(String)} instead.
*/
@Deprecated
public static Collection<X509Certificate> parseCertificates( InputStream pemRepresentation ) throws IOException, CertificateException
{
// see http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string
final java.util.Scanner s = new java.util.Scanner( pemRepresentation ).useDelimiter("\\A");
return parseCertificates( s.hasNext() ? s.next() : "" );
}
/**
* Parses a certificate chain from a PEM representation.
*
* @param pemRepresentation a PEM representation of a certificate or certificate chain (cannot be null or empty)
* @return A collection of certificates (possibly empty, but never null).
*/
public static Collection<X509Certificate> parseCertificates( String pemRepresentation ) throws IOException, CertificateException
{
if ( pemRepresentation == null || pemRepresentation.trim().isEmpty() ) {
throw new IllegalArgumentException( "Argument 'pemRepresentation' cannot be null or an empty String.");
}
final Collection<X509Certificate> certificates;
try ( InputStream inputStream = new ByteArrayInputStream( pemRepresentation.trim().getBytes() ) )
{
final CertificateFactory certificateFactory = CertificateFactory.getInstance( "X509" );
return (Collection<X509Certificate>) certificateFactory.generateCertificates( inputStream );
}
}
......@@ -820,7 +720,7 @@ public class CertificateManager {
if (listener == null) {
throw new NullPointerException();
}
listeners.add(listener);
listeners.add( listener );
}
/**
......@@ -829,12 +729,12 @@ public class CertificateManager {
* @param listener the listener.
*/
public static void removeListener(CertificateEventListener listener) {
listeners.remove(listener);
listeners.remove( listener );
}
private static List<X509Certificate> establishCertChain(KeyStore keyStore, KeyStore trustStore,
X509Certificate certificate,
X509Certificate certReply, boolean trustCACerts)
X509Certificate certReply)
throws Exception {
if (certificate != null) {
PublicKey publickey = certificate.getPublicKey();
......@@ -847,10 +747,12 @@ public class CertificateManager {
}
}
Map<Principal, List<X509Certificate>> knownCerts = new Hashtable<Principal, List<X509Certificate>>();
// TODO Figure out why we add keystore issuers. This implies that we always trust the issuer of our identitity (which probably is right, but shouldn't be required)
if (keyStore.size() > 0) {
knownCerts.putAll(getCertsByIssuer(keyStore));
}
if (trustCACerts && trustStore.size() > 0) {
if (trustStore.size() > 0) {
knownCerts.putAll(getCertsByIssuer(trustStore));
}
LinkedList<X509Certificate> answer = new LinkedList<X509Certificate>();
......@@ -903,7 +805,7 @@ public class CertificateManager {
return false;
}
}
answer.addFirst(certificate);
answer.addFirst( certificate );
return true;
}
......@@ -939,6 +841,81 @@ public class CertificateManager {
return answer;
}
/**
* Orders certificates, starting from the entity to be validated and progressing back toward the CA root.
*
* This implementation matches "issuers" to "subjects" of certificates in such a way that "issuer" value of a
* certificate matches the "subject" value of the next certificate.
*
* When certificates are provided that do not belong to the same chain, a CertificateException is thrown.
*
* @param certificates an unordered collection of certificates (cannot be null).
* @return An ordered list of certificates (possibly empty, but never null).
*/
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;
}
/**
* Validates chain in certification reply, and returns the ordered
* elements of the chain (with user certificate first, and root
......@@ -946,12 +923,12 @@ public class CertificateManager {
*
* @param alias the alias name
* @param userCert the user certificate of the alias
* @param replyCerts the chain provided in the reply
* @param certs the chain provided in the reply
*/
private static List<X509Certificate> validateReply(KeyStore keyStore, KeyStore trustStore, String alias,
X509Certificate userCert, List<X509Certificate> replyCerts,
boolean trustCACerts, boolean verifyRoot)
X509Certificate userCert, Collection<X509Certificate> certs)
throws Exception {
List<X509Certificate> replyCerts = new ArrayList<>(certs);
// order the certs in the reply (bottom-up).
int i;
X509Certificate tmpCert;
......@@ -1005,18 +982,13 @@ public class CertificateManager {
}
}
if (!verifyRoot) {
return replyCerts;
}
// do we trust the (root) cert at the top?
X509Certificate topCert = replyCerts.get(replyCerts.size() - 1);
boolean foundInKeyStore = keyStore.getCertificateAlias(topCert) != null;
boolean foundInCAStore = trustCACerts && (trustStore.getCertificateAlias(topCert) != null);
boolean foundInCAStore = trustStore.getCertificateAlias(topCert) != null;
if (!foundInKeyStore && !foundInCAStore) {
boolean verified = false;
X509Certificate rootCert = null;
if (trustCACerts) {
for (Enumeration<String> aliases = trustStore.aliases(); aliases.hasMoreElements();) {
String name = aliases.nextElement();
rootCert = (X509Certificate) trustStore.getCertificate(name);
......@@ -1031,7 +1003,6 @@ public class CertificateManager {
}
}
}
}
if (!verified) {
return null;
}
......@@ -1060,7 +1031,7 @@ public class CertificateManager {
* @throws GeneralSecurityException
* @throws IOException
*/
private static synchronized X509Certificate createX509V3Certificate(KeyPair kp, int months, String issuerDN,
public static synchronized X509Certificate createX509V3Certificate(KeyPair kp, int months, String issuerDN,
String subjectDN, String domain,
String signAlgoritm)
throws GeneralSecurityException, IOException {
......@@ -1101,24 +1072,4 @@ public class CertificateManager {
return cert;
}
/**
* Returns a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
*
* @param algorithm DSA, RSA, etc.
* @param keysize the keysize. This is an algorithm-specific metric, such as modulus
* length, specified in number of bits.
* @return a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
* @throws GeneralSecurityException
*/
private static KeyPair generateKeyPair(String algorithm, int keysize) throws GeneralSecurityException {
KeyPairGenerator generator;
if (provider == null) {
generator = KeyPairGenerator.getInstance(algorithm);
} else {
generator = KeyPairGenerator.getInstance(algorithm, provider);
}
generator.initialize(keysize, new SecureRandom());
return generator.generateKeyPair();
}
}
......@@ -142,20 +142,65 @@
<!-- TLS / SSL-->
<sidebar id="sidebar-certificates" name="${sidebar.sidebar-certificates}">
<!-- Server Certificates -->
<item id="security-keystore" name="${sidebar.security-keystore}"
url="security-keystore.jsp"
description="${sidebar.security-keystore.descr}"/>
<!-- C2S Certificate Truststore -->
<item id="security-truststore-c2s" name="${sidebar.security-truststore-c2s}"
url="security-truststore.jsp?type=c2s"
description="${sidebar.security-truststore-c2s.descr}"/>
<!-- S2S Certificate Truststore -->
<item id="security-truststore-s2s" name="${sidebar.security-truststore-s2s}"
url="security-truststore.jsp?type=s2s"
description="${sidebar.security-truststore-s2s.descr}"/>
<item id="security-certificate-store-management" name="Certificate Stores"
url="security-certificate-store-management.jsp"
description="Manage Openfire Certificate stores">
<!--&lt;!&ndash; Certificate key stores ("Openfire Certificates") &ndash;&gt;-->
<!--<item id="sidebar-certificates-keys" name="${sidebar.sidebar-certificates-keys}"-->
<!--url="security-keystore.jsp">-->
<sidebar id="sidebar-certificates-keys-submenu" name="${sidebar.sidebar-certificates-keys-submenu}">
<!-- Socket Server Certificates -->
<item id="security-keystore-socket" name="${sidebar.security-keystore-socket}"
url="security-keystore.jsp?connectivityType=socket"
description="${sidebar.security-keystore-socket.descr}"/>
<!-- BOSH Server Certificates -->
<item id="security-keystore-bosh" name="${sidebar.security-keystore-bosh}"
url="security-keystore.jsp?connectivityType=bosh"
description="${sidebar.security-keystore-bosh.descr}"/>
<!-- Administrative Server Certificates -->
<item id="security-keystore-administrative" name="${sidebar.security-keystore-administrative}"
url="security-keystore.jsp?connectivityType=administrative"
description="${sidebar.security-keystore-administrative.descr}"/>
</sidebar>
</item>
<!--&lt;!&ndash; Socket C2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-socket-c2s" name="${sidebar.security-truststore-socket-c2s}"-->
<!--url="security-truststore.jsp?connectivityType=socket&amp;type=c2s"-->
<!--description="${sidebar.security-truststore-socket-c2s.descr}"/>-->
<!--&lt;!&ndash; Socket S2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-socket-s2s" name="${sidebar.security-truststore-socket-s2s}"-->
<!--url="security-truststore.jsp?connectivityType=socket&amp;type=s2s"-->
<!--description="${sidebar.security-truststore-socket-s2s.descr}"/>-->
<!--&lt;!&ndash; BOSH C2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-bosh-c2s" name="${sidebar.security-truststore-bosh-c2s}"-->
<!--url="security-truststore.jsp?connectivityType=bosh&amp;type=c2s"-->
<!--description="${sidebar.security-truststore-bosh-c2s.descr}"/>-->
<!--&lt;!&ndash; BOSH S2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-bosh-s2s" name="${sidebar.security-truststore-bosh-s2s}"-->
<!--url="security-truststore.jsp?connectivityType=bosh&amp;type=s2s"-->
<!--description="${sidebar.security-truststore-bosh-s2s.descr}"/>-->
<!--&lt;!&ndash; Administrative C2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-administrative-c2s" name="${sidebar.security-truststore-administrative-c2s}"-->
<!--url="security-truststore.jsp?connectivityType=administrative&amp;type=c2s"-->
<!--description="${sidebar.security-truststore-administrative-c2s.descr}"/>-->
<!--&lt;!&ndash; Administrative S2S Certificate Truststore &ndash;&gt;-->
<!--<item id="security-truststore-administrative-s2s" name="${sidebar.security-truststore-administrative-s2s}"-->
<!--url="security-truststore.jsp?connectivityType=administrative&amp;type=s2s"-->
<!--description="${sidebar.security-truststore-administrative-s2s.descr}"/>-->
</sidebar>
......
package org.jivesoftware.openfire.keystore;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.jivesoftware.util.Base64;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import sun.security.provider.X509Factory;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.*;
/**
* Unit tests that verify the functionality of {@link TrustStoreConfig}.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class TrustStoreConfigTest
{
private static final Provider PROVIDER = new BouncyCastleProvider();
static
{
// Add the BC provider to the list of security providers
Security.addProvider( PROVIDER );
}
/**
* An instance that is freshly recreated before each test.
*/
private TrustStoreConfig trustStoreConfig;
@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");
final String location = tempDir + ( tempDir.endsWith( File.separator ) ? "" : File.separator ) + UUID.randomUUID();
final KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType());
final String password = "TS%WV@# aSG 4";
keyStore.load( null, password.toCharArray() );
// Populate the store with a valid CA certificate.
final X509Certificate validCertificate = generateTestSelfSignedCertificate( true );
keyStore.setCertificateEntry( "valid-ca", validCertificate );
// Populate the store with an invalid CA certificate.
final X509Certificate invalidCertificate = generateTestSelfSignedCertificate( false );
keyStore.setCertificateEntry( "invalid-ca", invalidCertificate );
// Persist the keystore file
try ( FileOutputStream fos = new FileOutputStream( location ) ) {
keyStore.store( fos, password.toCharArray() );
}
// Use the new keystore file to create a fresh trust store, which will be used as a fixture by the tests.
trustStoreConfig = new TrustStoreConfig( location, password, keyStore.getType(), false );
}
private static X509Certificate generateTestSelfSignedCertificate( boolean isValid ) throws Exception
{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "RSA" );
keyPairGenerator.initialize( 1024 );
KeyPair KPair = keyPairGenerator.generateKeyPair();
X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
v3CertGen.setSerialNumber( BigInteger.valueOf( Math.abs( new SecureRandom().nextInt() ) ) );
X509Principal principal;
if ( isValid ) {
principal = new X509Principal("CN=valid.example.org, OU=None, O=None L=None, C=None");
v3CertGen.setNotBefore( new Date( System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30 ) );
v3CertGen.setNotAfter( new Date( System.currentTimeMillis() + ( 1000L * 60 * 60 * 24 * 365 * 10 ) ) );
} else {
principal = new X509Principal("CN=invalid.example.org, OU=None, O=None L=None, C=None");
v3CertGen.setNotBefore( new Date( System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30 ) );
v3CertGen.setNotAfter( new Date( System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30 ) );
}
v3CertGen.setIssuerDN( principal );
v3CertGen.setSubjectDN( principal );
v3CertGen.setPublicKey( KPair.getPublic() );
v3CertGen.setSignatureAlgorithm( "MD5WithRSAEncryption" );
return v3CertGen.generateX509Certificate(KPair.getPrivate());
}
private static String toPEM( X509Certificate certificate ) throws Exception {
final StringBuilder sb = new StringBuilder();
sb.append( X509Factory.BEGIN_CERT ).append( '\n' );
sb.append( Base64.encodeBytes( certificate.getEncoded() ) ).append( '\n' );
sb.append( X509Factory.END_CERT).append( '\n' );
return sb.toString();
}
@After
public void tearDown() throws Exception
{
// Attempt to delete any left-overs from the test.
if (trustStoreConfig != null)
{
Files.deleteIfExists( Paths.get( trustStoreConfig.getCanonicalPath() ) );
trustStoreConfig = null;
}
}
/**
* The store in the fixture contains two certificates - one that is valid, and one that is invalid.
*
* This test verifies that {@link TrustStoreConfig#getAllCertificates()} returns both.
*/
@Test
public void testGetAll() throws Exception
{
// Setup fixture.
// Execute system under test.
final Map<String, X509Certificate> result = trustStoreConfig.getAllCertificates();
// Verify results.
Assert.assertEquals( 2, result.size() );
}
/**
* The store in the fixture contains two certificates - one that is valid, and one that is invalid.
*
* This test verifies that {@link TrustStoreConfig#getAllValidTrustAnchors()} returns only the valid one.
*/
@Test
public void testGetValid() throws Exception
{
// Setup fixture.
// Execute system under test.
final Set<TrustAnchor> result = trustStoreConfig.getAllValidTrustAnchors();
// Verify results.
Assert.assertEquals( 1, result.size() );
Assert.assertTrue( result.iterator().next().getTrustedCert().getIssuerDN().getName().contains( "CN=valid.example.org" ) );
}
/**
* A chain that has a trust anchor in the trust store (and is otherwise valid) should be trusted.
*/
@Test
public void testTrustCertSignedByCA() throws Exception
{
// Setup fixture
final Collection<X509Certificate> chain = new HashSet<>();
chain.add( (X509Certificate) trustStoreConfig.getStore().getCertificate( "valid-ca" ) ); // somewhat of a hack. Should use a distinct cert for the test.
// Execute System Under Test
final boolean result = trustStoreConfig.canTrust( chain );
// Verify
Assert.assertTrue( result );
}
/**
* A chain that has no trust anchor in the trust store (but is otherwise valid) should not be trusted.
*/
@Test
public void testDontTrustCertNotSignedByCA() throws Exception
{
// Setup fixture
final Collection<X509Certificate> chain = new HashSet<>();
chain.add( generateTestSelfSignedCertificate( true ) );
// Execute System Under Test
final boolean result = trustStoreConfig.canTrust( chain );
// Verify
Assert.assertFalse( result );
}
/**
* This test verifies that when a certificate is installed in the store using
* {@link TrustStoreConfig#installCertificate(String, String)} a certificate chain of which the anchor is that same
* certificate is successfully verified.
*/
@Test
public void verifyWithNewlyInstalledCACert() throws Exception
{
// Setup fixture
final X509Certificate cert = generateTestSelfSignedCertificate( true );
final String pemCert = toPEM( cert );
final Collection<X509Certificate> chain = new HashSet<>();
chain.add( cert ); // somewhat of a hack. Should use a distinct cert for the test.
// Execute System Under Test
trustStoreConfig.installCertificate( "new-cert", pemCert );
final boolean result = trustStoreConfig.canTrust( chain );
// Verify
Assert.assertTrue( result );
}
}
......@@ -59,7 +59,7 @@ public class DNSUtilTest {
final List<DNSUtil.WeightedHostAddress> result = DNSUtil.prioritize(new DNSUtil.WeightedHostAddress[]{host});
// verify
Assert.assertEquals(1, result.size());
Assert.assertEquals( 1, result.size() );
Assert.assertEquals(host, result.get(0));
}
......@@ -113,7 +113,7 @@ public class DNSUtilTest {
// verify
Assert.assertEquals(3, result.size());
Assert.assertEquals(hostA, result.get(0));
Assert.assertEquals(hostC, result.get(1));
Assert.assertEquals(hostC, result.get( 1 ));
Assert.assertEquals(hostB, result.get(2));
}
......@@ -174,8 +174,8 @@ public class DNSUtilTest {
}
// verify
Assert.assertTrue(hostAWasFirst);
Assert.assertTrue(hostBWasFirst);
Assert.assertTrue( hostAWasFirst );
Assert.assertTrue( hostBWasFirst );
}
/**
......@@ -216,4 +216,131 @@ public class DNSUtilTest {
Assert.assertTrue(hostBWasFirst);
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} finds a match when both
* arguments have the same value.
*/
@Test
public void testNameCoverageExactMatch() throws Exception
{
// setup
final String name = "xmpp.example.org";
final String pattern = name;
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertTrue( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} does not find a match when both
* arguments have different values.
*/
@Test
public void testNameCoverageUnequal() throws Exception
{
// setup
final String name = "xmpp.example.org";
final String pattern = "something.completely.different";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertFalse( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} does not find a match when the
* needle/name is a subdomain of the DNS pattern, without the DNS pattern including a wildcard.
*/
@Test
public void testNameCoverageSubdomainNoWildcard() throws Exception
{
// setup
final String name = "xmpp.example.org";
final String pattern = "example.org";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertFalse( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} does not find a match when the
* last part of the needle/name equals the pattern.
*/
@Test
public void testNameCoveragePartialMatchButNoSubdomain() throws Exception
{
// setup
final String name = "xmppexample.org";
final String pattern = "example.org";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertFalse( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} finds a match when the
* needle/name is a subdomain of the DNS pattern, while the DNS pattern includes a wildcard.
*/
@Test
public void testNameCoverageSubdomainWithWildcard() throws Exception
{
// setup
final String name = "xmpp.example.org";
final String pattern = "*.example.org";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertTrue( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} finds a match when the
* needle/name is a subdomain of a subdomain of the DNS pattern, while the DNS pattern includes a wildcard.
*/
@Test
public void testNameCoverageSubSubdomainWithWildcard() throws Exception
{
// setup
final String name = "deeper.xmpp.example.org";
final String pattern = "*.example.org";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertTrue( result );
}
/**
* A test that verifies that {@link DNSUtil#isNameCoveredByPattern(String, String)} finds a match when the
* needle/name equals the domain part of the DNS pattern, while the DNS pattern includes a wildcard.
*
* Although somewhat shady, the certificate management in Openfire depends on this to hold true.
*/
@Test
public void testNameCoverageSubdomainWithWildcardOfSameDomain() throws Exception
{
// setup
final String name = "xmpp.example.org";
final String pattern = "*.xmpp.example.org";
// do magic
final boolean result = DNSUtil.isNameCoveredByPattern( name, pattern );
// verify
Assert.assertTrue( result );
}
}
......@@ -155,4 +155,14 @@
<function-class>org.jivesoftware.admin.JSTLFunctions</function-class>
<function-signature>java.lang.String[] split(java.lang.String, java.lang.String)</function-signature>
</function>
<function>
<name>serverIdentities</name>
<function-class>org.jivesoftware.util.CertificateManager</function-class>
<function-signature>java.util.List getServerIdentities(java.security.cert.X509Certificate)</function-signature>
</function>
<function>
<name>clientIdentities</name>
<function-class>org.jivesoftware.util.CertificateManager</function-class>
<function-signature>java.util.List getClientIdentities(java.security.cert.X509Certificate)</function-signature>
</function>
</taglib>
<%@ page import="org.jivesoftware.util.CertificateManager,
org.jivesoftware.util.ParamUtils,
org.jivesoftware.util.StringUtils,
org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.openfire.net.SSLConfig,
java.io.ByteArrayInputStream,
java.util.HashMap,
java.util.Map"
errorPage="error.jsp"%>
<%@ page import="java.security.KeyStore" %>
<%@ page errorPage="error.jsp" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.IdentityStoreConfig" %>
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.XMPPServer" %>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
......@@ -16,12 +14,30 @@
<% webManager.init(request, response, session, application, out ); %>
<% // Get parameters:
boolean save = ParamUtils.getParameter(request, "save") != null;
String privateKey = ParamUtils.getParameter(request, "private-key");
String passPhrase = ParamUtils.getParameter(request, "passPhrase");
String certificate = ParamUtils.getParameter(request, "certificate");
final boolean save = ParamUtils.getParameter( request, "save" ) != null;
final String privateKey = ParamUtils.getParameter(request, "private-key");
final String passPhrase = ParamUtils.getParameter(request, "passPhrase");
final String certificate = ParamUtils.getParameter(request, "certificate");
final String storePurposeText = ParamUtils.getParameter(request, "storePurpose");
final Map<String, String> errors = new HashMap<String, String>();
Purpose storePurpose;
try
{
storePurpose = Purpose.valueOf( storePurposeText );
} catch (RuntimeException ex) {
errors.put( "storePurpose", ex.getMessage() );
storePurpose = null;
}
if (! storePurpose.isIdentityStore() ) {
errors.put( "storePurpose", "shoud be an identity store (not a trust store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose );
Map<String, String> errors = new HashMap<String, String>();
if (save) {
if (privateKey == null || "".equals(privateKey)) {
errors.put("privateKey", "privateKey");
......@@ -31,30 +47,24 @@
}
if (errors.isEmpty()) {
try {
KeyStore keystore;
try {
keystore = SSLConfig.getKeyStore();
}
catch (Exception e) {
keystore = SSLConfig.initializeKeyStore();
}
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose );
// Create an alias for the signed certificate
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
int index = 1;
String alias = domain + "_" + index;
while (keystore.containsAlias(alias)) {
while ( identityStoreConfig.getStore().containsAlias( alias )) {
index = index + 1;
alias = domain + "_" + index;
}
// Import certificate
CertificateManager.installCert(keystore, SSLConfig.gets2sTrustStore(),
SSLConfig.getKeyPassword(), alias, new ByteArrayInputStream(privateKey.getBytes()), passPhrase,
new ByteArrayInputStream(certificate.getBytes()), true, true);
// Save keystore
SSLConfig.saveStores();
identityStoreConfig.installCertificate( alias, privateKey, passPhrase, certificate );
// Log the event
webManager.logEvent("imported SSL certificate", "alias = "+alias);
response.sendRedirect("security-keystore.jsp");
webManager.logEvent("imported SSL certificate in "+ storePurposeText, "alias = "+alias);
response.sendRedirect("security-keystore.jsp?storePurpose="+storePurposeText);
return;
}
catch (Exception e) {
......@@ -67,8 +77,8 @@
<html>
<head>
<title><fmt:message key="ssl.import.certificate.keystore.title"/></title>
<meta name="pageID" content="security-keystore"/>
<title><fmt:message key="ssl.import.certificate.keystore.${connectivityType}.title"/></title>
<meta name="pageID" content="security-keystore-${connectivityType}"/>
</head>
<body>
......@@ -110,6 +120,7 @@
<!-- BEGIN 'Import Private Key and Certificate' -->
<form action="import-keystore-certificate.jsp" method="post" name="f">
<input type="hidden" name="connectivityType" value="${connectivityType}"/>
<div class="jive-contentBoxHeader">
<fmt:message key="ssl.import.certificate.keystore.boxtitle" />
</div>
......
<%@ page errorPage="error.jsp"%>
<%@ page import="org.jivesoftware.util.CertificateManager"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig"%>
<%@ page import="java.io.ByteArrayInputStream"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.security.KeyStore" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.TrustStoreConfig" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
......@@ -15,50 +14,37 @@
<% webManager.init(request, response, session, application, out ); %>
<% final boolean save = ParamUtils.getParameter(request, "save") != null;
final String type = ParamUtils.getParameter(request, "type");
final String alias = ParamUtils.getParameter(request, "alias");
final String certificate = ParamUtils.getParameter(request, "certificate");
final String storePurposeText = ParamUtils.getParameter(request, "storePurpose");
final Map<String, String> errors = new HashMap<String, String>();
KeyStore store = null;
if (type == null)
{
errors.put("type", "The store type has not been specified.");
}
else
{
Purpose storePurpose;
try
{
switch (type)
{
case "s2s":
store = SSLConfig.gets2sTrustStore();
break;
case "c2s":
store = SSLConfig.getc2sTrustStore();
break;
default:
throw new Exception("Unknown store type: " + type);
}
}
catch (Exception e)
{
e.printStackTrace();
errors.put("type", e.getMessage());
storePurpose = Purpose.valueOf( storePurposeText );
} catch (RuntimeException ex) {
errors.put( "storePurpose", ex.getMessage() );
storePurpose = null;
}
if (! storePurpose.isTrustStore() ) {
errors.put( "storePurpose", "shoud be a trust store (not an identity store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose );
if (save && errors.isEmpty())
{
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose );
if (alias == null || "".equals(alias))
{
errors.put("missingalias", "missingalias");
}
else if (store.containsAlias(alias))
else if (trustStoreConfig.getStore().containsAlias( alias ))
{
// Verify that the provided alias is not already available
errors.put("existingalias", "existingalias");
......@@ -73,14 +59,12 @@
try
{
// Import certificate
CertificateManager.installCertsInTrustStore(store, alias, new ByteArrayInputStream(certificate.getBytes()));
// Save keystore
SSLConfig.saveStores();
trustStoreConfig.installCertificate( alias, certificate );
// Log the event
webManager.logEvent("imported SSL certificate in "+type+" truststore", "alias = "+alias);
response.sendRedirect("security-truststore.jsp?type="+type+"&importsuccess=true");
webManager.logEvent("imported SSL certificate in "+ storePurposeText, "alias = "+alias);
response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurposeText + "&importsuccess=true" );
return;
}
catch (Throwable e)
......@@ -95,9 +79,9 @@
<html>
<head>
<title>
<fmt:message key="ssl.import.certificate.keystore.title"/> - <fmt:message key="ssl.certificates.truststore.${param.type}-title"/>
<fmt:message key="ssl.import.certificate.keystore.${connectivityType}.title"/> - <fmt:message key="ssl.certificates.truststore.${param.type}-title"/>
</title>
<meta name="pageID" content="security-truststore-${param.type}"/>
<meta name="pageID" content="security-truststore-${connectivityType}-${param.type}"/>
</head>
<body>
......@@ -145,6 +129,7 @@
<!-- BEGIN 'Import Certificate' -->
<form action="import-truststore-certificate.jsp?type=${param.type}" method="post" name="f">
<input type="hidden" name="connectivityType" value="${connectivityType}"/>
<div class="jive-contentBoxHeader">
<fmt:message key="ssl.import.certificate.keystore.boxtitle"/>
</div>
......
......@@ -43,6 +43,9 @@
<%@ page import="java.text.DecimalFormat" %>
<%@ page import="java.util.List" %>
<%@ page import="org.slf4j.LoggerFactory" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.CertificateStoreConfig" %>
<%@ page import="org.jivesoftware.openfire.keystore.IdentityStoreConfig" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
......@@ -73,7 +76,6 @@
boolean serverOn = (webManager.getXMPPServer() != null);
String interfaceName = JiveGlobals.getXMLProperty("network.interface");
ConnectionManagerImpl connectionManager = ((ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager());
NioSocketAcceptor socketAcceptor = connectionManager.getSocketAcceptor();
NioSocketAcceptor sslSocketAcceptor = connectionManager.getSSLSocketAcceptor();
......@@ -251,8 +253,9 @@
<fmt:message key="index.server_name" />
</td>
<td class="c2">
<% final IdentityStoreConfig storeConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE ); %>
<% try { %>
<% if (!CertificateManager.isRSACertificate(SSLConfig.getKeyStore(), XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {%>
<% if (!storeConfig.containsDomainCertificate( "RSA" )) {%>
<img src="images/warning-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="index.certificate-warning" />" title="<fmt:message key="index.certificate-warning" />">&nbsp;
<% } %>
<% } catch (Exception e) { %>
......@@ -452,7 +455,8 @@
<td><%= "0.0.0.0".equals(address.getHostName()) ? LocaleUtils.getLocalizedString("ports.all_ports") : address.getHostName() %></td>
<td><%= address.getPort() %></td>
<% try { %>
<% if (!CertificateManager.isRSACertificate(SSLConfig.getKeyStore(), XMPPServer.getInstance().getServerInfo().getXMPPDomain()) || LocalClientSession.getTLSPolicy() == org.jivesoftware.openfire.Connection.TLSPolicy.disabled) { %>
<% if (!storeConfig.containsDomainCertificate( "RSA" ) || LocalClientSession.getTLSPolicy() == org.jivesoftware.openfire.Connection.TLSPolicy.disabled) { %>
<td><img src="images/blank.gif" width="1" height="1" alt=""/></td>
<% } else { %>
<td><img src="images/lock.gif" width="16" height="16" border="0" alt="<fmt:message key="ports.secure.alt" />" title="<fmt:message key="ports.secure.alt" />"/></td>
......
......@@ -4,10 +4,11 @@
<%@ page import="org.jivesoftware.openfire.net.SSLConfig"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="java.security.KeyStore" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%@ page import="javax.xml.bind.DatatypeConverter" %>
<%@ page import="java.security.AlgorithmParameters" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.CertificateStoreConfig" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
......@@ -18,80 +19,69 @@
<jsp:useBean id="now" class="java.util.Date"/>
<% webManager.init(request, response, session, application, out );
final String type = ParamUtils.getParameter(request, "type");
final String alias = ParamUtils.getParameter(request, "alias");
final String alias = ParamUtils.getParameter( request, "alias" );
final String storePurposeText = ParamUtils.getParameter( request, "storePurpose" );
final Map<String, String> errors = new HashMap<String, String>();
KeyStore store = null;
if (type == null)
Purpose storePurpose;
try
{
errors.put("type", "The store type has not been specified.");
storePurpose = Purpose.valueOf( storePurposeText );
} catch (RuntimeException ex) {
errors.put( "storePurpose", ex.getMessage() );
storePurpose = null;
}
else if (alias == null) {
pageContext.setAttribute( "storePurpose", storePurpose );
if (alias == null) {
errors.put("alias", "The alias has not been specified.");
}
else
{
try
{
switch (type)
{
case "s2s":
store = SSLConfig.gets2sTrustStore();
break;
case "c2s":
store = SSLConfig.getc2sTrustStore();
break;
case "server":
store = SSLConfig.getKeyStore();
break;
default:
throw new Exception("Unknown store type: " + type);
}
final CertificateStoreConfig certificateStoreConfig = SSLConfig.getInstance().getStoreConfig( storePurpose );
// Get the certificate
final X509Certificate certificate = (X509Certificate) store.getCertificate(alias);
final X509Certificate certificate = (X509Certificate) certificateStoreConfig.getStore().getCertificate( alias );
if (certificate == null) {
errors.put("alias", "alias");
if ( certificate == null ) {
errors.put( "alias", "alias" );
} else {
pageContext.setAttribute("certificate", certificate);
pageContext.setAttribute( "certificate", certificate );
}
}
catch (Exception e)
catch ( Exception e )
{
e.printStackTrace();
errors.put("type", e.getMessage());
errors.put( "type", e.getMessage() );
}
}
// Handle a "go back" click:
if (request.getParameter("back") != null) {
if ("server".equals(type)) {
response.sendRedirect("security-keystore.jsp");
if ( request.getParameter( "back" ) != null ) {
if ( storePurpose.isTrustStore() ) {
response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurpose );
} else {
response.sendRedirect("security-truststore.jsp?type=" + type);
response.sendRedirect( "security-keystore.jsp?storePurpose=" + storePurpose );
}
return;
}
pageContext.setAttribute("errors", errors);
pageContext.setAttribute( "errors", errors );
%>
<html>
<head>
<title><fmt:message key="ssl.certificate.details.title"/></title>
<c:choose>
<c:when test="${param.type eq 'server'}">
<c:when test="${storePurpose.identityStore}">
<meta name="pageID" content="security-keystore"/>
</c:when>
<c:otherwise>
<meta name="pageID" content="security-truststore-${param.type}"/>
<meta name="pageID" content="security-truststore"/>
</c:otherwise>
</c:choose>
</head>
......@@ -414,6 +404,7 @@
</tr>
<c:if test="${not empty certificate.sigAlgParams}">
<%
final X509Certificate certificate = (X509Certificate) pageContext.getAttribute("certificate");
final AlgorithmParameters sigParams = AlgorithmParameters.getInstance(certificate.getSigAlgName());
sigParams.init( certificate.getSigAlgParams() );
......@@ -450,7 +441,7 @@
<br/>
<form action="security-certificate-details.jsp">
<input type="hidden" name="type" value="${param.type}"/>
<input type="hidden" name="storePurpose" value="${storePurpose}"/>
<div style="text-align: center;">
<input type="submit" name="back" value="<fmt:message key="session.details.back_button"/>">
</div>
......
<%@ page errorPage="error.jsp"%>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig"%>
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init( request, response, session, application, out );
// Read parameters
final boolean save = request.getParameter("save") != null;
final String paramLocKeySocket = ParamUtils.getParameter(request, "loc-key-socket");
final String paramLocTrustSocketS2S = ParamUtils.getParameter(request, "loc-trust-socket-s2s");
final String paramLocTrustSocketC2S = ParamUtils.getParameter(request, "loc-trust-socket-c2s");
final String paramLocKeyBosh = ParamUtils.getParameter(request, "loc-key-bosh");
final String paramLocTrustBoshC2S = ParamUtils.getParameter(request, "loc-trust-bosh-c2s");
final String paramLocKeyWebadmin = ParamUtils.getParameter(request, "loc-key-webadmin");
final String paramLocTrustWebadmin = ParamUtils.getParameter(request, "loc-trust-webadmin");
final String paramLocKeyAdministrative = ParamUtils.getParameter( request, "loc-key-administrative" );
final String paramLocTrustAdministrative = ParamUtils.getParameter( request, "loc-trust-administrative" );
// TODO actually save something!
// Pre-update property values
final String locKeySocket = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE );
final String locTrustSocketS2S = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final String locTrustSocketC2S = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final String locKeyBosh = SSLConfig.getNonCanonicalizedLocation( Purpose.BOSHBASED_IDENTITYSTORE );
final String locTrustBoshC2S = SSLConfig.getNonCanonicalizedLocation( Purpose.BOSHBASED_C2S_TRUSTSTORE );
final String locKeyWebadmin = SSLConfig.getNonCanonicalizedLocation( Purpose.WEBADMIN_IDENTITYSTORE );
final String locTrustWebadmin = SSLConfig.getNonCanonicalizedLocation( Purpose.WEBADMIN_TRUSTSTORE );
final String locKeyAdministrative = SSLConfig.getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_IDENTITYSTORE );
final String locTrustAdministrative = SSLConfig.getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_TRUSTSTORE );
final Map<String, String> errors = new HashMap<>();
pageContext.setAttribute( "errors", errors );
pageContext.setAttribute( "locKeySocket", locKeySocket );
pageContext.setAttribute( "locTrustSocketS2S",locTrustSocketS2S );
pageContext.setAttribute( "locTrustSocketC2S", locTrustSocketC2S );
pageContext.setAttribute( "locKeyBosh", locKeyBosh );
pageContext.setAttribute( "locTrustBoshC2S", locTrustBoshC2S );
pageContext.setAttribute( "locKeyWebadmin", locKeyWebadmin );
pageContext.setAttribute( "locTrustWebadmin", locTrustWebadmin );
pageContext.setAttribute( "locKeyAdministrative", locKeyAdministrative );
pageContext.setAttribute( "locTrustAdministrative", locTrustAdministrative );
%>
<html>
<head>
<title>Certificate Stores</title>
<meta name="pageID" content="security-certificate-store-management"/>
</head>
<>
<c:forEach var="err" items="${errors}">
<admin:infobox type="error">
<c:if test="${not empty err.value}">
<fmt:message key="admin.error"/>: <c:out value="${err.value}"/>
</c:if>
(<c:out value="${err.key}"/>)
</admin:infobox>
</c:forEach>
<c:if test="${param.success}">
<admin:infobox type="success">Settings Updated Successfully</admin:infobox>
</c:if>
<c:if test="${param.noChange}">
<admin:infobox type="info">The provided settings were no different than before. Nothing changed.</admin:infobox>
</c:if>
<p>
Certificates are used (through TLS and SSL protocols) to establish secure connections between servers and clients.
When a secured connection is being created, parties can retrieve a certificate from the other party and (amongst
others) examine the issuer of those certificates. If the issuer is trusted, a secured layer of communication can be
established.
</p>
<p>
Certificates are kept in specialized repositories, or 'stores'. Openfire provides two types of stores:
<ul>
<li><em>Identity stores</em> are used to store certificates that identify this instance of Openfire. On request,
they certificates from these stores are transmitted to other parties which use them to identify your server.
</li>
<li><em>Trust stores</em> contain certificates that identify parties that you choose to trust. Trust stores often do
not include the certificate from the remote party directly, but instead holds certificates from organizations
that are trusted to identify the certificate of the remote party. Such organizations are commonly referred to as
"Certificate Authorities".
</li>
</ul>
</p>
<p>
This section of the admin panel is dedicated to management of the various key and trust stores that act as
repositories for sets of security certificates. By default, a small set of stores is re-used for various purposes,
but Openfire allows you to configure a distinct set of stores for each type. To do so, please change the store
locations below.
</p>
<form action="security-certificate-store-management.jsp" method="post">
<div class="jive-contentBoxHeader">
Regular XMPP connection Stores
</div>
<div class="jive-contentBox">
<p>
These stores are used for regular, TCP-based XMPP communication. Three stores are provided: one identity store
and two trust stores. One of the trust stores applies to server-to-server federation. The other trust store
applies to the optional client-based mutual authentication feature in Openfire.
</p>
<p>
Openfire ships with an empty client trust store, as in typical environments, certificate-based authentication of
clients is not required.
</p>
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td><label for="loc-key-socket">Identity Store:</label></td>
<td><input id="loc-key-socket" name="loc-key-socket" type="text" size="40" value="${locKeySocket}"/></td>
<td><a href="security-keystore.jsp?storePurpose=SOCKETBASED_IDENTITYSTORE">Manage Store Contents</a></td>
</tr>
<tr>
<td><label for="loc-trust-socket-s2s">Server Trust Store:</label></td>
<td><input id="loc-trust-socket-s2s" name="loc-trust-socket-s2s" type="text" size="40" value="${locTrustSocketS2S}"/></td>
<td><a href="security-truststore.jsp?storePurpose=SOCKETBASED_S2S_TRUSTSTORE">Manage Store Contents</a></td>
</tr>
<tr>
<td><label for="loc-trust-socket-c2s">Client Trust Store:</label></td>
<td><input id="loc-trust-socket-c2s" name="loc-trust-socket-c2s" type="text" size="40" value="${locTrustSocketC2S}"/></td>
<td><a href="security-truststore.jsp?storePurpose=SOCKETBASED_C2S_TRUSTSTORE">Manage Store Contents</a></td>
</tr>
</tbody>
</table>
</div>
<div class="jive-contentBoxHeader">
BOSH (HTTP Binding) connection Stores
</div>
<div class="jive-contentBox">
<p>
These stores are used for BOSH-based XMPP communication. Two stores are provided: an identity store
and a client trust store (a server trust store is not provided, as BOSH-based server federation is
unsupported by Openfire).
</p>
<p>
Openfire ships with an empty client trust store, as in typical environments, certificate-based authentication of
clients is not required.
</p>
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td><label for="loc-key-bosh">Identity Store:</label></td>
<td><input id="loc-key-bosh" name="loc-key-bosh" type="text" size="40" value="${locKeyBosh}"/></td>
<td><a href="security-keystore.jsp?storePurpose=BOSHBASED_IDENTITYSTORE">Manage Store Contents</a></td>
</tr>
<tr>
<td><label for="loc-trust-bosh-c2s">Client Trust Store:</label></td>
<td><input id="loc-trust-bosh-c2s" name="loc-trust-bosh-c2s" type="text" size="40" value="${locTrustBoshC2S}"/></td>
<td><a href="security-truststore.jsp?storePurpose=BOSHBASED_C2S_TRUSTSTORE">Manage Store Contents</a></td>
</tr>
</tbody>
</table>
</div>
<div class="jive-contentBoxHeader">
Admin Panel Stores
</div>
<div class="jive-contentBox">
<p>
These stores are used for the web-based admin panel (you're looking at it right now!). Again, two stores are
provided an identity store and a trust store (used for optional authentication of browsers that use the admin
panel).
</p>
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td><label for="loc-key-webadmin">Identity Store:</label></td>
<td><input id="loc-key-webadmin" name="loc-key-webadmin" type="text" size="40" value="${locKeyWebadmin}"/></td>
<td><a href="security-keystore.jsp?storePurpose=WEBADMIN_IDENTITYSTORE">Manage Store Contents</a></td>
</tr>
<tr>
<td><label for="loc-trust-webadmin">Trust Store:</label></td>
<td><input id="loc-trust-webadmin" name="loc-trust-webadmin" type="text" size="40" value="${locTrustWebadmin}"/></td>
<td><a href="security-keystore.jsp?storePurpose=WEBADMIN_TRUSTSTORE">Manage Store Contents</a></td>
</tr>
</tbody>
</table>
</div>
<div class="jive-contentBoxHeader">
Administrative Stores
</div>
<div class="jive-contentBox">
<p>
These stores are used in communication with external servers that serves administrative purposes (such as user
providers or databases).
</p>
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td><label for="loc-key-administrative">Identity Store:</label></td>
<td><input id="loc-key-administrative" name="loc-key-administrative" type="text" size="40" value="${locKeyAdministrative}"/></td>
<td><a href="security-keystore.jsp?storePurpose=ADMINISTRATIVE_IDENTITYSTORE">Manage Store Contents</a></td>
</tr>
<tr>
<td><label for="loc-trust-administrative">Trust Store:</label></td>
<td><input id="loc-trust-administrative" name="loc-trust-administrative" type="text" size="40" value="${locTrustAdministrative}"/></td>
<td><a href="security-truststore.jsp?storePurpose=ADMINISTRATIVE_TRUSTSTORE">Manage Store Contents</a></td>
</tr>
</tbody>
</table>
</div>
<!-- TODO enable me <input type="submit" name="save" value="<fmt:message key="global.save_settings" />"> -->
</form>
</body>
</html>
......@@ -6,16 +6,13 @@
<%@ page import="org.jivesoftware.openfire.XMPPServer" %>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig" %>
<%@ page import="java.io.ByteArrayInputStream" %>
<%@ page import="java.security.KeyStore" %>
<%@ page import="java.security.PrivateKey" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.LinkedHashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.container.PluginManager" %>
<%@ page import="org.jivesoftware.openfire.container.AdminConsolePlugin" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.IdentityStoreConfig" %>
<%@ page import="java.util.*" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
......@@ -26,74 +23,118 @@
<% webManager.init(request, response, session, application, out); %>
<% // Get parameters:
boolean generate = ParamUtils.getBooleanParameter(request, "generate");
boolean delete = ParamUtils.getBooleanParameter(request, "delete");
boolean importReply = ParamUtils.getBooleanParameter(request, "importReply");
String alias = ParamUtils.getParameter(request, "alias");
Map<String, String> errors = new HashMap<String, String>();
KeyStore keyStore = null;
KeyStore s2sTrustStore = null;
KeyStore c2sTrustStore = null;
final boolean generate = ParamUtils.getBooleanParameter(request, "generate");
final boolean delete = ParamUtils.getBooleanParameter(request, "delete");
final boolean importReply = ParamUtils.getBooleanParameter(request, "importReply");
final String alias = ParamUtils.getParameter( request, "alias" );
final String storePurposeText = ParamUtils.getParameter( request, "storePurpose" );
try {
keyStore = SSLConfig.getKeyStore();
s2sTrustStore = SSLConfig.gets2sTrustStore();
c2sTrustStore = SSLConfig.getc2sTrustStore();
} catch (IOException e) {
e.printStackTrace();
errors.put("ioerror", e.getMessage());
final Map<String, String> errors = new HashMap<String, String>();
Purpose storePurpose = null;
IdentityStoreConfig storeConfig = null;
try
{
storePurpose = Purpose.valueOf( storePurposeText );
if ( !storePurpose.isIdentityStore() )
{
errors.put( "storePurpose", "should be an identity store (not a trust store)");
}
else
{
storeConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose );
if ( storeConfig == null )
{
errors.put( "storeConfig", "Unable to get an instance." );
}
}
}
catch (RuntimeException ex)
{
errors.put( "storePurpose", ex.getMessage() );
}
if ( errors.isEmpty() )
{
pageContext.setAttribute( "storePurpose", storePurpose );
pageContext.setAttribute( "storeConfig", storeConfig );
final Set<Purpose> sameStorePurposes = SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose );
pageContext.setAttribute( "sameStorePurposes", sameStorePurposes );
final Map<String, X509Certificate> certificates = storeConfig.getAllCertificates();
pageContext.setAttribute( "certificates", certificates );
pageContext.setAttribute( "validRSACert", storeConfig.containsDomainCertificate( "RSA" ) );
pageContext.setAttribute( "validDSACert", storeConfig.containsDomainCertificate( "DSA" ) );
if ( delete )
{
if ( alias == null )
{
errors.put( "alias", "The alias has not been specified." );
}
else
{
try
{
storeConfig.delete( alias );
// Log the event
webManager.logEvent( "deleted SSL cert from " + storePurposeText + " with alias " + alias, null );
response.sendRedirect( "security-keystore.jsp?storePurpose=" + storePurposeText + "&deletesuccess=true" );
return;
}
catch ( Exception e )
{
errors.put( "delete", e.getMessage() );
}
}
}
}
pageContext.setAttribute( "errors", errors );
/**
if (generate) {
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
try {
if (errors.containsKey("ioerror") && keyStore == null) {
keyStore = SSLConfig.initializeKeyStore();
keyStore = sslConfig.initializeKeyStore();
}
if (errors.containsKey("ioerror") || !CertificateManager.isDSACertificate(keyStore, domain)) {
CertificateManager
.createDSACert(keyStore, SSLConfig.getKeyPassword(), domain + "_dsa", "cn=" + domain, "cn=" + domain, "*." + domain);
.createDSACert(keyStore, sslConfig.getKeyStorePassword(), domain + "_dsa", "cn=" + domain, "cn=" + domain, "*." + domain);
}
if (errors.containsKey("ioerror") || !CertificateManager.isRSACertificate(keyStore, domain)) {
CertificateManager
.createRSACert(keyStore, SSLConfig.getKeyPassword(), domain + "_rsa", "cn=" + domain, "cn=" + domain, "*." + domain);
.createRSACert(keyStore, sslConfig.getKeyStorePassword(), domain + "_rsa", "cn=" + domain, "cn=" + domain, "*." + domain);
}
// Save new certificates into the key store
SSLConfig.saveStores();
sslConfig.saveStores();
// Log the event
webManager.logEvent("generated SSL self-signed certs", null);
response.sendRedirect("security-keystore.jsp");
response.sendRedirect("security-keystore.jsp?connectivityType="+connectivityType);
return;
} catch (Exception e) {
e.printStackTrace();
errors.put("generate", e.getMessage());
}
}
if (delete) {
if (alias != null) {
try {
CertificateManager.deleteCertificate(keyStore, alias);
SSLConfig.saveStores();
// Log the event
webManager.logEvent("deleted SSL cert with alias " + alias, null);
response.sendRedirect("security-keystore.jsp");
return;
} catch (Exception e) {
e.printStackTrace();
errors.put("delete", e.getMessage());
}
}
}
if (importReply) {
String reply = ParamUtils.getParameter(request, "reply");
if (alias != null && reply != null && reply.trim().length() > 0) {
try {
CertificateManager.installReply(keyStore, s2sTrustStore,
SSLConfig.getKeyPassword(), alias, new ByteArrayInputStream(reply.getBytes()), true, true);
SSLConfig.saveStores();
CertificateManager.installReply( keyStore, s2sTrustStore,
sslConfig.getKeyStorePassword(), alias, new ByteArrayInputStream( reply.getBytes() ) );
sslConfig.saveStores();
// Log the event
webManager.logEvent("imported SSL certificate with alias " + alias, null);
response.sendRedirect("security-keystore.jsp");
webManager.logEvent( "imported SSL certificate with alias " + alias, null );
response.sendRedirect("security-keystore.jsp?connectivityType="+connectivityType);
return;
} catch (Exception e) {
e.printStackTrace();
......@@ -101,28 +142,28 @@
}
}
}
*/
final boolean restartNeeded = ( (AdminConsolePlugin) XMPPServer.getInstance().getPluginManager().getPlugin( "admin" ) ).isRestartNeeded();
pageContext.setAttribute( "restartNeeded", restartNeeded );
PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
%>
<html>
<head>
<head>
<title><fmt:message key="ssl.certificates.keystore.title"/></title>
<meta name="pageID" content="security-keystore"/>
</head>
<body>
<% if (((AdminConsolePlugin) pluginManager.getPlugin("admin")).isRestartNeeded()) { %>
<admin:infobox type="warning">
</head>
<body>
<c:if test="${restartNeeded}">
<admin:infobox type="warning">
<fmt:message key="ssl.certificates.keystore.restart_server">
<fmt:param value="<a href='server-restart.jsp?page=security-keystore.jsp'>"/>
<fmt:param value="<a href='server-restart.jsp?page=security-keystore.jsp&storePurpose=${storePurpose}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</admin:infobox>
<% } %>
</admin:infobox>
</c:if>
<% pageContext.setAttribute("errors", errors); %>
<c:forEach var="err" items="${errors}">
<c:forEach var="err" items="${errors}">
<admin:infobox type="error">
<c:choose>
<c:when test="${err.key eq 'ioerror'}">
......@@ -139,46 +180,42 @@
</c:otherwise>
</c:choose>
</admin:infobox>
</c:forEach>
<%
if (keyStore != null) {
if (keyStore.size() > 1 && !CertificateManager.isRSACertificate(keyStore, XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
%>
<admin:infobox type="warning"><fmt:message key="index.certificate-warning"/></admin:infobox>
<% } else if (keyStore.size() < 2) { %>
<admin:infobox type="warning">
</c:forEach>
<c:if test="${not validDSACert or not validRSACert}">
<admin:infobox type="warning">
<fmt:message key="ssl.certificates.keystore.no_installed">
<fmt:param value="<a href='security-keystore.jsp?generate=true'>"/>
<fmt:param value="<a href='security-keystore.jsp?generate=true&storePurpose=${storePurpose}'>"/>
<fmt:param value="</a>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp'>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp?storePurpose=${storePurpose}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</admin:infobox>
<% }} %>
</admin:infobox>
</c:if>
<c:if test="${param.addupdatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.added_updated"/></admin:infobox></c:if>
<c:if test="${param.generatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.generated"/></admin:infobox></c:if>
<c:if test="${param.deletesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.deleted"/></admin:infobox></c:if>
<c:if test="${param.issuerUpdated}"><admin:infobox type="success"><fmt:message key="ssl.certificates.keystore.issuer-updated"/></admin:infobox></c:if>
<c:if test="${param.importsuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.keystore.ca-reply-imported"/></admin:infobox></c:if>
<c:if test="${param.addupdatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.added_updated"/></admin:infobox></c:if>
<c:if test="${param.generatesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.generated"/></admin:infobox></c:if>
<c:if test="${param.deletesuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.deleted"/></admin:infobox></c:if>
<c:if test="${param.issuerUpdated}"><admin:infobox type="success"><fmt:message key="ssl.certificates.keystore.issuer-updated"/></admin:infobox></c:if>
<c:if test="${param.importsuccess}"><admin:infobox type="success"><fmt:message key="ssl.certificates.keystore.ca-reply-imported"/></admin:infobox></c:if>
<!-- BEGIN 'Installed Certificates' -->
<p>
<!-- BEGIN 'Installed Certificates' -->
<p>
<fmt:message key="ssl.certificates.keystore.intro"/>
</p>
</p>
<p>
<p>
<fmt:message key="ssl.certificates.general-usage"/>
</p>
</p>
<p>
<p>
<fmt:message key="ssl.certificates.keystore.info">
<fmt:param value="<a href='import-keystore-certificate.jsp'>"/>
<fmt:param value="<a href='import-keystore-certificate.jsp?storePurpose=${storePurpose}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</p>
</p>
<table class="jive-table" cellpadding="0" cellspacing="0" border="0" width="100%">
<table class="jive-table" cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr>
<th>
......@@ -200,46 +237,58 @@
</tr>
</thead>
<tbody>
<% int i = 0;
boolean offerUpdateIssuer = false;
Map<String, String> signingRequests = new LinkedHashMap<String, String>();
if (keyStore != null && keyStore.aliases().hasMoreElements()) {
for (Enumeration aliases = keyStore.aliases(); aliases.hasMoreElements(); ) {
i++;
String a = (String) aliases.nextElement();
X509Certificate c = (X509Certificate) keyStore.getCertificate(a);
StringBuffer identities = new StringBuffer();
for (String identity : CertificateManager.getServerIdentities(c)) {
identities.append(identity).append(", ");
}
if (identities.length() > 0) {
identities.setLength(identities.length() - 2);
}
// Self-signed certs are certs generated by Openfire whose IssueDN equals SubjectDN
boolean isSelfSigned = CertificateManager.isSelfSignedCertificate(keyStore, a);
// Signing Request pending = not self signed certs whose chain has only 1 cert (the same cert)
boolean isSigningPending = CertificateManager.isSigningRequestPending(keyStore, a);
offerUpdateIssuer = offerUpdateIssuer || isSelfSigned || isSigningPending;
if (isSigningPending) {
// Generate new signing request for certificate
PrivateKey privKey = (PrivateKey) keyStore.getKey(a, SSLConfig.getKeyPassword().toCharArray());
if (privKey != null) {
signingRequests.put(a, CertificateManager.createSigningRequest(c, privKey));
}
}
pageContext.setAttribute("identities", identities);
pageContext.setAttribute("alias", a);
pageContext.setAttribute("certificate", c);
<c:choose>
<c:when test="${empty storeConfig.allCertificates}">
<tr valign="top">
<td colspan="5"><em>(<fmt:message key="global.none"/>)</em></td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="certificateEntry" items="${storeConfig.allCertificates}">
<c:set var="certificate" value="${certificateEntry.value}"/>
<c:set var="alias" value="${certificateEntry.key}"/>
<c:set var="identities" value="${admin:serverIdentities(certificateEntry.value)}"/>
<%
// TODO restore this functionality
// int i = 0;
// boolean offerUpdateIssuer = false;
// Map<String, String> signingRequests = new LinkedHashMap<String, String>();
// if (keyStore != null && keyStore.aliases().hasMoreElements()) {
// for (Enumeration aliases = keyStore.aliases(); aliases.hasMoreElements(); ) {
// i++;
// String a = (String) aliases.nextElement();
// X509Certificate c = (X509Certificate) keyStore.getCertificate(a);
// StringBuffer identities = new StringBuffer();
// for (String identity : CertificateManager.getServerIdentities(c)) {
// identities.append(identity).append(", ");
// }
// if (identities.length() > 0) {
// identities.setLength(identities.length() - 2);
// }
// // Self-signed certs are certs generated by Openfire whose IssueDN equals SubjectDN
// boolean isSelfSigned = CertificateManager.isSelfSignedCertificate(keyStore, a);
// // Signing Request pending = not self signed certs whose chain has only 1 cert (the same cert)
// boolean isSigningPending = CertificateManager.isSigningRequestPending(keyStore, a);
//
// offerUpdateIssuer = offerUpdateIssuer || isSelfSigned || isSigningPending;
// if (isSigningPending) {
// // Generate new signing request for certificate
// PrivateKey privKey = (PrivateKey) keyStore.getKey(a, sslConfig.getKeyStorePassword().toCharArray());
// if (privKey != null) {
// signingRequests.put(a, CertificateManager.createSigningRequest(c, privKey));
// }
// }
// pageContext.setAttribute("identities", identities);
// pageContext.setAttribute("alias", a);
// pageContext.setAttribute("certificate", c);
%>
<tr valign="top">
<td>
<a href="security-certificate-details.jsp?type=server&alias=${alias}"
title="<fmt:message key='session.row.cliked'/>">
<c:out value="${identities}"/>
<a href="security-certificate-details.jsp?storePurpose=${storePurpose}&alias=${alias}" title="<fmt:message key='session.row.cliked'/>">
<c:forEach items="${identities}" var="currentItem" varStatus="stat">
<c:out value="${stat.first ? '' : ','} ${currentItem}"/>
</c:forEach>
</a>
<small>(<c:out value="${alias}"/>)</small>
</td>
<td>
<c:choose>
......@@ -259,110 +308,119 @@
</c:otherwise>
</c:choose>
</td>
<% if (isSelfSigned && !isSigningPending) { %>
<td width="1%"><img src="images/certificate_warning-16x16.png" width="16" height="16" border="0"
alt="<fmt:message key="ssl.certificates.keystore.self-signed.info"/>"
title="<fmt:message key="ssl.certificates.keystore.self-signed.info"/>"></td>
<td width="1%" nowrap>
<fmt:message key="ssl.certificates.self-signed"/>
</td>
<% } else if (isSigningPending) { %>
<td width="1%"><img src="images/certificate_warning-16x16.png" width="16" height="16" border="0"
alt="<fmt:message key="ssl.certificates.keystore.signing-pending.info"/>"
title="<fmt:message key="ssl.certificates.keystore.signing-pending.info"/>"></td>
<td width="1%" nowrap>
<fmt:message key="ssl.certificates.signing-pending"/>
</td>
<% } else { %>
<td width="1%"><img src="images/certificate_ok-16x16.png" width="16" height="16" border="0"
alt="<fmt:message key="ssl.certificates.keystore.ca-signed.info"/>"
title="<fmt:message key="ssl.certificates.keystore.ca-signed.info"/>"></td>
<td width="1%" nowrap>
<fmt:message key="ssl.certificates.ca-signed"/>
</td>
<% } %>
<%--<% if (isSelfSigned && !isSigningPending) { %>--%>
<%--<td width="1%"><img src="images/certificate_warning-16x16.png" width="16" height="16" border="0"--%>
<%--alt="<fmt:message key="ssl.certificates.keystore.self-signed.info"/>"--%>
<%--title="<fmt:message key="ssl.certificates.keystore.self-signed.info"/>"></td>--%>
<%--<td width="1%" nowrap>--%>
<%--<fmt:message key="ssl.certificates.self-signed"/>--%>
<%--</td>--%>
<%--<% } else if (isSigningPending) { %>--%>
<%--<td width="1%"><img src="images/certificate_warning-16x16.png" width="16" height="16" border="0"--%>
<%--alt="<fmt:message key="ssl.certificates.keystore.signing-pending.info"/>"--%>
<%--title="<fmt:message key="ssl.certificates.keystore.signing-pending.info"/>"></td>--%>
<%--<td width="1%" nowrap>--%>
<%--<fmt:message key="ssl.certificates.signing-pending"/>--%>
<%--</td>--%>
<%--<% } else { %>--%>
<%--<td width="1%"><img src="images/certificate_ok-16x16.png" width="16" height="16" border="0"--%>
<%--alt="<fmt:message key="ssl.certificates.keystore.ca-signed.info"/>"--%>
<%--title="<fmt:message key="ssl.certificates.keystore.ca-signed.info"/>"></td>--%>
<%--<td width="1%" nowrap>--%>
<%--<fmt:message key="ssl.certificates.ca-signed"/>--%>
<%--</td>--%>
<%--<% } %>--%>
<td width="1%" nowrap><%-- Restore functionality above --%></td>
<td width="1%" nowrap><%-- Restore functionality above --%></td>
<td width="2%">
<c:out value="${certificate.publicKey.algorithm}"/>
</td>
<td width="1" align="center">
<a href="security-keystore.jsp?alias=${alias}&delete=true"
<a href="security-keystore.jsp?alias=${alias}&storePurpose=${storePurpose}&delete=true"
title="<fmt:message key="global.click_delete"/>"
onclick="return confirm('<fmt:message key="ssl.certificates.confirm_delete"/>');"
><img src="images/delete-16x16.gif" width="16" height="16" border="0" alt=""></a>
</td>
</tr>
<% if (isSigningPending) { %>
<form action="security-keystore.jsp" method="post">
<input name="importReply" type="hidden" value="true">
<input name="alias" type="hidden" value="${alias}">
<tr id="pk<%=i%>">
<td colspan="5">
<span class="jive-description">
<fmt:message key="ssl.certificates.truststore.ca-reply"/>
</span>
<textarea name="reply" cols="40" rows="3" style="width:100%;font-size:8pt;" wrap="virtual"></textarea>
</td>
<td valign="bottom">
<input type="submit" name="install" value="<fmt:message key="global.save"/>">
</td>
</tr>
</form>
<% } } } else { %>
<tr valign="top">
<td colspan="5"><em>(<fmt:message key="global.none"/>)</em></td>
</tr>
<% } %>
</c:forEach>
</c:otherwise>
</c:choose>
<%-- FIXME restore functionality below. --%>
<%--<% if (isSigningPending) { %>--%>
<%--<form action="security-keystore.jsp" method="post">--%>
<%--<input type="hidden" name="importReply" value="true">--%>
<%--<input type="hidden" name="alias" value="${alias}">--%>
<%--<input type="hidden" name="connectivityType" value="${connecticvityType}">--%>
<%--<tr id="pk<%=i%>">--%>
<%--<td colspan="5">--%>
<%--<span class="jive-description">--%>
<%--<fmt:message key="ssl.certificates.truststore.ca-reply"/>--%>
<%--</span>--%>
<%--<textarea name="reply" cols="40" rows="3" style="width:100%;font-size:8pt;" wrap="virtual"></textarea>--%>
<%--</td>--%>
<%--<td valign="bottom">--%>
<%--<input type="submit" name="install" value="<fmt:message key="global.save"/>">--%>
<%--</td>--%>
<%--</tr>--%>
<%--</form>--%>
</tbody>
</table>
<!-- END 'Installed Certificates' -->
<!-- BEGIN 'Signing request' -->
<% if (offerUpdateIssuer || !signingRequests.isEmpty()) { %>
<br>
<div class="jive-contentBoxHeader">
<fmt:message key="ssl.signing-request.title"/>
</div>
<div class="jive-contentBox">
<% if (offerUpdateIssuer) { %>
<p>
<fmt:message key="ssl.signing-request.offer-issuer-information">
<fmt:param value="<a href='ssl-signing-request.jsp'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</p>
<% } %>
<% if (!signingRequests.isEmpty()) { %>
<p>
<fmt:message key="ssl.signing-request.requests_info"/>
</p>
<table cellpadding="3" cellspacing="2" border="0">
<thead>
<tr>
<th>
<fmt:message key="ssl.signing-request.alias"/>
</th>
<th>
<fmt:message key="ssl.signing-request.signing-request"/>
</th>
</tr>
</thead>
<tbody>
<% for (Map.Entry<String, String> entry : signingRequests.entrySet()) { %>
<tr>
<td valign="top">
<%= entry.getKey() %>
</td>
<td style="font-family: monospace;">
<%= StringUtils.escapeHTMLTags(entry.getValue()) %>
</td>
</tr>
<% } %>
</tbody>
</table>
<% } %>
</div>
<% } %>
<!-- END 'Signing request' -->
</body>
<!-- END 'Installed Certificates' -->
<!-- BEGIN 'Signing request' -->
<%-- FIXME restore functionality below. --%>
<%--<% if (offerUpdateIssuer || !signingRequests.isEmpty()) { %>--%>
<%--<br>--%>
<%--<div class="jive-contentBoxHeader">--%>
<%--<fmt:message key="ssl.signing-request.title"/>--%>
<%--</div>--%>
<%--<div class="jive-contentBox">--%>
<%--<% if (offerUpdateIssuer) { %>--%>
<%--<p>--%>
<%--<fmt:message key="ssl.signing-request.offer-issuer-information">--%>
<%--<fmt:param value="<a href='ssl-signing-request.jsp?connectivityType=${connectivityType}'>"/>--%>
<%--<fmt:param value="</a>"/>--%>
<%--</fmt:message>--%>
<%--</p>--%>
<%--<% } %>--%>
<%--<% if (!signingRequests.isEmpty()) { %>--%>
<%--<p>--%>
<%--<fmt:message key="ssl.signing-request.requests_info"/>--%>
<%--</p>--%>
<%--<table cellpadding="3" cellspacing="2" border="0">--%>
<%--<thead>--%>
<%--<tr>--%>
<%--<th>--%>
<%--<fmt:message key="ssl.signing-request.alias"/>--%>
<%--</th>--%>
<%--<th>--%>
<%--<fmt:message key="ssl.signing-request.signing-request"/>--%>
<%--</th>--%>
<%--</tr>--%>
<%--</thead>--%>
<%--<tbody>--%>
<%--<% for (Map.Entry<String, String> entry : signingRequests.entrySet()) { %>--%>
<%--<tr>--%>
<%--<td valign="top">--%>
<%--<%= entry.getKey() %>--%>
<%--</td>--%>
<%--<td style="font-family: monospace;">--%>
<%--<%= StringUtils.escapeHTMLTags(entry.getValue()) %>--%>
<%--</td>--%>
<%--</tr>--%>
<%--<% } %>--%>
<%--</tbody>--%>
<%--</table>--%>
<%--<% } %>--%>
<%--</div>--%>
<%--<% } %>--%>
<%--<!-- END 'Signing request' -->--%>
<%--<form action="/security-certificate-store-management.jsp">--%>
<%--<input type="submit" name="done" value="<fmt:message key="global.done" />">--%>
<%--</form>--%>
</body>
</html>
<%@ page errorPage="error.jsp"%>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig"%>
<%@ page import="org.jivesoftware.util.CertificateManager"%>
<%@ page import="org.jivesoftware.util.ParamUtils"%>
<%@ page import="java.security.KeyStore"%>
<%@ page import="java.security.cert.X509Certificate"%>
<%@ page import="java.util.Enumeration"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="java.util.Map"%>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.TrustStoreConfig" %>
<%@ page import="java.util.Set" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
......@@ -18,59 +16,101 @@
<jsp:useBean id="now" class="java.util.Date"/>
<% webManager.init(request, response, session, application, out );
boolean delete = ParamUtils.getBooleanParameter(request, "delete");
String type = ParamUtils.getParameter(request, "type");
String alias = ParamUtils.getParameter(request, "alias");
Map<String, Exception> errors = new HashMap<String, Exception>();
KeyStore store = null;
if (type == null) {
errors.put("type", new Exception("The store type has not been specified."));
} else {
try {
switch (type) {
case "s2s":
store = SSLConfig.gets2sTrustStore();
break;
case "c2s":
store = SSLConfig.getc2sTrustStore();
break;
default:
throw new Exception("Unknown store type: " + type);
final boolean delete = ParamUtils.getBooleanParameter( request, "delete" );
final String alias = ParamUtils.getParameter( request, "alias" );
final String storePurposeText = ParamUtils.getParameter(request, "storePurpose");
final Map<String, String> errors = new HashMap<>();
Purpose storePurpose = null;
TrustStoreConfig storeConfig = null;
try
{
storePurpose = Purpose.valueOf( storePurposeText );
if ( !storePurpose.isTrustStore() )
{
errors.put( "storePurpose", "should be a trust store (not an identity store)");
}
else
{
storeConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose );
if ( storeConfig == null )
{
errors.put( "storeConfig", "Unable to get an instance." );
}
}
} catch (Exception e) {
errors.put("type", e);
}
catch (RuntimeException ex)
{
errors.put( "storePurpose", ex.getMessage() );
}
if (delete) {
if (store != null && alias != null) {
try {
CertificateManager.deleteCertificate(store, alias);
SSLConfig.saveStores();
if ( errors.isEmpty() )
{
pageContext.setAttribute( "storePurpose", storePurpose );
pageContext.setAttribute( "storeConfig", storeConfig );
final Set<Purpose> sameStorePurposes = SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose );
pageContext.setAttribute( "sameStorePurposes", sameStorePurposes );
if ( delete )
{
if ( alias == null )
{
errors.put( "alias", "The alias has not been specified." );
}
else
{
try
{
storeConfig.delete( alias );
// Log the event
webManager.logEvent("deleted SSL cert from "+type+"-truststore with alias "+alias, null);
response.sendRedirect("security-truststore.jsp?type="+type+"&deletesuccess=true");
webManager.logEvent( "deleted SSL cert from " + storePurposeText + " with alias " + alias, null );
response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurposeText + "&deletesuccess=true" );
return;
}
catch (Exception e) {
errors.put("delete", e);
catch ( Exception e )
{
errors.put( "delete", e.getMessage() );
}
}
}
}
pageContext.setAttribute( "errors", errors );
%>
<html>
<head>
<title><fmt:message key="ssl.certificates.truststore.${param.type}-title"/></title>
<meta name="pageID" content="security-truststore-${param.type}"/>
</head>
<body>
<% pageContext.setAttribute("errors", errors); %>
<c:forEach var="err" items="${errors}">
<head>
<title><fmt:message key="certificate-management.purpose.${storePurpose}.title"/></title>
<meta name="pageID" content="security-truststore"/>
<style>
.info-header {
background-color: #eee;
font-size: 10pt;
}
.info-table {
margin-right: 12px;
}
.info-table .c1 {
text-align: right;
vertical-align: top;
color: #666;
font-weight: bold;
font-size: 9pt;
white-space: nowrap;
}
.info-table .c2 {
font-size: 9pt;
width: 90%;
}
</style>
</head>
<body>
<c:forEach var="err" items="${errors}">
<admin:infobox type="error">
<c:choose>
<c:when test="${err.key eq 'type'}">
......@@ -88,7 +128,7 @@
</c:otherwise>
</c:choose>
</admin:infobox>
</c:forEach>
</c:forEach>
<c:if test="${param.deletesuccess}">
<admin:infobox type="success"><fmt:message key="ssl.certificates.deleted"/></admin:infobox>
......@@ -97,25 +137,55 @@
<admin:infobox type="success"><fmt:message key="ssl.certificates.added_updated"/></admin:infobox>
</c:if>
<% if (type != null && store != null) { %>
<p>
<fmt:message key="ssl.certificates.truststore.${param.type}-intro"/>
</p>
<c:if test="${storePurpose != null}">
<p>
<fmt:message key="ssl.certificates.general-usage"/>
</p>
<p>
<fmt:message key="ssl.certificates.truststore.${param.type}-info">
<fmt:param value="<a href='ssl-settings.jsp'>"/>
<fmt:param value="</a>"/>
</fmt:message>
<fmt:message key="certificate-management.purpose.${storePurpose}.description"/>
</p>
<table border="0" width="100%">
<td valign="top" width="60%">
<table cellpadding="2" cellspacing="2" border="0" class="info-table">
<thead>
<tr><th colspan="2" class="info-header">Store Configuration</th></tr>
</thead>
<tbody>
<tr>
<td class="c1">File location:</td>
<td class="c2"><c:out value="${storeConfig.path}"/></td>
</tr>
<tr>
<td class="c1">Type:</td>
<td class="c2"><c:out value="${storeConfig.type}"/></td>
</tr>
<tr>
<td class="c1">Password:</td>
<td class="c2"><c:out value="${storeConfig.password}"/></td>
</tr>
</tbody>
</table>
</td>
<td valign="top" width="40%">
<c:if test="${not empty sameStorePurposes}">
<admin:infobox type="info">
This store is re-used for these additional purposes. Any changes to this store will also affect that functionality!
<ul style="margin-top: 1em;">
<c:forEach var="sameStorePurpose" items="${sameStorePurposes}">
<li><fmt:message key="certificate-management.purpose.${sameStorePurpose}.title"/></li>
</c:forEach>
</ul>
</admin:infobox>
</c:if>
</td>
</table>
<p>
<fmt:message key="ssl.certificates.truststore.link-to-import">
<fmt:param value="<a href='import-truststore-certificate.jsp?type=${param.type}'>"/>
<fmt:param value="<a href='import-truststore-certificate.jsp?storePurpose=${storePurpose}'>"/>
<fmt:param value="</a>"/>
</fmt:message>
</p>
<table class="jive-table" cellpadding="0" cellspacing="0" border="0" width="100%">
<thead>
<tr>
......@@ -133,15 +203,19 @@
</th>
</tr>
</thead>
<tbody>
<% if (store != null && store.aliases().hasMoreElements()) {
for (Enumeration aliases = store.aliases(); aliases.hasMoreElements();) {
String a = (String) aliases.nextElement();
X509Certificate certificate = (X509Certificate) store.getCertificate(a);
pageContext.setAttribute("alias", a);
pageContext.setAttribute("certificate", certificate);
%>
<c:choose>
<c:when test="${empty storeConfig.allCertificates}">
<tr valign="top">
<td colspan="5"><em>(<fmt:message key="global.none"/>)</em></td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="certificateEntry" items="${storeConfig.allCertificates}">
<c:set var="certificate" value="${certificateEntry.value}"/>
<c:set var="alias" value="${certificateEntry.key}"/>
<c:set var="organization" value=""/>
<c:set var="commonname" value=""/>
<c:forEach var="subjectPart" items="${admin:split(certificate.subjectX500Principal.name, '(?<!\\\\\\\\),')}">
......@@ -160,7 +234,7 @@
<tr valign="top">
<td>
<a href="security-certificate-details.jsp?type=${param.type}&alias=${alias}" title="<fmt:message key='session.row.cliked'/>">
<a href="security-certificate-details.jsp?storePurpose=${storePurpose}&alias=${alias}" title="<fmt:message key='session.row.cliked'/>">
<c:choose>
<c:when test="${empty fn:trim(organization)}">
<c:out value="${commonname}"/>
......@@ -194,24 +268,17 @@
<c:out value="${certificate.publicKey.algorithm}"/>
</td>
<td width="1" align="center">
<a href="security-truststore.jsp?alias=${alias}&type=${param.type}&delete=true"
<a href="security-truststore.jsp?storePurpose=${storePurpose}&alias=${alias}&delete=true"
title="<fmt:message key="global.click_delete"/>"
onclick="return confirm('<fmt:message key="ssl.certificates.confirm_delete"/>');"
><img src="images/delete-16x16.gif" width="16" height="16" border="0" alt=""></a>
</td>
</tr>
<%
}
} else {
%>
<tr valign="top">
<td colspan="5"><em>(<fmt:message key="global.none"/>)</em></td>
</tr>
<% } %>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<% } %>
</c:if>
</body>
</html>
......@@ -2,12 +2,13 @@
<%@ page import="org.jivesoftware.util.ParamUtils" %>
<%@ page import="org.jivesoftware.util.StringUtils" %>
<%@ page import="org.jivesoftware.openfire.XMPPServer" %>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig" %>
<%@ page import="java.security.KeyStore" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.net.SSLConfig" %>
<%@ page import="org.jivesoftware.openfire.keystore.IdentityStoreConfig" %>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
......@@ -17,90 +18,102 @@
<% webManager.init(request, response, session, application, out ); %>
<% // Get parameters:
boolean save = ParamUtils.getParameter(request, "save") != null;
String name = ParamUtils.getParameter(request, "name");
String organizationalUnit = ParamUtils.getParameter(request, "ou");
String organization = ParamUtils.getParameter(request, "o");
String city = ParamUtils.getParameter(request, "city");
String state = ParamUtils.getParameter(request, "state");
String countryCode = ParamUtils.getParameter(request, "country");
final boolean save = ParamUtils.getParameter(request, "save") != null;
final String name = ParamUtils.getParameter(request, "name");
final String organizationalUnit = ParamUtils.getParameter(request, "ou");
final String organization = ParamUtils.getParameter(request, "o");
final String city = ParamUtils.getParameter(request, "city");
final String state = ParamUtils.getParameter(request, "state");
final String countryCode = ParamUtils.getParameter(request, "country");
final String storePurposeText = ParamUtils.getParameter(request, "storePurpose");
Map<String, Object> errors = new HashMap<String, Object>();
final Map<String, String> errors = new HashMap<String, String>();
if (save) {
KeyStore keyStore;
try {
keyStore = SSLConfig.getKeyStore();
}
catch (Exception e) {
keyStore = SSLConfig.initializeKeyStore();
Purpose storePurpose;
try
{
storePurpose = Purpose.valueOf( storePurposeText );
} catch (RuntimeException ex) {
errors.put( "storePurpose", ex.getMessage() );
storePurpose = null;
}
// Verify that fields were completed
if (name == null) {
errors.put("name", "");
}
if (organizationalUnit == null) {
errors.put("organizationalUnit", "");
}
if (organization == null) {
errors.put("organization", "");
}
if (city == null) {
errors.put("city", "");
}
if (state == null) {
errors.put("state", "");
}
if (countryCode == null) {
errors.put("countryCode", "");
}
if (errors.size() == 0) {
try {
// Regenerate self-sign certs whose subjectDN matches the issuerDN and set the new issuerDN
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
StringBuilder issuerDN = new StringBuilder();
issuerDN.append("CN=").append(name);
issuerDN.append(", OU=").append(organizationalUnit);
issuerDN.append(", O=").append(organization);
issuerDN.append(", L=").append(city);
issuerDN.append(", ST=").append(state);
issuerDN.append(", C=").append(countryCode);
StringBuilder subjectDN = new StringBuilder();
subjectDN.append("CN=").append(domain);
subjectDN.append(", OU=").append(organizationalUnit);
subjectDN.append(", O=").append(organization);
subjectDN.append(", L=").append(city);
subjectDN.append(", ST=").append(state);
subjectDN.append(", C=").append(countryCode);
// Update certs with new issuerDN information
for (Enumeration<String> certAliases = keyStore.aliases(); certAliases.hasMoreElements();) {
String alias = certAliases.nextElement();
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
// Update only Self-signed certs
if (CertificateManager.isSelfSignedCertificate(keyStore, alias)) {
if (CertificateManager.isDSACertificate(certificate)) {
CertificateManager.createDSACert(keyStore, SSLConfig.getKeyPassword(), alias,
issuerDN.toString(), subjectDN.toString(), "*." + domain);
} else {
CertificateManager.createRSACert(keyStore, SSLConfig.getKeyPassword(), alias,
issuerDN.toString(), subjectDN.toString(), "*." + domain);
}
}
}
// Save keystore
SSLConfig.saveStores();
// Log the event
webManager.logEvent("generated SSL signing request", null);
response.sendRedirect("security-keystore.jsp");
return;
}
catch (Exception e) {
e.printStackTrace();
errors.put("general", "");
}
}
if (! storePurpose.isIdentityStore() ) {
errors.put( "storePurpose", "shoud be an identity store (not a trust store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose );
// if (save) {
//
// // Verify that fields were completed
// if (name == null) {
// errors.put("name", "");
// }
// if (organizationalUnit == null) {
// errors.put("organizationalUnit", "");
// }
// if (organization == null) {
// errors.put("organization", "");
// }
// if (city == null) {
// errors.put("city", "");
// }
// if (state == null) {
// errors.put("state", "");
// }
// if (countryCode == null) {
// errors.put("countryCode", "");
// }
// if (errors.size() == 0) {
// try {
// final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose );
//
// identityStoreConfig.ensureSelfSignedDomainCertificates( name, organizationalUnit, organization, city, state, countryCode, "rsa", "dsa" );
// // Regenerate self-sign certs whose subjectDN matches the issuerDN and set the new issuerDN
// String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
// StringBuilder issuerDN = new StringBuilder();
// issuerDN.append("CN=").append(name);
// issuerDN.append(", OU=").append(organizationalUnit);
// issuerDN.append(", O=").append(organization);
// issuerDN.append(", L=").append(city);
// issuerDN.append(", ST=").append(state);
// issuerDN.append(", C=").append(countryCode);
// StringBuilder subjectDN = new StringBuilder();
// subjectDN.append("CN=").append(domain);
// subjectDN.append(", OU=").append(organizationalUnit);
// subjectDN.append(", O=").append(organization);
// subjectDN.append(", L=").append(city);
// subjectDN.append(", ST=").append(state);
// subjectDN.append(", C=").append(countryCode);
// // Update certs with new issuerDN information
// for (Enumeration<String> certAliases = keyStore.aliases(); certAliases.hasMoreElements();) {
// String alias = certAliases.nextElement();
// X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
// // Update only Self-signed certs
// if (CertificateManager.isSelfSignedCertificate(keyStore, alias)) {
// if (CertificateManager.isDSACertificate(certificate)) {
// CertificateManager.createDSACert(keyStore, sslConfig.getKeyStorePassword(), alias,
// issuerDN.toString(), subjectDN.toString(), "*." + domain);
// } else {
// CertificateManager.createRSACert(keyStore, sslConfig.getKeyStorePassword(), alias,
// issuerDN.toString(), subjectDN.toString(), "*." + domain);
// }
// }
// }
// // Save keystore
// sslConfig.saveStores();
// // Log the event
// webManager.logEvent("generated SSL signing request", null);
// response.sendRedirect("security-keystore.jsp?connectivityType="+connectivityType);
// return;
// }
// catch (Exception e) {
// e.printStackTrace();
// errors.put("general", "");
// }
// }
// }
%>
<html>
......@@ -108,7 +121,7 @@
<title>
<fmt:message key="ssl.signing-request.title"/>
</title>
<meta name="pageID" content="security-keystore"/>
<meta name="pageID" content="security-keystore-${connectivityType}"/>
</head>
<body>
......@@ -147,6 +160,7 @@
<!-- BEGIN 'Issuer information form' -->
<form action="ssl-signing-request.jsp" method="post">
<input type="hidden" name="save" value="true">
<input type="hidden" name="connectivityType" value="${connectivityType}">
<div class="jive-contentBoxHeader">
<fmt:message key="ssl.signing-request.issuer_information"/>
</div>
......
......@@ -174,7 +174,6 @@
<tr>
<td width="1%" nowrap>
<label for="nicknametf"><fmt:message key="user.roster.nickname" />:</label>
</td>
<td width="99%">
<input type="text" name="nickname" size="30" maxlength="255" value="<%= ((nickname!=null) ? StringUtils.escapeForXML(nickname) : "") %>"
id="nicknametf">
......
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