aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main/AndroidManifest.xml4
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java125
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java9
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java46
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java23
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java237
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java9
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java5
-rw-r--r--src/main/res/layout/activity_trust_keys.xml120
-rw-r--r--src/main/res/values/strings.xml3
11 files changed, 580 insertions, 57 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 9fe37017..b0611f84 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -131,6 +131,10 @@
</intent-filter>
</activity>
<activity
+ android:name=".ui.TrustKeysActivity"
+ android:label="@string/trust_keys"
+ android:windowSoftInputMode="stateAlwaysHidden"/>
+ <activity
android:name="de.duenndns.ssl.MemorizingActivity"
android:theme="@style/ConversationsTheme"
tools:replace="android:theme"/>
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 8358125d..b05112a3 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -115,6 +115,10 @@ public class AxolotlService {
return "Untrusted";
}
}
+
+ public static Trust fromBoolean(Boolean trusted) {
+ return trusted?TRUSTED:UNTRUSTED;
+ }
};
private static IdentityKeyPair generateIdentityKeyPair() {
@@ -275,6 +279,10 @@ public class AxolotlService {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
}
+ public Set<IdentityKey> getContactUndecidedKeys(String bareJid) {
+ return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, Trust.UNDECIDED);
+ }
+
// --------------------------------------
// SessionStore
// --------------------------------------
@@ -658,6 +666,14 @@ public class AxolotlService {
return axolotlStore.getIdentityKeyPair().getPublicKey();
}
+ public Set<IdentityKey> getPendingKeys() {
+ return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString());
+ }
+
+ public Set<IdentityKey> getPendingKeys(Contact contact) {
+ return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString());
+ }
+
private AxolotlAddress getAddressForJid(Jid jid) {
return new AxolotlAddress(jid.toString(), 0);
}
@@ -852,14 +868,32 @@ public class AxolotlService {
axolotlStore.setFingerprintTrust(fingerprint, trust);
}
- private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building new sesstion for " + address.getDeviceId());
+ private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId());
try {
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
Jid.fromString(address.getName()), address.getDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket);
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
+ private void finish() {
+ AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0);
+ AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
+ if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
+ && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
+ if (flushWaitingQueueAfterFetch) {
+ conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL,
+ new Conversation.OnMessageFound() {
+ @Override
+ public void onMessageFound(Message message) {
+ processSending(message);
+ }
+ });
+ }
+ mXmppConnectionService.newKeysAvailable();
+ }
+ }
+
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing...");
@@ -869,6 +903,7 @@ public class AxolotlService {
if (preKeyBundleList.isEmpty() || bundle == null) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet);
fetchStatusMap.put(address, FetchStatus.ERROR);
+ finish();
return;
}
Random random = new Random();
@@ -876,6 +911,7 @@ public class AxolotlService {
if (preKey == null) {
//should never happen
fetchStatusMap.put(address, FetchStatus.ERROR);
+ finish();
return;
}
@@ -898,18 +934,7 @@ public class AxolotlService {
fetchStatusMap.put(address, FetchStatus.ERROR);
}
- AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0);
- AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
- if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
- conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL,
- new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- processSending(message);
- }
- });
- }
+ finish();
}
});
} catch (InvalidJidException e) {
@@ -917,48 +942,75 @@ public class AxolotlService {
}
}
- private boolean createSessionsIfNeeded(Conversation conversation) {
- boolean newSessions = false;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Creating axolotl sessions if needed...");
+ public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid());
Jid contactJid = conversation.getContact().getJid().toBareJid();
Set<AxolotlAddress> addresses = new HashSet<>();
if(deviceIds.get(contactJid) != null) {
for(Integer foreignId:this.deviceIds.get(contactJid)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+foreignId);
- addresses.add(new AxolotlAddress(contactJid.toString(), foreignId));
+ AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
+ if(sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if ( identityKey != null ) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + foreignId);
+ addresses.add(new AxolotlAddress(contactJid.toString(), foreignId));
+ }
+ }
}
} else {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Checking own account "+account.getJid().toBareJid());
if(deviceIds.get(account.getJid().toBareJid()) != null) {
for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+ownId);
- addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId));
+ AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
+ if(sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if ( identityKey != null ) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
+ addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId));
+ }
+ }
}
}
+
+ return addresses;
+ }
+
+ public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
+ boolean newSessions = false;
+ Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
for (AxolotlAddress address : addresses) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Processing device: " + address.toString());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
FetchStatus status = fetchStatusMap.get(address);
- XmppAxolotlSession session = sessions.get(address);
- if ( session == null && ( status == null || status == FetchStatus.ERROR) ) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if ( identityKey != null ) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
- session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
- sessions.put(address, session);
- } else {
+ if ( status == null || status == FetchStatus.ERROR ) {
fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(conversation, address);
+ this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch);
newSessions = true;
- }
} else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString());
}
}
+
return newSessions;
}
+ public boolean hasPendingKeyFetches(Conversation conversation) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0);
+ AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
+ return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
+ ||fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
+
+ }
+
@Nullable
public XmppAxolotlMessage encrypt(Message message ){
final String content;
@@ -1013,10 +1065,9 @@ public class AxolotlService {
});
}
- public void prepareMessage(Message message) {
+ public void prepareMessage(final Message message) {
if (!messageCache.containsKey(message.getUuid())) {
- boolean newSessions = createSessionsIfNeeded(message.getConversation());
-
+ boolean newSessions = createSessionsIfNeeded(message.getConversation(), true);
if (!newSessions) {
this.processSending(message);
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 39ef5d36..a2c62a8c 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -834,10 +834,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
+ return loadIdentityKeys(account, name, null);
+ }
+
+ public Set<IdentityKey> loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) {
Set<IdentityKey> identityKeys = new HashSet<>();
Cursor cursor = getIdentityKeyCursor(account, name, false);
while(cursor.moveToNext()) {
+ if ( trust != null &&
+ cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED))
+ != trust.ordinal()) {
+ continue;
+ }
try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
} catch (InvalidKeyException e) {
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 08c0b3fa..cc113cef 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -85,6 +85,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.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
@@ -307,6 +308,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 SecureRandom mRandom;
private OpenPgpServiceConnection pgpServiceConnection;
private PgpEngine mPgpEngine = null;
@@ -1344,17 +1347,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
switchToForeground();
}
this.mOnUpdateBlocklist = listener;
- if (this.updateBlocklistListenerCount < 2) {
- this.updateBlocklistListenerCount++;
+ if (this.newKeysAvailableListenerCount < 2) {
+ this.newKeysAvailableListenerCount++;
}
}
}
public void removeOnUpdateBlocklistListener() {
synchronized (this) {
- this.updateBlocklistListenerCount--;
- if (this.updateBlocklistListenerCount <= 0) {
- this.updateBlocklistListenerCount = 0;
+ this.newKeysAvailableListenerCount--;
+ if (this.newKeysAvailableListenerCount <= 0) {
+ this.newKeysAvailableListenerCount = 0;
this.mOnUpdateBlocklist = null;
if (checkListeners()) {
switchToBackground();
@@ -1363,6 +1366,30 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void setOnNewKeysAvailableListener(final OnNewKeysAvailable listener) {
+ synchronized (this) {
+ if (checkListeners()) {
+ switchToForeground();
+ }
+ this.mOnNewKeysAvailable = listener;
+ if (this.newKeysAvailableListenerCount < 2) {
+ this.newKeysAvailableListenerCount++;
+ }
+ }
+ }
+
+ public void removeOnNewKeysAvailableListener() {
+ synchronized (this) {
+ this.newKeysAvailableListenerCount--;
+ if (this.newKeysAvailableListenerCount <= 0) {
+ this.newKeysAvailableListenerCount = 0;
+ this.mOnNewKeysAvailable = null;
+ if (checkListeners()) {
+ switchToBackground();
+ }
+ }
+ }
+ }
public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
synchronized (this) {
if (checkListeners()) {
@@ -1393,7 +1420,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
&& this.mOnConversationUpdate == null
&& this.mOnRosterUpdate == null
&& this.mOnUpdateBlocklist == null
- && this.mOnShowErrorToast == null);
+ && this.mOnShowErrorToast == null
+ && this.mOnNewKeysAvailable == null);
}
private void switchToForeground() {
@@ -2281,6 +2309,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void newKeysAvailable() {
+ if(mOnNewKeysAvailable != null) {
+ mOnNewKeysAvailable.onNewKeysAvailable();
+ }
+ }
+
public Account findAccountByJid(final Jid accountJid) {
for (Account account : this.accounts) {
if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 2e50af3b..a6cd0431 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -65,11 +65,14 @@ public class ConversationActivity extends XmppActivity
public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
+ public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208;
+ public static final int REQUEST_TRUST_KEYS_MENU = 0x0209;
public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
+ public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
private static final String STATE_PANEL_OPEN = "state_panel_open";
private static final String STATE_PENDING_URI = "state_pending_uri";
@@ -79,6 +82,7 @@ public class ConversationActivity extends XmppActivity
final private List<Uri> mPendingImageUris = new ArrayList<>();
final private List<Uri> mPendingFileUris = new ArrayList<>();
private Uri mPendingGeoUri = null;
+ private boolean forbidProcessingPendings = false;
private View mContentView;
@@ -401,7 +405,7 @@ public class ConversationActivity extends XmppActivity
return true;
}
- private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
+ protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
final Conversation conversation = getSelectedConversation();
final Account account = conversation.getAccount();
final OnPresenceSelected callback = new OnPresenceSelected() {
@@ -537,7 +541,9 @@ public class ConversationActivity extends XmppActivity
showInstallPgpDialog();
}
} else {
- selectPresenceToAttachFile(attachmentChoice,encryption);
+ if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
+ selectPresenceToAttachFile(attachmentChoice, encryption);
+ }
}
}
@@ -962,18 +968,23 @@ public class ConversationActivity extends XmppActivity
this.mConversationFragment.reInit(getSelectedConversation());
}
- for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
- attachImageToConversation(getSelectedConversation(),i.next());
- }
+ if(!forbidProcessingPendings) {
+ for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ Uri foo = i.next();
+ attachImageToConversation(getSelectedConversation(), foo);
+ }
- for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
- attachFileToConversation(getSelectedConversation(),i.next());
- }
+ for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ attachFileToConversation(getSelectedConversation(), i.next());
+ }
- if (mPendingGeoUri != null) {
- attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
- mPendingGeoUri = null;
+ if (mPendingGeoUri != null) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+ mPendingGeoUri = null;
+ }
}
+ forbidProcessingPendings = false;
+
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
setIntent(new Intent());
}
@@ -1083,6 +1094,9 @@ public class ConversationActivity extends XmppActivity
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
this.mPendingGeoUri = null;
}
+ } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) {
+ this.forbidProcessingPendings = !xmppConnectionServiceBound;
+ mConversationFragment.onActivityResult(requestCode, resultCode, data);
}
} else {
mPendingImageUris.clear();
@@ -1235,6 +1249,26 @@ public class ConversationActivity extends XmppActivity
return getPreferences().getBoolean("indicate_received", false);
}
+ protected boolean trustKeysIfNeeded(int requestCode) {
+ return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
+ }
+
+ protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
+ AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
+ if(!axolotlService.getPendingKeys(mSelectedConversation.getContact()).isEmpty()
+ || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty()) {
+ axolotlService.createSessionsIfNeeded(mSelectedConversation, false);
+ Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
+ intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
+ intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString());
+ intent.putExtra("choice", attachmentChoice);
+ startActivityForResult(intent, requestCode);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
@Override
protected void refreshUiReal() {
updateConversationList();
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 026c74ad..15491dea 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -1,5 +1,6 @@
package eu.siacs.conversations.ui;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.PendingIntent;
@@ -11,6 +12,7 @@ import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.text.InputType;
+import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
@@ -43,6 +45,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
@@ -304,7 +307,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
sendPgpMessage(message);
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
- sendAxolotlMessage(message);
+ if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
+ sendAxolotlMessage(message);
+ }
} else {
sendPlainTextMessage(message);
}
@@ -1128,7 +1133,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
protected void sendAxolotlMessage(final Message message) {
final ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService;
- //message.setCounterpart(conversation.getNextCounterpart());
xmppService.sendMessage(message);
messageSent();
}
@@ -1195,4 +1199,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton();
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ final Intent data) {
+ if (resultCode == Activity.RESULT_OK) {
+ if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
+ final String body = mEditMessage.getText().toString();
+ Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
+ sendAxolotlMessage(message);
+ } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
+ int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
+ activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption()));
+ }
+ }
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
new file mode 100644
index 00000000..4efa4f6c
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -0,0 +1,237 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
+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.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+public class TrustKeysActivity extends XmppActivity implements OnNewKeysAvailable {
+ private Jid accountJid;
+ private Jid contactJid;
+
+ private Contact contact;
+ private TextView ownKeysTitle;
+ private LinearLayout ownKeys;
+ private LinearLayout ownKeysCard;
+ private TextView foreignKeysTitle;
+ private LinearLayout foreignKeys;
+ private LinearLayout foreignKeysCard;
+ private Button mSaveButton;
+ private Button mCancelButton;
+
+ private final Map<IdentityKey, Boolean> ownKeysToTrust = new HashMap<>();
+ private final Map<IdentityKey, Boolean> foreignKeysToTrust = new HashMap<>();
+
+ private final OnClickListener mSaveButtonListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ commitTrusts();
+ Intent data = new Intent();
+ data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ };
+
+ private final OnClickListener mCancelButtonListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ };
+
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ populateView();
+ }
+
+ @Override
+ protected String getShareableUri() {
+ if (contact != null) {
+ return contact.getShareableUri();
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_trust_keys);
+ try {
+ this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
+ } catch (final InvalidJidException ignored) {
+ }
+ try {
+ this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
+ } catch (final InvalidJidException ignored) {
+ }
+
+ ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
+ ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
+ ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
+ foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
+ foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
+ foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
+ mCancelButton = (Button) findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(mCancelButtonListener);
+ mSaveButton = (Button) findViewById(R.id.save_button);
+ mSaveButton.setOnClickListener(mSaveButtonListener);
+
+
+ if (getActionBar() != null) {
+ getActionBar().setHomeButtonEnabled(true);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ private void populateView() {
+ setTitle(getString(R.string.trust_keys));
+ ownKeys.removeAllViews();
+ foreignKeys.removeAllViews();
+ boolean hasOwnKeys = false;
+ boolean hasForeignKeys = false;
+ for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
+ hasOwnKeys = true;
+ addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey,
+ Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ ownKeysToTrust.put(identityKey, isChecked);
+ refreshUi();
+ xmppConnectionService.updateAccountUi();
+ xmppConnectionService.updateConversationUi();
+ }
+ },
+ null
+ );
+ }
+ for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
+ hasForeignKeys = true;
+ addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey,
+ Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ foreignKeysToTrust.put(identityKey, isChecked);
+ refreshUi();
+ xmppConnectionService.updateAccountUi();
+ xmppConnectionService.updateConversationUi();
+ }
+ },
+ null
+ );
+ }
+
+ if(hasOwnKeys) {
+ ownKeysTitle.setText(accountJid.toString());
+ ownKeysCard.setVisibility(View.VISIBLE);
+ }
+ if(hasForeignKeys) {
+ foreignKeysTitle.setText(contactJid.toString());
+ foreignKeysCard.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void getFingerprints(final Account account) {
+ Set<IdentityKey> ownKeysSet = account.getAxolotlService().getPendingKeys();
+ for(final IdentityKey identityKey : ownKeysSet) {
+ if(!ownKeysToTrust.containsKey(identityKey)) {
+ ownKeysToTrust.put(identityKey, false);
+ }
+ }
+ Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getPendingKeys(contact);
+ for(final IdentityKey identityKey : foreignKeysSet) {
+ if(!foreignKeysToTrust.containsKey(identityKey)) {
+ foreignKeysToTrust.put(identityKey, false);
+ }
+ }
+ }
+
+ @Override
+ public void onBackendConnected() {
+ if ((accountJid != null) && (contactJid != null)) {
+ final Account account = xmppConnectionService
+ .findAccountByJid(accountJid);
+ if (account == null) {
+ return;
+ }
+ this.contact = account.getRoster().getContact(contactJid);
+ ownKeysToTrust.clear();
+ foreignKeysToTrust.clear();
+ getFingerprints(account);
+
+ Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false);
+ if(account.getAxolotlService().hasPendingKeyFetches(conversation)) {
+ lock();
+ }
+
+ populateView();
+ }
+ }
+
+ @Override
+ public void onNewKeysAvailable() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final Account account = xmppConnectionService
+ .findAccountByJid(accountJid);
+ unlock();
+ getFingerprints(account);
+ refreshUi();
+ }
+ });
+ }
+
+ private void commitTrusts() {
+ for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
+ contact.getAccount().getAxolotlService().setFingerprintTrust(
+ identityKey.getFingerprint().replaceAll("\\s", ""),
+ Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
+ }
+ for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
+ contact.getAccount().getAxolotlService().setFingerprintTrust(
+ identityKey.getFingerprint().replaceAll("\\s", ""),
+ Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
+ }
+ }
+
+ private void unlock() {
+ mSaveButton.setEnabled(true);
+ mSaveButton.setText(getString(R.string.done));
+ mSaveButton.setTextColor(getPrimaryTextColor());
+ }
+
+ private void lock() {
+ mSaveButton.setEnabled(false);
+ mSaveButton.setText(getString(R.string.fetching_keys));
+ mSaveButton.setTextColor(getSecondaryTextColor());
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index 9dfece2f..00322452 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -84,6 +84,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.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -296,6 +297,9 @@ 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);
+ }
}
protected void unregisterListeners() {
@@ -317,6 +321,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof XmppConnectionService.OnShowErrorToast) {
this.xmppConnectionService.removeOnShowErrorToastListener();
}
+ if (this instanceof OnNewKeysAvailable) {
+ this.xmppConnectionService.removeOnNewKeysAvailableListener();
+ }
}
@Override
@@ -452,7 +459,7 @@ public abstract class XmppActivity extends Activity {
@Override
public void userInputRequried(PendingIntent pi,
- Account account) {
+ Account account) {
try {
startIntentSenderForResult(pi.getIntentSender(),
REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java
new file mode 100644
index 00000000..59dc1c1e
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/xmpp/OnNewKeysAvailable.java
@@ -0,0 +1,5 @@
+package eu.siacs.conversations.xmpp;
+
+public interface OnNewKeysAvailable {
+ public void onNewKeysAvailable();
+}
diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml
new file mode 100644
index 00000000..c535d51d
--- /dev/null
+++ b/src/main/res/layout/activity_trust_keys.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/grey200" >
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/button_bar"
+ android:layout_alignParentTop="true" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/own_keys_card"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
+ android:background="@drawable/infocard_border"
+ android:orientation="vertical"
+ android:padding="@dimen/infocard_padding"
+ android:visibility="gone">
+
+ <TextView
+ android:id="@+id/own_keys_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold"/>
+
+ <LinearLayout
+ android:id="@+id/own_keys_details"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="?android:dividerHorizontal"
+ android:showDividers="middle"
+ android:orientation="vertical">
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/foreign_keys_card"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
+ android:background="@drawable/infocard_border"
+ android:orientation="vertical"
+ android:padding="@dimen/infocard_padding"
+ android:visibility="gone">
+
+ <TextView
+ android:id="@+id/foreign_keys_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold"/>
+
+ <LinearLayout
+ android:id="@+id/foreign_keys_details"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="?android:dividerHorizontal"
+ android:showDividers="middle"
+ android:orientation="vertical">
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+ </ScrollView>
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentRight="true" >
+
+ <Button
+ android:id="@+id/cancel_button"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/cancel"
+ android:textColor="@color/black87" />
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="fill_parent"
+ android:layout_marginBottom="7dp"
+ android:layout_marginTop="7dp"
+ android:background="@color/black12" />
+
+ <Button
+ android:id="@+id/save_button"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:enabled="true"
+ android:textColor="@color/black54"
+ android:text="@string/done"/>
+ </LinearLayout>
+</RelativeLayout>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index f4c75ee1..8537c118 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -211,6 +211,9 @@
<string name="axolotl_fingerprint">Axolotl fingerprint</string>
<string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string>
<string name="other_devices">Other devices</string>
+ <string name="trust_keys">Trust Axolotl Keys</string>
+ <string name="fetching_keys">Fetching keys...</string>
+ <string name="done">Done</string>
<string name="axolotl_devicelist">Other own Axolotl Devices</string>
<string name="verify">Verify</string>
<string name="decrypt">Decrypt</string>