Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions documentation/openfire.doap
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,13 @@
<xmpp:note xml:lang='en'>As Openfire does not support MIX, the 'service types' search form field is not supported by the implementation. All searches cover MUC only, as per the specification.</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0440.html"/>
<xmpp:status>full</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0478.html"/>
Expand Down
1 change: 1 addition & 0 deletions documentation/protocol-support.html
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ <h2>List of other XEPs Supported</h2>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0359.html">XEP-0359</a>: Unique and Stable Stanza IDs</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0398.html">XEP-0398</a>: User Avatar to vCard-Based Avatars Conversion</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0433.html">XEP-0433</a>: Extended Channel Search</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0440.html">XEP-0440</a>: SASL Channel-Binding Type Capability</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0478.html">XEP-0478</a>: Stream Limits Advertisement</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0483.html">XEP-0483</a>: HTTP Online Meetings [<a href="#fn19">19</a>]</td></tr>
<tr><td><a href="https://www.xmpp.org/extensions/xep-0485.html">XEP-0485</a>: PubSub Server Information [<a href="#fn18">18</a>]</td></tr>
Expand Down
2 changes: 2 additions & 0 deletions i18n/src/main/resources/openfire_i18n.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,7 @@ reg.settings.description.SKEY=An S/KEY mechanism.
reg.settings.description.CRAM-MD5=Simple challenge-response scheme based on HMAC-MD5.
reg.settings.description.DIGEST-MD5=Challenge-response scheme based upon MD5. DIGEST-MD5 offered a data security layer.
reg.settings.description.SCRAM-SHA-1=Salted challenge-response scheme based on SHA-1.
reg.settings.description.SCRAM-SHA-1-PLUS=Salted challenge-response scheme based on SHA-1 with channel binding.
reg.settings.description.SCRAM=Challenge-response scheme based mechanism with channel binding support.
reg.settings.description.NTLM=NT LAN Manager authentication mechanism.
reg.settings.description.GSSAPI=Kerberos V5 authentication via the GSSAPI. GSSAPI offers a data-security layer.
Expand Down Expand Up @@ -1855,6 +1856,7 @@ session.details.anon-status=Using Anonymous Authentication
session.details.anon-true=Yes
session.details.anon-false=No
session.details.sasl-mechanism=SASL Mechanism
session.details.channel-binding-type=Channel Binding Type
Comment thread
guusdk marked this conversation as resolved.
session.details.flomr-status=Flexible Offline Message Retrieval
session.details.flomr-enabled=Enabled
session.details.flomr-disabled=Disabled
Expand Down
6 changes: 4 additions & 2 deletions i18n/src/main/resources/openfire_i18n_nl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1071,8 +1071,9 @@ reg.settings.description.OTP=Eenmalig wachtwoord mechanisme.
reg.settings.description.SKEY=Een S/KEY mechanisme.
reg.settings.description.CRAM-MD5=Eenvoudig Challenge-response mechanisme gebaseed op HMAC-MD5.
reg.settings.description.DIGEST-MD5=Challenge-response mechanisme gebaseed op MD5. DIGEST-MD5 biedt data-laag beveiliging.
reg.settings.description.SCRAM-SHA-1=Salted challenge-response mechanisme gebaseed op SHA-1.
reg.settings.description.SCRAM=Chellenge-response-gebaseerd mechanisme met channel-binding ondersteuning.
reg.settings.description.SCRAM-SHA-1=Salted challenge-response mechanisme gebaseerd op SHA-1.
reg.settings.description.SCRAM-SHA-1-PLUS=Salted challenge-response mechanisme (met channel binding) gebaseerd op SHA-1.
reg.settings.description.SCRAM=Challenge-response-gebaseerd mechanisme met channel-binding ondersteuning.
reg.settings.description.NTLM=NT LAN Manager authenticatie mechanisme.
reg.settings.description.GSSAPI=Kerberos V5 authenticatie via GSSAPI. GSSAPI biedt een data-laag beveiliging.
reg.settings.description.EAP-AES128=GSS EAP authenticatie.
Expand Down Expand Up @@ -1732,6 +1733,7 @@ session.details.anon-status=Gebruik Anonieme Authenticatie
session.details.anon-true=Ja
session.details.anon-false=Nee
session.details.sasl-mechanism=SASL Mechanisme
session.details.channel-binding-type=Channel Binding Type
Comment thread
guusdk marked this conversation as resolved.
session.details.flomr-status=Offline Berichten Flexibel Ophalen
session.details.flomr-enabled=Ingeschakeld
session.details.flomr-disabled=Uitgeschakeld
Expand Down
44 changes: 44 additions & 0 deletions xmppserver/src/main/java/org/jivesoftware/openfire/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.io.Closeable;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletionStage;
Expand Down Expand Up @@ -136,6 +137,49 @@ public interface Connection extends Closeable {
*/
Certificate[] getPeerCertificates();

/**
* Returns channel binding data for this connection, as defined by the provided type.
*
* Channel binding data is used to bind higher-level authentication to the underlying transport layer, improving
* security against man-in-the-middle attacks.
*
* The type, identified by a unique prefix that's typically defined in an RFC, determines which channel binding
* mechanism is used, such as:
* <ul>
* <li><code>tls-exporter</code>: TLS exporter-based channel binding.</li>
* <li><code>tls-server-end-point</code>: Uses the hash of the server certificate (RFC 5929).</li>
* </ul>
*
* Note that channel binding type prefixes are case-sensitive.
*
* If the connection is not encrypted, or the requested channel binding type is not available, returns {@link Optional#empty()}.
*
* @param cbPrefix the RFC-defined unique prefix for the channel binding type (must not be null)
* @return An Optional containing the channel binding data, or empty if not available.
* @see <a href="https://datatracker.ietf.org/doc/html/rfc5705">RFC 5705: Keying Material Exporters for Transport Layer Security (TLS)</a>
* @see <a href="https://datatracker.ietf.org/doc/html/rfc5929">RFC 5929: Channel Bindings for TLS</a>
* @see <a href="https://datatracker.ietf.org/doc/html/rfc9266">RFC 9266: Channel Bindings for TLS 1.3</a>
*/
default Optional<byte[]> getChannelBindingData(@Nonnull final String cbPrefix) {
return Optional.empty();
}

/**
* Returns the unique prefixes of the channel binding types that are supported by this connection in its current
* state. Notably, this may change if the connection is encrypted or if the underlying TLS implementation changes.
* When no channel binding types are supported, an empty set is returned.
*
* <b>Implementation note:</b> This method is used to determine if SASL -PLUS mechanisms (such as SCRAM-SHA-1-PLUS)
* should be offered to the client. If channel binding is not supported in the current state (e.g., not encrypted,
* or the connection type does not support channel binding), this method <b>must</b> return an empty set.
*
* @return supported channel binding types.
*/
default Set<String> getSupportedChannelBindingTypes()
{
return Collections.emptySet();
}

/**
* Keeps track if the other peer of this session presented a self-signed certificate. When
* using self-signed certificate for server-2-server sessions then SASL EXTERNAL will not be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ public ContextHandlerCollection getContexts() {
}

/**
* Restart the admin console (and it's HTTP server) without restarting the plugin.
* Restart the admin console (and its HTTP server) without restarting the plugin.
*/
public void restart() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public Group createGroup(String name) throws GroupAlreadyExistsException, GroupN
sharedGroupMetaCache.clear();
}

