diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyOutboundConnectionHandler.java b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyOutboundConnectionHandler.java index 040e7ed26b..58fcb3d8b0 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyOutboundConnectionHandler.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/NettyOutboundConnectionHandler.java @@ -29,16 +29,19 @@ import org.jivesoftware.openfire.session.DomainPair; import org.jivesoftware.openfire.session.LocalOutgoingServerSession; import org.jivesoftware.openfire.spi.ConnectionConfiguration; +import org.jivesoftware.util.CertificateManager; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateRevokedException; +import java.security.cert.X509Certificate; import java.time.Duration; /** @@ -128,7 +131,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc return; } - if (!ServerDialback.isEnabled() && !ServerDialback.isEnabledForSelfSigned()) { + if (!ServerDialback.isEnabled()) { Log.warn("As peer's certificates are not valid for its domain ('{}'), the SASL EXTERNAL authentication mechanism cannot be used. The Server Dialback authentication mechanism is disabled by configuration. Aborting session, as this leaves no available authentication mechanisms.", domainPair.getRemote()); stanzaHandler.setSession(null); stanzaHandler.setAttemptedAllAuthenticationMethods(); @@ -136,6 +139,22 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc return; } + boolean usingSelfSigned; + final Certificate[] chain = connection.getPeerCertificates(); + if (chain == null || chain.length == 0) { + usingSelfSigned = true; + } else { + usingSelfSigned = CertificateManager.isSelfSignedCertificate((X509Certificate) chain[0]); + } + + if (usingSelfSigned && !ServerDialback.isEnabledForSelfSigned()) { + Log.warn("As peer's certificates are not valid for its domain ('{}'), the SASL EXTERNAL authentication mechanism cannot be used. The peer uses a selfsigned certificate and the Server Dialback authentication mechanism is not enabled for selfsigned certificate. Aborting session, as this leaves no available authentication mechanisms.", domainPair.getRemote()); + stanzaHandler.setSession(null); + stanzaHandler.setAttemptedAllAuthenticationMethods(); + ctx.channel().close(); + return; + } + // If TLS cannot be used for authentication, it is permissible to use another authentication mechanism // such as dialback. RFC 6120 does not explicitly allow this, as it does not take into account any other // authentication mechanism other than TLS (it does mention dialback in an interoperability note. However, @@ -144,7 +163,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc // keys [XEP-0220] are used." In short: if Dialback is allowed, unauthenticated TLS is better than no TLS. Log.warn("As peer's certificates are not valid for its domain ('{}'), the SASL EXTERNAL authentication mechanism cannot be used. The Server Dialback authentication mechanism is available.", domainPair.getRemote()); - + sendNewStreamHeader(connection); connection.setEncrypted(true); ctx.fireChannelActive(); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalIncomingServerSession.java b/xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalIncomingServerSession.java index b80ab56466..441d3bc9fa 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalIncomingServerSession.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/session/LocalIncomingServerSession.java @@ -27,6 +27,7 @@ import org.jivesoftware.openfire.server.ServerDialbackErrorException; import org.jivesoftware.openfire.server.ServerDialbackKeyInvalidException; import org.jivesoftware.util.CertificateManager; +import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.StreamErrorException; import org.jivesoftware.util.StringUtils; import org.slf4j.Logger; @@ -194,7 +195,7 @@ public static LocalIncomingServerSession createSession(String serverName, XmlPul 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 + // after TLS has been negotiated final Element dialback = DocumentHelper.createElement(QName.get("dialback", "urn:xmpp:features:dialback")); dialback.addElement("errors"); features.add(dialback); @@ -432,18 +433,25 @@ public List getAvailableStreamFeatures() } // Offer server dialback if using self-signed certificates and no authentication has been done yet - boolean usingSelfSigned; - final Certificate[] chain = conn.getLocalCertificates(); - if (chain == null || chain.length == 0) { - usingSelfSigned = true; - } else { - usingSelfSigned = CertificateManager.isSelfSignedCertificate((X509Certificate) chain[0]); - } - - if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) { - final Element dialback = DocumentHelper.createElement(QName.get("dialback", "urn:xmpp:features:dialback")); - dialback.addElement("errors"); - result.add(dialback); + // Also offer dialback if strict certificate validation is not set (fallback if SASL EXTERNAL fails), as per XEP-0178 section 3.9 + if (ServerDialback.isEnabled()) { + boolean usingSelfSigned; + final Certificate[] chain = conn.getLocalCertificates(); + if (chain == null || chain.length == 0) { + usingSelfSigned = true; + } else { + usingSelfSigned = CertificateManager.isSelfSignedCertificate((X509Certificate) chain[0]); + } + + boolean offerDialback = !JiveGlobals.getBooleanProperty(ConnectionSettings.Server.STRICT_CERTIFICATE_VALIDATION, true); + + if ((usingSelfSigned && ServerDialback.isEnabledForSelfSigned()) || offerDialback) { + if (validatedDomains.isEmpty()) { + final Element dialback = DocumentHelper.createElement(QName.get("dialback", "urn:xmpp:features:dialback")); + dialback.addElement("errors"); + result.add(dialback); + } + } } if (!ConnectionSettings.Server.STREAM_LIMITS_ADVERTISEMENT_DISABLED.getValue()) {