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:
Andreas Straub 2015-07-19 18:36:28 +02:00
parent ec0aff4ed7
commit 14010bf5a6
11 changed files with 586 additions and 63 deletions

View file

@ -130,6 +130,10 @@
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.TrustKeysActivity"
android:label="@string/trust_keys"
android:windowSoftInputMode="stateAlwaysHidden"/>
<activity <activity
android:name="de.duenndns.ssl.MemorizingActivity" android:name="de.duenndns.ssl.MemorizingActivity"
android:theme="@style/ConversationsTheme" android:theme="@style/ConversationsTheme"

View file

@ -115,6 +115,10 @@ public class AxolotlService {
return "Untrusted"; return "Untrusted";
} }
} }
public static Trust fromBoolean(Boolean trusted) {
return trusted?TRUSTED:UNTRUSTED;
}
}; };
private static IdentityKeyPair generateIdentityKeyPair() { private static IdentityKeyPair generateIdentityKeyPair() {
@ -275,6 +279,10 @@ public class AxolotlService {
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
} }
public Set<IdentityKey> getContactUndecidedKeys(String bareJid) {
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, Trust.UNDECIDED);
}
// -------------------------------------- // --------------------------------------
// SessionStore // SessionStore
// -------------------------------------- // --------------------------------------
@ -658,6 +666,14 @@ public class AxolotlService {
return axolotlStore.getIdentityKeyPair().getPublicKey(); 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) { private AxolotlAddress getAddressForJid(Jid jid) {
return new AxolotlAddress(jid.toString(), 0); return new AxolotlAddress(jid.toString(), 0);
} }
@ -852,14 +868,32 @@ public class AxolotlService {
axolotlStore.setFingerprintTrust(fingerprint, trust); 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()); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId());
try { try {
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
Jid.fromString(address.getName()), address.getDeviceId()); Jid.fromString(address.getName()), address.getDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket);
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { 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 @Override
public void onIqPacketReceived(Account account, IqPacket packet) { public void onIqPacketReceived(Account account, IqPacket packet) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing...");
@ -869,6 +903,7 @@ public class AxolotlService {
if (preKeyBundleList.isEmpty() || bundle == null) { if (preKeyBundleList.isEmpty() || bundle == null) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet); Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet);
fetchStatusMap.put(address, FetchStatus.ERROR); fetchStatusMap.put(address, FetchStatus.ERROR);
finish();
return; return;
} }
Random random = new Random(); Random random = new Random();
@ -876,6 +911,7 @@ public class AxolotlService {
if (preKey == null) { if (preKey == null) {
//should never happen //should never happen
fetchStatusMap.put(address, FetchStatus.ERROR); fetchStatusMap.put(address, FetchStatus.ERROR);
finish();
return; return;
} }
@ -898,18 +934,7 @@ public class AxolotlService {
fetchStatusMap.put(address, FetchStatus.ERROR); fetchStatusMap.put(address, FetchStatus.ERROR);
} }
AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0); finish();
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);
}
});
}
} }
}); });
} catch (InvalidJidException e) { } catch (InvalidJidException e) {
@ -917,48 +942,75 @@ public class AxolotlService {
} }
} }
private boolean createSessionsIfNeeded(Conversation conversation) { public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
boolean newSessions = false; Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid());
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Creating axolotl sessions if needed...");
Jid contactJid = conversation.getContact().getJid().toBareJid(); Jid contactJid = conversation.getContact().getJid().toBareJid();
Set<AxolotlAddress> addresses = new HashSet<>(); Set<AxolotlAddress> addresses = new HashSet<>();
if(deviceIds.get(contactJid) != null) { if(deviceIds.get(contactJid) != null) {
for(Integer foreignId:this.deviceIds.get(contactJid)) { for(Integer foreignId:this.deviceIds.get(contactJid)) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+foreignId); AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
addresses.add(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 { } else {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); 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) { if(deviceIds.get(account.getJid().toBareJid()) != null) {
for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found device "+account.getJid().toBareJid()+":"+ownId); AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId)); if(sessions.get(address) == null) {
} IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
} if ( identityKey != null ) {
for (AxolotlAddress address : addresses) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Processing device: " + address.toString()); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
FetchStatus status = fetchStatusMap.get(address); sessions.put(address, session);
XmppAxolotlSession session = sessions.get(address); } else {
if ( session == null && ( status == null || status == FetchStatus.ERROR) ) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId));
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 {
fetchStatusMap.put(address, FetchStatus.PENDING);
this.buildSessionFromPEP(conversation, address);
newSessions = true;
} }
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString());
} }
} }
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);
if ( status == null || status == FetchStatus.ERROR ) {
fetchStatusMap.put(address, FetchStatus.PENDING);
this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch);
newSessions = true;
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString());
}
}
return newSessions; 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 @Nullable
public XmppAxolotlMessage encrypt(Message message ){ public XmppAxolotlMessage encrypt(Message message ){
final String content; 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())) { if (!messageCache.containsKey(message.getUuid())) {
boolean newSessions = createSessionsIfNeeded(message.getConversation()); boolean newSessions = createSessionsIfNeeded(message.getConversation(), true);
if (!newSessions) { if (!newSessions) {
this.processSending(message); this.processSending(message);
} }

View file

@ -834,10 +834,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
} }
public Set<IdentityKey> loadIdentityKeys(Account account, String name) { 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<>(); Set<IdentityKey> identityKeys = new HashSet<>();
Cursor cursor = getIdentityKeyCursor(account, name, false); Cursor cursor = getIdentityKeyCursor(account, name, false);
while(cursor.moveToNext()) { while(cursor.moveToNext()) {
if ( trust != null &&
cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED))
!= trust.ordinal()) {
continue;
}
try { try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {

View file

@ -85,6 +85,7 @@ import eu.siacs.conversations.xmpp.OnContactStatusChanged;
import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnMessageAcknowledged; import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived; import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.OnStatusChanged; import eu.siacs.conversations.xmpp.OnStatusChanged;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
@ -307,6 +308,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private int rosterChangedListenerCount = 0; private int rosterChangedListenerCount = 0;
private OnMucRosterUpdate mOnMucRosterUpdate = null; private OnMucRosterUpdate mOnMucRosterUpdate = null;
private int mucRosterChangedListenerCount = 0; private int mucRosterChangedListenerCount = 0;
private OnNewKeysAvailable mOnNewKeysAvailable = null;
private int newKeysAvailableListenerCount = 0;
private SecureRandom mRandom; private SecureRandom mRandom;
private OpenPgpServiceConnection pgpServiceConnection; private OpenPgpServiceConnection pgpServiceConnection;
private PgpEngine mPgpEngine = null; private PgpEngine mPgpEngine = null;
@ -1344,17 +1347,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
switchToForeground(); switchToForeground();
} }
this.mOnUpdateBlocklist = listener; this.mOnUpdateBlocklist = listener;
if (this.updateBlocklistListenerCount < 2) { if (this.newKeysAvailableListenerCount < 2) {
this.updateBlocklistListenerCount++; this.newKeysAvailableListenerCount++;
} }
} }
} }
public void removeOnUpdateBlocklistListener() { public void removeOnUpdateBlocklistListener() {
synchronized (this) { synchronized (this) {
this.updateBlocklistListenerCount--; this.newKeysAvailableListenerCount--;
if (this.updateBlocklistListenerCount <= 0) { if (this.newKeysAvailableListenerCount <= 0) {
this.updateBlocklistListenerCount = 0; this.newKeysAvailableListenerCount = 0;
this.mOnUpdateBlocklist = null; this.mOnUpdateBlocklist = null;
if (checkListeners()) { if (checkListeners()) {
switchToBackground(); 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) { public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) {
synchronized (this) { synchronized (this) {
if (checkListeners()) { if (checkListeners()) {
@ -1393,7 +1420,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
&& this.mOnConversationUpdate == null && this.mOnConversationUpdate == null
&& this.mOnRosterUpdate == null && this.mOnRosterUpdate == null
&& this.mOnUpdateBlocklist == null && this.mOnUpdateBlocklist == null
&& this.mOnShowErrorToast == null); && this.mOnShowErrorToast == null
&& this.mOnNewKeysAvailable == null);
} }
private void switchToForeground() { 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) { public Account findAccountByJid(final Jid accountJid) {
for (Account account : this.accounts) { for (Account account : this.accounts) {
if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { if (account.getJid().toBareJid().equals(accountJid.toBareJid())) {

View file

@ -65,11 +65,14 @@ public class ConversationActivity extends XmppActivity
public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_DECRYPT_PGP = 0x0202;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; 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_CHOOSE_IMAGE = 0x0301;
public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; 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_OPEN_CONVERSATION = "state_open_conversation";
private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PANEL_OPEN = "state_panel_open";
private static final String STATE_PENDING_URI = "state_pending_uri"; 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> mPendingImageUris = new ArrayList<>();
final private List<Uri> mPendingFileUris = new ArrayList<>(); final private List<Uri> mPendingFileUris = new ArrayList<>();
private Uri mPendingGeoUri = null; private Uri mPendingGeoUri = null;
private boolean forbidProcessingPendings = false;
private View mContentView; private View mContentView;
@ -401,7 +405,7 @@ public class ConversationActivity extends XmppActivity
return true; 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 Conversation conversation = getSelectedConversation();
final Account account = conversation.getAccount(); final Account account = conversation.getAccount();
final OnPresenceSelected callback = new OnPresenceSelected() { final OnPresenceSelected callback = new OnPresenceSelected() {
@ -537,7 +541,9 @@ public class ConversationActivity extends XmppActivity
showInstallPgpDialog(); showInstallPgpDialog();
} }
} else { } 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()); this.mConversationFragment.reInit(getSelectedConversation());
} }
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { if(!forbidProcessingPendings) {
attachImageToConversation(getSelectedConversation(),i.next()); 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()) { for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
attachFileToConversation(getSelectedConversation(),i.next()); attachFileToConversation(getSelectedConversation(), i.next());
} }
if (mPendingGeoUri != null) { if (mPendingGeoUri != null) {
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
mPendingGeoUri = null; mPendingGeoUri = null;
}
} }
forbidProcessingPendings = false;
ExceptionHelper.checkForCrash(this, this.xmppConnectionService); ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
setIntent(new Intent()); setIntent(new Intent());
} }
@ -1083,6 +1094,9 @@ public class ConversationActivity extends XmppActivity
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
this.mPendingGeoUri = null; this.mPendingGeoUri = null;
} }
} else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) {
this.forbidProcessingPendings = !xmppConnectionServiceBound;
mConversationFragment.onActivityResult(requestCode, resultCode, data);
} }
} else { } else {
mPendingImageUris.clear(); mPendingImageUris.clear();
@ -1235,6 +1249,26 @@ public class ConversationActivity extends XmppActivity
return getPreferences().getBoolean("indicate_received", false); 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 @Override
protected void refreshUiReal() { protected void refreshUiReal() {
updateConversationList(); updateConversationList();

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.ui; package eu.siacs.conversations.ui;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -11,6 +12,7 @@ import android.content.IntentSender;
import android.content.IntentSender.SendIntentException; import android.content.IntentSender.SendIntentException;
import android.os.Bundle; import android.os.Bundle;
import android.text.InputType; import android.text.InputType;
import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity; import android.view.Gravity;
@ -43,6 +45,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation; 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) { } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
sendPgpMessage(message); sendPgpMessage(message);
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) { } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
sendAxolotlMessage(message); if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
sendAxolotlMessage(message);
}
} else { } else {
sendPlainTextMessage(message); sendPlainTextMessage(message);
} }
@ -1128,7 +1133,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
protected void sendAxolotlMessage(final Message message) { protected void sendAxolotlMessage(final Message message) {
final ConversationActivity activity = (ConversationActivity) getActivity(); final ConversationActivity activity = (ConversationActivity) getActivity();
final XmppConnectionService xmppService = activity.xmppConnectionService; final XmppConnectionService xmppService = activity.xmppConnectionService;
//message.setCounterpart(conversation.getNextCounterpart());
xmppService.sendMessage(message); xmppService.sendMessage(message);
messageSent(); messageSent();
} }
@ -1195,4 +1199,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton(); 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()));
}
}
}
} }

View 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());
}
}

View file

@ -84,6 +84,7 @@ import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinde
import eu.siacs.conversations.ui.widget.Switch; import eu.siacs.conversations.ui.widget.Switch;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.xmpp.OnNewKeysAvailable;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
@ -296,6 +297,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof XmppConnectionService.OnShowErrorToast) { if (this instanceof XmppConnectionService.OnShowErrorToast) {
this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
} }
if (this instanceof OnNewKeysAvailable) {
this.xmppConnectionService.setOnNewKeysAvailableListener((OnNewKeysAvailable) this);
}
} }
protected void unregisterListeners() { protected void unregisterListeners() {
@ -317,6 +321,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof XmppConnectionService.OnShowErrorToast) { if (this instanceof XmppConnectionService.OnShowErrorToast) {
this.xmppConnectionService.removeOnShowErrorToastListener(); this.xmppConnectionService.removeOnShowErrorToastListener();
} }
if (this instanceof OnNewKeysAvailable) {
this.xmppConnectionService.removeOnNewKeysAvailableListener();
}
} }
@Override @Override
@ -452,7 +459,7 @@ public abstract class XmppActivity extends Activity {
@Override @Override
public void userInputRequried(PendingIntent pi, public void userInputRequried(PendingIntent pi,
Account account) { Account account) {
try { try {
startIntentSenderForResult(pi.getIntentSender(), startIntentSenderForResult(pi.getIntentSender(),
REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);

View file

@ -0,0 +1,5 @@
package eu.siacs.conversations.xmpp;
public interface OnNewKeysAvailable {
public void onNewKeysAvailable();
}

View 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>

View file

@ -211,6 +211,9 @@
<string name="axolotl_fingerprint">Axolotl fingerprint</string> <string name="axolotl_fingerprint">Axolotl fingerprint</string>
<string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string> <string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string>
<string name="other_devices">Other devices</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="axolotl_devicelist">Other own Axolotl Devices</string>
<string name="verify">Verify</string> <string name="verify">Verify</string>
<string name="decrypt">Decrypt</string> <string name="decrypt">Decrypt</string>