show warning dialog beforing verifying keys via a link

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java | 58 
src/main/java/eu/siacs/conversations/utils/XmppUri.java                | 16 
src/main/res/layout/dialog_verify_fingerprints.xml                     | 25 
src/main/res/values/strings.xml                                        |  3 
4 files changed, 92 insertions(+), 10 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java 🔗

@@ -25,12 +25,10 @@ import android.nfc.NfcAdapter;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.support.v13.app.FragmentPagerAdapter;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.text.Editable;
 import android.text.TextWatcher;
-import android.util.Log;
 import android.util.Pair;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -65,7 +63,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Blockable;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
@@ -788,12 +785,15 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
         if (this.mPendingInvite != null) {
             mPendingInvite.invite();
             this.mPendingInvite = null;
+            filter(null);
         } else if (!handleIntent(getIntent())) {
             if (mSearchEditText != null) {
                 filter(mSearchEditText.getText().toString());
             } else {
                 filter(null);
             }
+        } else {
+            filter(null);
         }
         setIntent(null);
     }
@@ -812,15 +812,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
             case Intent.ACTION_VIEW:
                 Uri uri = intent.getData();
                 if (uri != null) {
-                    Log.d(Config.LOGTAG, "received uri=" + intent.getData());
-                    return new Invite(intent.getData()).invite();
+                    return new Invite(intent.getData(),false).invite();
                 } else {
                     return false;
                 }
             case NfcAdapter.ACTION_NDEF_DISCOVERED:
                 for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
                     if (message instanceof NdefMessage) {
-                        Log.d(Config.LOGTAG, "received message=" + message);
                         for (NdefRecord record : ((NdefMessage) message).getRecords()) {
                             switch (record.getTnf()) {
                                 case NdefRecord.TNF_WELL_KNOWN:
@@ -867,10 +865,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
             return false;
         } else if (contacts.size() == 1) {
             Contact contact = contacts.get(0);
-            if (invite.hasFingerprints()) {
-                xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
+            if (!invite.isSafeSource() && invite.hasFingerprints()) {
+                displayVerificationWarningDialog(contact,invite);
+            } else {
+                if (invite.hasFingerprints()) {
+                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
+                }
+                switchToConversation(contact, invite.getBody());
             }
-            switchToConversation(contact,invite.getBody());
             return true;
         } else {
             if (mMenuSearchView != null) {
@@ -885,6 +887,40 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
         }
     }
 
+    private void displayVerificationWarningDialog(final Contact contact, final Invite invite) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle(R.string.verify_omemo_keys);
+        View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
+        final CheckBox isTrustedSource = (CheckBox) view.findViewById(R.id.trusted_source);
+        TextView warning = (TextView) view.findViewById(R.id.warning);
+        warning.setText(getString(R.string.verifying_omemo_keys_trusted_source,contact.getJid().toBareJid().toString(),contact.getDisplayName()));
+        builder.setView(view);
+        builder.setPositiveButton(R.string.confirm, new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
+                    xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
+                }
+                switchToConversation(contact, invite.getBody());
+            }
+        });
+        builder.setNegativeButton(R.string.cancel, new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                StartConversationActivity.this.finish();
+            }
+        });
+        AlertDialog dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+            @Override
+            public void onCancel(DialogInterface dialog) {
+                StartConversationActivity.this.finish();
+            }
+        });
+        dialog.show();
+    }
+
     protected void filter(String needle) {
         if (xmppConnectionServiceBound) {
             this.filterContacts(needle);
@@ -1111,6 +1147,10 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
             super(uri);
         }
 
+        public Invite(Uri uri, boolean safeSource) {
+            super(uri,safeSource);
+        }
+
         boolean invite() {
             if (getJid() != null) {
                 return handleJid(this);

src/main/java/eu/siacs/conversations/utils/XmppUri.java 🔗

@@ -17,6 +17,7 @@ public class XmppUri {
 	protected boolean muc;
 	protected List<Fingerprint> fingerprints = new ArrayList<>();
 	private String body;
+	protected boolean safeSource = true;
 
 	public static final String OMEMO_URI_PARAM = "omemo-sid-";
 	public static final String OTR_URI_PARAM = "otr-fingerprint";
@@ -37,6 +38,15 @@ public class XmppUri {
 		parse(uri);
 	}
 
+	public XmppUri(Uri uri, boolean safeSource) {
+		this.safeSource = safeSource;
+		parse(uri);
+	}
+
+	public boolean isSafeSource() {
+		return safeSource;
+	}
+
 	protected void parse(Uri uri) {
 		String scheme = uri.getScheme();
 		String host = uri.getHost();
@@ -81,8 +91,12 @@ public class XmppUri {
 	}
 
 	protected List<Fingerprint> parseFingerprints(String query) {
+		return parseFingerprints(query,';');
+	}
+
+	protected List<Fingerprint> parseFingerprints(String query, char seperator) {
 		List<Fingerprint> fingerprints = new ArrayList<>();
-		String[] pairs = query == null ? new String[0] : query.split(";");
+		String[] pairs = query == null ? new String[0] : query.split(String.valueOf(seperator));
 		for(String pair : pairs) {
 			String[] parts = pair.split("=",2);
 			if (parts.length == 2) {

src/main/res/layout/dialog_verify_fingerprints.xml 🔗

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingLeft="?attr/dialog_horizontal_padding"
+    android:paddingRight="?attr/dialog_horizontal_padding"
+    android:paddingBottom="?attr/dialog_vertical_padding"
+    android:paddingTop="?attr/dialog_vertical_padding">
+
+    <TextView
+        android:id="@+id/warning"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="?attr/TextSizeBody"
+        android:textColor="?attr/color_text_primary"/>
+    <CheckBox
+        android:layout_marginTop="8dp"
+        android:id="@+id/trusted_source"
+        android:layout_width="wrap_content"
+        android:textColor="?attr/color_text_primary"
+        android:layout_height="wrap_content"
+        android:text="@string/i_followed_this_link_from_a_trusted_source" />
+
+</LinearLayout>

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

@@ -720,4 +720,7 @@
 	<string name="pref_clean_cache">Clean cache</string>
 	<string name="pref_clean_private_storage">Clean private storage</string>
 	<string name="pref_clean_private_storage_summary">Clean private storage where files are kept (They can be re-downloaded from the server)</string>
+	<string name="i_followed_this_link_from_a_trusted_source">I followed this link from a trusted source</string>
+	<string name="verifying_omemo_keys_trusted_source">You are about to verify the OMEMO keys form %1$s after clicking a link. This is only secure if you followed this link from a trusted source where only %2$s could have published this link.</string>
+	<string name="verify_omemo_keys">Verify OMEMO keys</string>
 </resources>