Add option to remove manually approved certificates

BrianBlade created

- "Remove certificates" option brings up a dialog that allows
  to delete certificates from MemorizingTrustManager's keystore
- Reconnect active accounts when certificate-settings are changed
- new preference category "Certificate options"

Change summary

src/main/java/eu/siacs/conversations/ui/SettingsActivity.java | 91 +++++
src/main/res/values-de/strings.xml                            | 11 
src/main/res/values/strings.xml                               | 11 
src/main/res/xml/preferences.xml                              | 16 
4 files changed, 124 insertions(+), 5 deletions(-)

Detailed changes

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

@@ -1,20 +1,29 @@
 package eu.siacs.conversations.ui;
 
+import java.security.KeyStoreException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Locale;
 
+import de.duenndns.ssl.MemorizingTrustManager;
+
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.xmpp.XmppConnection;
 
+import android.app.AlertDialog;
 import android.app.Fragment;
 import android.app.FragmentManager;
+import android.content.DialogInterface;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Build;
 import android.os.Bundle;
 import android.preference.ListPreference;
+import android.preference.Preference;
 import android.preference.PreferenceManager;
+import android.widget.Toast;
 
 public class SettingsActivity extends XmppActivity implements
 		OnSharedPreferenceChangeListener {
@@ -49,6 +58,68 @@ public class SettingsActivity extends XmppActivity implements
 				resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
 			}
 		}
+
+		final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
+		removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+			@Override
+			public boolean onPreferenceClick(Preference preference) {
+				final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
+				final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
+				if (aliases.size() == 0) {
+					displayToast(getString(R.string.toast_no_trusted_certs));
+					return true;
+				}
+				final ArrayList selectedItems = new ArrayList<Integer>();
+				final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
+				dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
+				dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
+						new DialogInterface.OnMultiChoiceClickListener() {
+							@Override
+							public void onClick(DialogInterface dialog, int indexSelected,
+												boolean isChecked) {
+								if (isChecked) {
+									selectedItems.add(indexSelected);
+								} else if (selectedItems.contains(indexSelected)) {
+									selectedItems.remove(Integer.valueOf(indexSelected));
+								}
+								if (selectedItems.size() > 0)
+									((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+								else {
+									((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+								}
+							}
+						});
+
+				dialogBuilder.setPositiveButton(
+						getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
+							@Override
+							public void onClick(DialogInterface dialog, int which) {
+								int count = selectedItems.size();
+								if (count > 0) {
+									for (int i = 0; i < count; i++) {
+										try {
+											Integer item = Integer.valueOf(selectedItems.get(i).toString());
+											String alias = aliases.get(item);
+											mtm.deleteCertificate(alias);
+										} catch (KeyStoreException e) {
+											e.printStackTrace();
+											displayToast("Error: " + e.getLocalizedMessage());
+										}
+									}
+									if (xmppConnectionServiceBound) {
+										reconnectAccounts();
+									}
+									displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
+								}
+							}
+						});
+				dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
+				AlertDialog removeCertsDialog = dialogBuilder.create();
+				removeCertsDialog.show();
+				removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+				return true;
+			}
+		});
 	}
 
 	@Override
@@ -89,6 +160,26 @@ public class SettingsActivity extends XmppActivity implements
 			}
 		} else if (name.equals("dont_trust_system_cas")) {
 			xmppConnectionService.updateMemorizingTrustmanager();
+			reconnectAccounts();
+		}
+
+	}
+
+	private void displayToast(final String msg) {
+		runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
+			}
+		});
+	}
+
+	private void reconnectAccounts() {
+		for (Account account : xmppConnectionService.getAccounts()) {
+			if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+				xmppConnectionService.reconnectAccountInBackground(account);
+			}
 		}
 	}
+
 }

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

@@ -429,8 +429,19 @@
   <string name="received_location">Standort empfangen</string>
   <string name="title_undo_swipe_out_conversation">Unterhaltung beendet</string>
   <string name="title_undo_swipe_out_muc">Konferenz verlassen</string>
+  <string name="pref_certificate_options">Zertifikats-Optionen</string>
   <string name="pref_dont_trust_system_cas_title">Misstraue Zertifizierungsstellen</string>
   <string name="pref_dont_trust_system_cas_summary">Alle Zertifikate müssen manuell bestätigt werden</string>
+  <string name="pref_remove_trusted_certificates_title">Zertifikate löschen</string>
+  <string name="pref_remove_trusted_certificates_summary">Als vertrauenswürdig bestätigte Zertifikate löschen</string>
+  <string name="toast_no_trusted_certs">Keine manuell bestätigten Zertifikate</string>
+  <string name="dialog_manage_certs_title">Zertifikate löschen</string>
+  <string name="dialog_manage_certs_positivebutton">Auswahl löschen</string>
+  <string name="dialog_manage_certs_negativebutton">Abbrechen</string>
+  <plurals name="toast_delete_certificates">
+	<item quantity="one">%d Zertifikat gelöscht</item>
+    <item quantity="other">%d Zertifikate gelöscht</item>
+  </plurals>
   <plurals name="select_contact">
     <item quantity="one">%d Kontakt ausgewählt</item>
     <item quantity="other">%d Kontakte ausgewählt</item>

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

@@ -457,8 +457,19 @@
     <string name="received_location">Received location</string>
 	<string name="title_undo_swipe_out_conversation">Conversation closed</string>
 	<string name="title_undo_swipe_out_muc">Left conference</string>
+	<string name="pref_certificate_options">Certificate options</string>
 	<string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string>
 	<string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string>
+	<string name="pref_remove_trusted_certificates_title">Remove certificates</string>
+	<string name="pref_remove_trusted_certificates_summary">Delete manually approved certificates</string>
+	<string name="toast_no_trusted_certs">No manually approved certificates</string>
+	<string name="dialog_manage_certs_title">Remove certificates</string>
+	<string name="dialog_manage_certs_positivebutton">Delete selection</string>
+	<string name="dialog_manage_certs_negativebutton">Cancel</string>
+	<plurals name="toast_delete_certificates">
+		<item quantity="one">%d certificate deleted</item>
+		<item quantity="other">%d certificates deleted</item>
+	</plurals>
 	<plurals name="select_contact">
 		<item quantity="one">Select %d contact</item>
 		<item quantity="other">Select %d contacts</item>

src/main/res/xml/preferences.xml 🔗

@@ -142,6 +142,17 @@
 			        android:title="@string/pref_display_enter_key"
 			        android:summary="@string/pref_display_enter_key_summary" />
 	        </PreferenceCategory>
+			<PreferenceCategory android:title="@string/pref_certificate_options">
+				<CheckBoxPreference
+					android:defaultValue="false"
+					android:key="dont_trust_system_cas"
+					android:title="@string/pref_dont_trust_system_cas_title"
+					android:summary="@string/pref_dont_trust_system_cas_summary" />
+				<Preference
+					android:key="remove_trusted_certificates"
+					android:title="@string/pref_remove_trusted_certificates_title"
+					android:summary="@string/pref_remove_trusted_certificates_summary" />
+			</PreferenceCategory>
             <PreferenceCategory android:title="@string/pref_expert_options_other" >
                 <CheckBoxPreference
                     android:defaultValue="false"
@@ -153,11 +164,6 @@
                     android:key="keep_foreground_service"
                     android:title="@string/pref_keep_foreground_service"
                     android:summary="@string/pref_keep_foreground_service_summary" />
-				<CheckBoxPreference
-					android:defaultValue="false"
-					android:key="dont_trust_system_cas"
-					android:title="@string/pref_dont_trust_system_cas_title"
-					android:summary="@string/pref_dont_trust_system_cas_summary" />
             </PreferenceCategory>
         </PreferenceScreen>