Add INACTIVE state for removed keys

Andreas Straub created

We introduce a new trust state: INACTIVE. This state is intended for
old keys that have been removed.

When a TRUSTED device is removed from the PEP devicelist, it's status
will be set to INACTIVE. INACTIVE keys are shown in the UI as greyed
out, non-interactible key rows. Messages are not encrypted for INACTIVE
devices.

When an INACTIVE device reappears in PEP, or a message is received from
an INACTIVE device, it is set back to trusted.

Change summary

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java  | 54 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 31 
src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java      |  8 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java         |  8 
src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java           |  6 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java                | 24 
src/main/java/eu/siacs/conversations/xmpp/OnKeyStatusUpdated.java        |  5 
src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java        |  5 
src/main/res/values/colors.xml                                           |  1 
9 files changed, 102 insertions(+), 40 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java 🔗

@@ -102,7 +102,8 @@ public class AxolotlService {
 			UNDECIDED(0),
 			TRUSTED(1),
 			UNTRUSTED(2),
-			COMPROMISED(3);
+			COMPROMISED(3),
+			INACTIVE(4);
 
 			private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
 
@@ -125,12 +126,16 @@ public class AxolotlService {
 			public String toString() {
 				switch(this){
 					case UNDECIDED:
-						return "Trust undecided";
+						return "Trust undecided "+getCode();
 					case TRUSTED:
-						return "Trusted";
+						return "Trusted "+getCode();
+					case COMPROMISED:
+						return "Compromised "+getCode();
+					case INACTIVE:
+						return "Inactive "+getCode();
 					case UNTRUSTED:
 					default:
-						return "Untrusted";
+						return "Untrusted "+getCode();
 				}
 			}
 
@@ -538,14 +543,20 @@ public class AxolotlService {
 			return fingerprint;
 		}
 
-		private SQLiteAxolotlStore.Trust getTrust() {
+		protected void setTrust(SQLiteAxolotlStore.Trust trust) {
+			sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
+		}
+
+		protected SQLiteAxolotlStore.Trust getTrust() {
 			return sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
 		}
 
 		@Nullable
 		public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
 			byte[] plaintext = null;
-			switch (getTrust()) {
+			SQLiteAxolotlStore.Trust trust = getTrust();
+			switch (trust) {
+				case INACTIVE:
 				case UNDECIDED:
 				case UNTRUSTED:
 				case TRUSTED:
@@ -574,6 +585,10 @@ public class AxolotlService {
 						Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
 					}
 
+					if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) {
+						setTrust(SQLiteAxolotlStore.Trust.TRUSTED);
+					}
+
 					break;
 
 				case COMPROMISED:
@@ -774,15 +789,32 @@ public class AxolotlService {
 		return this.deviceIds.get(account.getJid().toBareJid());
 	}
 
+	private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
+	                                final SQLiteAxolotlStore.Trust from,
+	                                final SQLiteAxolotlStore.Trust to) {
+		for(Integer deviceId:deviceIds) {
+			AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+			XmppAxolotlSession session = sessions.get(address);
+			if (session != null && session.getFingerprint() != null
+					&& session.getTrust() == from) {
+				session.setTrust(to);
+			}
+		}
+	}
+
 	public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
 		if(deviceIds.contains(getOwnDeviceId())) {
-			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Skipping own Device ID:"+ jid + ":"+getOwnDeviceId());
 			deviceIds.remove(getOwnDeviceId());
 		}
-		for(Integer i:deviceIds) {
-			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding Device ID:"+ jid + ":"+i);
-		}
+		Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
+		expiredDevices.removeAll(deviceIds);
+		setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED,
+				SQLiteAxolotlStore.Trust.INACTIVE);
+		Set<Integer> newDevices = new HashSet<>(deviceIds);
+		setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE,
+				SQLiteAxolotlStore.Trust.TRUSTED);
 		this.deviceIds.put(jid, deviceIds);
+		mXmppConnectionService.keyStatusUpdated();
 		publishOwnDeviceIdIfNeeded();
 	}
 
