diff options
Diffstat (limited to 'src/main/java/eu/siacs/conversations')
9 files changed, 287 insertions, 73 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java new file mode 100644 index 000000000..ed67dc65f --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java @@ -0,0 +1,162 @@ +package eu.siacs.conversations.crypto; + +import android.app.PendingIntent; + +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.UiCallback; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public class PgpDecryptionService { + + private final XmppConnectionService xmppConnectionService; + private final ConcurrentHashMap<String, List<Message>> messages = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<String, Boolean> decryptingMessages = new ConcurrentHashMap<>(); + private Boolean keychainLocked = false; + private final Object keychainLockedLock = new Object(); + + public PgpDecryptionService(XmppConnectionService xmppConnectionService) { + this.xmppConnectionService = xmppConnectionService; + } + + public void add(Message message) { + if (isRunning()) { + decryptDirectly(message); + } else { + store(message); + } + } + + public void addAll(List<Message> messagesList) { + if (!messagesList.isEmpty()) { + String conversationUuid = messagesList.get(0).getConversation().getUuid(); + if (!messages.containsKey(conversationUuid)) { + List<Message> list = Collections.synchronizedList(new LinkedList<Message>()); + messages.put(conversationUuid, list); + } + synchronized (messages.get(conversationUuid)) { + messages.get(conversationUuid).addAll(messagesList); + } + decryptAllMessages(); + } + } + + public void onKeychainUnlocked() { + synchronized (keychainLockedLock) { + keychainLocked = false; + } + decryptAllMessages(); + } + + public void onKeychainLocked() { + synchronized (keychainLockedLock) { + keychainLocked = true; + } + xmppConnectionService.updateConversationUi(); + } + + public void onOpenPgpServiceBound() { + decryptAllMessages(); + } + + public boolean isRunning() { + synchronized (keychainLockedLock) { + return !keychainLocked; + } + } + + private void store(Message message) { + if (messages.containsKey(message.getConversation().getUuid())) { + messages.get(message.getConversation().getUuid()).add(message); + } else { + List<Message> messageList = Collections.synchronizedList(new LinkedList<Message>()); + messageList.add(message); + messages.put(message.getConversation().getUuid(), messageList); + } + } + + private void decryptAllMessages() { + for (String uuid : messages.keySet()) { + decryptMessages(uuid); + } + } + + private void decryptMessages(final String uuid) { + synchronized (decryptingMessages) { + Boolean decrypting = decryptingMessages.get(uuid); + if ((decrypting != null && !decrypting) || decrypting == null) { + decryptingMessages.put(uuid, true); + decryptMessage(uuid); + } + } + } + + private void decryptMessage(final String uuid) { + Message message = null; + synchronized (messages.get(uuid)) { + while (!messages.get(uuid).isEmpty()) { + if (messages.get(uuid).get(0).getEncryption() == Message.ENCRYPTION_PGP) { + if (isRunning()) { + message = messages.get(uuid).remove(0); + } + break; + } else { + messages.get(uuid).remove(0); + } + } + if (message != null && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + messages.get(uuid).add(0, message); + decryptingMessages.put(uuid, false); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + }); + } else { + decryptingMessages.put(uuid, false); + } + } + } + + private void decryptDirectly(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_PGP && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + store(message); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + xmppConnectionService.getNotificationService().updateNotification(false); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + } + }); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index 8f8122f0e..257d0f7e8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -50,6 +50,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -64,6 +65,7 @@ public class PgpEngine { && manager.getAutoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } + mXmppConnectionService.updateMessage(message); callback.success(message); } } catch (IOException e) { @@ -158,6 +160,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -203,6 +206,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getContact().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -252,6 +256,7 @@ public class PgpEngine { InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); ByteArrayOutputStream os = new ByteArrayOutputStream(); Intent result = api.executeApi(params, is, os); + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -282,6 +287,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { case OpenPgpApi.RESULT_CODE_SUCCESS: StringBuilder signatureBuilder = new StringBuilder(); @@ -368,4 +374,17 @@ public class PgpEngine { return (PendingIntent) result .getParcelableExtra(OpenPgpApi.RESULT_INTENT); } + + private void notifyPgpDecryptionService(Account account, String action, final Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + if (OpenPgpApi.ACTION_SIGN.equals(action)) { + account.getPgpDecryptionService().onKeychainUnlocked(); + } + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + account.getPgpDecryptionService().onKeychainLocked(); + break; + } + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index ebfd98055..06f2264b4 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -4,6 +4,7 @@ import android.content.ContentValues; import android.database.Cursor; import android.os.SystemClock; +import eu.siacs.conversations.crypto.PgpDecryptionService; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; @@ -127,6 +128,7 @@ public class Account extends AbstractEntity { protected boolean online = false; private OtrService mOtrService = null; private AxolotlService axolotlService = null; + private PgpDecryptionService pgpDecryptionService = null; private XmppConnection xmppConnection = null; private long mEndGracePeriod = 0L; private String otrFingerprint; @@ -300,12 +302,17 @@ public class Account extends AbstractEntity { if (xmppConnection != null) { xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); } + this.pgpDecryptionService = new PgpDecryptionService(context); } public OtrService getOtrService() { return this.mOtrService; } + public PgpDecryptionService getPgpDecryptionService() { + return pgpDecryptionService; + } + public XmppConnection getXmppConnection() { return this.xmppConnection; } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 5f3f1a6e3..473ef0fe0 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -777,6 +777,7 @@ public class Conversation extends AbstractEntity implements Blockable { synchronized (this.messages) { this.messages.addAll(index, messages); } + account.getPgpDecryptionService().addAll(messages); } public void sort() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index e39df085f..c534a4176 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.parser; import android.util.Log; import android.util.Pair; +import eu.siacs.conversations.crypto.PgpDecryptionService; import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; @@ -114,6 +115,13 @@ public class MessageParser extends AbstractParser implements return finishedMessage; } + private Message parsePGPChat(final Conversation conversation, String pgpEncrypted, int status) { + final Message message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + PgpDecryptionService pgpDecryptionService = conversation.getAccount().getPgpDecryptionService(); + pgpDecryptionService.add(message); + return message; + } + private class Invite { Jid jid; String password; @@ -337,7 +345,7 @@ public class MessageParser extends AbstractParser implements message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } } else if (pgpEncrypted != null) { - message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + message = parsePGPChat(conversation, pgpEncrypted, status); } else if (axolotlEncrypted != null) { message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status); if (message == null) { diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index e9095020d..125b1c268 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -177,7 +177,7 @@ public class NotificationService { mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary)); } - private void updateNotification(final boolean notify) { + public void updateNotification(final boolean notify) { final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService .getSystemService(Context.NOTIFICATION_SERVICE); final SharedPreferences preferences = mXmppConnectionService.getPreferences(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cc8f4784c..613a57585 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -37,6 +37,7 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; +import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; @@ -659,7 +660,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); this.fileObserver.startWatching(); - this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain"); + this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { + @Override + public void onBound(IOpenPgpService service) { + for (Account account : accounts) { + if (account.getPgpDecryptionService() != null) { + account.getPgpDecryptionService().onOpenPgpServiceBound(); + } + } + } + + @Override + public void onError(Exception e) { } + }); this.pgpServiceConnection.bindToService(); this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 8bd512144..1345ada54 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -1194,8 +1194,7 @@ public class ConversationActivity extends XmppActivity super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_DECRYPT_PGP) { - mConversationFragment.hideSnackbar(); - mConversationFragment.updateMessages(); + mConversationFragment.onActivityResult(requestCode, resultCode, data); } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { mPendingImageUris.clear(); mPendingImageUris.addAll(extractUriFromIntent(data)); @@ -1240,6 +1239,9 @@ public class ConversationActivity extends XmppActivity } else { mPendingImageUris.clear(); mPendingFileUris.clear(); + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 05d6e5ee3..7b83c2bef 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -11,6 +11,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; +import android.support.annotation.Nullable; import android.text.InputType; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -199,21 +200,47 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } }; - private IntentSender askForPassphraseIntent = null; + private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0; + private final int KEYCHAIN_UNLOCK_REQUIRED = 1; + private final int KEYCHAIN_UNLOCK_PENDING = 2; + private int keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; protected OnClickListener clickToDecryptListener = new OnClickListener() { @Override public void onClick(View v) { - if (activity.hasPgp() && askForPassphraseIntent != null) { - try { - getActivity().startIntentSenderForResult( - askForPassphraseIntent, - ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, - 0, 0); - askForPassphraseIntent = null; - } catch (SendIntentException e) { - // + if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED + && activity.hasPgp() && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_PENDING; + updateSnackBar(conversation); + Message message = getLastPgpDecryptableMessage(); + if (message != null) { + activity.xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + @Override + public void success(Message object) { + conversation.getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void error(int errorCode, Message object) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + try { + activity.startIntentSenderForResult(pi.getIntentSender(), + ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); + } catch (SendIntentException e) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } + } + }); } + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); } } }; @@ -224,8 +251,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.verifyOtrSessionDialog(conversation, v); } }; - private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); - private boolean mDecryptJobRunning = false; private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { @Override @@ -629,7 +654,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onStop() { - mDecryptJobRunning = false; super.onStop(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); @@ -661,10 +685,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.conversation.trim(); } - this.askForPassphraseIntent = null; + this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; this.conversation = conversation; - this.mDecryptJobRunning = false; - this.mEncryptedMessages.clear(); if (this.conversation.getMode() == Conversation.MODE_MULTI) { this.conversation.setNextCounterpart(null); } @@ -767,7 +789,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa default: break; } - } else if (askForPassphraseIntent != null) { + } else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) { showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); } else if (mode == Conversation.MODE_SINGLE && conversation.smpRequested()) { @@ -791,19 +813,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { - updateSnackBar(this.conversation); conversation.populateWithMessages(ConversationFragment.this.messageList); - for (final Message message : this.messageList) { - if (message.getEncryption() == Message.ENCRYPTION_PGP - && (message.getStatus() == Message.STATUS_RECEIVED || message - .getStatus() >= Message.STATUS_SEND) - && message.getTransferable() == null) { - if (!mEncryptedMessages.contains(message)) { - mEncryptedMessages.add(message); - } - } - } - decryptNext(); + updatePgpMessages(); + updateSnackBar(conversation); updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); updateChatMsgHint(); @@ -815,46 +827,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void decryptNext() { - Message next = this.mEncryptedMessages.peek(); - PgpEngine engine = activity.xmppConnectionService.getPgpEngine(); - - if (next != null && engine != null && !mDecryptJobRunning) { - mDecryptJobRunning = true; - engine.decrypt(next, new UiCallback<Message>() { - - @Override - public void userInputRequried(PendingIntent pi, Message message) { - mDecryptJobRunning = false; - askForPassphraseIntent = pi.getIntentSender(); - updateSnackBar(conversation); - } - - @Override - public void success(Message message) { - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { - - } - askForPassphraseIntent = null; - activity.xmppConnectionService.updateMessage(message); - } - - @Override - public void error(int error, Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { + public void updatePgpMessages() { + if (keychainUnlock != KEYCHAIN_UNLOCK_PENDING) { + if (getLastPgpDecryptableMessage() != null + && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_REQUIRED; + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + } + } - } - activity.refreshUi(); - } - }); + @Nullable + private Message getLastPgpDecryptableMessage() { + for (final Message message : this.messageList) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND) + && message.getTransferable() == null) { + return message; + } } + return null; } private void messageSent() { @@ -1274,7 +1267,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void onActivityResult(int requestCode, int resultCode, final Intent data) { if (resultCode == Activity.RESULT_OK) { - if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { final String body = mEditMessage.getText().toString(); Message message = new Message(conversation, body, conversation.getNextEncryption()); sendAxolotlMessage(message); @@ -1282,6 +1279,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); } + } else { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } } } |