Commit dc21027b authored by Dave Cridland's avatar Dave Cridland

More S2S fixes

Kim 'Zash' Alvefur commented that an empty authzid in EXTERNAL wasn't working.

This patch adds this handling, and also changes authorization checks from a
domain.contains() to a domain.equals().
parent fad60d88
...@@ -391,12 +391,12 @@ public class SessionManager extends BasicModule implements ClusterEventListener ...@@ -391,12 +391,12 @@ public class SessionManager extends BasicModule implements ClusterEventListener
* @return the newly created {@link IncomingServerSession}. * @return the newly created {@link IncomingServerSession}.
* @throws UnauthorizedException if the local server has not been initialized yet. * @throws UnauthorizedException if the local server has not been initialized yet.
*/ */
public LocalIncomingServerSession createIncomingServerSession(Connection conn, StreamID id) public LocalIncomingServerSession createIncomingServerSession(Connection conn, StreamID id, String fromDomain)
throws UnauthorizedException { throws UnauthorizedException {
if (serverName == null) { if (serverName == null) {
throw new UnauthorizedException("Server not initialized"); throw new UnauthorizedException("Server not initialized");
} }
LocalIncomingServerSession session = new LocalIncomingServerSession(serverName, conn, id); LocalIncomingServerSession session = new LocalIncomingServerSession(serverName, conn, id, fromDomain);
conn.init(session); conn.init(session);
// Register to receive close notification on this session so we can // Register to receive close notification on this session so we can
// remove its route from the sessions set // remove its route from the sessions set
......
...@@ -193,13 +193,17 @@ public class SASLAuthentication { ...@@ -193,13 +193,17 @@ public class SASLAuthentication {
Element mechs = DocumentHelper.createElement(new QName("mechanisms", Element mechs = DocumentHelper.createElement(new QName("mechanisms",
new Namespace("", "urn:ietf:params:xml:ns:xmpp-sasl"))); new Namespace("", "urn:ietf:params:xml:ns:xmpp-sasl")));
if (session instanceof IncomingServerSession) { if (session instanceof LocalIncomingServerSession) {
// Server connections don't follow the same rules as clients // Server connections don't follow the same rules as clients
if (session.isSecure()) { if (session.isSecure()) {
boolean haveTrustedCertificate = false; boolean haveTrustedCertificate = false;
try { try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(((LocalSession)session).getConnection().getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore()); LocalIncomingServerSession svr = (LocalIncomingServerSession)session;
X509Certificate trusted = CertificateManager.getEndEntityCertificate(svr.getConnection().getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
haveTrustedCertificate = trusted != null; haveTrustedCertificate = trusted != null;
if (trusted != null && svr.getDefaultIdentity() != null) {
haveTrustedCertificate = verifyCertificate(trusted, svr.getDefaultIdentity());
}
} catch (IOException ex) { } catch (IOException ex) {
Log.warn("Exception occurred while trying to determine whether remote certificate is trusted. Treating as untrusted.", ex); Log.warn("Exception occurred while trying to determine whether remote certificate is trusted. Treating as untrusted.", ex);
} }
...@@ -520,6 +524,28 @@ public class SASLAuthentication { ...@@ -520,6 +524,28 @@ public class SASLAuthentication {
} }
hostname = new String(StringUtils.decodeBase64(hostname), CHARSET); hostname = new String(StringUtils.decodeBase64(hostname), CHARSET);
if (hostname.length() == 0) {
hostname = null;
}
try {
LocalIncomingServerSession svr = (LocalIncomingServerSession)session;
String defHostname = svr.getDefaultIdentity();
if (hostname == null) {
hostname = defHostname;
} else if (!hostname.equals(defHostname)) {
// Mismatch; really odd.
Log.info("SASLAuthentication rejected from='{}' and authzid='{}'", hostname, defHostname);
authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed;
}
} catch(Exception e) {
// Erm. Nothing?
}
if (hostname == null) {
Log.info("No authzid supplied for anonymous session.");
authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed;
}
// Check if certificate validation is disabled for s2s // Check if certificate validation is disabled for s2s
// Flag that indicates if certificates of the remote server should be validated. // Flag that indicates if certificates of the remote server should be validated.
// Disabling certificate validation is not recommended for production environments. // Disabling certificate validation is not recommended for production environments.
...@@ -552,9 +578,18 @@ public class SASLAuthentication { ...@@ -552,9 +578,18 @@ public class SASLAuthentication {
return Status.failed; return Status.failed;
} }
for (Certificate certificate : connection.getPeerCertificates()) { X509Certificate trusted;
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)certificate)); try {
trusted = CertificateManager.getEndEntityCertificate(connection.getPeerCertificates(), SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
} catch (IOException e) {
trusted = null;
} }
if (trusted == null) {
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but EE cert untrusted.");
authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed;
}
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)trusted));
if(principals.size() == 1) { if(principals.size() == 1) {
principal = principals.get(0); principal = principals.get(0);
...@@ -602,22 +637,27 @@ public class SASLAuthentication { ...@@ -602,22 +637,27 @@ public class SASLAuthentication {
authenticationFailed(session, Failure.NOT_AUTHORIZED); authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed; return Status.failed;
} }
public static boolean verifyCertificate(X509Certificate trustedCert, String hostname) {
for (String identity : CertificateManager.getPeerIdentities(trustedCert)) {
// 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;
}
}
return false;
}
public static boolean verifyCertificates(Certificate[] chain, String hostname) { public static boolean verifyCertificates(Certificate[] chain, String hostname) {
try { try {
X509Certificate trusted = CertificateManager.getEndEntityCertificate(chain, SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore()); X509Certificate trusted = CertificateManager.getEndEntityCertificate(chain, SSLConfig.getKeyStore(), SSLConfig.gets2sTrustStore());
if (trusted != null) { if (trusted != null) {
for (String identity : CertificateManager.getPeerIdentities(trusted)) { return verifyCertificate(trusted, hostname);
// 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) { } catch(IOException e) {
Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage()); Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage());
......
...@@ -431,7 +431,7 @@ public class ServerDialback { ...@@ -431,7 +431,7 @@ public class ServerDialback {
Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was successful.", hostname, recipient); Log.debug("ServerDialback: RS - Validation of remote domain for incoming session from {} to {} was successful.", hostname, recipient);
// Create a server Session for the remote server // Create a server Session for the remote server
LocalIncomingServerSession session = sessionManager. LocalIncomingServerSession session = sessionManager.
createIncomingServerSession(connection, streamID); createIncomingServerSession(connection, streamID, hostname);
// Add the validated domain as a valid domain // Add the validated domain as a valid domain
session.addValidatedDomain(hostname); session.addValidatedDomain(hostname);
// Set the domain or subdomain of the local server used when // Set the domain or subdomain of the local server used when
......
...@@ -85,6 +85,11 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -85,6 +85,11 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
* many connections from the same remote server to the same local domain. * many connections from the same remote server to the same local domain.
*/ */
private String localDomain = null; private String localDomain = null;
/**
* Default domain, as supplied in stream header typically.
*/
private String fromDomain = null;
/** /**
* Creates a new session that will receive packets. The new session will be authenticated * Creates a new session that will receive packets. The new session will be authenticated
...@@ -105,6 +110,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -105,6 +110,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
XmlPullParser xpp = reader.getXPPParser(); XmlPullParser xpp = reader.getXPPParser();
String version = xpp.getAttributeValue("", "version"); String version = xpp.getAttributeValue("", "version");
String fromDomain = xpp.getAttributeValue("", "from");
int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0}; int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0};
try { try {
...@@ -112,7 +118,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -112,7 +118,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
StreamID streamID = SessionManager.getInstance().nextStreamID(); StreamID streamID = SessionManager.getInstance().nextStreamID();
// Create a server Session for the remote server // Create a server Session for the remote server
LocalIncomingServerSession session = LocalIncomingServerSession session =
SessionManager.getInstance().createIncomingServerSession(connection, streamID); SessionManager.getInstance().createIncomingServerSession(connection, streamID, fromDomain);
// Send the stream header // Send the stream header
StringBuilder openingStream = new StringBuilder(); StringBuilder openingStream = new StringBuilder();
...@@ -121,6 +127,9 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -121,6 +127,9 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\""); openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" from=\"").append(serverName).append("\""); openingStream.append(" from=\"").append(serverName).append("\"");
if (fromDomain != null) {
openingStream.append(" to=\"").append(fromDomain).append("\"");
}
openingStream.append(" id=\"").append(streamID).append("\""); openingStream.append(" id=\"").append(streamID).append("\"");
// OF-443: Not responding with a 1.0 version in the stream header when federating with older // OF-443: Not responding with a 1.0 version in the stream header when federating with older
...@@ -184,7 +193,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -184,7 +193,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
if (ServerDialback.isEnabled()) { if (ServerDialback.isEnabled()) {
// Also offer server dialback (when TLS is not required). Server dialback may be offered // Also offer server dialback (when TLS is not required). Server dialback may be offered
// after TLS has been negotiated and a self-signed certificate is being used // after TLS has been negotiated and a self-signed certificate is being used
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>"); sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
} }
sb.append("</stream:features>"); sb.append("</stream:features>");
...@@ -204,8 +213,13 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -204,8 +213,13 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
} }
public LocalIncomingServerSession(String serverName, Connection connection, StreamID streamID) { public LocalIncomingServerSession(String serverName, Connection connection, StreamID streamID, String fromDomain) {
super(serverName, connection, streamID); super(serverName, connection, streamID);
this.fromDomain = fromDomain;
}
public String getDefaultIdentity() {
return this.fromDomain;
} }
@Override @Override
...@@ -255,7 +269,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In ...@@ -255,7 +269,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
public boolean isValidDomain(String domain) { public boolean isValidDomain(String domain) {
// Check if the specified domain is contained in any of the validated domains // Check if the specified domain is contained in any of the validated domains
for (String validatedDomain : getValidatedDomains()) { for (String validatedDomain : getValidatedDomains()) {
if (domain.contains(validatedDomain)) { if (domain.equals(validatedDomain)) {
return true; return true;
} }
} }
......
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