@@ -957,7 +989,7 @@ public class AxolotlService {
 										}
 									});
 						}
-						mXmppConnectionService.newKeysAvailable();
+						mXmppConnectionService.keyStatusUpdated();
 					}
 				}
 

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -86,7 +86,7 @@ import eu.siacs.conversations.xmpp.OnContactStatusChanged;
 import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
 import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
-import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
 import eu.siacs.conversations.xmpp.OnStatusChanged;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
@@ -309,8 +309,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	private int rosterChangedListenerCount = 0;
 	private OnMucRosterUpdate mOnMucRosterUpdate = null;
 	private int mucRosterChangedListenerCount = 0;
-	private OnNewKeysAvailable mOnNewKeysAvailable = null;
-	private int newKeysAvailableListenerCount = 0;
+	private OnKeyStatusUpdated mOnKeyStatusUpdated = null;
+	private int keyStatusUpdatedListenerCount = 0;
 	private SecureRandom mRandom;
 	private OpenPgpServiceConnection pgpServiceConnection;
 	private PgpEngine mPgpEngine = null;
@@ -1372,30 +1372,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	}
 
-	public void setOnNewKeysAvailableListener(final OnNewKeysAvailable listener) {
+	public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) {
 		synchronized (this) {
 			if (checkListeners()) {
 				switchToForeground();
 			}
-			this.mOnNewKeysAvailable = listener;
-			if (this.newKeysAvailableListenerCount < 2) {
-				this.newKeysAvailableListenerCount++;
+			this.mOnKeyStatusUpdated = listener;
+			if (this.keyStatusUpdatedListenerCount < 2) {
+				this.keyStatusUpdatedListenerCount++;
 			}
 		}
 	}
 
 	public void removeOnNewKeysAvailableListener() {
 		synchronized (this) {
-			this.newKeysAvailableListenerCount--;
-			if (this.newKeysAvailableListenerCount <= 0) {
-				this.newKeysAvailableListenerCount = 0;
-				this.mOnNewKeysAvailable = null;
+			this.keyStatusUpdatedListenerCount--;
+			if (this.keyStatusUpdatedListenerCount <= 0) {
+				this.keyStatusUpdatedListenerCount = 0;
+				this.mOnKeyStatusUpdated = null;
 				if (checkListeners()) {
 					switchToBackground();
 				}
 			}
 		}
 	}
+
 	public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
 		synchronized (this) {
 			if (checkListeners()) {
@@ -1427,7 +1428,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 				&& this.mOnRosterUpdate == null
 				&& this.mOnUpdateBlocklist == null
 				&& this.mOnShowErrorToast == null
-				&& this.mOnNewKeysAvailable == null);
+				&& this.mOnKeyStatusUpdated == null);
 	}
 
 	private void switchToForeground() {
@@ -2316,9 +2317,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	}
 
-	public void newKeysAvailable() {
-		if(mOnNewKeysAvailable != null) {
-			mOnNewKeysAvailable.onNewKeysAvailable();
+	public void keyStatusUpdated() {
+		if(mOnKeyStatusUpdated != null) {
+			mOnKeyStatusUpdated.onKeyStatusUpdated();
 		}
 	}
 

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

@@ -42,12 +42,13 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
-public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist {
+public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
 	public static final String ACTION_VIEW_CONTACT = "view_contact";
 
 	private Contact contact;
@@ -468,4 +469,9 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
 			populateView();
 		}
 	}
+
+	@Override
+	public void onKeyStatusUpdated() {
+		refreshUi();
+	}
 }

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

@@ -35,12 +35,13 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
 import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.XmppConnection.Features;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 
-public class EditAccountActivity extends XmppActivity implements OnAccountUpdate{
+public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, OnKeyStatusUpdated {
 
 	private AutoCompleteTextView mAccountJid;
 	private EditText mPassword;
@@ -618,4 +619,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 				});
 		builder.create().show();
 	}
+
+	@Override
+	public void onKeyStatusUpdated() {
+		refreshUi();
+	}
 }

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

