Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ jobs:
- name: demoboot
maestro-tags: demoboot
config-file: '' # Use default demoboot config
- name: Channel Binding
maestro-tags: cb
config-file: '' # Use default demoboot config
#- name: sasl2
# maestro-tags: sasl2
# config-file: build/ci/conversations/configs/sasl2.xml
Expand Down
47 changes: 47 additions & 0 deletions build/ci/conversations/flows/cb.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
appId: eu.siacs.conversations
tags:
- cb
onFlowStart:
- runScript: scripts/checkHealth.js
---

- runScript: scripts/startSession.js

- launchApp:
clearState: true

- assertVisible: "I already have an account"

- runScript:
file: scripts/checkForLogs.js
env:
PATTERN: initializing database.*

- tapOn: "I already have an account"
- tapOn: "More options"
- tapOn: "Settings"
- tapOn: "Connection"
- tapOn: "Show extended connection settings when setting up an account"
- tapOn: "Navigate up"
- tapOn: "Navigate up"

- tapOn: "XMPP address"
- inputText: "jane@example.org"
- tapOn: "Password"
- inputText: "secret"
- tapOn: "Hostname"
- inputText: "10.0.2.2"
- tapOn: "Next"

- assertVisible: "Accept Unknown Certificate?"
- tapOn: "Always"

- runScript:
file: scripts/checkForLogs.js
env:
PATTERN: 'jane@example\.org.*Authenticating with SASL\/[A-Z0-9-]+-PLUS'

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- runScript:
file: scripts/checkForLogs.js
env:
PATTERN: 'jane@example\.org.*logged in \(using SASL\)'
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
Loading
Loading