return null; // aught to be overridden.
return null; // ought to be overridden.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,21 @@
import org.jivesoftware.openfire.sasl.Failure;
import org.jivesoftware.openfire.sasl.JiveSharedSecretSaslServer;
import org.jivesoftware.openfire.sasl.SaslFailureException;
import org.jivesoftware.openfire.session.*;
import org.jivesoftware.openfire.sasl.ScramSha1SaslServer;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.openfire.session.IncomingServerSession;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.LocalIncomingServerSession;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.ServerSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.spi.ConnectionType;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.SystemProperty;
import org.jivesoftware.util.channelbinding.ChannelBindingProviderManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -277,6 +286,19 @@ public static Element getSASLMechanismsElement( ClientSession session )
continue; // Do not offer EXTERNAL.
}
}
if (mech.endsWith("-PLUS")) {
// Prevent offering channel binding if the Connection implementation does not support it.
final Connection connection = ( (LocalClientSession) session ).getConnection();
assert connection != null; // While the client is performing a SASL negotiation, the connection can't be null.
if (connection.getSupportedChannelBindingTypes().isEmpty()) {
continue;
}

// Channel binding would be a binding to TLS, thus encryption is required for channel binding.
if (!session.isEncrypted()) { // This ought to be redundant, as getSupportedChannelBindingTypes() will return an empty set if not encrypted.
continue;
}
}
Comment thread
guusdk marked this conversation as resolved.
final Element mechanism = result.addElement("mechanism");
mechanism.setText(mech);
}
Expand Down Expand Up @@ -454,6 +476,9 @@ else if (encoded.equals("="))
authenticationSuccessful( session, saslServer.getAuthorizationID(), challenge );
session.removeSessionData( "SaslServer" );
session.setSessionData("SaslMechanism", saslServer.getMechanismName());
if (saslServer.getMechanismName().endsWith("-PLUS")) {
session.setSessionData("ChannelBindingType", saslServer.getNegotiatedProperty(ScramSha1SaslServer.PROPNAME_CHANNELBINDINGTYPE));
}
return Status.authenticated;

