extract channel binding types via XEP-0440

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java | 27 
src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java  |  3 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java |  5 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java      |  2 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java    |  2 
src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java    |  2 
src/main/java/eu/siacs/conversations/xml/Namespace.java              |  1 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java        | 39 
8 files changed, 63 insertions(+), 18 deletions(-)

Detailed changes

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;
+        }
+    }
+}

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<String> mechanisms) {
+        public SaslMechanism of(
+                final Collection<String> mechanisms, final Collection<ChannelBinding> bindings) {
             if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
                 return new External(account);
             } else if (mechanisms.contains(ScramSha512.MECHANISM)) {

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<CacheKey, KeyPair> 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 = "";

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";

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<String> 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<String> 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<ChannelBinding> 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(