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
* @return the newly created {@link IncomingServerSession}.
* @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 {
if (serverName == null) {
throw new UnauthorizedException("Server not initialized");
}
LocalIncomingServerSession session = new LocalIncomingServerSession(serverName, conn, id);
LocalIncomingServerSession session = new LocalIncomingServerSession(serverName, conn, id, fromDomain);
conn.init(session);
// Register to receive close notification on this session so we can
// remove its route from the sessions set
......
......@@ -193,13 +193,17 @@ public class SASLAuthentication {
Element mechs = DocumentHelper.createElement(new QName("mechanisms",
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
if (session.isSecure()) {
boolean haveTrustedCertificate = false;
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;
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);
}
......@@ -520,6 +524,28 @@ public class SASLAuthentication {
}
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
// Flag that indicates if certificates of the remote server should be validated.
// Disabling certificate validation is not recommended for production environments.
......@@ -552,9 +578,18 @@ public class SASLAuthentication {
return Status.failed;
}
for (Certificate certificate : connection.getPeerCertificates()) {
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)certificate));
X509Certificate trusted;
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) {
principal = principals.get(0);
......@@ -602,22 +637,27 @@ public class SASLAuthentication {
authenticationFailed(session, Failure.NOT_AUTHORIZED);
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) {
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;
}
}
return verifyCertificate(trusted, hostname);
}
} catch(IOException e) {
Log.warn("Keystore issue while verifying certificate chain: {}", e.getMessage());
......
......@@ -431,7 +431,7 @@ public class ServerDialback {
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
LocalIncomingServerSession session = sessionManager.
createIncomingServerSession(connection, streamID);
createIncomingServerSession(connection, streamID, hostname);
// Add the validated domain as a valid domain
session.addValidatedDomain(hostname);
// Set the domain or subdomain of the local server used when
......
......@@ -85,6 +85,11 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
* many connections from the same remote server to the same local domain.
*/
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
......@@ -105,6 +110,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
XmlPullParser xpp = reader.getXPPParser();
String version = xpp.getAttributeValue("", "version");
String fromDomain = xpp.getAttributeValue("", "from");
int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0};
try {
......@@ -112,7 +118,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
StreamID streamID = SessionManager.getInstance().nextStreamID();
// Create a server Session for the remote server
LocalIncomingServerSession session =
SessionManager.getInstance().createIncomingServerSession(connection, streamID);
SessionManager.getInstance().createIncomingServerSession(connection, streamID, fromDomain);
// Send the stream header
StringBuilder openingStream = new StringBuilder();
......@@ -121,6 +127,9 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" from=\"").append(serverName).append("\"");
if (fromDomain != null) {
openingStream.append(" to=\"").append(fromDomain).append("\"");
}
openingStream.append(" id=\"").append(streamID).append("\"");
// 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
if (ServerDialback.isEnabled()) {
// 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
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"/>");
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
}
sb.append("</stream:features>");
......@@ -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);
this.fromDomain = fromDomain;
}
public String getDefaultIdentity() {
return this.fromDomain;
}
@Override
......@@ -255,7 +269,7 @@ public class LocalIncomingServerSession extends LocalServerSession implements In
public boolean isValidDomain(String domain) {
// Check if the specified domain is contained in any of the validated domains
for (String validatedDomain : getValidatedDomains()) {
if (domain.contains(validatedDomain)) {
if (domain.equals(validatedDomain)) {
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