Ask for key trust when sending messages
If the contact (or the own account) has keys that have UNDECIDED trust, we now drop the user into the new TrustKeysActivity, where they have to decide for each new key whether it should be TRUSTED or UNTRUSTED.
This commit is contained in:
parent
ec0aff4ed7
commit
14010bf5a6
|
@ -130,6 +130,10 @@
|
|||
<data android:mimeType="image/*" />
|
||||
</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"
|
||||
|
|
|
@ -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,7 +868,7 @@ public class AxolotlService {
|
|||
axolotlStore.setFingerprintTrust(fingerprint, trust);
|
||||
}
|
||||
|
||||
private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address) {
|
||||
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 {
|
||||
|
@ -860,6 +876,24 @@ public class AxolotlService {
|
|||
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)) {
|
||||
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())) {
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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,9 +541,11 @@ public class ConversationActivity extends XmppActivity
|
|||
showInstallPgpDialog();
|
||||
}
|
||||
} else {
|
||||
if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
|
||||
selectPresenceToAttachFile(attachmentChoice, encryption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
|
@ -962,8 +968,10 @@ public class ConversationActivity extends XmppActivity
|
|||
this.mConversationFragment.reInit(getSelectedConversation());
|
||||
}
|
||||
|
||||
if(!forbidProcessingPendings) {
|
||||
for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||
attachImageToConversation(getSelectedConversation(),i.next());
|
||||
Uri foo = i.next();
|
||||
attachImageToConversation(getSelectedConversation(), foo);
|
||||
}
|
||||
|
||||
for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
|
||||
|
@ -974,6 +982,9 @@ public class ConversationActivity extends XmppActivity
|
|||
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();
|
||||
|
|
|
@ -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) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
237
src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
Normal file
237
src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
public interface OnNewKeysAvailable {
|
||||
public void onNewKeysAvailable();
|
||||
}
|
120
src/main/res/layout/activity_trust_keys.xml
Normal file
120
src/main/res/layout/activity_trust_keys.xml
Normal file
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue