mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-28 21:57:17 +00:00
Introduce AEP and SSRE2.
This commit is contained in:
@@ -8,11 +8,12 @@ object AppCapabilities {
|
||||
* asking if the user has set a Signal PIN or not.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getCapabilities(storageCapable: Boolean): AccountAttributes.Capabilities {
|
||||
fun getCapabilities(storageCapable: Boolean, storageServiceEncryptionV2: Boolean): AccountAttributes.Capabilities {
|
||||
return AccountAttributes.Capabilities(
|
||||
storage = storageCapable,
|
||||
deleteSync = true,
|
||||
versionedExpirationTimer = true
|
||||
versionedExpirationTimer = true,
|
||||
storageServiceEncryptionV2 = storageServiceEncryptionV2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +420,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
var value: Long = 0
|
||||
value = Bitmask.update(value, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isDeleteSync).serialize().toLong())
|
||||
value = Bitmask.update(value, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isVersionedExpirationTimer).serialize().toLong())
|
||||
value = Bitmask.update(value, Capabilities.STORAGE_SERVICE_ENCRYPTION_V2, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isStorageServiceEncryptionV2).serialize().toLong())
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -4713,6 +4714,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
||||
// const val PAYMENT_ACTIVATION = 8
|
||||
const val DELETE_SYNC = 9
|
||||
const val VERSIONED_EXPIRATION_TIMER = 10
|
||||
const val STORAGE_SERVICE_ENCRYPTION_V2 = 11
|
||||
|
||||
// IMPORTANT: We cannot sore more than 32 capabilities in the bitmask.
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ object RecipientTableCursorUtil {
|
||||
return RecipientRecord.Capabilities(
|
||||
rawBits = capabilities,
|
||||
deleteSync = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.DELETE_SYNC, Capabilities.BIT_LENGTH).toInt()),
|
||||
versionedExpirationTimer = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH).toInt())
|
||||
versionedExpirationTimer = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.VERSIONED_EXPIRATION_TIMER, Capabilities.BIT_LENGTH).toInt()),
|
||||
storageServiceEncryptionV2 = Recipient.Capability.deserialize(Bitmask.read(capabilities, Capabilities.STORAGE_SERVICE_ENCRYPTION_V2, Capabilities.BIT_LENGTH).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -121,14 +121,16 @@ data class RecipientRecord(
|
||||
data class Capabilities(
|
||||
val rawBits: Long,
|
||||
val deleteSync: Recipient.Capability,
|
||||
val versionedExpirationTimer: Recipient.Capability
|
||||
val versionedExpirationTimer: Recipient.Capability,
|
||||
val storageServiceEncryptionV2: Recipient.Capability
|
||||
) {
|
||||
companion object {
|
||||
@JvmField
|
||||
val UNKNOWN = Capabilities(
|
||||
rawBits = 0,
|
||||
deleteSync = Recipient.Capability.UNKNOWN,
|
||||
versionedExpirationTimer = Recipient.Capability.UNKNOWN
|
||||
versionedExpirationTimer = Recipient.Capability.UNKNOWN,
|
||||
storageServiceEncryptionV2 = Recipient.Capability.UNKNOWN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.whispersystems.signalservice.api.registration.RegistrationApi
|
||||
import org.whispersystems.signalservice.api.services.CallLinksService
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.services.ProfileService
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||
@@ -303,6 +304,9 @@ object AppDependencies {
|
||||
val registrationApi: RegistrationApi
|
||||
get() = networkModule.registrationApi
|
||||
|
||||
val storageServiceApi: StorageServiceApi
|
||||
get() = networkModule.storageServiceApi
|
||||
|
||||
@JvmStatic
|
||||
val okHttpClient: OkHttpClient
|
||||
get() = networkModule.okHttpClient
|
||||
@@ -367,5 +371,6 @@ object AppDependencies {
|
||||
fun provideAttachmentApi(signalWebSocket: SignalWebSocket, pushServiceSocket: PushServiceSocket): AttachmentApi
|
||||
fun provideLinkDeviceApi(pushServiceSocket: PushServiceSocket): LinkDeviceApi
|
||||
fun provideRegistrationApi(pushServiceSocket: PushServiceSocket): RegistrationApi
|
||||
fun provideStorageServiceApi(pushServiceSocket: PushServiceSocket): StorageServiceApi
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ import org.whispersystems.signalservice.api.registration.RegistrationApi;
|
||||
import org.whispersystems.signalservice.api.services.CallLinksService;
|
||||
import org.whispersystems.signalservice.api.services.DonationsService;
|
||||
import org.whispersystems.signalservice.api.services.ProfileService;
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||
@@ -244,6 +245,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
public @NonNull Network provideLibsignalNetwork(@NonNull SignalServiceConfiguration config) {
|
||||
Network network = new Network(BuildConfig.LIBSIGNAL_NET_ENV, StandardUserAgentInterceptor.USER_AGENT);
|
||||
LibSignalNetworkExtensions.applyConfiguration(network, config);
|
||||
|
||||
return network;
|
||||
}
|
||||
|
||||
@@ -480,6 +482,11 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
return new RegistrationApi(pushServiceSocket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull StorageServiceApi provideStorageServiceApi(@NonNull PushServiceSocket pushServiceSocket) {
|
||||
return new StorageServiceApi(pushServiceSocket);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.whispersystems.signalservice.api.registration.RegistrationApi
|
||||
import org.whispersystems.signalservice.api.services.CallLinksService
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import org.whispersystems.signalservice.api.services.ProfileService
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
import org.whispersystems.signalservice.api.util.Tls12SocketFactory
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||
@@ -148,6 +149,10 @@ class NetworkDependenciesModule(
|
||||
provider.provideRegistrationApi(pushServiceSocket)
|
||||
}
|
||||
|
||||
val storageServiceApi: StorageServiceApi by lazy {
|
||||
provider.provideStorageServiceApi(pushServiceSocket)
|
||||
}
|
||||
|
||||
val okHttpClient: OkHttpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(StandardUserAgentInterceptor())
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigra
|
||||
import org.thoughtcrime.securesms.jobmanager.migrations.SenderKeyDistributionSendJobRecipientMigration;
|
||||
import org.thoughtcrime.securesms.migrations.AccountConsistencyMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AccountRecordMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AepMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.ApplyUnknownFieldsToSelfMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AttachmentCleanupMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.AttachmentHashBackfillMigrationJob;
|
||||
@@ -136,7 +137,6 @@ public final class JobManagerFactories {
|
||||
put(CheckRestoreMediaLeftJob.KEY, new CheckRestoreMediaLeftJob.Factory());
|
||||
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
|
||||
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
|
||||
put(ContactLinkRebuildMigrationJob.KEY, new ContactLinkRebuildMigrationJob.Factory());
|
||||
put(ConversationShortcutRankingUpdateJob.KEY, new ConversationShortcutRankingUpdateJob.Factory());
|
||||
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());
|
||||
put(CopyAttachmentToArchiveJob.KEY, new CopyAttachmentToArchiveJob.Factory());
|
||||
@@ -238,6 +238,7 @@ public final class JobManagerFactories {
|
||||
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
|
||||
put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory());
|
||||
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
|
||||
put(StorageRotateManifestJob.KEY, new StorageRotateManifestJob.Factory());
|
||||
put(SyncSystemContactLinksJob.KEY, new SyncSystemContactLinksJob.Factory());
|
||||
put(MultiDeviceStorySendSyncJob.KEY, new MultiDeviceStorySendSyncJob.Factory());
|
||||
put(ResetSvrGuessCountJob.KEY, new ResetSvrGuessCountJob.Factory());
|
||||
@@ -247,7 +248,6 @@ public final class JobManagerFactories {
|
||||
put(StorageAccountRestoreJob.KEY, new StorageAccountRestoreJob.Factory());
|
||||
put(StorageForcePushJob.KEY, new StorageForcePushJob.Factory());
|
||||
put(StorageSyncJob.KEY, new StorageSyncJob.Factory());
|
||||
put(SubscriberIdMigrationJob.KEY, new SubscriberIdMigrationJob.Factory());
|
||||
put(StoryOnboardingDownloadJob.KEY, new StoryOnboardingDownloadJob.Factory());
|
||||
put(SubmitRateLimitPushChallengeJob.KEY, new SubmitRateLimitPushChallengeJob.Factory());
|
||||
put(Svr2MirrorJob.KEY, new Svr2MirrorJob.Factory());
|
||||
@@ -261,6 +261,7 @@ public final class JobManagerFactories {
|
||||
// Migrations
|
||||
put(AccountConsistencyMigrationJob.KEY, new AccountConsistencyMigrationJob.Factory());
|
||||
put(AccountRecordMigrationJob.KEY, new AccountRecordMigrationJob.Factory());
|
||||
put(AepMigrationJob.KEY, new AepMigrationJob.Factory());
|
||||
put(ApplyUnknownFieldsToSelfMigrationJob.KEY, new ApplyUnknownFieldsToSelfMigrationJob.Factory());
|
||||
put(AttachmentCleanupMigrationJob.KEY, new AttachmentCleanupMigrationJob.Factory());
|
||||
put(AttachmentHashBackfillMigrationJob.KEY, new AttachmentHashBackfillMigrationJob.Factory());
|
||||
@@ -275,6 +276,7 @@ public final class JobManagerFactories {
|
||||
put(BlobStorageLocationMigrationJob.KEY, new BlobStorageLocationMigrationJob.Factory());
|
||||
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
|
||||
put(ClearGlideCacheMigrationJob.KEY, new ClearGlideCacheMigrationJob.Factory());
|
||||
put(ContactLinkRebuildMigrationJob.KEY, new ContactLinkRebuildMigrationJob.Factory());
|
||||
put(CopyUsernameToSignalStoreMigrationJob.KEY, new CopyUsernameToSignalStoreMigrationJob.Factory());
|
||||
put(DatabaseMigrationJob.KEY, new DatabaseMigrationJob.Factory());
|
||||
put(DeleteDeprecatedLogsMigrationJob.KEY, new DeleteDeprecatedLogsMigrationJob.Factory());
|
||||
@@ -306,6 +308,7 @@ public final class JobManagerFactories {
|
||||
put(StorageServiceMigrationJob.KEY, new StorageServiceMigrationJob.Factory());
|
||||
put(StorageServiceSystemNameMigrationJob.KEY, new StorageServiceSystemNameMigrationJob.Factory());
|
||||
put(StoryViewedReceiptsStateMigrationJob.KEY, new StoryViewedReceiptsStateMigrationJob.Factory());
|
||||
put(SubscriberIdMigrationJob.KEY, new SubscriberIdMigrationJob.Factory());
|
||||
put(Svr2MirrorMigrationJob.KEY, new Svr2MirrorMigrationJob.Factory());
|
||||
put(SyncCallLinksMigrationJob.KEY, new SyncCallLinksMigrationJob.Factory());
|
||||
put(SyncDistributionListsMigrationJob.KEY, new SyncDistributionListsMigrationJob.Factory());
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
|
||||
class MultiDeviceKeysUpdateJob private constructor(parameters: Parameters) : BaseJob(parameters) {
|
||||
|
||||
@@ -54,8 +53,10 @@ class MultiDeviceKeysUpdateJob private constructor(parameters: Parameters) : Bas
|
||||
|
||||
val syncMessage = SignalServiceSyncMessage.forKeys(
|
||||
KeysMessage(
|
||||
Optional.of(SignalStore.storageService.storageKey),
|
||||
Optional.of(SignalStore.svr.masterKey)
|
||||
storageService = SignalStore.storageService.storageKey,
|
||||
master = SignalStore.svr.masterKey,
|
||||
accountEntropyPool = SignalStore.account.accountEntropyPool,
|
||||
mediaRootBackupKey = SignalStore.backup.mediaRootBackupKey
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.keyvalue.SvrValues;
|
||||
import org.thoughtcrime.securesms.registration.data.RegistrationRepository;
|
||||
import org.thoughtcrime.securesms.registration.secondary.DeviceNameCipher;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
@@ -103,7 +104,7 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
String deviceName = SignalStore.account().getDeviceName();
|
||||
byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), SignalStore.account().getAciIdentityKey());
|
||||
|
||||
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasOptedInWithAccess() && !svrValues.hasOptedOut());
|
||||
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasOptedInWithAccess() && !svrValues.hasOptedOut(), RemoteConfig.getStorageServiceEncryptionV2());
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + svrValues.hasPin() + ", access? " + svrValues.hasOptedInWithAccess() +
|
||||
"\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) +
|
||||
"\n Phone number discoverable : " + phoneNumberDiscoverable +
|
||||
|
||||
@@ -216,17 +216,25 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Recipient.self().getDeleteSyncCapability().isSupported() && capabilities.isDeleteSync()) {
|
||||
Recipient selfSnapshot = Recipient.self();
|
||||
|
||||
SignalDatabase.recipients().setCapabilities(Recipient.self().getId(), capabilities);
|
||||
|
||||
if (!selfSnapshot.getDeleteSyncCapability().isSupported() && capabilities.isDeleteSync()) {
|
||||
Log.d(TAG, "Transitioned to delete sync capable, notify linked devices in case we were the last one");
|
||||
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||
}
|
||||
|
||||
if (!Recipient.self().getVersionedExpirationTimerCapability().isSupported() && capabilities.isVersionedExpirationTimer()) {
|
||||
if (!selfSnapshot.getVersionedExpirationTimerCapability().isSupported() && capabilities.isVersionedExpirationTimer()) {
|
||||
Log.d(TAG, "Transitioned to versioned expiration timer capable, notify linked devices in case we were the last one");
|
||||
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||
}
|
||||
|
||||
SignalDatabase.recipients().setCapabilities(Recipient.self().getId(), capabilities);
|
||||
if (selfSnapshot.getStorageServiceEncryptionV2Capability() == Recipient.Capability.NOT_SUPPORTED && capabilities.isStorageServiceEncryptionV2()) {
|
||||
Log.i(TAG, "Transitioned to storageServiceEncryptionV2 capable. Notifying other devices and pushing to storage service with a recordIkm.");
|
||||
AppDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
|
||||
AppDependencies.getJobManager().add(new StorageForcePushJob());
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureUnidentifiedAccessCorrect(@Nullable String unidentifiedAccessVerifier, boolean universalUnidentifiedAccess) {
|
||||
|
||||
@@ -6,12 +6,16 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.reclaimUsernameIfNecessary
|
||||
import org.thoughtcrime.securesms.recipients.Recipient.Companion.self
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.applyAccountStorageSyncUpdates
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestResult
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -43,13 +47,26 @@ class StorageAccountRestoreJob private constructor(parameters: Parameters) : Bas
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun onRun() {
|
||||
val accountManager = AppDependencies.signalServiceAccountManager
|
||||
val storageServiceKey = SignalStore.storageService.storageKey
|
||||
val storageServiceKey = SignalStore.storageService.storageKeyForInitialDataRestore?.let {
|
||||
Log.i(TAG, "Using temporary storage key.")
|
||||
it
|
||||
} ?: run {
|
||||
Log.i(TAG, "Using normal storage key.")
|
||||
SignalStore.storageService.storageKey
|
||||
}
|
||||
|
||||
val repository = StorageServiceRepository(SignalNetwork.storageService)
|
||||
|
||||
Log.i(TAG, "Retrieving manifest...")
|
||||
val manifest = accountManager.getStorageManifest(storageServiceKey)
|
||||
val manifest: SignalStorageManifest? = when (val result = repository.getStorageManifest(storageServiceKey)) {
|
||||
is ManifestResult.Success -> result.manifest
|
||||
is ManifestResult.DecryptionError -> null
|
||||
is ManifestResult.NotFoundError -> null
|
||||
is ManifestResult.NetworkError -> throw result.exception
|
||||
is ManifestResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
|
||||
if (!manifest.isPresent) {
|
||||
if (manifest == null) {
|
||||
Log.w(TAG, "Manifest did not exist or was undecryptable (bad key). Not restoring. Force-pushing.")
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return
|
||||
@@ -58,7 +75,7 @@ class StorageAccountRestoreJob private constructor(parameters: Parameters) : Bas
|
||||
Log.i(TAG, "Resetting the local manifest to an empty state so that it will sync later.")
|
||||
SignalStore.storageService.manifest = SignalStorageManifest.EMPTY
|
||||
|
||||
val accountId = manifest.get().accountStorageId
|
||||
val accountId = manifest.accountStorageId
|
||||
|
||||
if (!accountId.isPresent) {
|
||||
Log.w(TAG, "Manifest had no account record! Not restoring.")
|
||||
@@ -66,8 +83,18 @@ class StorageAccountRestoreJob private constructor(parameters: Parameters) : Bas
|
||||
}
|
||||
|
||||
Log.i(TAG, "Retrieving account record...")
|
||||
val records = accountManager.readStorageRecords(storageServiceKey, listOf(accountId.get()))
|
||||
val record = if (records.size > 0) records[0] else null
|
||||
val records: List<SignalStorageRecord> = when (val result = repository.readStorageRecords(storageServiceKey, manifest.recordIkm, listOf(accountId.get()))) {
|
||||
is StorageServiceRepository.StorageRecordResult.Success -> result.records
|
||||
is StorageServiceRepository.StorageRecordResult.DecryptionError -> {
|
||||
Log.w(TAG, "Account record was undecryptable. Not restoring. Force-pushing.")
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return
|
||||
}
|
||||
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
|
||||
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
|
||||
val record = if (records.isNotEmpty()) records[0] else null
|
||||
|
||||
if (record == null) {
|
||||
Log.w(TAG, "Could not find account record, even though we had an ID! Not restoring.")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.protocol.InvalidKeyException
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
@@ -13,10 +12,13 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncModels
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncValidations
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||
import org.whispersystems.signalservice.api.storage.RecordIkm
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
|
||||
import java.io.IOException
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -62,9 +64,14 @@ class StorageForcePushJob private constructor(parameters: Parameters) : BaseJob(
|
||||
}
|
||||
|
||||
val storageServiceKey = SignalStore.storageService.storageKey
|
||||
val accountManager = AppDependencies.signalServiceAccountManager
|
||||
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
|
||||
|
||||
val currentVersion = accountManager.storageManifestVersion
|
||||
val currentVersion = when (val result = repository.getManifestVersion()) {
|
||||
is NetworkResult.Success -> result.result
|
||||
is NetworkResult.ApplicationError -> throw result.throwable
|
||||
is NetworkResult.NetworkError -> throw result.exception
|
||||
is NetworkResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
val oldContactStorageIds: Map<RecipientId, StorageId> = SignalDatabase.recipients.getContactStorageSyncIdsMap()
|
||||
|
||||
val newVersion = currentVersion + 1
|
||||
@@ -80,30 +87,44 @@ class StorageForcePushJob private constructor(parameters: Parameters) : BaseJob(
|
||||
inserts.add(accountRecord)
|
||||
allNewStorageIds.add(accountRecord.id)
|
||||
|
||||
val manifest = SignalStorageManifest(newVersion, SignalStore.account.deviceId, allNewStorageIds)
|
||||
val recordIkm: RecordIkm? = if (Recipient.self().storageServiceEncryptionV2Capability.isSupported) {
|
||||
Log.i(TAG, "Generating and including a new recordIkm.")
|
||||
RecordIkm.generate()
|
||||
} else {
|
||||
Log.i(TAG, "SSRE2 not yet supported. Not including recordIkm.")
|
||||
null
|
||||
}
|
||||
|
||||
val manifest = SignalStorageManifest(newVersion, SignalStore.account.deviceId, recordIkm, allNewStorageIds)
|
||||
StorageSyncValidations.validateForcePush(manifest, inserts, Recipient.self().fresh())
|
||||
|
||||
try {
|
||||
if (newVersion > 1) {
|
||||
Log.i(TAG, "Force-pushing data. Inserting ${inserts.size} IDs.")
|
||||
if (accountManager.resetStorageRecords(storageServiceKey, manifest, inserts).isPresent) {
|
||||
Log.w(TAG, "Hit a conflict. Trying again.")
|
||||
throw RetryLaterException()
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "First version, normal push. Inserting ${inserts.size} IDs.")
|
||||
if (accountManager.writeStorageRecords(storageServiceKey, manifest, inserts, emptyList()).isPresent) {
|
||||
if (newVersion > 1) {
|
||||
Log.i(TAG, "Force-pushing data. Inserting ${inserts.size} IDs.")
|
||||
when (val result = repository.resetAndWriteStorageRecords(storageServiceKey, manifest, inserts)) {
|
||||
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
|
||||
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
|
||||
Log.w(TAG, "Hit a conflict. Trying again.")
|
||||
throw RetryLaterException()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "First version, normal push. Inserting ${inserts.size} IDs.")
|
||||
when (val result = repository.writeStorageRecords(storageServiceKey, manifest, inserts, emptyList())) {
|
||||
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
|
||||
Log.w(TAG, "Hit a conflict. Trying again.")
|
||||
throw RetryLaterException()
|
||||
}
|
||||
}
|
||||
} catch (e: InvalidKeyException) {
|
||||
Log.w(TAG, "Hit an invalid key exception, which likely indicates a conflict.")
|
||||
throw RetryLaterException(e)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Force push succeeded. Updating local manifest version to: $newVersion")
|
||||
SignalStore.storageService.manifest = manifest
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = null
|
||||
SignalDatabase.recipients.applyStorageIdUpdates(newContactStorageIds)
|
||||
SignalDatabase.recipients.applyStorageIdUpdates(Collections.singletonMap(Recipient.self().id, accountRecord.id))
|
||||
SignalDatabase.unknownStorageIds.deleteAll()
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* After registration, if the user did not restore their AEP, they'll have a new master key and need to write a newly-encrypted manifest.
|
||||
* If the account is SSRE2-capable, that's all we have to upload.
|
||||
* If they're not, this job will recognize it and schedule a [StorageForcePushJob] instead.
|
||||
*/
|
||||
class StorageRotateManifestJob private constructor(parameters: Parameters) : Job(parameters) {
|
||||
companion object {
|
||||
const val KEY: String = "StorageRotateManifestJob"
|
||||
|
||||
private val TAG = Log.tag(StorageRotateManifestJob::class.java)
|
||||
}
|
||||
|
||||
constructor() : this(
|
||||
Parameters.Builder().addConstraint(NetworkConstraint.KEY)
|
||||
.setQueue(StorageSyncJob.QUEUE_KEY)
|
||||
.setMaxInstancesForFactory(1)
|
||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||
.build()
|
||||
)
|
||||
|
||||
override fun serialize(): ByteArray? = null
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun run(): Result {
|
||||
if (SignalStore.account.isLinkedDevice) {
|
||||
Log.i(TAG, "Only the primary device can rotate the manifest.")
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
if (!SignalStore.account.isRegistered || SignalStore.account.e164 == null) {
|
||||
Log.w(TAG, "User not registered. Skipping.")
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val restoreKey: StorageKey? = SignalStore.storageService.storageKeyForInitialDataRestore
|
||||
if (restoreKey == null) {
|
||||
Log.w(TAG, "There was no restore key present! Someone must have written to storage service in the meantime.")
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val storageServiceKey = SignalStore.storageService.storageKey
|
||||
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
|
||||
|
||||
val currentManifest: SignalStorageManifest = when (val result = repository.getStorageManifest(restoreKey)) {
|
||||
is StorageServiceRepository.ManifestResult.Success -> {
|
||||
result.manifest
|
||||
}
|
||||
is StorageServiceRepository.ManifestResult.DecryptionError -> {
|
||||
Log.w(TAG, "Failed to decrypt the manifest! Only recourse is to force push.", result.exception)
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return Result.failure()
|
||||
}
|
||||
is StorageServiceRepository.ManifestResult.NetworkError -> {
|
||||
Log.w(TAG, "Encountered a network error during read, retrying.", result.exception)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
StorageServiceRepository.ManifestResult.NotFoundError -> {
|
||||
Log.w(TAG, "No existing manifest was found! Force pushing.")
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return Result.failure()
|
||||
}
|
||||
is StorageServiceRepository.ManifestResult.StatusCodeError -> {
|
||||
Log.w(TAG, "Encountered a status code error during read, retrying.", result.exception)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
}
|
||||
|
||||
if (currentManifest.recordIkm == null) {
|
||||
Log.w(TAG, "No recordIkm set! Can't just rotate the manifest -- we need to re-encrypt all fo the records, too. Force pushing.")
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val manifestWithNewVersion = currentManifest.copy(version = currentManifest.version + 1)
|
||||
|
||||
return when (val result = repository.writeUnchangedManifest(storageServiceKey, manifestWithNewVersion)) {
|
||||
StorageServiceRepository.WriteStorageRecordsResult.Success -> {
|
||||
Log.i(TAG, "Successfully rotated the manifest. Clearing restore key.")
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = null
|
||||
Result.success()
|
||||
}
|
||||
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
|
||||
Log.w(TAG, "Hit a conflict! Enqueuing a sync followed by another rotation.")
|
||||
AppDependencies.jobManager.add(StorageSyncJob())
|
||||
AppDependencies.jobManager.add(StorageRotateManifestJob())
|
||||
Result.failure()
|
||||
}
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> {
|
||||
Log.w(TAG, "Encountered a status code error during write, retrying.", result.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> {
|
||||
Log.w(TAG, "Encountered a network error during write, retrying.", result.exception)
|
||||
Result.retry(defaultBackoff())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<StorageRotateManifestJob?> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): StorageRotateManifestJob {
|
||||
return StorageRotateManifestJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.storage.AccountRecordProcessor
|
||||
import org.thoughtcrime.securesms.storage.CallLinkRecordProcessor
|
||||
@@ -37,6 +38,9 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult
|
||||
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
|
||||
import org.whispersystems.signalservice.api.storage.toSignalCallLinkRecord
|
||||
import org.whispersystems.signalservice.api.storage.toSignalContactRecord
|
||||
@@ -163,8 +167,20 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
return
|
||||
}
|
||||
|
||||
val (storageServiceKey, usingTempKey) = SignalStore.storageService.storageKeyForInitialDataRestore?.let {
|
||||
Log.i(TAG, "Using temporary storage key.")
|
||||
it to true
|
||||
} ?: run {
|
||||
SignalStore.storageService.storageKey to false
|
||||
}
|
||||
|
||||
try {
|
||||
val needsMultiDeviceSync = performSync()
|
||||
val needsMultiDeviceSync = performSync(storageServiceKey)
|
||||
|
||||
if (usingTempKey) {
|
||||
Log.i(TAG, "Used a temp key. Scheduling a job to rotate the manifest.")
|
||||
AppDependencies.jobManager.add(StorageRotateManifestJob())
|
||||
}
|
||||
|
||||
if (SignalStore.account.hasLinkedDevices && needsMultiDeviceSync) {
|
||||
AppDependencies.jobManager.add(MultiDeviceStorageSyncRequestJob())
|
||||
@@ -196,15 +212,19 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
}
|
||||
|
||||
@Throws(IOException::class, RetryLaterException::class, InvalidKeyException::class)
|
||||
private fun performSync(): Boolean {
|
||||
private fun performSync(storageServiceKey: StorageKey): Boolean {
|
||||
val stopwatch = Stopwatch("StorageSync")
|
||||
val db = SignalDatabase.rawDatabase
|
||||
val accountManager = AppDependencies.signalServiceAccountManager
|
||||
val storageServiceKey = SignalStore.storageService.storageKey
|
||||
val repository = StorageServiceRepository(SignalNetwork.storageService)
|
||||
|
||||
val localManifest = SignalStore.storageService.manifest
|
||||
val remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.version).orElse(localManifest)
|
||||
|
||||
val remoteManifest = when (val result = repository.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.version)) {
|
||||
is ManifestIfDifferentVersionResult.DifferentVersion -> result.manifest
|
||||
ManifestIfDifferentVersionResult.SameVersion -> localManifest
|
||||
is ManifestIfDifferentVersionResult.DecryptionError -> throw result.exception
|
||||
is ManifestIfDifferentVersionResult.NetworkError -> throw result.exception
|
||||
is ManifestIfDifferentVersionResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
stopwatch.split("remote-manifest")
|
||||
|
||||
var self = freshSelf()
|
||||
@@ -248,7 +268,12 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
if (!idDifference.isEmpty) {
|
||||
Log.i(TAG, "[Remote Sync] Retrieving records for key difference.")
|
||||
|
||||
val remoteOnlyRecords = accountManager.readStorageRecords(storageServiceKey, idDifference.remoteOnlyIds)
|
||||
val remoteOnlyRecords = when (val result = repository.readStorageRecords(storageServiceKey, remoteManifest.recordIkm, idDifference.remoteOnlyIds)) {
|
||||
is StorageServiceRepository.StorageRecordResult.Success -> result.records
|
||||
is StorageServiceRepository.StorageRecordResult.DecryptionError -> throw result.exception
|
||||
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
|
||||
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
|
||||
stopwatch.split("remote-records")
|
||||
|
||||
@@ -292,6 +317,12 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
|
||||
Log.i(TAG, "We are up-to-date with the remote storage state.")
|
||||
|
||||
if (remoteManifest.recordIkm == null && Recipient.self().storageServiceEncryptionV2Capability.isSupported) {
|
||||
Log.w(TAG, "The SSRE2 capability is supported, but no recordIkm is set! Force pushing.")
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
return false
|
||||
}
|
||||
|
||||
val remoteWriteOperation: WriteOperationResult = db.withinTransaction {
|
||||
self = freshSelf()
|
||||
|
||||
@@ -308,9 +339,14 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
Log.i(TAG, "ID Difference :: $idDifference")
|
||||
|
||||
WriteOperationResult(
|
||||
SignalStorageManifest(remoteManifest.version + 1, SignalStore.account.deviceId, localStorageIds),
|
||||
remoteInserts,
|
||||
remoteDeletes
|
||||
manifest = SignalStorageManifest(
|
||||
version = remoteManifest.version + 1,
|
||||
sourceDeviceId = SignalStore.account.deviceId,
|
||||
recordIkm = remoteManifest.recordIkm,
|
||||
storageIds = localStorageIds
|
||||
),
|
||||
inserts = remoteInserts,
|
||||
deletes = remoteDeletes
|
||||
)
|
||||
}
|
||||
stopwatch.split("local-data-transaction")
|
||||
@@ -321,15 +357,19 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
|
||||
StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self)
|
||||
|
||||
val conflict = accountManager.writeStorageRecords(storageServiceKey, remoteWriteOperation.manifest, remoteWriteOperation.inserts, remoteWriteOperation.deletes)
|
||||
|
||||
if (conflict.isPresent) {
|
||||
Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.")
|
||||
throw RetryLaterException()
|
||||
when (val result = repository.writeStorageRecords(storageServiceKey, remoteWriteOperation.manifest, remoteWriteOperation.inserts, remoteWriteOperation.deletes)) {
|
||||
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
|
||||
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
|
||||
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
|
||||
Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.")
|
||||
throw RetryLaterException()
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Saved new manifest. Now at version: ${remoteWriteOperation.manifest.versionString}")
|
||||
SignalStore.storageService.manifest = remoteWriteOperation.manifest
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = null
|
||||
|
||||
stopwatch.split("remote-write")
|
||||
|
||||
@@ -344,7 +384,12 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param
|
||||
if (knownUnknownIds.isNotEmpty()) {
|
||||
Log.i(TAG, "We have ${knownUnknownIds.size} unknown records that we can now process.")
|
||||
|
||||
val remote = accountManager.readStorageRecords(storageServiceKey, knownUnknownIds)
|
||||
val remote = when (val result = repository.readStorageRecords(storageServiceKey, remoteManifest.recordIkm, knownUnknownIds)) {
|
||||
is StorageServiceRepository.StorageRecordResult.Success -> result.records
|
||||
is StorageServiceRepository.StorageRecordResult.DecryptionError -> throw result.exception
|
||||
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
|
||||
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
|
||||
}
|
||||
val records = StorageRecordCollection(remote)
|
||||
|
||||
Log.i(TAG, "Found ${remote.size} of the known-unknowns remotely.")
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import org.whispersystems.signalservice.api.push.ServiceIds
|
||||
@@ -30,6 +31,9 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.api.util.toByteArray
|
||||
import java.security.SecureRandom
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import org.signal.libsignal.messagebackup.AccountEntropyPool as LibSignalAccountEntropyPool
|
||||
|
||||
class AccountValues internal constructor(store: KeyValueStore, context: Context) : SignalStoreValues(store) {
|
||||
|
||||
@@ -79,6 +83,10 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
||||
private const val KEY_IS_REGISTERED = "account.is_registered"
|
||||
|
||||
private const val KEY_HAS_LINKED_DEVICES = "account.has_linked_devices"
|
||||
|
||||
private const val KEY_ACCOUNT_ENTROPY_POOL = "account.account_entropy_pool"
|
||||
|
||||
private val AEP_LOCK = ReentrantLock()
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -111,10 +119,37 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
||||
KEY_PNI_IDENTITY_PRIVATE_KEY,
|
||||
KEY_USERNAME,
|
||||
KEY_USERNAME_LINK_ENTROPY,
|
||||
KEY_USERNAME_LINK_SERVER_ID
|
||||
KEY_USERNAME_LINK_SERVER_ID,
|
||||
KEY_ACCOUNT_ENTROPY_POOL
|
||||
)
|
||||
}
|
||||
|
||||
val accountEntropyPool: AccountEntropyPool
|
||||
get() {
|
||||
AEP_LOCK.withLock {
|
||||
getString(KEY_ACCOUNT_ENTROPY_POOL, null)?.let {
|
||||
return AccountEntropyPool(it)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Generating Account Entropy Pool (AEP)...")
|
||||
val newAep = LibSignalAccountEntropyPool.generate()
|
||||
putString(KEY_ACCOUNT_ENTROPY_POOL, newAep)
|
||||
return AccountEntropyPool(newAep)
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreAccountEntropyPool(aep: AccountEntropyPool) {
|
||||
AEP_LOCK.withLock {
|
||||
store.beginWrite().putString(KEY_ACCOUNT_ENTROPY_POOL, aep.value).commit()
|
||||
}
|
||||
}
|
||||
|
||||
fun resetAccountEntropyPool() {
|
||||
AEP_LOCK.withLock {
|
||||
store.beginWrite().putString(KEY_ACCOUNT_ENTROPY_POOL, null).commit()
|
||||
}
|
||||
}
|
||||
|
||||
/** The local user's [ACI]. */
|
||||
val aci: ACI?
|
||||
get() = ACI.parseOrNull(getString(KEY_ACI, null))
|
||||
|
||||
@@ -101,7 +101,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
* Key used to backup messages.
|
||||
*/
|
||||
val messageBackupKey: MessageBackupKey
|
||||
get() = SignalStore.svr.masterKey.derivateMessageBackupKey()
|
||||
get() = SignalStore.account.accountEntropyPool.deriveMessageBackupKey()
|
||||
|
||||
/**
|
||||
* Key used to backup media. Purely random and separate from the message backup key.
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey
|
||||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
|
||||
class StorageServiceValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
companion object {
|
||||
private val TAG = Log.tag(StorageServiceValues::class)
|
||||
|
||||
private const val LAST_SYNC_TIME = "storage.last_sync_time"
|
||||
private const val NEEDS_ACCOUNT_RESTORE = "storage.needs_account_restore"
|
||||
private const val MANIFEST = "storage.manifest"
|
||||
|
||||
// TODO [linked-device] No need to track this separately -- we'd get the AEP from the primary
|
||||
private const val SYNC_STORAGE_KEY = "storage.syncStorageKey"
|
||||
private const val INITIAL_RESTORE_STORAGE_KEY = "storage.initialRestoreStorageKey"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
|
||||
public override fun getKeysToIncludeInBackup(): List<String> = emptyList()
|
||||
|
||||
@get:Synchronized
|
||||
val storageKey: StorageKey
|
||||
get() {
|
||||
if (store.containsKey(SYNC_STORAGE_KEY)) {
|
||||
@@ -54,4 +59,30 @@ class StorageServiceValues internal constructor(store: KeyValueStore) : SignalSt
|
||||
set(manifest) {
|
||||
putBlob(MANIFEST, manifest.serialize())
|
||||
}
|
||||
|
||||
/**
|
||||
* The [StorageKey] that should be used for our initial storage service data restore.
|
||||
* The presence of this value indicates that it hasn't been used yet.
|
||||
* Once there has been *any* write to storage service, this value needs to be cleared.
|
||||
*/
|
||||
@get:Synchronized
|
||||
@set:Synchronized
|
||||
var storageKeyForInitialDataRestore: StorageKey?
|
||||
get() {
|
||||
return getBlob(INITIAL_RESTORE_STORAGE_KEY, null)?.let { StorageKey(it) }
|
||||
}
|
||||
set(value) {
|
||||
if (value != storageKeyForInitialDataRestore) {
|
||||
if (value == storageKey) {
|
||||
Log.w(TAG, "The key already matches the one derived from the AEP! All good, no need to store it.")
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, null).commit()
|
||||
} else if (value != null) {
|
||||
Log.w(TAG, "Setting initial restore key!", Throwable())
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, value.serialize()).commit()
|
||||
} else {
|
||||
Log.w(TAG, "Clearing initial restore key!", Throwable())
|
||||
store.beginWrite().putBlob(INITIAL_RESTORE_STORAGE_KEY, null).commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,8 @@ package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import org.signal.core.util.StringStringSerializer
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil.localPinHash
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
|
||||
class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
companion object {
|
||||
@@ -16,8 +12,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
const val REGISTRATION_LOCK_ENABLED: String = "kbs.v2_lock_enabled"
|
||||
const val OPTED_OUT: String = "kbs.opted_out"
|
||||
|
||||
private const val MASTER_KEY = "kbs.registration_lock_master_key"
|
||||
private const val TOKEN_RESPONSE = "kbs.token_response"
|
||||
private const val PIN = "kbs.pin"
|
||||
private const val LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash"
|
||||
private const val LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp"
|
||||
@@ -42,7 +36,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
fun clearRegistrationLockAndPin() {
|
||||
store.beginWrite()
|
||||
.remove(REGISTRATION_LOCK_ENABLED)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.remove(PIN)
|
||||
.remove(LAST_CREATE_FAILED_TIMESTAMP)
|
||||
@@ -52,10 +45,11 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
.commit()
|
||||
}
|
||||
|
||||
@Deprecated("Switch to restoring AEP instead")
|
||||
@Synchronized
|
||||
fun setMasterKey(masterKey: MasterKey, pin: String?) {
|
||||
store.beginWrite().apply {
|
||||
putBlob(MASTER_KEY, masterKey.serialize())
|
||||
// putBlob(MASTER_KEY, masterKey.serialize())
|
||||
putLong(LAST_CREATE_FAILED_TIMESTAMP, -1)
|
||||
putBoolean(OPTED_OUT, false)
|
||||
|
||||
@@ -71,10 +65,21 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
}.commit()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun setPin(pin: String) {
|
||||
store.beginWrite()
|
||||
.putString(PIN, pin)
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin))
|
||||
.commit()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun setPinIfNotPresent(pin: String) {
|
||||
if (store.getString(PIN, null) == null) {
|
||||
store.beginWrite().putString(PIN, pin).commit()
|
||||
store.beginWrite()
|
||||
.putString(PIN, pin)
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash(pin))
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,33 +99,18 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
return getLong(LAST_CREATE_FAILED_TIMESTAMP, -1) > 0
|
||||
}
|
||||
|
||||
/** Returns the Master Key, lazily creating one if needed. */
|
||||
@get:Synchronized
|
||||
/** Returns the Master Key */
|
||||
val masterKey: MasterKey
|
||||
get() {
|
||||
val blob = store.getBlob(MASTER_KEY, null)
|
||||
if (blob != null) {
|
||||
return MasterKey(blob)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Generating Master Key...", Throwable())
|
||||
val masterKey = MasterKey.createNew(SecureRandom())
|
||||
store.beginWrite().putBlob(MASTER_KEY, masterKey.serialize()).commit()
|
||||
return masterKey
|
||||
}
|
||||
get() = SignalStore.account.accountEntropyPool.deriveMasterKey()
|
||||
|
||||
@get:Synchronized
|
||||
val pinBackedMasterKey: MasterKey?
|
||||
/** Returns null if master key is not backed up by a pin. */
|
||||
get() {
|
||||
if (!isRegistrationLockEnabled) return null
|
||||
return rawMasterKey
|
||||
return masterKey
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
private val rawMasterKey: MasterKey?
|
||||
get() = getBlob(MASTER_KEY, null)?.let { MasterKey(it) }
|
||||
|
||||
@get:Synchronized
|
||||
val registrationLockToken: String?
|
||||
get() {
|
||||
@@ -131,8 +121,7 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
@get:Synchronized
|
||||
val recoveryPassword: String?
|
||||
get() {
|
||||
val masterKey = rawMasterKey
|
||||
return if (masterKey != null && hasOptedInWithAccess()) {
|
||||
return if (hasOptedInWithAccess()) {
|
||||
masterKey.deriveRegistrationRecoveryPassword()
|
||||
} else {
|
||||
null
|
||||
@@ -242,8 +231,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
fun optOut() {
|
||||
store.beginWrite()
|
||||
.putBoolean(OPTED_OUT, true)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
.putBlob(MASTER_KEY, MasterKey.createNew(SecureRandom()).serialize())
|
||||
.remove(LOCK_LOCAL_PIN_HASH)
|
||||
.remove(PIN)
|
||||
.remove(RESTORED_VIA_ACCOUNT_ENTROPY_KEY)
|
||||
@@ -256,17 +243,5 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
|
||||
return getBoolean(OPTED_OUT, false)
|
||||
}
|
||||
|
||||
@get:Synchronized
|
||||
val registrationLockTokenResponse: TokenResponse?
|
||||
get() {
|
||||
val token = store.getString(TOKEN_RESPONSE, null) ?: return null
|
||||
|
||||
try {
|
||||
return JsonUtils.fromJson(token, TokenResponse::class.java)
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
var lastRefreshAuthTimestamp: Long by longValue(SVR_LAST_AUTH_REFRESH_TIMESTAMP, 0L)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.RecipientRecord;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
|
||||
public final class LogSectionCapabilities implements LogSection {
|
||||
@@ -30,17 +31,20 @@ public final class LogSectionCapabilities implements LogSection {
|
||||
|
||||
Recipient self = Recipient.self();
|
||||
|
||||
AccountAttributes.Capabilities localCapabilities = AppCapabilities.getCapabilities(false);
|
||||
AccountAttributes.Capabilities localCapabilities = AppCapabilities.getCapabilities(false, RemoteConfig.getStorageServiceEncryptionV2());
|
||||
RecipientRecord.Capabilities globalCapabilities = SignalDatabase.recipients().getCapabilities(self.getId());
|
||||
|
||||
StringBuilder builder = new StringBuilder().append("-- Local").append("\n")
|
||||
.append("DeleteSync: ").append(localCapabilities.getDeleteSync()).append("\n")
|
||||
.append("VersionedExpirationTimer: ").append(localCapabilities.getVersionedExpirationTimer()).append("\n")
|
||||
.append("StorageServiceEncryptionV2: ").append(localCapabilities.getStorageServiceEncryptionV2()).append("\n")
|
||||
.append("\n")
|
||||
.append("-- Global").append("\n");
|
||||
|
||||
if (globalCapabilities != null) {
|
||||
builder.append("DeleteSync: ").append(globalCapabilities.getDeleteSync()).append("\n");
|
||||
builder.append("VersionedExpirationTimer: ").append(globalCapabilities.getVersionedExpirationTimer()).append("\n");
|
||||
builder.append("StorageServiceEncryptionV2: ").append(globalCapabilities.getStorageServiceEncryptionV2()).append("\n");
|
||||
builder.append("\n");
|
||||
} else {
|
||||
builder.append("Self not found!");
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.migrations
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
import org.thoughtcrime.securesms.jobs.Svr2MirrorJob
|
||||
|
||||
/**
|
||||
* Migration for when we introduce the Account Entropy Pool (AEP).
|
||||
*/
|
||||
internal class AepMigrationJob(
|
||||
parameters: Parameters = Parameters.Builder().build()
|
||||
) : MigrationJob(parameters) {
|
||||
|
||||
companion object {
|
||||
val TAG = Log.tag(AepMigrationJob::class.java)
|
||||
const val KEY = "AepMigrationJob"
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun isUiBlocking(): Boolean = false
|
||||
|
||||
override fun performMigration() {
|
||||
AppDependencies.jobManager.add(Svr2MirrorJob())
|
||||
AppDependencies.jobManager.add(StorageForcePushJob())
|
||||
}
|
||||
|
||||
override fun shouldRetry(e: Exception): Boolean = false
|
||||
|
||||
class Factory : Job.Factory<AepMigrationJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): AepMigrationJob {
|
||||
return AepMigrationJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,9 +160,10 @@ public class ApplicationMigrations {
|
||||
static final int BACKFILL_DIGESTS_V3 = 116;
|
||||
static final int SVR2_ENCLAVE_UPDATE_2 = 117;
|
||||
static final int WALLPAPER_MIGRATION_CLEANUP = 118;
|
||||
static final int AEP_INTRODUCTION = 119;
|
||||
}
|
||||
|
||||
public static final int CURRENT_VERSION = 118;
|
||||
public static final int CURRENT_VERSION = 119;
|
||||
|
||||
/**
|
||||
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
|
||||
@@ -733,6 +734,10 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.WALLPAPER_MIGRATION_CLEANUP, new WallpaperCleanupMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.AEP_INTRODUCTION) {
|
||||
jobs.put(Version.AEP_INTRODUCTION, new AepMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.whispersystems.signalservice.api.archive.ArchiveApi
|
||||
import org.whispersystems.signalservice.api.attachment.AttachmentApi
|
||||
import org.whispersystems.signalservice.api.keys.KeysApi
|
||||
import org.whispersystems.signalservice.api.link.LinkDeviceApi
|
||||
import org.whispersystems.signalservice.api.storage.StorageServiceApi
|
||||
|
||||
/**
|
||||
* A convenient way to access network operations, similar to [org.thoughtcrime.securesms.database.SignalDatabase] and [org.thoughtcrime.securesms.keyvalue.SignalStore].
|
||||
@@ -26,4 +27,7 @@ object SignalNetwork {
|
||||
|
||||
val linkDevice: LinkDeviceApi
|
||||
get() = AppDependencies.linkDeviceApi
|
||||
|
||||
val storageService: StorageServiceApi
|
||||
get() = AppDependencies.storageServiceApi
|
||||
}
|
||||
|
||||
@@ -168,7 +168,8 @@ object SvrRepository {
|
||||
SignalStore.registration.localRegistrationMetadata = metadata.copy(masterKey = response.masterKey.serialize().toByteString(), pin = userPin)
|
||||
}
|
||||
|
||||
SignalStore.svr.setMasterKey(response.masterKey, userPin)
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = response.masterKey.deriveStorageServiceKey()
|
||||
SignalStore.svr.setPin(userPin)
|
||||
SignalStore.svr.isRegistrationLockEnabled = false
|
||||
SignalStore.pin.resetPinReminders()
|
||||
SignalStore.pin.keyboardType = pinKeyboardType
|
||||
@@ -267,7 +268,7 @@ object SvrRepository {
|
||||
if (overallResponse is BackupResponse.Success) {
|
||||
Log.i(TAG, "[setPin] Success!", true)
|
||||
|
||||
SignalStore.svr.setMasterKey(masterKey, userPin)
|
||||
SignalStore.svr.setPin(userPin)
|
||||
responses
|
||||
.filterIsInstance<BackupResponse.Success>()
|
||||
.forEach {
|
||||
@@ -320,13 +321,14 @@ object SvrRepository {
|
||||
Log.i(TAG, "[onRegistrationComplete] ReRegistration Skip SMS", true)
|
||||
}
|
||||
|
||||
SignalStore.svr.setMasterKey(masterKey, userPin)
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = masterKey.deriveStorageServiceKey()
|
||||
SignalStore.svr.setPin(userPin)
|
||||
SignalStore.pin.resetPinReminders()
|
||||
|
||||
AppDependencies.jobManager.add(ResetSvrGuessCountJob())
|
||||
} else if (masterKey != null) {
|
||||
Log.i(TAG, "[onRegistrationComplete] ReRegistered with key without pin")
|
||||
SignalStore.svr.setMasterKey(masterKey, null)
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = masterKey.deriveStorageServiceKey()
|
||||
} else if (hasPinToRestore) {
|
||||
Log.i(TAG, "[onRegistrationComplete] Has a PIN to restore.", true)
|
||||
SignalStore.svr.clearRegistrationLockAndPin()
|
||||
|
||||
@@ -321,6 +321,9 @@ class Recipient(
|
||||
/** The user's capability to handle tracking an expire timer version. */
|
||||
val versionedExpirationTimerCapability: Capability = capabilities.versionedExpirationTimer
|
||||
|
||||
/** The user's capability to handle the new storage record encryption scheme. */
|
||||
val storageServiceEncryptionV2Capability: Capability = capabilities.storageServiceEncryptionV2
|
||||
|
||||
/** The state around whether we can send sealed sender to this user. */
|
||||
val sealedSenderAccessMode: SealedSenderAccessMode = if (pni.isPresent && pni == serviceId) {
|
||||
SealedSenderAccessMode.DISABLED
|
||||
|
||||
@@ -58,6 +58,7 @@ import org.thoughtcrime.securesms.registration.fcm.PushChallengeRequest
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
@@ -274,7 +275,8 @@ object RegistrationRepository {
|
||||
withContext(Dispatchers.IO) {
|
||||
val credentialSet = SvrAuthCredentialSet(svr2Credentials = svr2Credentials, svr3Credentials = svr3Credentials)
|
||||
val masterKey = SvrRepository.restoreMasterKeyPreRegistration(credentialSet, pin)
|
||||
SignalStore.svr.setMasterKey(masterKey, pin)
|
||||
SignalStore.storageService.storageKeyForInitialDataRestore = masterKey.deriveStorageServiceKey()
|
||||
SignalStore.svr.setPin(pin)
|
||||
return@withContext masterKey
|
||||
}
|
||||
|
||||
@@ -420,7 +422,7 @@ object RegistrationRepository {
|
||||
registrationLock = registrationLock,
|
||||
unidentifiedAccessKey = unidentifiedAccessKey,
|
||||
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
|
||||
capabilities = AppCapabilities.getCapabilities(true),
|
||||
capabilities = AppCapabilities.getCapabilities(true, RemoteConfig.storageServiceEncryptionV2),
|
||||
discoverableByPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.DISCOVERABLE,
|
||||
name = null,
|
||||
pniRegistrationId = registrationData.pniRegistrationId,
|
||||
|
||||
@@ -81,13 +81,13 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
|
||||
}
|
||||
|
||||
private fun createChallengeRequiredProcessor(errorResult: NetworkResult.StatusCodeError<RegistrationSessionMetadataResponse>): VerificationCodeRequestResult {
|
||||
if (errorResult.body == null) {
|
||||
if (errorResult.stringBody == null) {
|
||||
Log.w(TAG, "Attempted to parse error body with response code ${errorResult.code} for list of requested information, but body was null.")
|
||||
return UnknownError(errorResult.exception)
|
||||
}
|
||||
|
||||
try {
|
||||
val response = JsonUtil.fromJson(errorResult.body, RegistrationSessionMetadataJson::class.java)
|
||||
val response = JsonUtil.fromJson(errorResult.stringBody, RegistrationSessionMetadataJson::class.java)
|
||||
return ChallengeRequired(Challenge.parse(response.requestedInformation))
|
||||
} catch (parseException: IOException) {
|
||||
Log.w(TAG, "Attempted to parse error body for list of requested information, but encountered exception.", parseException)
|
||||
|
||||
@@ -61,6 +61,7 @@ import org.thoughtcrime.securesms.registration.fcm.PushChallengeRequest
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
@@ -414,7 +415,7 @@ object RegistrationRepository {
|
||||
registrationLock = registrationLock,
|
||||
unidentifiedAccessKey = unidentifiedAccessKey,
|
||||
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
|
||||
capabilities = AppCapabilities.getCapabilities(true),
|
||||
capabilities = AppCapabilities.getCapabilities(true, RemoteConfig.storageServiceEncryptionV2),
|
||||
discoverableByPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.DISCOVERABLE,
|
||||
name = null,
|
||||
pniRegistrationId = registrationData.pniRegistrationId,
|
||||
|
||||
@@ -1143,5 +1143,13 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/** Whether or not this device supports the new storage service recordIkm encryption. */
|
||||
@JvmStatic
|
||||
val storageServiceEncryptionV2: Boolean by remoteBoolean(
|
||||
key = "android.ssre2",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user