add require channel binding

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/AppSettings.java                                   |  4 
src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java                     | 17 
src/main/java/eu/siacs/conversations/ui/fragment/settings/SecuritySettingsFragment.java |  4 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java                           | 22 
src/main/res/values/defaults.xml                                                        |  1 
src/main/res/xml/preferences_security.xml                                               |  3 
6 files changed, 41 insertions(+), 10 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/AppSettings.java 🔗

@@ -119,4 +119,8 @@ public class AppSettings {
                 PreferenceManager.getDefaultSharedPreferences(context);
         sharedPreferences.edit().putBoolean(SEND_CRASH_REPORTS, value).apply();
     }
+
+    public boolean isRequireChannelBinding() {
+        return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding);
+    }
 }

src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java 🔗

@@ -6,17 +6,17 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
 
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.net.ssl.SSLSocket;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.utils.SSLSockets;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.net.ssl.SSLSocket;
+
 public abstract class SaslMechanism {
 
     protected final Account account;
@@ -170,7 +170,9 @@ public abstract class SaslMechanism {
     }
 
     public static SaslMechanism ensureAvailable(
-            final SaslMechanism mechanism, final SSLSockets.Version sslVersion) {
+            final SaslMechanism mechanism,
+            final SSLSockets.Version sslVersion,
+            final boolean requireChannelBinding) {
         if (mechanism instanceof ChannelBindingMechanism) {
             final ChannelBinding cb = ((ChannelBindingMechanism) mechanism).getChannelBinding();
             if (ChannelBinding.isAvailable(cb, sslVersion)) {
@@ -181,6 +183,9 @@ public abstract class SaslMechanism {
                         "pinned channel binding method " + cb + " no longer available");
                 return null;
             }
+        } else if (requireChannelBinding) {
+            Log.d(Config.LOGTAG, "pinned mechanism did not provide channel binding");
+            return null;
         } else {
             return mechanism;
         }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 
+import eu.siacs.conversations.AppSettings;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.XmppDomainVerifier;
@@ -157,6 +158,7 @@ public class XmppConnection implements Runnable {
             new Hashtable<>();
     private final Set<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners =
             new HashSet<>();
+    private final AppSettings appSettings;
     private final XmppConnectionService mXmppConnectionService;
     private Socket socket;
     private XmlReader tagReader;
@@ -203,9 +205,10 @@ public class XmppConnection implements Runnable {
     public XmppConnection(final Account account, final XmppConnectionService service) {
         this.account = account;
         this.mXmppConnectionService = service;
+        this.appSettings = new AppSettings(mXmppConnectionService.getApplicationContext());
     }
 
-    private static void fixResource(Context context, Account account) {
+    private static void fixResource(final Context context, final Account account) {
         String resource = account.getResource();
         int fixedPartLength =
                 context.getString(R.string.app_name).length() + 1; // include the trailing dot
@@ -1646,6 +1649,7 @@ public class XmppConnection implements Runnable {
                             + mechanisms);
             throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
         }
+        validateRequireChannelBinding(saslMechanism);
         if (SaslMechanism.hashedToken(saslMechanism)) {
             return;
         }
@@ -1664,6 +1668,17 @@ public class XmppConnection implements Runnable {
         }
     }
 
+    private void validateRequireChannelBinding(@NonNull final SaslMechanism mechanism)
+            throws StateChangingException {
+        if (appSettings.isRequireChannelBinding()) {
+            if (mechanism instanceof ChannelBindingMechanism) {
+                return;
+            }
+            Log.d(Config.LOGTAG, account.getJid() + ": server did not offer channel binding");
+            throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
+        }
+    }
+
     private Element generateAuthenticationRequest(
             final String firstMessage, final boolean usingFast) {
         return generateAuthenticationRequest(
@@ -2365,7 +2380,10 @@ public class XmppConnection implements Runnable {
         final SaslMechanism quickStartMechanism;
         if (secureConnection) {
             quickStartMechanism =
-                    SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion);
+                    SaslMechanism.ensureAvailable(
+                            account.getQuickStartMechanism(),
+                            sslVersion,
+                            appSettings.isRequireChannelBinding());
         } else {
             quickStartMechanism = null;
         }

src/main/res/values/defaults.xml 🔗

@@ -44,4 +44,5 @@
     <bool name="allow_screenshots">true</bool>
     <string name="default_push_server">up.conversations.im</string>
     <string name="default_push_account">none</string>
+    <bool name="require_channel_binding">false</bool>
 </resources>

src/main/res/xml/preferences_security.xml 🔗

@@ -30,6 +30,7 @@
             android:title="@string/pref_remove_trusted_certificates_title" />
 
         <SwitchPreferenceCompat
+            android:defaultValue="@bool/require_channel_binding"
             android:icon="@drawable/ic_private_connectivity_24dp"
             android:key="channel_binding_required"
             android:summary="@string/detect_mim_summary"
@@ -37,8 +38,8 @@
     </PreferenceCategory>
     <PreferenceCategory android:title="@string/pref_category_on_this_device">
         <ListPreference
-            android:icon="@drawable/ic_auto_delete_24dp"
             android:defaultValue="@integer/automatic_message_deletion"
+            android:icon="@drawable/ic_auto_delete_24dp"
             android:key="automatic_message_deletion"
             android:summary="@string/pref_automatically_delete_messages_description"
             android:title="@string/pref_automatically_delete_messages" />