diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt new file mode 100644 index 000000000..cfab95f4b --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJobTest.kt @@ -0,0 +1,212 @@ +package org.thoughtcrime.securesms.jobs + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import okhttp3.mockwebserver.MockResponse +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.signal.libsignal.protocol.ecc.Curve +import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.testing.Get +import org.thoughtcrime.securesms.testing.Put +import org.thoughtcrime.securesms.testing.SignalActivityRule +import org.thoughtcrime.securesms.testing.assertIs +import org.thoughtcrime.securesms.testing.assertIsNot +import org.thoughtcrime.securesms.testing.parsedRequestBody +import org.thoughtcrime.securesms.testing.success +import org.whispersystems.signalservice.api.push.SignedPreKeyEntity +import org.whispersystems.signalservice.internal.push.PreKeyState +import org.whispersystems.signalservice.internal.push.PreKeyStatus + +@RunWith(AndroidJUnit4::class) +class PreKeysSyncJobTest { + + @get:Rule + val harness = SignalActivityRule() + + private val aciPreKeyMeta: PreKeyMetadataStore + get() = SignalStore.account().aciPreKeys + + private val pniPreKeyMeta: PreKeyMetadataStore + get() = SignalStore.account().pniPreKeys + + private lateinit var job: PreKeysSyncJob + + @Before + fun setUp() { + job = PreKeysSyncJob() + } + + @After + fun tearDown() { + InstrumentationApplicationDependencyProvider.clearHandlers() + } + + /** + * Create signed prekeys for both identities when both do not have registered prekeys according + * to our local state. + */ + @Test + fun runWithoutRegisteredKeysForBothIdentities() { + // GIVEN + aciPreKeyMeta.isSignedPreKeyRegistered = false + pniPreKeyMeta.isSignedPreKeyRegistered = false + + lateinit var aciSignedPreKey: SignedPreKeyEntity + lateinit var pniSignedPreKey: SignedPreKeyEntity + + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Put("/v2/keys/signed?identity=aci") { r -> + aciSignedPreKey = r.parsedRequestBody() + MockResponse().success() + }, + Put("/v2/keys/signed?identity=pni") { r -> + pniSignedPreKey = r.parsedRequestBody() + MockResponse().success() + }, + ) + + // WHEN + val result: Job.Result = job.run() + + // THEN + result.isSuccess assertIs true + + aciPreKeyMeta.isSignedPreKeyRegistered assertIs true + pniPreKeyMeta.isSignedPreKeyRegistered assertIs true + + val aciVerifySignatureResult = Curve.verifySignature( + ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey, + aciSignedPreKey.publicKey.serialize(), + aciSignedPreKey.signature + ) + aciVerifySignatureResult assertIs true + + val pniVerifySignatureResult = Curve.verifySignature( + ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey, + pniSignedPreKey.publicKey.serialize(), + pniSignedPreKey.signature + ) + pniVerifySignatureResult assertIs true + } + + /** + * With 100 prekeys registered for each identity, do nothing. + */ + @Test + fun runWithRegisteredKeysForBothIdentities() { + // GIVEN + val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId + val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId + + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) }, + Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }, + ) + + // WHEN + val result: Job.Result = job.run() + + // THEN + result.isSuccess assertIs true + + aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId + pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId + } + + /** + * With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state, + * do nothing for ACI but create PNI prekeys and update local state. + */ + @Test + fun runWithRegisteredKeysForAciIdentityOnly() { + // GIVEN + pniPreKeyMeta.isSignedPreKeyRegistered = false + + val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId + val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId + + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) }, + Put("/v2/keys/signed?identity=pni") { MockResponse().success() }, + ) + + // WHEN + val result: Job.Result = job.run() + + // THEN + result.isSuccess assertIs true + + pniPreKeyMeta.isSignedPreKeyRegistered assertIs true + aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId + pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId + } + + /** + * With <10 prekeys registered for each identity, upload new. + */ + @Test + fun runWithLowNumberOfRegisteredKeysForBothIdentities() { + // GIVEN + val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId + val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId + + val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId + val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId + + lateinit var aciPreKeyStateRequest: PreKeyState + lateinit var pniPreKeyStateRequest: PreKeyState + + InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( + Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) }, + Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) }, + Put("/v2/keys/?identity=aci") { r -> + aciPreKeyStateRequest = r.parsedRequestBody() + MockResponse().success() + }, + Put("/v2/keys/?identity=pni") { r -> + pniPreKeyStateRequest = r.parsedRequestBody() + MockResponse().success() + }, + ) + + // WHEN + val result: Job.Result = job.run() + + // THEN + result.isSuccess assertIs true + aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId + pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId + + aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId + pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId + + ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey -> + aciPreKeyStateRequest.identityKey assertIs aciIdentityKey + + val verifySignatureResult = Curve.verifySignature( + aciIdentityKey.publicKey, + aciPreKeyStateRequest.signedPreKey.publicKey.serialize(), + aciPreKeyStateRequest.signedPreKey.signature + ) + verifySignatureResult assertIs true + } + + ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey -> + pniPreKeyStateRequest.identityKey assertIs pniIdentityKey + + val verifySignatureResult = Curve.verifySignature( + pniIdentityKey.publicKey, + pniPreKeyStateRequest.signedPreKey.publicKey.serialize(), + pniPreKeyStateRequest.signedPreKey.signature + ) + verifySignatureResult assertIs true + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 90e1d81ef..053315541 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -50,16 +50,15 @@ import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.thoughtcrime.securesms.gcm.FcmJobService; import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob; -import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.FcmRefreshJob; import org.thoughtcrime.securesms.jobs.FontDownloaderJob; import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; -import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob; import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob; @@ -180,13 +179,12 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr .addNonBlocking(this::initializeRevealableMessageManager) .addNonBlocking(this::initializePendingRetryReceiptManager) .addNonBlocking(this::initializeFcmCheck) - .addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded) + .addNonBlocking(PreKeysSyncJob::enqueueIfNeeded) .addNonBlocking(this::initializePeriodicTasks) .addNonBlocking(this::initializeCircumvention) .addNonBlocking(this::initializePendingMessages) .addNonBlocking(this::initializeCleanup) .addNonBlocking(this::initializeGlideCodecs) - .addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary) .addNonBlocking(StorageSyncHelper::scheduleRoutineSync) .addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop()) .addNonBlocking(EmojiSource::refresh) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index d4884b78d..8bddec65a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -217,7 +217,7 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan // Signed Prekeys val signedPreKeyRecord = if (deviceId == primaryDeviceId) { - PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey, false) + PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey) } else { PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java index cf743eef3..78fd404ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java @@ -64,14 +64,13 @@ public class PreKeyUtil { return records; } - public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) { - return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey(), setAsActive); + public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) { + return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey()); } public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, - @NonNull ECPrivateKey privateKey, - boolean setAsActive) + @NonNull ECPrivateKey privateKey) { Log.i(TAG, "Generating signed prekeys..."); @@ -81,10 +80,6 @@ public class PreKeyUtil { protocolStore.storeSignedPreKey(signedPreKeyId, record); metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE); - if (setAsActive) { - metadataStore.setActiveSignedPreKeyId(signedPreKeyId); - } - return record; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt index c6b4f3c2b..51c980275 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt @@ -16,8 +16,7 @@ import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations.migrate import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations.migratePostTransaction import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob import org.thoughtcrime.securesms.migrations.LegacyMigrationJob import org.thoughtcrime.securesms.migrations.LegacyMigrationJob.DatabaseUpgradeListener import org.thoughtcrime.securesms.service.KeyCachingService @@ -145,7 +144,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data val masterSecret = KeyCachingService.getMasterSecret(context) if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null) else TextSecurePreferences.setNeedsSqlCipherMigration(context, true) if (!PreKeyMigrationHelper.migratePreKeys(context, db)) { - ApplicationDependencies.getJobManager().add(RefreshPreKeysJob()) + PreKeysSyncJob.enqueue() } SessionStoreMigrationHelper.migrateSessions(context, db) PreKeyMigrationHelper.cleanUpPreKeys(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index c51de8c2a..fef051702 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -30,9 +30,8 @@ import org.thoughtcrime.securesms.database.RecipientDatabase import org.thoughtcrime.securesms.database.helpers.migration.MyStoryMigration import org.thoughtcrime.securesms.database.helpers.migration.UrgentMslFlagMigration import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.GroupId -import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter @@ -224,7 +223,7 @@ object SignalDatabaseMigrations { db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)") if (!PreKeyMigrationHelper.migratePreKeys(context, db)) { - ApplicationDependencies.getJobManager().add(RefreshPreKeysJob()) + PreKeysSyncJob.enqueue() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index e8cdcaf2a..1b7c492ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -31,11 +31,11 @@ import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobMigrator; import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; -import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.FastJobStorage; import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.jobs.MarkerJob; +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob; @@ -317,7 +317,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr } if (needsPreKeyJob) { - CreateSignedPreKeyJob.enqueueIfNeeded(); + PreKeysSyncJob.enqueueIfNeeded(); } SignalBaseIdentityKeyStore baseIdentityStore = new SignalBaseIdentityKeyStore(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java index eb292e0c5..2c2ce59f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java @@ -4,6 +4,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; @@ -14,6 +15,8 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE; + /** * A durable unit of work. * @@ -193,15 +196,18 @@ public abstract class Job { return new Result(ResultType.FAILURE, runtimeException, null, INVALID_BACKOFF); } - boolean isSuccess() { + @VisibleForTesting(otherwise = PACKAGE_PRIVATE) + public boolean isSuccess() { return resultType == ResultType.SUCCESS; } - boolean isRetry() { + @VisibleForTesting(otherwise = PACKAGE_PRIVATE) + public boolean isRetry() { return resultType == ResultType.RETRY; } - boolean isFailure() { + @VisibleForTesting(otherwise = PACKAGE_PRIVATE) + public boolean isFailure() { return resultType == ResultType.FAILURE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java index f69316338..bab642ae9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java @@ -92,8 +92,8 @@ public abstract class BaseJob extends Job { warn(tag, "", message, null); } - protected void warn(@NonNull String tag, @NonNull String event, @NonNull String message) { - warn(tag, event, message, null); + protected void warn(@NonNull String tag, @NonNull Object extra, @NonNull String message) { + warn(tag, extra.toString(), message, null); } protected void warn(@NonNull String tag, @Nullable Throwable t) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java deleted file mode 100644 index a0c4d2e71..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/CreateSignedPreKeyJob.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.thoughtcrime.securesms.crypto.PreKeyUtil; -import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.ServiceId; -import org.whispersystems.signalservice.api.push.ServiceIdType; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * Creates and uploads a new signed prekey for an identity if one hasn't been uploaded yet. - */ -public class CreateSignedPreKeyJob extends BaseJob { - - public static final String KEY = "CreateSignedPreKeyJob"; - - private static final String TAG = Log.tag(CreateSignedPreKeyJob.class); - - private CreateSignedPreKeyJob() { - this(new Job.Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setMaxInstancesForFactory(1) - .setQueue("CreateSignedPreKeyJob") - .setLifespan(TimeUnit.DAYS.toMillis(30)) - .setMaxAttempts(Parameters.UNLIMITED) - .build()); - } - - private CreateSignedPreKeyJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - /** - * Enqueues an instance of this job if we not yet created + uploaded signed prekeys for one of our identities. - */ - public static void enqueueIfNeeded() { - if (!SignalStore.account().aciPreKeys().isSignedPreKeyRegistered() || !SignalStore.account().pniPreKeys().isSignedPreKeyRegistered()) { - Log.i(TAG, "Some signed prekeys aren't registered yet. Enqueuing a job."); - ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob()); - } - } - - @Override - public @NonNull Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException { - if (!SignalStore.account().isRegistered() || SignalStore.account().getAci() == null || SignalStore.account().getPni() == null) { - Log.w(TAG, "Not yet registered..."); - return; - } - - createPreKeys(ServiceIdType.ACI, SignalStore.account().getAci(), ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys()); - createPreKeys(ServiceIdType.PNI, SignalStore.account().getPni(), ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys()); - } - - private void createPreKeys(@NonNull ServiceIdType serviceIdType, @Nullable ServiceId serviceId, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) - throws IOException - { - if (serviceId == null) { - warn(TAG, "AccountId not set!"); - return; - } - - if (metadataStore.isSignedPreKeyRegistered()) { - warn(TAG, "Signed prekey for " + serviceIdType + " already registered..."); - return; - } - - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true); - - accountManager.setSignedPreKey(serviceIdType, signedPreKeyRecord); - metadataStore.setSignedPreKeyRegistered(true); - } - - @Override - public void onFailure() {} - - @Override - public boolean onShouldRetry(@NonNull Exception exception) { - if (exception instanceof PushNetworkException) return true; - return false; - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull CreateSignedPreKeyJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new CreateSignedPreKeyJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 76a27773a..94ac44085 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.migrations.EmojiDownloadMigrationJob; import org.thoughtcrime.securesms.migrations.KbsEnclaveMigrationJob; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.migrations.MigrationCompleteJob; -import org.thoughtcrime.securesms.migrations.SyncDistributionListsMigrationJob; import org.thoughtcrime.securesms.migrations.PassingMigrationJob; import org.thoughtcrime.securesms.migrations.PinOptOutMigration; import org.thoughtcrime.securesms.migrations.PinReminderMigrationJob; @@ -62,6 +61,7 @@ import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob; import org.thoughtcrime.securesms.migrations.StickerMyDailyLifeMigrationJob; import org.thoughtcrime.securesms.migrations.StorageCapabilityMigrationJob; import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob; +import org.thoughtcrime.securesms.migrations.SyncDistributionListsMigrationJob; import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob; import org.thoughtcrime.securesms.migrations.UserNotificationMigrationJob; import org.thoughtcrime.securesms.migrations.UuidMigrationJob; @@ -89,7 +89,6 @@ public final class JobManagerFactories { put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory()); put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory()); put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory()); - put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory()); put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory()); put(DonationReceiptRedemptionJob.KEY, new DonationReceiptRedemptionJob.Factory()); put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory()); @@ -136,6 +135,7 @@ public final class JobManagerFactories { put(PaymentNotificationSendJob.KEY, new PaymentNotificationSendJob.Factory()); put(PaymentSendJob.KEY, new PaymentSendJob.Factory()); put(PaymentTransactionCheckJob.KEY, new PaymentTransactionCheckJob.Factory()); + put(PreKeysSyncJob.KEY, new PreKeysSyncJob.Factory()); put(ProfileKeySendJob.KEY, new ProfileKeySendJob.Factory()); put(ProfileUploadJob.KEY, new ProfileUploadJob.Factory()); put(PushDecryptMessageJob.KEY, new PushDecryptMessageJob.Factory()); @@ -152,7 +152,6 @@ public final class JobManagerFactories { put(RecipientChangedNumberJob.KEY, new RecipientChangedNumberJob.Factory()); put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory()); put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory()); - put(RefreshPreKeysJob.KEY, new RefreshPreKeysJob.Factory()); put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory()); put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory()); put(ReportSpamJob.KEY, new ReportSpamJob.Factory()); @@ -165,7 +164,6 @@ public final class JobManagerFactories { put(RetrieveRemoteAnnouncementsJob.KEY, new RetrieveRemoteAnnouncementsJob.Factory()); put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory()); put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory()); - put(RotateSignedPreKeyJob.KEY, new RotateSignedPreKeyJob.Factory()); put(SenderKeyDistributionSendJob.KEY, new SenderKeyDistributionSendJob.Factory()); put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory()); put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application)); @@ -241,6 +239,9 @@ public final class JobManagerFactories { put("LeaveGroupJob", new FailingJob.Factory()); put("PushGroupUpdateJob", new FailingJob.Factory()); put("RequestGroupInfoJob", new FailingJob.Factory()); + put("RotateSignedPreKeyJob", new PreKeysSyncJob.Factory()); + put("CreateSignedPreKeyJob", new PreKeysSyncJob.Factory()); + put("RefreshPreKeysJob", new PreKeysSyncJob.Factory()); }}; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt new file mode 100644 index 000000000..1888e3114 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PreKeysSyncJob.kt @@ -0,0 +1,178 @@ +package org.thoughtcrime.securesms.jobs + +import androidx.annotation.VisibleForTesting +import org.signal.core.util.logging.Log +import org.signal.libsignal.protocol.state.SignalProtocolStore +import org.thoughtcrime.securesms.crypto.PreKeyUtil +import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.whispersystems.signalservice.api.push.ServiceId +import org.whispersystems.signalservice.api.push.ServiceIdType +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException +import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException +import java.util.concurrent.TimeUnit + +/** + * Regardless of the current state of affairs with respect to prekeys for either ACI or PNI identities, will + * attempt to make the state valid. + * + * If prekeys aren't registered for an identity they will be created. + * + * If prekeys are registered but the count is below the minimum threshold, then new ones will be uploaded. + */ +class PreKeysSyncJob private constructor(private val forceRotate: Boolean = false, parameters: Parameters) : BaseJob(parameters) { + + companion object { + const val KEY = "PreKeysSyncJob" + + private val TAG = Log.tag(PreKeysSyncJob::class.java) + private val KEY_FORCE_ROTATE = "force_rotate" + private const val PREKEY_MINIMUM = 10 + private val REFRESH_INTERVAL = TimeUnit.DAYS.toMillis(3) + + @JvmStatic + @JvmOverloads + fun enqueue(forceRotate: Boolean = false) { + ApplicationDependencies.getJobManager().add(PreKeysSyncJob(forceRotate)) + } + + @JvmStatic + fun enqueueIfNeeded() { + if (!SignalStore.account().aciPreKeys.isSignedPreKeyRegistered || !SignalStore.account().pniPreKeys.isSignedPreKeyRegistered) { + Log.i(TAG, "Some signed prekeys aren't registered yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.isSignedPreKeyRegistered} PNI: ${SignalStore.account().pniPreKeys.isSignedPreKeyRegistered}") + ApplicationDependencies.getJobManager().add(PreKeysSyncJob()) + } else if (SignalStore.account().aciPreKeys.activeSignedPreKeyId < 0 || SignalStore.account().pniPreKeys.activeSignedPreKeyId < 0) { + Log.i(TAG, "Some signed prekeys aren't active yet. Enqueuing a job. ACI: ${SignalStore.account().aciPreKeys.activeSignedPreKeyId >= 0} PNI: ${SignalStore.account().pniPreKeys.activeSignedPreKeyId >= 0}") + ApplicationDependencies.getJobManager().add(PreKeysSyncJob()) + } else { + val timeSinceLastRefresh = System.currentTimeMillis() - SignalStore.misc().lastPrekeyRefreshTime + + if (timeSinceLastRefresh > REFRESH_INTERVAL) { + Log.i(TAG, "Scheduling a prekey refresh. Time since last schedule: $timeSinceLastRefresh ms") + ApplicationDependencies.getJobManager().add(PreKeysSyncJob()) + } + } + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + constructor(forceRotate: Boolean = false) : this( + forceRotate, + Parameters.Builder() + .setQueue("PreKeysSyncJob") + .addConstraint(NetworkConstraint.KEY) + .setMaxInstancesForFactory(1) + .setMaxAttempts(Parameters.UNLIMITED) + .setLifespan(TimeUnit.DAYS.toMillis(30)) + .build() + ) + + override fun getFactoryKey(): String = KEY + + override fun serialize(): Data { + return Data.Builder() + .putBoolean(KEY_FORCE_ROTATE, forceRotate) + .build() + } + + override fun onRun() { + if (!SignalStore.account().isRegistered || SignalStore.account().aci == null || SignalStore.account().pni == null) { + warn(TAG, "Not yet registered") + return + } + + syncPreKeys(ServiceIdType.ACI, SignalStore.account().aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys) + syncPreKeys(ServiceIdType.PNI, SignalStore.account().pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys) + SignalStore.misc().lastPrekeyRefreshTime = System.currentTimeMillis() + } + + private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore) { + if (serviceId == null) { + warn(TAG, serviceIdType, "AccountId not set!") + return + } + + if (metadataStore.isSignedPreKeyRegistered && metadataStore.activeSignedPreKeyId >= 0) { + if (forceRotate || System.currentTimeMillis() > TextSecurePreferences.getSignedPreKeyRotationTime(context) || metadataStore.signedPreKeyFailureCount > 5) { + log(serviceIdType, "Rotating signed prekey...") + rotateSignedPreKey(serviceIdType, protocolStore, metadataStore) + } else { + log(serviceIdType, "Refreshing prekeys...") + refreshKeys(serviceIdType, protocolStore, metadataStore) + } + } else { + log(serviceIdType, "Creating signed prekey...") + rotateSignedPreKey(serviceIdType, protocolStore, metadataStore) + } + } + + private fun rotateSignedPreKey(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore) { + val signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore) + ApplicationDependencies.getSignalServiceAccountManager().setSignedPreKey(serviceIdType, signedPreKeyRecord) + + metadataStore.activeSignedPreKeyId = signedPreKeyRecord.id + metadataStore.isSignedPreKeyRegistered = true + metadataStore.signedPreKeyFailureCount = 0 + } + + private fun refreshKeys(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore) { + val accountManager = ApplicationDependencies.getSignalServiceAccountManager() + val availableKeys = accountManager.getPreKeysCount(serviceIdType) + + log(serviceIdType, "Available keys: $availableKeys") + + if (availableKeys >= PREKEY_MINIMUM && metadataStore.isSignedPreKeyRegistered) { + log(serviceIdType, "Available keys sufficient.") + return + } + + val preKeyRecords = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore) + val signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore) + val identityKey = protocolStore.identityKeyPair + + log(serviceIdType, "Registering new prekeys...") + + accountManager.setPreKeys(serviceIdType, identityKey.publicKey, signedPreKeyRecord, preKeyRecords) + metadataStore.activeSignedPreKeyId = signedPreKeyRecord.id + metadataStore.isSignedPreKeyRegistered = true + + log(serviceIdType, "Cleaning prekeys...") + PreKeyUtil.cleanSignedPreKeys(protocolStore, metadataStore) + + SignalStore.misc().lastPrekeyRefreshTime = System.currentTimeMillis() + log(serviceIdType, "Successfully refreshed prekeys.") + } + + override fun onShouldRetry(e: Exception): Boolean { + return when (e) { + is NonSuccessfulResponseCodeException -> false + is PushNetworkException -> true + else -> false + } + } + + override fun onFailure() { + val aciStore = SignalStore.account().aciPreKeys + val pniStore = SignalStore.account().pniPreKeys + + if ((aciStore.isSignedPreKeyRegistered || pniStore.isSignedPreKeyRegistered) && forceRotate) { + aciStore.signedPreKeyFailureCount++ + pniStore.signedPreKeyFailureCount++ + } + } + + private fun log(serviceIdType: ServiceIdType, message: String) { + Log.i(TAG, "[$serviceIdType] $message") + } + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): PreKeysSyncJob { + return PreKeysSyncJob(data.getBooleanOrDefault(KEY_FORCE_ROTATE, false), parameters) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index c93983c9d..5a99bd423 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -103,7 +103,7 @@ public abstract class PushSendJob extends SendJob { @Override protected final void onSend() throws Exception { if (SignalStore.account().aciPreKeys().getSignedPreKeyFailureCount() > 5) { - ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob()); + PreKeysSyncJob.enqueue(true); throw new TextSecureExpiredException("Too many signed prekey rotation failures"); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java deleted file mode 100644 index 489679ee5..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshPreKeysJob.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.IdentityKeyPair; -import org.signal.libsignal.protocol.state.PreKeyRecord; -import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.thoughtcrime.securesms.crypto.PreKeyUtil; -import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.ServiceIdType; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * Ensures that our prekeys are up to date for both our ACI and PNI identities. - * Specifically, if we have less than {@link #PREKEY_MINIMUM} one-time prekeys, we will generate and upload - * a new batch of one-time prekeys, as well as a new signed prekey. - */ -public class RefreshPreKeysJob extends BaseJob { - - public static final String KEY = "RefreshPreKeysJob"; - - private static final String TAG = Log.tag(RefreshPreKeysJob.class); - - private static final int PREKEY_MINIMUM = 10; - - private static final long REFRESH_INTERVAL = TimeUnit.DAYS.toMillis(3); - - public RefreshPreKeysJob() { - this(new Job.Parameters.Builder() - .setQueue("RefreshPreKeysJob") - .addConstraint(NetworkConstraint.KEY) - .setMaxInstancesForFactory(1) - .setMaxAttempts(Parameters.UNLIMITED) - .setLifespan(TimeUnit.DAYS.toMillis(30)) - .build()); - } - - public static void scheduleIfNecessary() { - long timeSinceLastRefresh = System.currentTimeMillis() - SignalStore.misc().getLastPrekeyRefreshTime(); - - if (timeSinceLastRefresh > REFRESH_INTERVAL) { - Log.i(TAG, "Scheduling a prekey refresh. Time since last schedule: " + timeSinceLastRefresh + " ms"); - ApplicationDependencies.getJobManager().add(new RefreshPreKeysJob()); - } - } - - private RefreshPreKeysJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException { - if (!SignalStore.account().isRegistered() || SignalStore.account().getAci() == null || SignalStore.account().getPni() == null) { - Log.w(TAG, "Not registered. Skipping."); - return; - } - - SignalProtocolStore aciProtocolStore = ApplicationDependencies.getProtocolStore().aci(); - PreKeyMetadataStore aciPreKeyStore = SignalStore.account().aciPreKeys(); - - SignalProtocolStore pniProtocolStore = ApplicationDependencies.getProtocolStore().pni(); - PreKeyMetadataStore pniPreKeyStore = SignalStore.account().pniPreKeys(); - - if (refreshKeys(ServiceIdType.ACI, aciProtocolStore, aciPreKeyStore)) { - PreKeyUtil.cleanSignedPreKeys(aciProtocolStore, aciPreKeyStore); - } - - if (refreshKeys(ServiceIdType.PNI, pniProtocolStore, pniPreKeyStore)) { - PreKeyUtil.cleanSignedPreKeys(pniProtocolStore, pniPreKeyStore); - } - - SignalStore.misc().setLastPrekeyRefreshTime(System.currentTimeMillis()); - Log.i(TAG, "Successfully refreshed prekeys."); - } - - /** - * @return True if we need to clean prekeys, otherwise false. - */ - private boolean refreshKeys(@NonNull ServiceIdType serviceIdType, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) throws IOException { - String logPrefix = "[" + serviceIdType + "] "; - - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - - int availableKeys = accountManager.getPreKeysCount(serviceIdType); - log(TAG, logPrefix + "Available keys: " + availableKeys); - - if (availableKeys >= PREKEY_MINIMUM && metadataStore.isSignedPreKeyRegistered()) { - log(TAG, logPrefix + "Available keys sufficient."); - return false; - } - - List preKeyRecords = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore); - SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, false); - IdentityKeyPair identityKey = protocolStore.getIdentityKeyPair(); - - log(TAG, logPrefix + "Registering new prekeys..."); - - accountManager.setPreKeys(serviceIdType, identityKey.getPublicKey(), signedPreKeyRecord, preKeyRecords); - - metadataStore.setActiveSignedPreKeyId(signedPreKeyRecord.getId()); - metadataStore.setSignedPreKeyRegistered(true); - - log(TAG, logPrefix + "Need to clean prekeys."); - return true; - } - - @Override - public boolean onShouldRetry(@NonNull Exception exception) { - if (exception instanceof NonSuccessfulResponseCodeException) return false; - if (exception instanceof PushNetworkException) return true; - - return false; - } - - @Override - public void onFailure() { - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull RefreshPreKeysJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new RefreshPreKeysJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java deleted file mode 100644 index e1f5f77ff..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RotateSignedPreKeyJob.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.state.SignalProtocolStore; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.thoughtcrime.securesms.crypto.PreKeyUtil; -import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.PNI; -import org.whispersystems.signalservice.api.push.ServiceIdType; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * Forces the creation + upload of new signed prekeys for both the ACI and PNI identities. - */ -public class RotateSignedPreKeyJob extends BaseJob { - - public static final String KEY = "RotateSignedPreKeyJob"; - - private static final String TAG = Log.tag(RotateSignedPreKeyJob.class); - - public RotateSignedPreKeyJob() { - this(new Job.Parameters.Builder() - .setQueue("RotateSignedPreKeyJob") - .addConstraint(NetworkConstraint.KEY) - .setMaxInstancesForFactory(1) - .setMaxAttempts(Parameters.UNLIMITED) - .setLifespan(TimeUnit.DAYS.toMillis(2)) - .build()); - } - - private RotateSignedPreKeyJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws Exception { - if (!SignalStore.account().isRegistered() || SignalStore.account().getAci() == null || SignalStore.account().getPni() == null) { - Log.w(TAG, "Not registered. Skipping."); - return; - } - - Log.i(TAG, "Rotating signed prekey..."); - - ACI aci = SignalStore.account().getAci(); - PNI pni = SignalStore.account().getPni(); - - if (aci == null) { - Log.w(TAG, "ACI is unset!"); - return; - } - - if (pni == null) { - Log.w(TAG, "PNI is unset!"); - return; - } - - rotate(ServiceIdType.ACI, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys()); - rotate(ServiceIdType.PNI, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys()); - } - - private void rotate(@NonNull ServiceIdType serviceIdType, @NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) - throws IOException - { - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, false); - - accountManager.setSignedPreKey(serviceIdType, signedPreKeyRecord); - - metadataStore.setActiveSignedPreKeyId(signedPreKeyRecord.getId()); - metadataStore.setSignedPreKeyRegistered(true); - metadataStore.setSignedPreKeyFailureCount(0); - } - - @Override - public boolean onShouldRetry(@NonNull Exception exception) { - return exception instanceof PushNetworkException; - } - - @Override - public void onFailure() { - PreKeyMetadataStore aciStore = SignalStore.account().aciPreKeys(); - PreKeyMetadataStore pniStore = SignalStore.account().pniPreKeys(); - - aciStore.setSignedPreKeyFailureCount(aciStore.getSignedPreKeyFailureCount() + 1); - pniStore.setSignedPreKeyFailureCount(pniStore.getSignedPreKeyFailureCount() + 1); - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull RotateSignedPreKeyJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new RotateSignedPreKeyJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java index 74946836c..f58dbb63b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptionUtil.java @@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.groups.BadGroupIdException; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob; -import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity; @@ -43,7 +43,6 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationIds; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.signalservice.api.InvalidMessageStructureException; import org.whispersystems.signalservice.api.SignalServiceAccountDataStore; import org.whispersystems.signalservice.api.crypto.ContentHint; @@ -101,7 +100,7 @@ public final class MessageDecryptionUtil { List jobs = new LinkedList<>(); if (envelope.isPreKeySignalMessage()) { - jobs.add(new RefreshPreKeysJob()); + PreKeysSyncJob.enqueue(); } try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java index cde7724b8..a893b7a20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java @@ -20,8 +20,8 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; -import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -129,7 +129,7 @@ public class LegacyMigrationJob extends MigrationJob { } if (lastSeenVersion < SIGNED_PREKEY_VERSION) { - CreateSignedPreKeyJob.enqueueIfNeeded(); + PreKeysSyncJob.enqueueIfNeeded(); } if (lastSeenVersion < NO_DECRYPT_QUEUE_VERSION) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java index e02a6a37d..8ec948f45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/PniAccountInitializationMigrationJob.java @@ -75,10 +75,11 @@ public class PniAccountInitializationMigrationJob extends MigrationJob { if (!metadataStore.isSignedPreKeyRegistered()) { Log.i(TAG, "Uploading signed prekey for PNI."); - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true); + SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore); List oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore); accountManager.setPreKeys(ServiceIdType.PNI, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys); + metadataStore.setActiveSignedPreKeyId(signedPreKey.getId()); metadataStore.setSignedPreKeyRegistered(true); } else { Log.w(TAG, "Already uploaded signed prekey for PNI. Skipping this step."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index 1c0d7570e..755563fad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -190,10 +190,11 @@ public final class RegistrationRepository { @NonNull PreKeyMetadataStore metadataStore) throws IOException { - SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore, true); + SignedPreKeyRecord signedPreKey = PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore); List oneTimePreKeys = PreKeyUtil.generateAndStoreOneTimePreKeys(protocolStore, metadataStore); accountManager.setPreKeys(serviceIdType, protocolStore.getIdentityKeyPair().getPublicKey(), signedPreKey, oneTimePreKeys); + metadataStore.setActiveSignedPreKeyId(signedPreKey.getId()); metadataStore.setSignedPreKeyRegistered(true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java index 57664f2c4..7c8d52461 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/RotateSignedPreKeyListener.java @@ -5,7 +5,7 @@ import android.content.Context; import android.content.Intent; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob; +import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -23,7 +23,7 @@ public class RotateSignedPreKeyListener extends PersistentAlarmManagerListener { @Override protected long onAlarm(Context context, long scheduledTime) { if (scheduledTime != 0 && SignalStore.account().isRegistered()) { - ApplicationDependencies.getJobManager().add(new RotateSignedPreKeyJob()); + PreKeysSyncJob.enqueue(true); } long nextTime = System.currentTimeMillis() + INTERVAL; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PreKeyStatus.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PreKeyStatus.java index d4723448d..9416c42bc 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PreKeyStatus.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PreKeyStatus.java @@ -15,6 +15,10 @@ public class PreKeyStatus { public PreKeyStatus() {} + public PreKeyStatus(int count) { + this.count = count; + } + public int getCount() { return count; }