From ab2d114bbc21a5c2d684f8760cb8e4cea54be5de Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Mon, 20 Jul 2015 22:18:24 +0200 Subject: [PATCH] Add purge axolotl key option Can now long-press a key to permanently purge it. --- .../crypto/axolotl/AxolotlService.java | 84 ++++++++++++------- .../crypto/axolotl/XmppAxolotlMessage.java | 7 +- .../ui/ContactDetailsActivity.java | 3 +- .../conversations/ui/EditAccountActivity.java | 3 +- .../siacs/conversations/ui/XmppActivity.java | 37 +++++++- src/main/res/values/strings.xml | 3 + 6 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 1fe455ff14851e2b824221ef4fe8fb160bcd75df..6e28f11196f1cdc0b1727252b827a4a7db12477d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -100,7 +100,8 @@ public class AxolotlService { public enum Trust { UNDECIDED, // 0 TRUSTED, - UNTRUSTED; + UNTRUSTED, + COMPROMISED; public String toString() { switch(this){ @@ -514,41 +515,64 @@ public class AxolotlService { return fingerprint; } + private SQLiteAxolotlStore.Trust getTrust() { + return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + } + + @Nullable public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { byte[] plaintext = null; - try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); - if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint); - } else { - this.fingerprint = fingerprint; - plaintext = cipher.decrypt(message); - if (message.getPreKeyId().isPresent()) { - preKeyId = message.getPreKeyId().get(); + switch (getTrust()) { + case UNDECIDED: + case TRUSTED: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); + if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint); + } else { + this.fingerprint = fingerprint; + plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } + } + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received"); + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); } - } catch (InvalidMessageException | InvalidVersionException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received"); - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); - plaintext = cipher.decrypt(message); - } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); - } - } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); + + break; + + case COMPROMISED: + case UNTRUSTED: + default: + // ignore + break; } return plaintext; } - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(byte[] outgoingMessage) { - CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; + @Nullable + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + SQLiteAxolotlStore.Trust trust = getTrust(); + if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; + } else { + return null; + } } } @@ -742,6 +766,10 @@ public class AxolotlService { }); } + public void purgeKey(IdentityKey identityKey) { + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED); + } + public void publishOwnDeviceIdIfNeeded() { IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 1378c94a89efc23e10a23139c0d179f7a2983a08..ec068ec7ed289a38be71801c0d663b33cbd05d75 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.crypto.axolotl; +import android.support.annotation.Nullable; import android.util.Base64; import java.security.InvalidAlgorithmParameterException; @@ -145,8 +146,10 @@ public class XmppAxolotlMessage { return headers; } - public void addHeader(XmppAxolotlMessageHeader header) { - headers.add(header); + public void addHeader(@Nullable XmppAxolotlMessageHeader header) { + if (header != null) { + headers.add(header); + } } public byte[] getInnerKey(){ diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index cc2ef27c01f6510b677173a4634af3975c194b52..16e16cff43d32ef732862fde49241acec5cc0513 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -384,8 +384,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( contact.getAccount(), contact.getJid().toBareJid().toString())) { - hasKeys = true; - addFingerprintRow(keys, contact.getAccount(), identityKey); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey); } if (contact.getPgpKeyId() != 0) { hasKeys = true; diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index e7b513293c19b7f4d85eeb99e6783e37934a162c..c9e700822d2b07a82d1bc39e4d54d9201898bcf5 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -566,8 +566,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if(ownKey.equals(identityKey)) { continue; } - hasKeys = true; - addFingerprintRow(keys, mAccount, identityKey); + hasKeys |= addFingerprintRow(keys, mAccount, identityKey); } if (hasKeys) { keysCard.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 69357224d4e130299f6422c8f82c60807d4c1917..197836278d4a180c4e5a9fbf890bac1cee30f902 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -601,11 +601,11 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - protected void addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { + protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); - addFingerprintRowWithListeners(keys, account, identityKey, trust, true, + return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -633,13 +633,16 @@ public abstract class XmppActivity extends Activity { ); } - protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account, - IdentityKey identityKey, + protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, + final IdentityKey identityKey, AxolotlService.SQLiteAxolotlStore.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { + if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) { + return false; + } View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); TextView key = (TextView) view.findViewById(R.id.key); TextView keyType = (TextView) view.findViewById(R.id.key_type); @@ -647,6 +650,13 @@ public abstract class XmppActivity extends Activity { trustToggle.setVisibility(View.VISIBLE); trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); trustToggle.setOnClickListener(onClickListener); + view.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showPurgeKeyDialog(account, identityKey); + return true; + } + }); switch (trust) { case UNTRUSTED: @@ -668,7 +678,26 @@ public abstract class XmppActivity extends Activity { key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); keys.addView(view); + return true; + } + public void showPurgeKeyDialog(final Account account, final IdentityKey identityKey) { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.purge_key)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.purge_key_desc_part1) + + "\n\n" + CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()) + + "\n\n" + getString(R.string.purge_key_desc_part2)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + account.getAxolotlService().purgeKey(identityKey); + refreshUi(); + } + }); + builder.create().show(); } public void selectPresence(final Conversation conversation, diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 0f992884e7c2c397cd0012c69c6607ae389d8f2f..7f3408fc9e3c2a8a9b33703bf8dd327ba227441c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -393,6 +393,9 @@ Wipe other devices from PEP Clear devices Are you sure you want to clear all other devices from the axolotl announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime. + Purge key + Are you sure you want to purge this key? + It will irreversibly be considered compromised, and you can never build a session with it again. Fetching history from server No more history on server Updating…