package org.thoughtcrime.securesms.jobs; import android.content.Context; import android.util.Log; import android.util.Pair; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.LegacyMessageException; import org.whispersystems.libaxolotl.NoSessionException; import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.SessionStore; import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.textsecure.api.crypto.TextSecureCipher; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.api.messages.TextSecureMessage; import ws.com.google.android.mms.MmsException; public class PushDecryptJob extends MasterSecretJob { public static final String TAG = PushDecryptJob.class.getSimpleName(); private final long messageId; private final long smsMessageId; public PushDecryptJob(Context context, long pushMessageId, String sender) { this(context, pushMessageId, -1, sender); } public PushDecryptJob(Context context, long pushMessageId, long smsMessageId, String sender) { super(context, JobParameters.newBuilder() .withPersistence() .withRequirement(new MasterSecretRequirement(context)) .withGroupId(sender) .create()); this.messageId = pushMessageId; this.smsMessageId = smsMessageId; } @Override public void onAdded() { if (KeyCachingService.getMasterSecret(context) == null) { MessageNotifier.updateNotification(context, null, -2); } } @Override public void onRun(MasterSecret masterSecret) throws NoSuchMessageException { PushDatabase database = DatabaseFactory.getPushDatabase(context); TextSecureEnvelope envelope = database.get(messageId); handleMessage(masterSecret, envelope, smsMessageId); database.delete(messageId); } @Override public boolean onShouldRetryThrowable(Exception exception) { return false; } @Override public void onCanceled() { } private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { try { Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); long recipientId = recipients.getPrimaryRecipient().getRecipientId(); int deviceId = envelope.getSourceDevice(); AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret); TextSecureCipher cipher = new TextSecureCipher(axolotlStore, recipientId, deviceId); TextSecureMessage message = cipher.decrypt(envelope); if (message.isEndSession()) handleEndSessionMessage(masterSecret, recipientId, envelope, message, smsMessageId); else if (message.isGroupUpdate()) handleGroupMessage(masterSecret, envelope, message, smsMessageId); else if (message.getAttachments().isPresent()) handleMediaMessage(masterSecret, envelope, message, smsMessageId); else handleTextMessage(masterSecret, envelope, message, smsMessageId); if (envelope.isPreKeyWhisperMessage()) { ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context)); } } catch (InvalidVersionException e) { Log.w(TAG, e); handleInvalidVersionMessage(masterSecret, envelope, smsMessageId); } catch (InvalidMessageException | InvalidKeyIdException | InvalidKeyException | MmsException | RecipientFormattingException e) { Log.w(TAG, e); handleCorruptMessage(masterSecret, envelope, smsMessageId); } catch (NoSessionException e) { Log.w(TAG, e); handleNoSessionMessage(masterSecret, envelope, smsMessageId); } catch (LegacyMessageException e) { Log.w(TAG, e); handleLegacyMessage(masterSecret, envelope, smsMessageId); } catch (DuplicateMessageException e) { Log.w(TAG, e); handleDuplicateMessage(masterSecret, envelope, smsMessageId); } catch (UntrustedIdentityException e) { Log.w(TAG, e); handleUntrustedIdentityMessage(masterSecret, envelope, smsMessageId); } } private void handleEndSessionMessage(MasterSecret masterSecret, long recipientId, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), message.getTimestamp(), "", Optional.absent()); long threadId; if (smsMessageId <= 0) { IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage); Pair messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage); threadId = messageAndThreadId.second; } else { smsDatabase.markAsEndSession(smsMessageId); threadId = smsDatabase.getThreadIdForMessage(smsMessageId); } SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); sessionStore.deleteAllSessions(recipientId); SecurityEvent.broadcastSecurityUpdateEvent(context, threadId); MessageNotifier.updateNotification(context, masterSecret, threadId); } private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) { GroupMessageProcessor.process(context, masterSecret, envelope, message); if (smsMessageId > 0) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId); } } private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) throws MmsException { String localNumber = TextSecurePreferences.getLocalNumber(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context); IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(), localNumber, message.getTimestamp(), Optional.fromNullable(envelope.getRelay()), message.getBody(), message.getGroupInfo(), message.getAttachments()); Pair messageAndThreadId; if (message.isSecure()) { messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1); } else { messageAndThreadId = database.insertMessageInbox(masterSecret, mediaMessage, null, -1); } ApplicationContext.getInstance(context) .getJobManager() .add(new AttachmentDownloadJob(context, messageAndThreadId.first)); if (smsMessageId >= 0) { DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId); } MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message, long smsMessageId) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); String body = message.getBody().isPresent() ? message.getBody().get() : ""; if (smsMessageId > 0) { database.updateBundleMessageBody(masterSecret, smsMessageId, body); } else { IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), message.getTimestamp(), body, message.getGroupInfo()); if (message.isSecure()) { textMessage = new IncomingEncryptedMessage(textMessage, body); } Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } } private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (smsMessageId <= 0) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsInvalidVersionKeyExchange(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId); } } private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (smsMessageId <= 0) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsDecryptFailed(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { smsDatabase.markAsDecryptFailed(smsMessageId); } } private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (smsMessageId <= 0) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsNoSession(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { smsDatabase.markAsNoSession(smsMessageId); } } private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); if (smsMessageId <= 0) { Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); smsDatabase.markAsLegacyVersion(messageAndThreadId.first); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { smsDatabase.markAsLegacyVersion(smsMessageId); } } private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { // Let's start ignoring these now // SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); // // if (smsMessageId <= 0) { // Pair messageAndThreadId = insertPlaceholder(masterSecret, envelope); // smsDatabase.markAsDecryptDuplicate(messageAndThreadId.first); // MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); // } else { // smsDatabase.markAsDecryptDuplicate(smsMessageId); // } } private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, long smsMessageId) { try { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false); long recipientId = recipients.getPrimaryRecipient().getRecipientId(); PreKeyWhisperMessage whisperMessage = new PreKeyWhisperMessage(envelope.getMessage()); IdentityKey identityKey = whisperMessage.getIdentityKey(); String encoded = Base64.encodeBytes(envelope.getMessage()); IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), encoded, Optional.absent()); if (smsMessageId <= 0) { IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded); Pair messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage); database.addMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey); MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } else { database.updateMessageBody(masterSecret, smsMessageId, encoded); database.markAsPreKeyBundle(smsMessageId); database.addMismatchedIdentity(smsMessageId, recipientId, identityKey); } } catch (RecipientFormattingException | InvalidMessageException | InvalidVersionException e) { throw new AssertionError(e); } } private Pair insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) { EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), "", Optional.absent()); textMessage = new IncomingEncryptedMessage(textMessage, ""); return database.insertMessageInbox(masterSecret, textMessage); } }