256 lines
12 KiB
Java
256 lines
12 KiB
Java
package org.thoughtcrime.securesms.jobs;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.core.app.NotificationCompat;
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
|
|
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
|
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
|
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
|
|
import org.signal.libsignal.metadata.ProtocolException;
|
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
|
|
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
|
|
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
|
import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
|
|
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
|
|
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
|
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
|
import org.signal.libsignal.metadata.SelfSendException;
|
|
import org.thoughtcrime.securesms.MainActivity;
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
|
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
|
import org.thoughtcrime.securesms.database.PushDatabase;
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
|
import org.thoughtcrime.securesms.util.GroupUtil;
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
|
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
public final class PushDecryptMessageJob extends BaseJob {
|
|
|
|
public static final String KEY = "PushDecryptJob";
|
|
public static final String QUEUE = "__PUSH_DECRYPT_JOB__";
|
|
|
|
public static final String TAG = Log.tag(PushDecryptMessageJob.class);
|
|
|
|
private static final String KEY_MESSAGE_ID = "message_id";
|
|
private static final String KEY_SMS_MESSAGE_ID = "sms_message_id";
|
|
|
|
private final long messageId;
|
|
private final long smsMessageId;
|
|
|
|
public PushDecryptMessageJob(Context context, long pushMessageId) {
|
|
this(context, pushMessageId, -1);
|
|
}
|
|
|
|
public PushDecryptMessageJob(Context context, long pushMessageId, long smsMessageId) {
|
|
this(new Parameters.Builder()
|
|
.setQueue(QUEUE)
|
|
.setMaxAttempts(Parameters.UNLIMITED)
|
|
.build(),
|
|
pushMessageId,
|
|
smsMessageId);
|
|
setContext(context);
|
|
}
|
|
|
|
private PushDecryptMessageJob(@NonNull Parameters parameters, long pushMessageId, long smsMessageId) {
|
|
super(parameters);
|
|
|
|
this.messageId = pushMessageId;
|
|
this.smsMessageId = smsMessageId;
|
|
}
|
|
|
|
@Override
|
|
public @NonNull Data serialize() {
|
|
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
|
|
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
|
.build();
|
|
}
|
|
|
|
@Override
|
|
public @NonNull String getFactoryKey() {
|
|
return KEY;
|
|
}
|
|
|
|
@Override
|
|
public void onRun() throws NoSuchMessageException, RetryLaterException {
|
|
if (needsMigration()) {
|
|
Log.w(TAG, "Migration is still needed.");
|
|
postMigrationNotification();
|
|
throw new RetryLaterException();
|
|
}
|
|
|
|
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
|
SignalServiceEnvelope envelope = database.get(messageId);
|
|
JobManager jobManager = ApplicationDependencies.getJobManager();
|
|
|
|
try {
|
|
List<Job> jobs = handleMessage(envelope);
|
|
|
|
for (Job job: jobs) {
|
|
jobManager.add(job);
|
|
}
|
|
} catch (NoSenderException e) {
|
|
Log.w(TAG, "Invalid message, but no sender info!");
|
|
}
|
|
|
|
database.delete(messageId);
|
|
}
|
|
|
|
@Override
|
|
public boolean onShouldRetry(@NonNull Exception exception) {
|
|
return exception instanceof RetryLaterException;
|
|
}
|
|
|
|
@Override
|
|
public void onFailure() {
|
|
}
|
|
|
|
private boolean needsMigration() {
|
|
return !IdentityKeyUtil.hasIdentityKey(context) || TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
|
}
|
|
|
|
private void postMigrationNotification() {
|
|
// TODO [greyson] Navigation
|
|
NotificationManagerCompat.from(context).notify(494949,
|
|
new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
|
|
.setSmallIcon(R.drawable.ic_notification)
|
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
|
.setContentTitle(context.getString(R.string.PushDecryptJob_new_locked_message))
|
|
.setContentText(context.getString(R.string.PushDecryptJob_unlock_to_view_pending_messages))
|
|
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0))
|
|
.setDefaults(NotificationCompat.DEFAULT_SOUND | NotificationCompat.DEFAULT_VIBRATE)
|
|
.build());
|
|
|
|
}
|
|
|
|
private @NonNull List<Job> handleMessage(@NonNull SignalServiceEnvelope envelope) throws NoSenderException {
|
|
try {
|
|
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
|
SignalServiceAddress localAddress = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context)));
|
|
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator());
|
|
|
|
SignalServiceContent content = cipher.decrypt(envelope);
|
|
|
|
List<Job> jobs = new ArrayList<>(2);
|
|
|
|
if (content != null) {
|
|
jobs.add(new PushProcessMessageJob(content.serialize(), messageId, smsMessageId, envelope.getTimestamp()));
|
|
}
|
|
|
|
if (envelope.isPreKeySignalMessage()) {
|
|
jobs.add(new RefreshPreKeysJob());
|
|
}
|
|
|
|
return jobs;
|
|
|
|
} catch (ProtocolInvalidVersionException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.INVALID_VERSION,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
|
|
} catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.CORRUPT_MESSAGE,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
|
|
} catch (ProtocolNoSessionException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.NO_SESSION,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
|
|
} catch (ProtocolLegacyMessageException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.LEGACY_MESSAGE,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
|
|
} catch (ProtocolDuplicateMessageException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.DUPLICATE_MESSAGE,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
|
|
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.emptyList();
|
|
|
|
} catch (SelfSendException e) {
|
|
Log.i(TAG, "Dropping UD message from self.");
|
|
return Collections.emptyList();
|
|
|
|
} catch (UnsupportedDataMessageException e) {
|
|
Log.w(TAG, e);
|
|
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.UNSUPPORTED_DATA_MESSAGE,
|
|
toExceptionMetadata(e),
|
|
messageId,
|
|
smsMessageId,
|
|
envelope.getTimestamp()));
|
|
}
|
|
}
|
|
|
|
private static PushProcessMessageJob.ExceptionMetadata toExceptionMetadata(@NonNull UnsupportedDataMessageException e) throws NoSenderException {
|
|
String sender = e.getSender();
|
|
|
|
if (sender == null) throw new NoSenderException();
|
|
|
|
return new PushProcessMessageJob.ExceptionMetadata(sender,
|
|
e.getSenderDevice(),
|
|
e.getGroup().transform(GroupUtil::idFromGroupContext).orNull());
|
|
}
|
|
|
|
private static PushProcessMessageJob.ExceptionMetadata toExceptionMetadata(@NonNull ProtocolException e) throws NoSenderException {
|
|
String sender = e.getSender();
|
|
|
|
if (sender == null) throw new NoSenderException();
|
|
|
|
return new PushProcessMessageJob.ExceptionMetadata(sender, e.getSenderDevice());
|
|
}
|
|
|
|
public static final class Factory implements Job.Factory<PushDecryptMessageJob> {
|
|
@Override
|
|
public @NonNull PushDecryptMessageJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
|
return new PushDecryptMessageJob(parameters, data.getLong(KEY_MESSAGE_ID), data.getLong(KEY_SMS_MESSAGE_ID));
|
|
}
|
|
}
|
|
|
|
private static class NoSenderException extends Exception {}
|
|
}
|