@@ -20,11 +20,11 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.T
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
-public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable {
+public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
 	private Jid accountJid;
 	private Jid contactJid;
 	private boolean hasOtherTrustedKeys = false;
@@ -215,7 +215,7 @@ public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailabl
 	}
 
 	@Override
-	public void onNewKeysAvailable() {
+	public void onKeyStatusUpdated() {
 		final Account account = xmppConnectionService.findAccountByJid(accountJid);
 		hasPendingFetches = false;
 		getFingerprints(account);

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

@@ -83,7 +83,7 @@ import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinde
 import eu.siacs.conversations.ui.widget.Switch;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.ExceptionHelper;
-import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
+import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
@@ -99,6 +99,7 @@ public abstract class XmppActivity extends Activity {
 
 	protected int mPrimaryTextColor;
 	protected int mSecondaryTextColor;
+	protected int mTertiaryTextColor;
 	protected int mPrimaryBackgroundColor;
 	protected int mSecondaryBackgroundColor;
 	protected int mColorRed;
@@ -294,8 +295,8 @@ public abstract class XmppActivity extends Activity {
 		if (this instanceof XmppConnectionService.OnShowErrorToast) {
 			this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
 		}
-		if (this instanceof OnNewKeysAvailable) {
-			this.xmppConnectionService.setOnNewKeysAvailableListener((OnNewKeysAvailable) this);
+		if (this instanceof OnKeyStatusUpdated) {
+			this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
 		}
 	}
 
@@ -318,7 +319,7 @@ public abstract class XmppActivity extends Activity {
 		if (this instanceof XmppConnectionService.OnShowErrorToast) {
 			this.xmppConnectionService.removeOnShowErrorToastListener();
 		}
-		if (this instanceof OnNewKeysAvailable) {
+		if (this instanceof OnKeyStatusUpdated) {
 			this.xmppConnectionService.removeOnNewKeysAvailableListener();
 		}
 	}
@@ -349,6 +350,7 @@ public abstract class XmppActivity extends Activity {
 		ExceptionHelper.init(getApplicationContext());
 		mPrimaryTextColor = getResources().getColor(R.color.black87);
 		mSecondaryTextColor = getResources().getColor(R.color.black54);
+		mTertiaryTextColor = getResources().getColor(R.color.black12);
 		mColorRed = getResources().getColor(R.color.red500);
 		mColorOrange = getResources().getColor(R.color.orange500);
 		mColorGreen = getResources().getColor(R.color.green500);
@@ -668,10 +670,20 @@ public abstract class XmppActivity extends Activity {
 			case TRUSTED:
 				trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false);
 				trustToggle.setEnabled(true);
+				key.setTextColor(getPrimaryTextColor());
+				keyType.setTextColor(getSecondaryTextColor());
 				break;
 			case UNDECIDED:
 				trustToggle.setChecked(false, false);
 				trustToggle.setEnabled(false);
+				key.setTextColor(getPrimaryTextColor());
+				keyType.setTextColor(getSecondaryTextColor());
+				break;
+			case INACTIVE:
+				trustToggle.setChecked(true, false);
+				trustToggle.setEnabled(false);
+				key.setTextColor(getTertiaryTextColor());
+				keyType.setTextColor(getTertiaryTextColor());
 				break;
 		}
 
@@ -824,6 +836,10 @@ public abstract class XmppActivity extends Activity {
 		}
 	};
 
+	public int getTertiaryTextColor() {
+		return this.mTertiaryTextColor;
+	}
+
 	public int getSecondaryTextColor() {
 		return this.mSecondaryTextColor;
 	}

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

@@ -5,6 +5,7 @@
 	<color name="accent">#ff0091ea</color>
 	<color name="black87">#de000000</color>
 	<color name="black54">#8a000000</color>
+	<color name="black26">#42000000</color>
 	<color name="black12">#1f000000</color>
 	<color name="white">#ffffffff</color>
 	<color name="white70">#b2ffffff</color>