Commit bcacd508 authored by Dele Olajide's avatar Dele Olajide

Merge pull request #47 from surevine/s2s-fixes

S2s fixes. Thanks for doing this. I am experimenting with websockets for Openfire s2s and I need these fixes.
parents 873a5f98 5eb60080
...@@ -13,6 +13,9 @@ clean: ...@@ -13,6 +13,9 @@ clean:
dpkg: dpkg:
cd build && ant installer.debian cd build && ant installer.debian
plugins:
cd build && ant plugins
eclipse: .settings .classpath .project eclipse: .settings .classpath .project
.settings: .settings:
......
...@@ -1773,9 +1773,13 @@ server.session.details.info=Below are details about the sessions with the remote ...@@ -1773,9 +1773,13 @@ server.session.details.info=Below are details about the sessions with the remote
server.session.details.hostname=Remote server IP / Hostname: server.session.details.hostname=Remote server IP / Hostname:
server.session.details.incoming_session=Incoming Session Details server.session.details.incoming_session=Incoming Session Details
server.session.details.streamid=Stream ID server.session.details.streamid=Stream ID
server.session.details.incoming_statistics=Statistics (Packets Received) server.session.details.authentication=Authentication
server.session.details.dialback=Dialback
server.session.details.tlsauth=Certificate
server.session.details.cipher=Cipher Suite
server.session.details.incoming_statistics=Packets RX
server.session.details.outgoing_session=Outgoing Session Details server.session.details.outgoing_session=Outgoing Session Details
server.session.details.outgoing_statistics=Statistics (Packets Sent) server.session.details.outgoing_statistics=Packets TX
# External Component Session summary Page # External Component Session summary Page
......
...@@ -528,29 +528,14 @@ public class SASLAuthentication { ...@@ -528,29 +528,14 @@ public class SASLAuthentication {
if (!verify) { if (!verify) {
authenticationSuccessful(session, hostname, null); authenticationSuccessful(session, hostname, null);
return Status.authenticated; return Status.authenticated;
} } else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname)) {
// Check that hostname matches the one provided in a certificate
Connection connection = session.getConnection();
try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(connection.getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
if (trusted != null) {
for (String identity : CertificateManager.getPeerIdentities(trusted)) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (hostname.endsWith(identity.replace("*.", "."))
|| hostname.equals(identity.replace("*.", ""))))
|| hostname.equals(identity)) {
authenticationSuccessful(session, hostname, null); authenticationSuccessful(session, hostname, null);
return Status.authenticated; LocalIncomingServerSession s = (LocalIncomingServerSession)session;
} if (s != null) {
s.tlsAuth();
} }
return Status.authenticated;
} }
} catch(IOException e) {
/// Keystore problem.
}
} }
else if (session instanceof LocalClientSession) { else if (session instanceof LocalClientSession) {
// Client EXTERNALL login // Client EXTERNALL login
...@@ -618,6 +603,28 @@ public class SASLAuthentication { ...@@ -618,6 +603,28 @@ public class SASLAuthentication {
return Status.failed; return Status.failed;
} }
public static boolean verifyCertificates(Certificate[] chain, String hostname) {
try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(chain, SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
if (trusted != null) {
for (String identity : CertificateManager.getPeerIdentities(trusted)) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (hostname.endsWith(identity.replace("*.", "."))
|| hostname.equals(identity.replace("*.", ""))))
|| hostname.equals(identity)) {
return true;
}
}
}
} catch(IOException e) {
Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage());
}
return false;
}
private static Status doSharedSecretAuthentication(LocalSession session, Element doc) private static Status doSharedSecretAuthentication(LocalSession session, Element doc)
throws UnsupportedEncodingException throws UnsupportedEncodingException
{ {
...@@ -686,6 +693,7 @@ public class SASLAuthentication { ...@@ -686,6 +693,7 @@ public class SASLAuthentication {
// Add the validated domain as a valid domain. The remote server can // Add the validated domain as a valid domain. The remote server can
// now send packets from this address // now send packets from this address
((LocalIncomingServerSession) session).addValidatedDomain(hostname); ((LocalIncomingServerSession) session).addValidatedDomain(hostname);
Log.info("Inbound Server {} authenticated (via TLS)", username);
} }
} }
......
...@@ -98,116 +98,7 @@ public class ServerTrustManager implements X509TrustManager { ...@@ -98,116 +98,7 @@ public class ServerTrustManager implements X509TrustManager {
*/ */
public void checkServerTrusted(X509Certificate[] x509Certificates, String string) public void checkServerTrusted(X509Certificate[] x509Certificates, String string)
throws CertificateException { throws CertificateException {
// Do nothing here. As before, the certificate will be validated when the remote server authenticates.
// Flag that indicates if certificates of the remote server should be validated. Disabling
// certificate validation is not recommended for production environments.
boolean verify = JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true);
if (verify) {
int nSize = x509Certificates.length;
if (Log.isDebugEnabled()) {
Log.debug("Certificate chain:");
for (int i=1; i<= nSize; i++) {
Log.debug("Certificate " + i + ": " + x509Certificates[i-1].toString());
}
}
List<String> peerIdentities = CertificateManager.getPeerIdentities(x509Certificates[0]);
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_CHAIN_VERIFY, true)) {
Log.debug("Verifying certificate chain...");
// Working down the chain, for every certificate in the chain,
// verify that the subject of the certificate is the issuer of the
// next certificate in the chain.
Principal principalLast = null;
for (int i = nSize -1; i >= 0 ; i--) {
X509Certificate x509certificate = x509Certificates[i];
Principal principalIssuer = x509certificate.getIssuerDN();
Principal principalSubject = x509certificate.getSubjectDN();
Log.debug("Certificate " + (i+1) + " issuer: '" + principalIssuer + "' subject: '" + principalSubject + "'");
if (principalLast != null) {
if (principalIssuer.equals(principalLast)) {
try {
PublicKey publickey =
x509Certificates[i + 1].getPublicKey();
x509Certificates[i].verify(publickey);
}
catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException(
"signature verification failed of " + peerIdentities, generalsecurityexception);
}
}
else {
throw new CertificateException(
"subject/issuer verification failed of " + peerIdentities + ". In certificate "
+ (i+1) + " of the chain, I expected the issuer to be '" + principalLast
+"' but was '"+principalIssuer+"'.");
}
}
principalLast = principalSubject;
}
}
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_ROOT_VERIFY, true)) {
Log.debug("Verifying certificate chain root certificate...");
// Verify that the the last certificate in the chain was issued
// by a third-party that the client trusts.
boolean trusted = false;
try {
trusted = trustStore.getCertificateAlias(x509Certificates[nSize - 1]) != null;
// Keep track if the other peer presented a self-signed certificate
connection.setUsingSelfSignedCertificate(!trusted && nSize == 1);
if (!trusted && nSize == 1 && JiveGlobals
.getBooleanProperty(ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS, false))
{
Log.warn("Accepting self-signed certificate of remote server: " +
peerIdentities);
trusted = true;
}
}
catch (KeyStoreException e) {
Log.error(e.getMessage(), e);
}
if (!trusted) {
throw new CertificateException("Root certificate (subject: "+x509Certificates[nSize - 1].getSubjectX500Principal()
+ ") of " + peerIdentities + " not trusted.");
}
}
// Verify that the server either matches an identity from the chain, or
// a wildcard.
Boolean found = false;
for (String identity : peerIdentities) {
// Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.")
&& (server.endsWith(identity.replace("*.", "."))
|| server.equals(identity.replace("*.", ""))))
|| server.equals(identity)) {
found = true;
break;
}
}
if (!found) {
throw new CertificateException("target verification failed of " + peerIdentities);
}
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY_VALIDITY, true)) {
Log.debug("Verifying certificate chain validity (by date)...");
// For every certificate in the chain, verify that the certificate
// is valid at the current time.
Date date = new Date();
for (X509Certificate x509Certificate : x509Certificates) {
try {
x509Certificate.checkValidity(date);
} catch (GeneralSecurityException generalsecurityexception) {
throw new CertificateException("invalid date of " + peerIdentities);
}
}
}
}
} }
public X509Certificate[] getAcceptedIssuers() { public X509Certificate[] getAcceptedIssuers() {
......
...@@ -43,7 +43,7 @@ import java.util.Collection; ...@@ -43,7 +43,7 @@ import java.util.Collection;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public interface IncomingServerSession extends Session { public interface IncomingServerSession extends ServerSession {
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Returns a collection with all the domains, subdomains and virtual hosts that where
......
...@@ -68,7 +68,7 @@ import org.xmpp.packet.Packet; ...@@ -68,7 +68,7 @@ import org.xmpp.packet.Packet;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class LocalIncomingServerSession extends LocalSession implements IncomingServerSession { public class LocalIncomingServerSession extends LocalServerSession implements IncomingServerSession {
private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class); private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class);
...@@ -368,9 +368,13 @@ public class LocalIncomingServerSession extends LocalSession implements Incoming ...@@ -368,9 +368,13 @@ public class LocalIncomingServerSession extends LocalSession implements Incoming
} }
if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) { if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) {
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>"); sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
} }
return sb.toString(); return sb.toString();
} }
public void tlsAuth() {
usingServerDialback = false;
}
} }
/**
*
*/
package org.jivesoftware.openfire.session;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.xmpp.packet.Packet;
/**
* @author dwd
*
*/
public class LocalServerSession extends LocalSession implements ServerSession {
protected boolean usingServerDialback = true;
protected boolean outboundAllowed = false;
protected boolean inboundAllowed = false;
public LocalServerSession(String serverName, Connection connection,
StreamID streamID) {
super(serverName, connection, streamID);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#canProcess(org.xmpp.packet.Packet)
*/
@Override
boolean canProcess(Packet packet) {
// TODO Auto-generated method stub
return false;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#deliver(org.xmpp.packet.Packet)
*/
@Override
void deliver(Packet packet) throws UnauthorizedException {
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.session.LocalSession#getAvailableStreamFeatures()
*/
@Override
public String getAvailableStreamFeatures() {
// TODO Auto-generated method stub
return null;
}
public boolean isUsingServerDialback() {
return usingServerDialback;
}
}
...@@ -21,6 +21,8 @@ import java.util.Date; ...@@ -21,6 +21,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.net.ssl.SSLSession;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.StreamID;
...@@ -28,6 +30,8 @@ import org.jivesoftware.openfire.XMPPServer; ...@@ -28,6 +30,8 @@ import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.interceptor.InterceptorManager; import org.jivesoftware.openfire.interceptor.InterceptorManager;
import org.jivesoftware.openfire.interceptor.PacketRejectedException; import org.jivesoftware.openfire.interceptor.PacketRejectedException;
import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.net.TLSStreamHandler;
import org.jivesoftware.openfire.spi.RoutingTableImpl; import org.jivesoftware.openfire.spi.RoutingTableImpl;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -387,4 +391,22 @@ public abstract class LocalSession implements Session { ...@@ -387,4 +391,22 @@ public abstract class LocalSession implements Session {
public boolean isUsingSelfSignedCertificate() { public boolean isUsingSelfSignedCertificate() {
return conn.isUsingSelfSignedCertificate(); return conn.isUsingSelfSignedCertificate();
} }
/**
* Returns a String representing the Cipher Suite Name, or "NONE".
* @return String
*/
public String getCipherSuiteName() {
SocketConnection s = (SocketConnection)getConnection();
if (s != null) {
TLSStreamHandler t = s.getTLSStreamHandler();
if (t != null) {
SSLSession ssl = t.getSSLSession();
if (ssl != null) {
return ssl.getCipherSuite();
}
}
}
return "NONE";
}
} }
...@@ -36,8 +36,7 @@ import java.util.Collection; ...@@ -36,8 +36,7 @@ import java.util.Collection;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public interface OutgoingServerSession extends Session { public interface OutgoingServerSession extends ServerSession {
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Returns a collection with all the domains, subdomains and virtual hosts that where
* authenticated. The remote server will accept packets sent from any of these domains, * authenticated. The remote server will accept packets sent from any of these domains,
...@@ -84,11 +83,4 @@ public interface OutgoingServerSession extends Session { ...@@ -84,11 +83,4 @@ public interface OutgoingServerSession extends Session {
* @return True if the subdomain was authenticated by the remote server. * @return True if the subdomain was authenticated by the remote server.
*/ */
boolean authenticateSubdomain(String domain, String hostname); boolean authenticateSubdomain(String domain, String hostname);
/**
* Returns true if this outgoing session was established using server dialback.
*
* @return true if this outgoing session was established using server dialback.
*/
boolean isUsingServerDialback();
} }
package org.jivesoftware.openfire.session;
public interface ServerSession extends Session {
/**
* Returns true if this outgoing session was established using server dialback.
*
* @return true if this outgoing session was established using server dialback.
*/
boolean isUsingServerDialback();
}
...@@ -195,4 +195,11 @@ public interface Session extends RoutableChannelHandler { ...@@ -195,4 +195,11 @@ public interface Session extends RoutableChannelHandler {
* @return true if the socket remains valid, false otherwise. * @return true if the socket remains valid, false otherwise.
*/ */
public boolean validate(); public boolean validate();
/**
* Returns the TLS cipher suite name, if any.
* Always returns a valid string, though the string may be "NONE"
* @return cipher suite name.
*/
public String getCipherSuiteName();
} }
\ No newline at end of file
...@@ -123,9 +123,12 @@ ...@@ -123,9 +123,12 @@
<table cellpadding="3" cellspacing="1" border="0" width="100%"> <table cellpadding="3" cellspacing="1" border="0" width="100%">
<tr> <tr>
<th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th> <th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th>
<th width="10%"><fmt:message key="server.session.details.authentication"/></th>
<th width="10%"><fmt:message key="server.session.details.cipher"/></th>
<th width="20%"><fmt:message key="server.session.label.creation" /></th> <th width="20%"><fmt:message key="server.session.label.creation" /></th>
<th width="20%"><fmt:message key="server.session.label.last_active" /></th> <th width="20%"><fmt:message key="server.session.label.last_active" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th> <th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th>
</tr> </tr>
<tr> <tr>
<% if (inSession.isSecure()) { %> <% if (inSession.isSecure()) { %>
...@@ -151,9 +154,12 @@ ...@@ -151,9 +154,12 @@
boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR); boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR);
%> %>
<td><%= inSession.getStreamID()%></td> <td><%= inSession.getStreamID()%></td>
<td><% if (inSession.isUsingServerDialback()) { %><fmt:message key="server.session.details.dialback"/><% } else { %><fmt:message key="server.session.details.tlsauth"/><% } %></td>
<td><%= inSession.getCipherSuiteName() %></td>
<td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td> <td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td>
<td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td> <td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td>
<td align="center"><%= numFormatter.format(inSession.getNumClientPackets()) %></td> <td align="center"><%= numFormatter.format(inSession.getNumClientPackets()) %></td>
<td align="center"><%= numFormatter.format(inSession.getNumServerPackets()) %></td>
</tr> </tr>
</table> </table>
</div> </div>
...@@ -169,8 +175,11 @@ ...@@ -169,8 +175,11 @@
<table cellpadding="3" cellspacing="1" border="0" width="100%"> <table cellpadding="3" cellspacing="1" border="0" width="100%">
<tr> <tr>
<th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th> <th width="35%" colspan="2"><fmt:message key="server.session.details.streamid" /></th>
<th width="10%"><fmt:message key="server.session.details.authentication"/></th>
<th width="10%"><fmt:message key="server.session.details.cipher"/></th>
<th width="20%"><fmt:message key="server.session.label.creation" /></th> <th width="20%"><fmt:message key="server.session.label.creation" /></th>
<th width="20%"><fmt:message key="server.session.label.last_active" /></th> <th width="20%"><fmt:message key="server.session.label.last_active" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.incoming_statistics" /></th>
<th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th> <th width="25%" nowrap><fmt:message key="server.session.details.outgoing_statistics" /></th>
</tr> </tr>
<tr> <tr>
...@@ -197,8 +206,11 @@ ...@@ -197,8 +206,11 @@
boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR); boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR);
%> %>
<td><%= outSession.getStreamID()%></td> <td><%= outSession.getStreamID()%></td>
<td><% if (outSession.isUsingServerDialback()) { %><fmt:message key="server.session.details.dialback"/><% } else { %><fmt:message key="server.session.details.tlsauth"/><% } %></td>
<td><%= outSession.getCipherSuiteName() %></td>
<td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td> <td align="center"><%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %></td>
<td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td> <td align="center"><%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %></td>
<td align="center"><%= numFormatter.format(outSession.getNumClientPackets()) %></td>
<td align="center"><%= numFormatter.format(outSession.getNumServerPackets()) %></td> <td align="center"><%= numFormatter.format(outSession.getNumServerPackets()) %></td>
</tr> </tr>
</table> </table>
......
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