default:
Expand Down Expand Up @@ -639,22 +664,29 @@ public static Set<String> getSupportedMechanisms()
continue;
}

if (mechanism.endsWith("-PLUS") && ChannelBindingProviderManager.getInstance().getSupportedChannelBindingTypes().isEmpty()) {
Log.trace( "Cannot support '{}' as there's no implementation available for channel binding.", mechanism );
it.remove();
continue;
}

switch ( mechanism )
{
case "CRAM-MD5": // intended fall-through
case "DIGEST-MD5":
// Check if the user provider in use supports passwords retrieval. Access to the users passwords will be required by the CallbackHandler.
if ( !AuthFactory.supportsPasswordRetrieval() )
{
Log.trace( "Cannot support '{}' as the AuthFactory that's in use does not support password retrieval.", mechanism );
Log.trace( "Cannot support '{}' as the AuthProvider that's in use does not support password retrieval.", mechanism );
it.remove();
}
break;

case "SCRAM-SHA-1":
case "SCRAM-SHA-1": // intended fall-through
case "SCRAM-SHA-1-PLUS":
if ( !AuthFactory.supportsScram() )
{
Log.trace( "Cannot support '{}' as the AuthFactory that's in use does not support SCRAM.", mechanism );
Log.trace( "Cannot support '{}' as the AuthProvider that's in use does not support SCRAM.", mechanism );
it.remove();
}
break;
Expand Down Expand Up @@ -728,7 +760,7 @@ public static Set<String> getImplementedMechanisms()
*/
public static List<String> getEnabledMechanisms()
{
return JiveGlobals.getListProperty("sasl.mechs", Arrays.asList( "ANONYMOUS","PLAIN","DIGEST-MD5","CRAM-MD5","SCRAM-SHA-1","JIVE-SHAREDSECRET","GSSAPI","EXTERNAL" ) );
return JiveGlobals.getListProperty("sasl.mechs", Arrays.asList( "ANONYMOUS","PLAIN","DIGEST-MD5","CRAM-MD5","SCRAM-SHA-1","SCRAM-SHA-1-PLUS","JIVE-SHAREDSECRET","GSSAPI","EXTERNAL" ) );
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2008 Jive Software, 2017-2025 Ignite Realtime Foundation. All rights reserved.
* Copyright (C) 2005-2008 Jive Software, 2017-2026 Ignite Realtime Foundation. 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.
Expand All @@ -20,6 +20,7 @@
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.channelbinding.ChannelBindingProviderManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParserException;
Expand Down Expand Up @@ -108,6 +109,7 @@ protected void tlsNegotiated() throws XmlPullParserException, IOException
final Element features = DocumentHelper.createElement(QName.get("features", "stream", "http://etherx.jabber.org/streams"));
final Element mechanisms = SASLAuthentication.getSASLMechanisms(socketReader.session);
if (mechanisms != null) {
ChannelBindingProviderManager.getInstance().getSASLChannelBindingTypeCapabilityElement(mechanisms).ifPresent(features::add);
features.add(mechanisms);
}
final List<Element> specificFeatures = socketReader.session.getAvailableStreamFeatures();
Expand Down Expand Up @@ -253,6 +255,7 @@ protected void compressionSuccessful() throws XmlPullParserException, IOExceptio
// Include available SASL Mechanisms
final Element saslMechanisms = SASLAuthentication.getSASLMechanisms(socketReader.session);
if (saslMechanisms != null) {
ChannelBindingProviderManager.getInstance().getSASLChannelBindingTypeCapabilityElement(saslMechanisms).ifPresent(features::add);
features.add(saslMechanisms);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.jivesoftware.openfire.spi.BasicStreamIDFactory;
import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.util.*;
import org.jivesoftware.util.channelbinding.ChannelBindingProviderManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
Expand Down Expand Up @@ -494,7 +495,8 @@ protected void tlsNegotiated(XmlPullParser xpp) throws XmlPullParserException, I
// Include available SASL Mechanisms
final Element mechanismsElement=SASLAuthentication.getSASLMechanisms(session);
if (mechanismsElement!=null) {
features.add(mechanismsElement);
ChannelBindingProviderManager.getInstance().getSASLChannelBindingTypeCapabilityElement(mechanismsElement).ifPresent(features::add);
features.add(mechanismsElement);
}

// Include specific features such as auth and register for client sessions
Expand Down Expand Up @@ -597,6 +599,7 @@ protected void compressionSuccessful() {
if (!session.isAuthenticated()) {
final Element saslMechanisms = SASLAuthentication.getSASLMechanisms(session);
if (saslMechanisms != null) {
ChannelBindingProviderManager.getInstance().getSASLChannelBindingTypeCapabilityElement(saslMechanisms).ifPresent(features::add);
features.add(saslMechanisms);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,24 @@
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.spi.EncryptionArtifactFactory;
import org.jivesoftware.util.channelbinding.ChannelBindingProviderManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.Packet;
import org.xmpp.packet.StreamError;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import static com.jcraft.jzlib.JZlib.Z_BEST_COMPRESSION;
Expand Down Expand Up @@ -83,10 +88,13 @@ public class NettyConnection extends AbstractConnection
private final AtomicReference<State> state = new AtomicReference<>(State.OPEN);
private boolean isEncrypted = false;

private ChannelBindingProviderManager channelBindingProviderManager; // TODO allow this to be set for unit testing.

public NettyConnection(ChannelHandlerContext channelHandlerContext, @Nullable PacketDeliverer packetDeliverer, ConnectionConfiguration configuration ) {
this.channelHandlerContext = channelHandlerContext;
this.backupDeliverer = packetDeliverer;
this.configuration = configuration;
this.channelBindingProviderManager = ChannelBindingProviderManager.getInstance();
}

@Override
Expand Down Expand Up @@ -405,6 +413,28 @@ public boolean isCompressed() {
return channelHandlerContext.channel().pipeline().get(JZlibDecoder.class) != null;
}

@Override
public Optional<byte[]> getChannelBindingData(@Nonnull final String cbPrefix)
{
final SslHandler sslhandler = (SslHandler) channelHandlerContext.channel().pipeline().get(SSL_HANDLER_NAME);
if (sslhandler == null) {
return Optional.empty();
}

final SSLEngine engine = sslhandler.engine();
return channelBindingProviderManager.getChannelBinding(cbPrefix, engine);
}

@Override
public Set<String> getSupportedChannelBindingTypes()
{
final SslHandler sslhandler = (SslHandler) channelHandlerContext.channel().pipeline().get(SSL_HANDLER_NAME);
if (sslhandler == null) {
return Collections.emptySet();
}
return channelBindingProviderManager.getSupportedChannelBindingTypes();
}

@Override
public String toString() {
final SocketAddress peer = channelHandlerContext.channel().remoteAddress();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2004-2008 Jive Software, 2017-2018 Ignite Realtime Foundation. All rights reserved.
* Copyright (C) 2004-2008 Jive Software, 2017-2026 Ignite Realtime Foundation. 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.
Expand Down Expand Up @@ -31,7 +31,7 @@ public class SaslProvider extends Provider {
*/
public SaslProvider()
{
super("JiveSoftware", 1.1, "JiveSoftware Openfire SASL provider v1.1" );
super("JiveSoftware", 1.2, "JiveSoftware Openfire SASL provider v1.2" );

final SaslServerFactoryImpl serverFactory = new SaslServerFactoryImpl();
for ( final String name : serverFactory.getMechanismNames( null ) )
Expand Down
Loading
Loading