diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java new file mode 100644 index 000000000..847c50e9d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java @@ -0,0 +1,27 @@ +package eu.siacs.conversations.crypto.sasl; + +import android.util.Log; + +import com.google.common.base.CaseFormat; + +import eu.siacs.conversations.Config; + +public enum ChannelBinding { + NONE, + TLS_EXPORTER, + TLS_SERVER_END_POINT, + TLS_UNIQUE; + + public static ChannelBinding of(final String type) { + if (type == null) { + return null; + } + try { + return valueOf( + CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_UNDERSCORE).convert(type)); + } catch (final IllegalArgumentException e) { + Log.d(Config.LOGTAG, type + " is not a known channel binding"); + return null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java index ce2d5cd6a..13360a063 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java @@ -90,7 +90,8 @@ public abstract class SaslMechanism { this.account = account; } - public SaslMechanism of(final Collection mechanisms) { + public SaslMechanism of( + final Collection mechanisms, final Collection bindings) { if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) { return new External(account); } else if (mechanisms.contains(ScramSha512.MECHANISM)) { diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index 0fe7434a8..887128a0c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java @@ -25,14 +25,15 @@ abstract class ScramMechanism extends SaslMechanism { private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes(); private static final Cache CACHE = CacheBuilder.newBuilder().maximumSize(10).build(); + protected final ChannelBinding channelBinding; private final String clientNonce; protected State state = State.INITIAL; private String clientFirstMessageBare; private byte[] serverSignature = null; - ScramMechanism(final Account account) { + ScramMechanism(final Account account, final ChannelBinding channelBinding) { super(account); - + this.channelBinding = channelBinding; // This nonce should be different for each authentication attempt. this.clientNonce = CryptoHelper.random(100); clientFirstMessageBare = ""; diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java index 472c4dea1..9bcc8ad47 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java @@ -11,7 +11,7 @@ public class ScramSha1 extends ScramMechanism { public static final String MECHANISM = "SCRAM-SHA-1"; public ScramSha1(final Account account) { - super(account); + super(account, ChannelBinding.NONE); } @Override diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java index f3f6cab57..610ed788b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java @@ -11,7 +11,7 @@ public class ScramSha256 extends ScramMechanism { public static final String MECHANISM = "SCRAM-SHA-256"; public ScramSha256(final Account account) { - super(account); + super(account, ChannelBinding.NONE); } @Override diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java index 9a2f1a82d..3d54b39e9 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java @@ -11,7 +11,7 @@ public class ScramSha512 extends ScramMechanism { public static final String MECHANISM = "SCRAM-SHA-512"; public ScramSha512(final Account account) { - super(account); + super(account, ChannelBinding.NONE); } @Override diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index a4fd8c063..e9f9639ec 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -17,6 +17,7 @@ public final class Namespace { public static final String OOB = "jabber:x:oob"; public static final String SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; public static final String SASL_2 = "urn:xmpp:sasl:1"; + public static final String CHANNEL_BINDING = "urn:xmpp:sasl-cb:0"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String PUBSUB = "http://jabber.org/protocol/pubsub"; public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options"; diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a1336e22c..b5d7fd1af 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -14,6 +14,7 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.Collections2; @@ -62,14 +63,8 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.sasl.Anonymous; -import eu.siacs.conversations.crypto.sasl.DigestMd5; -import eu.siacs.conversations.crypto.sasl.External; -import eu.siacs.conversations.crypto.sasl.Plain; +import eu.siacs.conversations.crypto.sasl.ChannelBinding; import eu.siacs.conversations.crypto.sasl.SaslMechanism; -import eu.siacs.conversations.crypto.sasl.ScramSha1; -import eu.siacs.conversations.crypto.sasl.ScramSha256; -import eu.siacs.conversations.crypto.sasl.ScramSha512; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.ServiceDiscoveryResult; @@ -720,7 +715,7 @@ public class XmppConnection implements Runnable { Log.d( Config.LOGTAG, account.getJid().asBareJid().toString() + ": logged in (using " + version + ")"); - //TODO store mechanism name + // TODO store mechanism name account.setKey(Account.PINNED_MECHANISM_KEY, String.valueOf(saslMechanism.getPriority())); if (version == SaslMechanism.Version.SASL_2) { final String authorizationIdentifier = @@ -784,7 +779,7 @@ public class XmppConnection implements Runnable { account.getJid().asBareJid() + ": successfully enabled carbons"); features.carbonsEnabled = true; } - //TODO if both are set mark account ready for pipelining + // TODO if both are set mark account ready for pipelining sendPostBindInitialization(streamManagementEnabled != null, carbonsEnabled != null); } } @@ -1218,10 +1213,30 @@ public class XmppConnection implements Runnable { } private void authenticate(final SaslMechanism.Version version) throws IOException { - final Element element = streamFeatures.findChild("mechanisms"); - final Collection mechanisms = Collections2.transform(element.getChildren(), c -> c == null ? null : c.getContent()); + Log.d(Config.LOGTAG, "stream features: " + this.streamFeatures); + final Element element = + this.streamFeatures.findChild("mechanisms"); // TODO get from correct NS + final Collection mechanisms = + Collections2.transform( + Collections2.filter( + element.getChildren(), + c -> c != null && "mechanism".equals(c.getName())), + c -> c == null ? null : c.getContent()); + final Element cbElement = + this.streamFeatures.findChild("sasl-channel-binding", Namespace.CHANNEL_BINDING); + final Collection channelBindings = + Collections2.filter( + Collections2.transform( + Collections2.filter( + cbElement == null + ? Collections.emptyList() + : cbElement.getChildren(), + c -> c != null && "channel-binding".equals(c.getName())), + c -> c == null ? null : ChannelBinding.of(c.getAttribute("type"))), + Predicates.notNull()); + Log.d(Config.LOGTAG, "channel bindings: " + channelBindings); final SaslMechanism.Factory factory = new SaslMechanism.Factory(account); - this.saslMechanism = factory.of(mechanisms); + this.saslMechanism = factory.of(mechanisms, channelBindings); if (saslMechanism == null) { Log.d(