mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 15:46:30 +00:00
Separate PINs from Registration Lock.
You can now have a PIN without having registration lock. Note: We still need to change the registration flow to allow non-reglock users to enter their PIN.
This commit is contained in:
@@ -10,8 +10,11 @@ public final class AppCapabilities {
|
||||
private static final boolean UUID_CAPABLE = false;
|
||||
private static final boolean GROUPS_V2_CAPABLE = false;
|
||||
|
||||
public static SignalServiceProfile.Capabilities getCapabilities() {
|
||||
return new SignalServiceProfile.Capabilities(UUID_CAPABLE,
|
||||
GROUPS_V2_CAPABLE);
|
||||
/**
|
||||
* @param storageCapable Whether or not the user can use storage service. This is another way of
|
||||
* asking if the user has set a Signal PIN or not.
|
||||
*/
|
||||
public static SignalServiceProfile.Capabilities getCapabilities(boolean storageCapable) {
|
||||
return new SignalServiceProfile.Capabilities(UUID_CAPABLE, GROUPS_V2_CAPABLE, storageCapable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
@@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.CensorshipUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -182,9 +180,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||
}
|
||||
|
||||
private boolean userMustSetKbsPin() {
|
||||
// TODO [greyson] [pins] Maybe re-enable in the future
|
||||
// return !SignalStore.registrationValues().isRegistrationComplete() && !PinUtil.userHasPin(this);
|
||||
return false;
|
||||
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin();
|
||||
}
|
||||
|
||||
private boolean userMustSetProfileName() {
|
||||
|
||||
@@ -102,7 +102,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||
import org.thoughtcrime.securesms.insights.InsightsLauncher;
|
||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockV1Dialog;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||
@@ -238,7 +238,7 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
||||
|
||||
RatingManager.showRatingDialogIfNecessary(requireContext());
|
||||
|
||||
RegistrationLockDialog.showReminderIfNecessary(this);
|
||||
RegistrationLockV1Dialog.showReminderIfNecessary(this);
|
||||
|
||||
TooltipCompat.setTooltipText(searchAction, getText(R.string.SearchToolbar_search_for_conversations_contacts_and_messages));
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ public class ApplicationDependencies {
|
||||
|
||||
public static synchronized @NonNull KeyBackupService getKeyBackupService() {
|
||||
return getSignalServiceAccountManager().getKeyBackupService(IasKeyStore.getIasKeyStore(application),
|
||||
BuildConfig.KEY_BACKUP_ENCLAVE_NAME,
|
||||
BuildConfig.KEY_BACKUP_MRENCLAVE,
|
||||
BuildConfig.KBS_ENCLAVE_NAME,
|
||||
BuildConfig.KBS_MRENCLAVE,
|
||||
10);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageKeyRotationMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageCapabilityMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UuidMigrationJob;
|
||||
|
||||
@@ -119,7 +119,7 @@ public final class JobManagerFactories {
|
||||
put(RegistrationPinV2MigrationJob.KEY, new RegistrationPinV2MigrationJob.Factory());
|
||||
put(StickerLaunchMigrationJob.KEY, new StickerLaunchMigrationJob.Factory());
|
||||
put(StickerAdditionMigrationJob.KEY, new StickerAdditionMigrationJob.Factory());
|
||||
put(StorageKeyRotationMigrationJob.KEY, new StorageKeyRotationMigrationJob.Factory());
|
||||
put(StorageCapabilityMigrationJob.KEY, new StorageCapabilityMigrationJob.Factory());
|
||||
put(StorageServiceMigrationJob.KEY, new StorageServiceMigrationJob.Factory());
|
||||
put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory());
|
||||
|
||||
@@ -132,6 +132,7 @@ public final class JobManagerFactories {
|
||||
put("RefreshUnidentifiedDeliveryAbilityJob", new FailingJob.Factory());
|
||||
put("Argon2TestJob", new FailingJob.Factory());
|
||||
put("Argon2TestMigrationJob", new PassingMigrationJob.Factory());
|
||||
put("StorageKeyRotationMigrationJob", new PassingMigrationJob.Factory());
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class MultiDeviceKeysUpdateJob extends BaseJob {
|
||||
}
|
||||
|
||||
SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageMasterKey().deriveStorageServiceKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forKeys(new KeysMessage(Optional.fromNullable(storageServiceKey))),
|
||||
UnidentifiedAccessUtil.getAccessForSync(context));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.AppCapabilities;
|
||||
@@ -51,22 +53,24 @@ public class RefreshAttributesJob extends BaseJob {
|
||||
boolean fetchesMessages = TextSecurePreferences.isFcmDisabled(context);
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
String pin = null;
|
||||
String registrationLockToken = null;
|
||||
String registrationLockV1 = null;
|
||||
String registrationLockV2 = null;
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
|
||||
if (kbsValues.isV2RegistrationLockEnabled()) {
|
||||
registrationLockToken = kbsValues.getRegistrationLockToken();
|
||||
registrationLockV2 = kbsValues.getRegistrationLockToken();
|
||||
} else if (TextSecurePreferences.isV1RegistrationLockEnabled(context)) {
|
||||
//noinspection deprecation Ok to read here as they have not migrated
|
||||
pin = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
registrationLockV1 = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin());
|
||||
|
||||
SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
signalAccountManager.setAccountAttributes(null, registrationId, fetchesMessages,
|
||||
pin, registrationLockToken,
|
||||
registrationLockV1, registrationLockV2,
|
||||
unidentifiedAccessKey, universalUnidentifiedAccess,
|
||||
AppCapabilities.getCapabilities());
|
||||
AppCapabilities.getCapabilities(kbsValues.hasPin()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
@@ -63,7 +64,7 @@ public class StorageAccountRestoreJob extends BaseJob {
|
||||
@Override
|
||||
protected void onRun() throws Exception {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageMasterKey().deriveStorageServiceKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
|
||||
Optional<SignalStorageManifest> manifest = accountManager.getStorageManifest(storageServiceKey);
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ public class StorageForcePushJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, RetryLaterException {
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageMasterKey().deriveStorageServiceKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context);
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncValidations;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -86,11 +87,16 @@ public class StorageSyncJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
protected void onRun() throws IOException, RetryLaterException {
|
||||
if (!FeatureFlags.storageService()) {
|
||||
if (!FeatureFlags.pinsForAll()) {
|
||||
Log.i(TAG, "Not enabled. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SignalStore.kbsValues().hasPin()) {
|
||||
Log.i(TAG, "Doesn't have a PIN. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
Log.i(TAG, "Not registered. Skipping.");
|
||||
return;
|
||||
@@ -127,7 +133,7 @@ public class StorageSyncJob extends BaseJob {
|
||||
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
|
||||
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context);
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageMasterKey().deriveStorageServiceKey();
|
||||
StorageKey storageServiceKey = SignalStore.storageServiceValues().getOrCreateStorageKey();
|
||||
|
||||
boolean needsMultiDeviceSync = false;
|
||||
long localManifestVersion = TextSecurePreferences.getStorageManifestVersion(context);
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.keyvalue;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
@@ -13,7 +15,7 @@ import java.security.SecureRandom;
|
||||
|
||||
public final class KbsValues {
|
||||
|
||||
private static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
||||
public static final String V2_LOCK_ENABLED = "kbs.v2_lock_enabled";
|
||||
private static final String MASTER_KEY = "kbs.registration_lock_master_key";
|
||||
private static final String TOKEN_RESPONSE = "kbs.token_response";
|
||||
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
|
||||
@@ -27,7 +29,7 @@ public final class KbsValues {
|
||||
/**
|
||||
* Deliberately does not clear the {@link #MASTER_KEY}.
|
||||
*/
|
||||
public void clearRegistrationLock() {
|
||||
public void clearRegistrationLockAndPin() {
|
||||
store.beginWrite()
|
||||
.remove(V2_LOCK_ENABLED)
|
||||
.remove(TOKEN_RESPONSE)
|
||||
@@ -35,23 +37,30 @@ public final class KbsValues {
|
||||
.commit();
|
||||
}
|
||||
|
||||
public synchronized void setRegistrationLockMasterKey(@NonNull RegistrationLockData registrationLockData, @NonNull String localPinHash) {
|
||||
MasterKey masterKey = registrationLockData.getMasterKey();
|
||||
public synchronized void setKbsMasterKey(@NonNull KbsPinData pinData, @NonNull String localPinHash) {
|
||||
MasterKey masterKey = pinData.getMasterKey();
|
||||
String tokenResponse;
|
||||
try {
|
||||
tokenResponse = JsonUtils.toJson(registrationLockData.getTokenResponse());
|
||||
tokenResponse = JsonUtils.toJson(pinData.getTokenResponse());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
store.beginWrite()
|
||||
.putBoolean(V2_LOCK_ENABLED, true)
|
||||
.putString(TOKEN_RESPONSE, tokenResponse)
|
||||
.putBlob(MASTER_KEY, masterKey.serialize())
|
||||
.putString(LOCK_LOCAL_PIN_HASH, localPinHash)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public synchronized void setV2RegistrationLockEnabled(boolean enabled) {
|
||||
store.beginWrite().putBoolean(V2_LOCK_ENABLED, enabled).apply();
|
||||
}
|
||||
|
||||
public synchronized boolean isV2RegistrationLockEnabled() {
|
||||
return store.getBoolean(V2_LOCK_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds or creates the master key. Therefore this will always return a master key whether backed
|
||||
* up or not.
|
||||
@@ -97,8 +106,8 @@ public final class KbsValues {
|
||||
return store.getString(LOCK_LOCAL_PIN_HASH, null);
|
||||
}
|
||||
|
||||
public synchronized boolean isV2RegistrationLockEnabled() {
|
||||
return store.getBoolean(V2_LOCK_ENABLED, false);
|
||||
public synchronized boolean hasPin() {
|
||||
return getLocalPinHash() != null;
|
||||
}
|
||||
|
||||
public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
@@ -19,6 +20,7 @@ public final class PinValues {
|
||||
private static final String LAST_SUCCESSFUL_ENTRY = "pin.last_successful_entry";
|
||||
private static final String NEXT_INTERVAL = "pin.interval_index";
|
||||
private static final String KEYBOARD_TYPE = "kbs.keyboard_type";
|
||||
private static final String PIN_STATE = "pin.pin_state";
|
||||
|
||||
private final KeyValueStore store;
|
||||
|
||||
@@ -55,9 +57,9 @@ public final class PinValues {
|
||||
.apply();
|
||||
}
|
||||
|
||||
public void onPinChange() {
|
||||
public void resetPinReminders() {
|
||||
long nextInterval = SignalPinReminders.INITIAL_INTERVAL;
|
||||
Log.i(TAG, "onPinChange() nextInterval: " + nextInterval);
|
||||
Log.i(TAG, "resetPinReminders() nextInterval: " + nextInterval, new Throwable());
|
||||
|
||||
store.beginWrite()
|
||||
.putLong(NEXT_INTERVAL, nextInterval)
|
||||
@@ -82,4 +84,12 @@ public final class PinValues {
|
||||
public @NonNull PinKeyboardType getKeyboardType() {
|
||||
return PinKeyboardType.fromCode(store.getString(KEYBOARD_TYPE, null));
|
||||
}
|
||||
|
||||
public void setPinState(@NonNull String pinState) {
|
||||
store.beginWrite().putString(PIN_STATE, pinState).commit();
|
||||
}
|
||||
|
||||
public @Nullable String getPinState() {
|
||||
return store.getString(PIN_STATE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link PreferenceDataStore} interface to let us link preference screens
|
||||
* to the {@link SignalStore}.
|
||||
*/
|
||||
public class SignalPreferenceDataStore extends PreferenceDataStore {
|
||||
|
||||
private final KeyValueStore store;
|
||||
|
||||
SignalPreferenceDataStore(@NonNull KeyValueStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putString(String key, @Nullable String value) {
|
||||
store.beginWrite().putString(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInt(String key, int value) {
|
||||
store.beginWrite().putInteger(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putLong(String key, long value) {
|
||||
store.beginWrite().putLong(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putFloat(String key, float value) {
|
||||
store.beginWrite().putFloat(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBoolean(String key, boolean value) {
|
||||
store.beginWrite().putBoolean(key, value).apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getString(String key, @Nullable String defValue) {
|
||||
return store.getString(key, defValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defValue) {
|
||||
return store.getInteger(key, defValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defValue) {
|
||||
return store.getLong(key, defValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String key, float defValue) {
|
||||
return store.getFloat(key, defValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
return store.getBoolean(key, defValue);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Simple, encrypted key-value store.
|
||||
@@ -56,6 +56,10 @@ public final class SignalStore {
|
||||
putLong(MESSAGE_REQUEST_ENABLE_TIME, time);
|
||||
}
|
||||
|
||||
public static @NonNull PreferenceDataStore getPreferenceDataStore() {
|
||||
return new SignalPreferenceDataStore(getStore());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures any pending writes are finished. Only intended to be called by
|
||||
* {@link SignalUncaughtExceptionHandler}.
|
||||
|
||||
@@ -4,13 +4,13 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.storage.StorageKey;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class StorageServiceValues {
|
||||
|
||||
private static final String STORAGE_MASTER_KEY = "storage.storage_master_key";
|
||||
private static final String LAST_SYNC_TIME = "storage.last_sync_time";
|
||||
private static final String LAST_SYNC_TIME = "storage.last_sync_time";
|
||||
|
||||
private final KeyValueStore store;
|
||||
|
||||
@@ -18,23 +18,8 @@ public class StorageServiceValues {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public synchronized MasterKey getOrCreateStorageMasterKey() {
|
||||
byte[] blob = store.getBlob(STORAGE_MASTER_KEY, null);
|
||||
|
||||
if (blob == null) {
|
||||
store.beginWrite()
|
||||
.putBlob(STORAGE_MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
|
||||
.commit();
|
||||
blob = store.getBlob(STORAGE_MASTER_KEY, null);
|
||||
}
|
||||
|
||||
return new MasterKey(blob);
|
||||
}
|
||||
|
||||
public synchronized void rotateStorageMasterKey() {
|
||||
store.beginWrite()
|
||||
.putBlob(STORAGE_MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize())
|
||||
.commit();
|
||||
public synchronized StorageKey getOrCreateStorageKey() {
|
||||
return SignalStore.kbsValues().getOrCreateMasterKey().deriveStorageServiceKey();
|
||||
}
|
||||
|
||||
public long getLastSyncTime() {
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.text.Editable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
@@ -33,35 +32,27 @@ import androidx.fragment.app.Fragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsConstants;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class RegistrationLockDialog {
|
||||
public final class RegistrationLockV1Dialog {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationLockDialog.class);
|
||||
private static final String TAG = Log.tag(RegistrationLockV1Dialog.class);
|
||||
|
||||
public static void showReminderIfNecessary(@NonNull Fragment fragment) {
|
||||
final Context context = fragment.requireContext();
|
||||
|
||||
if (!TextSecurePreferences.isV1RegistrationLockEnabled(context) && !SignalStore.kbsValues().isV2RegistrationLockEnabled()) {
|
||||
if (!PinState.shouldShowRegistrationLockV1Reminder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,9 +115,7 @@ public final class RegistrationLockDialog {
|
||||
reminder.setText(new SpannableStringBuilder(reminderIntro).append(" ").append(reminderText).append(" ").append(forgotText));
|
||||
reminder.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
pinEditText.addTextChangedListener(SignalStore.kbsValues().isV2RegistrationLockEnabled()
|
||||
? getV2PinWatcher(context, dialog)
|
||||
: getV1PinWatcher(context, dialog));
|
||||
pinEditText.addTextChangedListener(getV1PinWatcher(context, dialog));
|
||||
}
|
||||
|
||||
private static TextWatcher getV1PinWatcher(@NonNull Context context, AlertDialog dialog) {
|
||||
@@ -144,25 +133,6 @@ public final class RegistrationLockDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private static TextWatcher getV2PinWatcher(@NonNull Context context, AlertDialog dialog) {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
String localPinHash = kbsValues.getLocalPinHash();
|
||||
|
||||
if (localPinHash == null) throw new AssertionError("No local pin hash set at time of reminder");
|
||||
|
||||
return new AfterTextChanged((Editable s) -> {
|
||||
if (s == null) return;
|
||||
String pin = s.toString();
|
||||
if (TextUtils.isEmpty(pin)) return;
|
||||
if (pin.length() < KbsConstants.MINIMUM_PIN_LENGTH) return;
|
||||
|
||||
if (PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||
dialog.dismiss();
|
||||
RegistrationLockReminders.scheduleReminder(context, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static void showRegistrationLockPrompt(@NonNull Context context, @NonNull SwitchPreferenceCompat preference) {
|
||||
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||
@@ -210,19 +180,7 @@ public final class RegistrationLockDialog {
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
Log.i(TAG, "Setting pin on KBS - dialog");
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pinValue, pinChangeSession);
|
||||
RegistrationLockData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(kbsData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
|
||||
PinState.onCompleteRegistrationLockV1Reminder(context, pinValue);
|
||||
Log.i(TAG, "Pin set on KBS");
|
||||
return true;
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
@@ -277,23 +235,7 @@ public final class RegistrationLockDialog {
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
|
||||
if (kbsValues.isV2RegistrationLockEnabled()) {
|
||||
Log.i(TAG, "Removing v2 registration lock pin from server");
|
||||
TokenResponse currentToken = kbsValues.getRegistrationLockTokenResponse();
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
keyBackupService.newPinChangeSession(currentToken).removePin();
|
||||
kbsValues.clearRegistrationLock();
|
||||
}
|
||||
|
||||
// It is possible a migration has not occurred, in this case, we need to remove the old V1 Pin
|
||||
if (TextSecurePreferences.isV1RegistrationLockEnabled(context)) {
|
||||
Log.i(TAG, "Removing v1 registration lock pin from server");
|
||||
ApplicationDependencies.getSignalServiceAccountManager().removeV1Pin();
|
||||
}
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
PinState.onDisableRegistrationLockV1(context);
|
||||
return true;
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, e);
|
||||
@@ -2,16 +2,13 @@ package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RawRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.util.Preconditions;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
@@ -25,6 +22,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewModel> {
|
||||
|
||||
private ConfirmKbsPinViewModel viewModel;
|
||||
@@ -43,7 +42,7 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
||||
@Override
|
||||
protected ConfirmKbsPinViewModel initializeViewModel() {
|
||||
ConfirmKbsPinFragmentArgs args = ConfirmKbsPinFragmentArgs.fromBundle(requireArguments());
|
||||
KbsPin userEntry = Preconditions.checkNotNull(args.getUserEntry());
|
||||
KbsPin userEntry = Objects.requireNonNull(args.getUserEntry());
|
||||
PinKeyboardType keyboard = args.getKeyboard();
|
||||
ConfirmKbsPinRepository repository = new ConfirmKbsPinRepository();
|
||||
ConfirmKbsPinViewModel.Factory factory = new ConfirmKbsPinViewModel.Factory(userEntry, keyboard, repository);
|
||||
@@ -111,7 +110,6 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
closeNavGraphBranch();
|
||||
SignalStore.registrationValues().setRegistrationComplete();
|
||||
SignalStore.pinValues().onPinChange();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -6,20 +6,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -36,22 +25,9 @@ final class ConfirmKbsPinRepository {
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
Log.i(TAG, "Setting pin on KBS");
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pinValue, pinChangeSession);
|
||||
RegistrationLockData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(kbsData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
SignalStore.pinValues().setKeyboardType(keyboard);
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
|
||||
PinState.onPinChangedOrCreated(context, pinValue, keyboard);
|
||||
Log.i(TAG, "Pin set on KBS");
|
||||
|
||||
return PinSetResult.SUCCESS;
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, e);
|
||||
|
||||
@@ -2,23 +2,22 @@ package org.thoughtcrime.securesms.lock.v2;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Preconditions;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.lock.v2.ConfirmKbsPinRepository.PinSetResult;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
|
||||
final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewModel {
|
||||
|
||||
private final ConfirmKbsPinRepository repository;
|
||||
|
||||
private final MutableLiveData<KbsPin> userEntry = new MutableLiveData<>(KbsPin.EMPTY);
|
||||
private final MutableLiveData<PinKeyboardType> keyboard = new MutableLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final MutableLiveData<SaveAnimation> saveAnimation = new MutableLiveData<>(SaveAnimation.NONE);
|
||||
private final MutableLiveData<Label> label = new MutableLiveData<>(Label.RE_ENTER_PIN);
|
||||
private final DefaultValueLiveData<KbsPin> userEntry = new DefaultValueLiveData<>(KbsPin.EMPTY);
|
||||
private final DefaultValueLiveData<PinKeyboardType> keyboard = new DefaultValueLiveData<>(PinKeyboardType.NUMERIC);
|
||||
private final DefaultValueLiveData<SaveAnimation> saveAnimation = new DefaultValueLiveData<>(SaveAnimation.NONE);
|
||||
private final DefaultValueLiveData<Label> label = new DefaultValueLiveData<>(Label.RE_ENTER_PIN);
|
||||
|
||||
private final KbsPin pinToConfirm;
|
||||
|
||||
@@ -49,7 +48,7 @@ final class ConfirmKbsPinViewModel extends ViewModel implements BaseKbsPinViewMo
|
||||
this.label.setValue(Label.CREATING_PIN);
|
||||
this.saveAnimation.setValue(SaveAnimation.LOADING);
|
||||
|
||||
repository.setPin(pinToConfirm, Preconditions.checkNotNull(this.keyboard.getValue()), this::handleResult);
|
||||
repository.setPin(pinToConfirm, this.keyboard.getValue(), this::handleResult);
|
||||
} else {
|
||||
this.label.setValue(Label.PIN_DOES_NOT_MATCH);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
||||
public final class KbsSplashFragment extends Fragment {
|
||||
|
||||
@@ -42,7 +43,7 @@ public final class KbsSplashFragment extends Fragment {
|
||||
primaryAction.setOnClickListener(v -> onCreatePin());
|
||||
secondaryAction.setOnClickListener(v -> onLearnMore());
|
||||
|
||||
if (PinUtil.userHasPin(requireContext())) {
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) {
|
||||
setUpRegLockEnabled();
|
||||
} else {
|
||||
setUpRegLockDisabled();
|
||||
@@ -73,7 +74,7 @@ public final class KbsSplashFragment extends Fragment {
|
||||
private void onCreatePin() {
|
||||
KbsSplashFragmentDirections.ActionCreateKbsPin action = KbsSplashFragmentDirections.actionCreateKbsPin();
|
||||
|
||||
action.setIsPinChange(PinUtil.userHasPin(requireContext()));
|
||||
action.setIsPinChange(SignalStore.kbsValues().hasPin());
|
||||
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import org.thoughtcrime.securesms.util.CensorshipUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public final class PinUtil {
|
||||
public final class RegistrationLockUtil {
|
||||
|
||||
private PinUtil() {}
|
||||
private RegistrationLockUtil() {}
|
||||
|
||||
public static boolean userHasPin(@NonNull Context context) {
|
||||
public static boolean userHasRegistrationLock(@NonNull Context context) {
|
||||
return TextSecurePreferences.isV1RegistrationLockEnabled(context) || SignalStore.kbsValues().isV2RegistrationLockEnabled();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.megaphone;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
@@ -46,7 +47,7 @@ public class MegaphoneRepository {
|
||||
/**
|
||||
* Marks any megaphones a new user shouldn't see as "finished".
|
||||
*/
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void onFirstEverAppLaunch() {
|
||||
executor.execute(() -> {
|
||||
database.markFinished(Event.REACTIONS);
|
||||
@@ -55,12 +56,12 @@ public class MegaphoneRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void onAppForegrounded() {
|
||||
executor.execute(() -> enabled = true);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void getNextMegaphone(@NonNull Callback<Megaphone> callback) {
|
||||
executor.execute(() -> {
|
||||
if (enabled) {
|
||||
@@ -72,7 +73,7 @@ public class MegaphoneRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void markVisible(@NonNull Megaphones.Event event) {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
@@ -84,7 +85,7 @@ public class MegaphoneRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void markSeen(@NonNull Event event) {
|
||||
long lastSeen = System.currentTimeMillis();
|
||||
|
||||
@@ -96,7 +97,7 @@ public class MegaphoneRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@AnyThread
|
||||
public void markFinished(@NonNull Event event) {
|
||||
executor.execute(() -> {
|
||||
database.markFinished(event);
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.lock.SignalPinReminderDialog;
|
||||
import org.thoughtcrime.securesms.lock.SignalPinReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.KbsMigrationActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestMegaphoneActivity;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
@@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ApplicationMigrations {
|
||||
|
||||
private static final int LEGACY_CANONICAL_VERSION = 455;
|
||||
|
||||
public static final int CURRENT_VERSION = 13;
|
||||
public static final int CURRENT_VERSION = 14;
|
||||
|
||||
private static final class Version {
|
||||
static final int LEGACY = 1;
|
||||
@@ -55,6 +55,7 @@ public class ApplicationMigrations {
|
||||
static final int STORAGE_SERVICE = 11;
|
||||
static final int STORAGE_KEY_ROTATE = 12;
|
||||
static final int REMOVE_AVATAR_ID = 13;
|
||||
static final int STORAGE_CAPABILITY = 14;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,14 +213,19 @@ public class ApplicationMigrations {
|
||||
jobs.put(Version.STORAGE_SERVICE, new StorageServiceMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.STORAGE_KEY_ROTATE) {
|
||||
jobs.put(Version.STORAGE_KEY_ROTATE, new StorageKeyRotationMigrationJob());
|
||||
}
|
||||
// Superceded by StorageCapabilityMigrationJob
|
||||
// if (lastSeenVersion < Version.STORAGE_KEY_ROTATE) {
|
||||
// jobs.put(Version.STORAGE_KEY_ROTATE, new StorageKeyRotationMigrationJob());
|
||||
// }
|
||||
|
||||
if (lastSeenVersion < Version.REMOVE_AVATAR_ID) {
|
||||
jobs.put(Version.REMOVE_AVATAR_ID, new AvatarIdRemovalMigrationJob());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.STORAGE_CAPABILITY) {
|
||||
jobs.put(Version.STORAGE_CAPABILITY, new StorageCapabilityMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
@@ -26,6 +26,9 @@ import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Migrates an existing V1 registration lock user to a V2 registration lock that is backed by a
|
||||
* Signal PIN.
|
||||
*
|
||||
* Deliberately not a {@link MigrationJob} because it is not something that needs to run at app start.
|
||||
* This migration can run at anytime.
|
||||
*/
|
||||
@@ -77,10 +80,12 @@ public final class RegistrationPinV2MigrationJob extends BaseJob {
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pinValue, pinChangeSession);
|
||||
RegistrationLockData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(kbsData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pinValue));
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
|
||||
Log.i(TAG, "Pin migrated to Key Backup Service");
|
||||
}
|
||||
|
||||
@@ -8,23 +8,32 @@ import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceStorageSyncRequestJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class StorageKeyRotationMigrationJob extends MigrationJob {
|
||||
/**
|
||||
* This does a couple things:
|
||||
* (1) Sets the storage capability for reglockv2 users by refreshing account attributes.
|
||||
* (2) Force-pushes storage, which is now backed by the KBS master key.
|
||||
*
|
||||
* Note: *All* users need to do this force push, because some people were in the storage service FF
|
||||
* bucket in the past, and if we don't schedule a force push, they could enter a situation
|
||||
* where different storage items are encrypted with different keys.
|
||||
*/
|
||||
public class StorageCapabilityMigrationJob extends MigrationJob {
|
||||
|
||||
private static final String TAG = Log.tag(StorageKeyRotationMigrationJob.class);
|
||||
private static final String TAG = Log.tag(StorageCapabilityMigrationJob.class);
|
||||
|
||||
public static final String KEY = "StorageKeyRotationMigrationJob";
|
||||
public static final String KEY = "StorageCapabilityMigrationJob";
|
||||
|
||||
StorageKeyRotationMigrationJob() {
|
||||
StorageCapabilityMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private StorageKeyRotationMigrationJob(@NonNull Parameters parameters) {
|
||||
private StorageCapabilityMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@@ -41,7 +50,8 @@ public class StorageKeyRotationMigrationJob extends MigrationJob {
|
||||
@Override
|
||||
public void performMigration() {
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
SignalStore.storageServiceValues().rotateStorageMasterKey();
|
||||
|
||||
jobManager.add(new RefreshAttributesJob());
|
||||
|
||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
||||
Log.i(TAG, "Multi-device.");
|
||||
@@ -60,10 +70,10 @@ public class StorageKeyRotationMigrationJob extends MigrationJob {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<StorageKeyRotationMigrationJob> {
|
||||
public static class Factory implements Job.Factory<StorageCapabilityMigrationJob> {
|
||||
@Override
|
||||
public @NonNull StorageKeyRotationMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new StorageKeyRotationMigrationJob(parameters);
|
||||
public @NonNull StorageCapabilityMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new StorageCapabilityMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
374
app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java
Normal file
374
app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java
Normal file
@@ -0,0 +1,374 @@
|
||||
package org.thoughtcrime.securesms.pin;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class PinState {
|
||||
|
||||
private static final String TAG = Log.tag(PinState.class);
|
||||
|
||||
/**
|
||||
* Invoked during registration to restore the master key based on the server response during
|
||||
* verification.
|
||||
*
|
||||
* Does not affect {@link PinState}.
|
||||
*/
|
||||
public static synchronized @Nullable KbsPinData restoreMasterKey(@Nullable String pin,
|
||||
@Nullable String basicStorageCredentials,
|
||||
@NonNull TokenResponse tokenResponse)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
Log.i(TAG, "restoreMasterKey()");
|
||||
|
||||
if (pin == null) return null;
|
||||
|
||||
if (basicStorageCredentials == null) {
|
||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
||||
}
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
|
||||
Log.i(TAG, "Opening key backup service session");
|
||||
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Restoring pin from KBS");
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||
KbsPinData kbsData = session.restorePin(hashedPin);
|
||||
if (kbsData != null) {
|
||||
Log.i(TAG, "Found registration lock token on KBS.");
|
||||
} else {
|
||||
throw new AssertionError("Null not expected");
|
||||
}
|
||||
return kbsData;
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to restore key", e);
|
||||
throw new IOException(e);
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
Log.w(TAG, "Incorrect pin", e);
|
||||
throw new KeyBackupSystemWrongPinException(e.getToken());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a user has successfully registered. Ensures all the necessary state is updated.
|
||||
*/
|
||||
public static synchronized void onRegistration(@NonNull Context context,
|
||||
@Nullable KbsPinData kbsData,
|
||||
@Nullable String pin)
|
||||
{
|
||||
Log.i(TAG, "onNewRegistration()");
|
||||
|
||||
if (kbsData == null) {
|
||||
Log.i(TAG, "No KBS PIN. Clearing any PIN state.");
|
||||
SignalStore.kbsValues().clearRegistrationLockAndPin();
|
||||
//noinspection deprecation Only acceptable place to write the old pin.
|
||||
TextSecurePreferences.setV1RegistrationLockPin(context, pin);
|
||||
//noinspection deprecation Only acceptable place to write the old pin enabled state.
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, pin != null);
|
||||
} else {
|
||||
Log.i(TAG, "Had a KBS PIN. Saving data.");
|
||||
SignalStore.kbsValues().setKbsMasterKey(kbsData, PinHashing.localPinHash(pin));
|
||||
// TODO [greyson] [pins] Not always true -- when this flow is reworked, you can have a PIN but no reglock
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
resetPinRetryCount(context, pin, kbsData);
|
||||
}
|
||||
|
||||
if (pin != null) {
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
}
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever the Signal PIN is changed or created.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onPinChangedOrCreated(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onPinChangedOrCreated()");
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
boolean isFirstPin = !kbsValues.hasPin();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pin));
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
SignalStore.pinValues().setKeyboardType(keyboard);
|
||||
SignalStore.pinValues().resetPinReminders();
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
|
||||
|
||||
if (isFirstPin) {
|
||||
Log.i(TAG, "First time setting a PIN. Refreshing attributes to set the 'storage' capability.");
|
||||
bestEffortRefreshAttributes();
|
||||
} else {
|
||||
Log.i(TAG, "Not the first time setting a PIN.");
|
||||
}
|
||||
|
||||
updateState(buildInferredStateFromOtherFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever a Signal PIN user enables registration lock.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onEnableRegistrationLockForUserWithPin() throws IOException {
|
||||
Log.i(TAG, "onEnableRegistrationLockForUserWithPin()");
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
||||
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
ApplicationDependencies.getKeyBackupService()
|
||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||
.enableRegistrationLock(SignalStore.kbsValues().getOrCreateMasterKey());
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
|
||||
updateState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever a Signal PIN user disables registration lock.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onDisableRegistrationLockForUserWithPin() throws IOException {
|
||||
Log.i(TAG, "onDisableRegistrationLockForUserWithPin()");
|
||||
assertState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
|
||||
ApplicationDependencies.getKeyBackupService()
|
||||
.newPinChangeSession(SignalStore.kbsValues().getRegistrationLockTokenResponse())
|
||||
.disableRegistrationLock();
|
||||
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
|
||||
|
||||
updateState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever registration lock is disabled for a user without a Signal PIN.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static synchronized void onDisableRegistrationLockV1(@NonNull Context context)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onDisableRegistrationLockV1()");
|
||||
assertState(State.REGISTRATION_LOCK_V1);
|
||||
|
||||
Log.i(TAG, "Removing v1 registration lock pin from server");
|
||||
ApplicationDependencies.getSignalServiceAccountManager().removeRegistrationLockV1();
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
|
||||
updateState(State.NO_REGISTRATION_LOCK);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static synchronized void onCompleteRegistrationLockV1Reminder(@NonNull Context context, @NonNull String pin)
|
||||
throws IOException, UnauthenticatedResponseException
|
||||
{
|
||||
Log.i(TAG, "onCompleteRegistrationLockV1Reminder()");
|
||||
assertState(State.REGISTRATION_LOCK_V1);
|
||||
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(kbsData, PinHashing.localPinHash(pin));
|
||||
kbsValues.setV2RegistrationLockEnabled(true);
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
|
||||
updateState(State.PIN_WITH_REGISTRATION_LOCK_ENABLED);
|
||||
}
|
||||
|
||||
public static synchronized boolean shouldShowRegistrationLockV1Reminder() {
|
||||
return getState() == State.REGISTRATION_LOCK_V1;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void bestEffortRefreshAttributes() {
|
||||
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) {
|
||||
Log.w(TAG, "Attributes were refreshed successfully.");
|
||||
} else if (result.isPresent()) {
|
||||
Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")");
|
||||
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
|
||||
} else {
|
||||
Log.w(TAG, "Job did not finish in the allotted time. It'll finish later.");
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private static void resetPinRetryCount(@NonNull Context context, @Nullable String pin, @NonNull KbsPinData kbsData) {
|
||||
if (pin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
|
||||
try {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(kbsData.getTokenResponse());
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
KbsPinData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setKbsMasterKey(newData, PinHashing.localPinHash(pin));
|
||||
TextSecurePreferences.clearRegistrationLockV1(context);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to reset pin attempts", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull State assertState(State... allowed) {
|
||||
State currentState = getState();
|
||||
|
||||
for (State state : allowed) {
|
||||
if (currentState == state) {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private static @NonNull State getState() {
|
||||
String serialized = SignalStore.pinValues().getPinState();
|
||||
|
||||
if (serialized != null) {
|
||||
return State.deserialize(serialized);
|
||||
} else {
|
||||
State state = buildInferredStateFromOtherFields();
|
||||
SignalStore.pinValues().setPinState(state.serialize());
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateState(@NonNull State state) {
|
||||
SignalStore.pinValues().setPinState(state.serialize());
|
||||
}
|
||||
|
||||
private static @NonNull State buildInferredStateFromOtherFields() {
|
||||
Context context = ApplicationDependencies.getApplication();
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
|
||||
boolean v1Enabled = TextSecurePreferences.isV1RegistrationLockEnabled(context);
|
||||
boolean v2Enabled = kbsValues.isV2RegistrationLockEnabled();
|
||||
boolean hasPin = kbsValues.hasPin();
|
||||
|
||||
if (!v1Enabled && !v2Enabled && !hasPin) {
|
||||
return State.NO_REGISTRATION_LOCK;
|
||||
}
|
||||
|
||||
if (v1Enabled && !v2Enabled && !hasPin) {
|
||||
return State.REGISTRATION_LOCK_V1;
|
||||
}
|
||||
|
||||
if (v2Enabled && hasPin) {
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
return State.PIN_WITH_REGISTRATION_LOCK_ENABLED;
|
||||
}
|
||||
|
||||
if (!v2Enabled && hasPin) {
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
|
||||
return State.PIN_WITH_REGISTRATION_LOCK_DISABLED;
|
||||
}
|
||||
|
||||
throw new InvalidInferredStateError(String.format(Locale.ENGLISH, "Invalid state! v1: %b, v2: %b, pin: %b", v1Enabled, v2Enabled, hasPin));
|
||||
}
|
||||
|
||||
private enum State {
|
||||
/**
|
||||
* User has nothing -- either in the process of registration, or pre-PIN-migration
|
||||
*/
|
||||
NO_REGISTRATION_LOCK("no_registration_lock"),
|
||||
|
||||
/**
|
||||
* User has a V1 registration lock set
|
||||
*/
|
||||
REGISTRATION_LOCK_V1("registration_lock_v1"),
|
||||
|
||||
/**
|
||||
* User has a PIN, and registration lock is enabled.
|
||||
*/
|
||||
PIN_WITH_REGISTRATION_LOCK_ENABLED("pin_with_registration_lock_enabled"),
|
||||
|
||||
/**
|
||||
* User has a PIN, but registration lock is disabled.
|
||||
*/
|
||||
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled");
|
||||
|
||||
/**
|
||||
* Using a string key so that people can rename/reorder values in the future without breaking
|
||||
* serialization.
|
||||
*/
|
||||
private final String key;
|
||||
|
||||
State(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public @NonNull String serialize() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public static @NonNull State deserialize(@NonNull String serialized) {
|
||||
for (State state : values()) {
|
||||
if (state.key.equals(serialized)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No state for value: " + serialized);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidInferredStateError extends Error {
|
||||
InvalidInferredStateError(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,17 @@ import org.thoughtcrime.securesms.PassphraseChangeActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.Database;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockV1Dialog;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
@@ -37,7 +38,10 @@ import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -45,6 +49,8 @@ import mobi.upod.timedurationpicker.TimeDurationPickerDialog;
|
||||
|
||||
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
|
||||
private static final String TAG = Log.tag(AppProtectionPreferenceFragment.class);
|
||||
|
||||
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
|
||||
private static final String PREFERENCE_UNIDENTIFIED_LEARN_MORE = "pref_unidentified_learn_more";
|
||||
|
||||
@@ -55,28 +61,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
|
||||
|
||||
SwitchPreferenceCompat regLock = (SwitchPreferenceCompat) this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF_V1);
|
||||
Preference kbsPinChange = this.findPreference(TextSecurePreferences.KBS_PIN_CHANGE);
|
||||
Preference regGroup = this.findPreference("prefs_lock_v1");
|
||||
Preference kbsGroup = this.findPreference("prefs_lock_v2");
|
||||
|
||||
if (FeatureFlags.pinsForAll()) {
|
||||
Preference preference = this.findPreference("pref_kbs_change");
|
||||
regGroup.setVisible(false);
|
||||
|
||||
if (PinUtil.userHasPin(ApplicationDependencies.getApplication())) {
|
||||
kbsPinChange.setOnPreferenceClickListener(new KbsPinUpdateListener());
|
||||
preference.setWidgetLayoutResource(R.layout.kbs_pin_change_preference);
|
||||
} else {
|
||||
kbsPinChange.setOnPreferenceClickListener(new KbsPinCreateListener());
|
||||
preference.setWidgetLayoutResource(R.layout.kbs_pin_create_preference);
|
||||
}
|
||||
} else {
|
||||
kbsGroup.setVisible(false);
|
||||
regLock.setChecked(PinUtil.userHasPin(requireContext()));
|
||||
regLock.setOnPreferenceClickListener(new AccountLockClickListener());
|
||||
}
|
||||
this.findPreference(KbsValues.V2_LOCK_ENABLED).setPreferenceDataStore(SignalStore.getPreferenceDataStore());
|
||||
((SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED)).setChecked(SignalStore.kbsValues().isV2RegistrationLockEnabled());
|
||||
this.findPreference(KbsValues.V2_LOCK_ENABLED).setOnPreferenceChangeListener(new RegistrationLockV2ChangedListener());
|
||||
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
|
||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
|
||||
@@ -109,6 +96,31 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
else initializeScreenLockTimeoutSummary();
|
||||
|
||||
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
|
||||
|
||||
Preference registrationLockV1Group = this.findPreference("prefs_lock_v1");
|
||||
SwitchPreferenceCompat registrationLockV1 = (SwitchPreferenceCompat) this.findPreference(TextSecurePreferences.REGISTRATION_LOCK_PREF_V1);
|
||||
Preference signalPinGroup = this.findPreference("prefs_signal_pin");
|
||||
Preference signalPinCreateChange = this.findPreference(TextSecurePreferences.SIGNAL_PIN_CHANGE);
|
||||
SwitchPreferenceCompat registrationLockV2 = (SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED);
|
||||
|
||||
|
||||
if (FeatureFlags.pinsForAll()) {
|
||||
registrationLockV1Group.setVisible(false);
|
||||
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinUpdateListener());
|
||||
signalPinCreateChange.setTitle(R.string.preferences_app_protection__change_your_pin);
|
||||
registrationLockV2.setEnabled(true);
|
||||
} else {
|
||||
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinCreateListener());
|
||||
signalPinCreateChange.setTitle(R.string.preferences_app_protection__create_a_pin);
|
||||
registrationLockV2.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
signalPinGroup.setVisible(false);
|
||||
registrationLockV1.setChecked(RegistrationLockUtil.userHasRegistrationLock(requireContext()));
|
||||
registrationLockV1.setOnPreferenceClickListener(new AccountLockClickListener());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,10 +218,10 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Context context = requireContext();
|
||||
|
||||
if (PinUtil.userHasPin(context)) {
|
||||
RegistrationLockDialog.showRegistrationUnlockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
if (RegistrationLockUtil.userHasRegistrationLock(context)) {
|
||||
RegistrationLockV1Dialog.showRegistrationUnlockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
} else {
|
||||
RegistrationLockDialog.showRegistrationLockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
RegistrationLockV1Dialog.showRegistrationLockPrompt(context, (SwitchPreferenceCompat)preference);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -283,7 +295,7 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
: R.string.ApplicationPreferencesActivity_privacy_summary;
|
||||
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
|
||||
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
|
||||
boolean registrationLockEnabled = PinUtil.userHasPin(context);
|
||||
boolean registrationLockEnabled = RegistrationLockUtil.userHasRegistrationLock(context);
|
||||
|
||||
if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
|
||||
if (registrationLockEnabled) {
|
||||
@@ -400,4 +412,40 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class RegistrationLockV2ChangedListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
boolean value = (boolean) newValue;
|
||||
AlertDialog loading = SimpleProgressDialog.show(requireContext());
|
||||
|
||||
Log.i(TAG, "Getting ready to change registration lock setting to: " + value);
|
||||
|
||||
SimpleTask.run(SignalExecutors.UNBOUNDED, () -> {
|
||||
try {
|
||||
if (value) {
|
||||
PinState.onEnableRegistrationLockForUserWithPin();
|
||||
Log.i(TAG, "Successfully enabled registration lock.");
|
||||
} else {
|
||||
PinState.onDisableRegistrationLockForUserWithPin();
|
||||
Log.i(TAG, "Successfully disabled registration lock.");
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to change registration lock setting.", e);
|
||||
return false;
|
||||
}
|
||||
}, (success) -> {
|
||||
loading.dismiss();
|
||||
|
||||
if (!success) {
|
||||
int stringRes = value ? R.string.preferences_app_protection__failed_to_enable_registration_lock
|
||||
: R.string.preferences_app_protection__failed_to_disable_registration_lock;
|
||||
|
||||
Toast.makeText(requireContext(), stringRes, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.lock.v2.RegistrationLockUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
||||
@@ -307,6 +307,10 @@ public class EditProfileFragment extends Fragment {
|
||||
private void handleUpload() {
|
||||
viewModel.submitProfile(uploadResult -> {
|
||||
if (uploadResult == EditProfileRepository.UploadResult.SUCCESS) {
|
||||
if (SignalStore.kbsValues().hasPin()) {
|
||||
SignalStore.registrationValues().setRegistrationComplete();
|
||||
}
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PROFILE_NAMES_FOR_ALL);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) handleFinishedLollipop();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -15,10 +14,7 @@ import androidx.navigation.ActivityNavigator;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinUtil;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.util.CensorshipUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
||||
public final class RegistrationCompleteFragment extends BaseRegistrationFragment {
|
||||
|
||||
|
||||
@@ -305,24 +305,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
private void handleSuccessfulPinEntry() {
|
||||
SignalStore.pinValues().setKeyboardType(getPinEntryKeyboardType());
|
||||
|
||||
if (FeatureFlags.storageServiceRestore()) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
SimpleTask.run(() -> {
|
||||
return ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
|
||||
}, result -> {
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
long startTime = System.currentTimeMillis();
|
||||
SimpleTask.run(() -> {
|
||||
return ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
|
||||
}, result -> {
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (result.isPresent()) {
|
||||
Log.i(TAG, "Storage Service account restore completed: " + result.get().name() + ". (Took " + elapsedTime + " ms)");
|
||||
} else {
|
||||
Log.i(TAG, "Storage Service account restore failed to complete in the allotted time. (" + elapsedTime + " ms elapsed)");
|
||||
}
|
||||
cancelSpinning(pinButton);
|
||||
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration());
|
||||
});
|
||||
} else {
|
||||
if (result.isPresent()) {
|
||||
Log.i(TAG, "Storage Service account restore completed: " + result.get().name() + ". (Took " + elapsedTime + " ms)");
|
||||
} else {
|
||||
Log.i(TAG, "Storage Service account restore failed to complete in the allotted time. (" + elapsedTime + " ms elapsed)");
|
||||
}
|
||||
cancelSpinning(pinButton);
|
||||
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.registration.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -19,11 +20,8 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.KbsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -36,16 +34,11 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.KeyHelper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.RegistrationLockData;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
|
||||
@@ -194,7 +187,7 @@ public final class CodeVerificationRequest {
|
||||
@Nullable String fcmToken)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
boolean isV2KbsPin = kbsTokenResponse != null;
|
||||
boolean isV2RegistrationLock = kbsTokenResponse != null;
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number());
|
||||
@@ -209,16 +202,24 @@ public final class CodeVerificationRequest {
|
||||
TextSecurePreferences.setLocalRegistrationId(context, registrationId);
|
||||
SessionUtil.archiveAllSessions(context);
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
||||
RegistrationLockData kbsData = isV2KbsPin ? restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null;
|
||||
String registrationLock = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
||||
boolean present = fcmToken != null;
|
||||
String pinForServer = isV2KbsPin ? null : pin;
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
||||
KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null;
|
||||
String registrationLockV2 = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
||||
String registrationLockV1 = isV2RegistrationLock ? null : pin;
|
||||
boolean hasFcm = fcmToken != null;
|
||||
|
||||
UUID uuid = accountManager.verifyAccountWithCode(code, null, registrationId, !present,
|
||||
pinForServer, registrationLock,
|
||||
unidentifiedAccessKey, universalUnidentifiedAccess,
|
||||
AppCapabilities.getCapabilities());
|
||||
Log.i(TAG, "Calling verifyAccountWithCode(): reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2));
|
||||
|
||||
UUID uuid = accountManager.verifyAccountWithCode(code,
|
||||
null,
|
||||
registrationId,
|
||||
!hasFcm,
|
||||
registrationLockV1,
|
||||
registrationLockV2,
|
||||
unidentifiedAccessKey,
|
||||
universalUnidentifiedAccess,
|
||||
AppCapabilities.getCapabilities(isV2RegistrationLock));
|
||||
// TODO [greyson] [pins] ^^ This needs to be updated. It's not just for reglock, but also if they needed to enter a PIN at all
|
||||
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(context);
|
||||
@@ -227,7 +228,7 @@ public final class CodeVerificationRequest {
|
||||
accountManager = AccountManagerFactory.createAuthenticated(context, uuid, credentials.getE164number(), credentials.getPassword());
|
||||
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
|
||||
|
||||
if (present) {
|
||||
if (hasFcm) {
|
||||
accountManager.setGcmId(Optional.fromNullable(fcmToken));
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ public final class CodeVerificationRequest {
|
||||
ApplicationDependencies.getRecipientCache().clearSelf();
|
||||
|
||||
TextSecurePreferences.setFcmToken(context, fcmToken);
|
||||
TextSecurePreferences.setFcmDisabled(context, !present);
|
||||
TextSecurePreferences.setFcmDisabled(context, !hasFcm);
|
||||
TextSecurePreferences.setWebsocketRegistered(context, true);
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
@@ -257,20 +258,8 @@ public final class CodeVerificationRequest {
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||
if (kbsData == null) {
|
||||
SignalStore.kbsValues().clearRegistrationLock();
|
||||
//noinspection deprecation Only acceptable place to write the old pin.
|
||||
TextSecurePreferences.setDeprecatedRegistrationLockPin(context, pin);
|
||||
//noinspection deprecation Only acceptable place to write the old pin enabled state.
|
||||
TextSecurePreferences.setV1RegistrationLockEnabled(context, pin != null);
|
||||
} else {
|
||||
SignalStore.kbsValues().setRegistrationLockMasterKey(kbsData, PinHashing.localPinHash(pin));
|
||||
repostPinToResetTries(context, pin, kbsData);
|
||||
}
|
||||
if (pin != null) {
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
}
|
||||
|
||||
PinState.onRegistration(context, kbsData, pin);
|
||||
}
|
||||
|
||||
private static @Nullable ProfileKey findExistingProfileKey(@NonNull Context context, @NonNull String e164number) {
|
||||
@@ -284,62 +273,6 @@ public final class CodeVerificationRequest {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void repostPinToResetTries(@NonNull Context context, @Nullable String pin, @NonNull RegistrationLockData kbsData) {
|
||||
if (pin == null) return;
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
|
||||
try {
|
||||
KbsValues kbsValues = SignalStore.kbsValues();
|
||||
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
|
||||
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession(kbsData.getTokenResponse());
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
RegistrationLockData newData = pinChangeSession.setPin(hashedPin, masterKey);
|
||||
|
||||
kbsValues.setRegistrationLockMasterKey(newData, PinHashing.localPinHash(pin));
|
||||
TextSecurePreferences.clearOldRegistrationLockPin(context);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "May have failed to reset pin attempts!", e);
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to reset pin attempts", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable RegistrationLockData restoreMasterKey(@Nullable String pin,
|
||||
@Nullable String basicStorageCredentials,
|
||||
@NonNull TokenResponse tokenResponse)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
if (pin == null) return null;
|
||||
|
||||
if (basicStorageCredentials == null) {
|
||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
||||
}
|
||||
|
||||
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
|
||||
|
||||
Log.i(TAG, "Opening key backup service session");
|
||||
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
|
||||
|
||||
try {
|
||||
Log.i(TAG, "Restoring pin from KBS");
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||
RegistrationLockData kbsData = session.restorePin(hashedPin);
|
||||
if (kbsData != null) {
|
||||
Log.i(TAG, "Found registration lock token on KBS.");
|
||||
} else {
|
||||
throw new AssertionError("Null not expected");
|
||||
}
|
||||
return kbsData;
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
Log.w(TAG, "Failed to restore key", e);
|
||||
throw new IOException(e);
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
Log.w(TAG, "Incorrect pin", e);
|
||||
throw new KeyBackupSystemWrongPinException(e.getToken());
|
||||
}
|
||||
}
|
||||
|
||||
public interface VerifyCallback {
|
||||
|
||||
void onSuccessfulRegistration();
|
||||
|
||||
@@ -4,11 +4,11 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
final class KeyBackupSystemWrongPinException extends Exception {
|
||||
public final class KeyBackupSystemWrongPinException extends Exception {
|
||||
|
||||
private final TokenResponse tokenResponse;
|
||||
|
||||
KeyBackupSystemWrongPinException(@NonNull TokenResponse tokenResponse){
|
||||
public KeyBackupSystemWrongPinException(@NonNull TokenResponse tokenResponse){
|
||||
this.tokenResponse = tokenResponse;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.util;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
/**
|
||||
* Helps prevent all the @Nullable warnings when working with LiveData.
|
||||
*/
|
||||
public class DefaultValueLiveData<T> extends MutableLiveData<T> {
|
||||
|
||||
private final T defaultValue;
|
||||
|
||||
@@ -53,7 +53,6 @@ public final class FeatureFlags {
|
||||
private static final String PINS_FOR_ALL_MANDATORY = "android.pinsForAllMandatory";
|
||||
private static final String PINS_MEGAPHONE_KILL_SWITCH = "android.pinsMegaphoneKillSwitch";
|
||||
private static final String PROFILE_NAMES_MEGAPHONE = "android.profileNamesMegaphone";
|
||||
private static final String STORAGE_SERVICE = "android.storageService.2";
|
||||
private static final String ATTACHMENTS_V3 = "android.attachmentsV3";
|
||||
|
||||
/**
|
||||
@@ -67,7 +66,6 @@ public final class FeatureFlags {
|
||||
PINS_MEGAPHONE_KILL_SWITCH,
|
||||
PROFILE_NAMES_MEGAPHONE,
|
||||
MESSAGE_REQUESTS,
|
||||
STORAGE_SERVICE,
|
||||
ATTACHMENTS_V3
|
||||
);
|
||||
|
||||
@@ -90,7 +88,6 @@ public final class FeatureFlags {
|
||||
*/
|
||||
private static final Set<String> HOT_SWAPPABLE = Sets.newHashSet(
|
||||
PINS_MEGAPHONE_KILL_SWITCH,
|
||||
STORAGE_SERVICE,
|
||||
ATTACHMENTS_V3
|
||||
);
|
||||
|
||||
@@ -183,9 +180,15 @@ public final class FeatureFlags {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Starts showing prompts for users to create PINs. */
|
||||
/**
|
||||
* - Starts showing prompts for users to create PINs.
|
||||
* - Shows new reminder UI.
|
||||
* - Shows new settings UI.
|
||||
* - Syncs to storage service.
|
||||
*/
|
||||
public static boolean pinsForAll() {
|
||||
return SignalStore.registrationValues().pinWasRequiredAtRegistration() ||
|
||||
SignalStore.kbsValues().isV2RegistrationLockEnabled() ||
|
||||
pinsForAllMandatory() ||
|
||||
getValue(PINS_FOR_ALL, false);
|
||||
}
|
||||
@@ -206,16 +209,6 @@ public final class FeatureFlags {
|
||||
TextSecurePreferences.getFirstInstallVersion(ApplicationDependencies.getApplication()) < 600;
|
||||
}
|
||||
|
||||
/** Whether or not we can actually restore data on a new installation. NOT remote-configurable. */
|
||||
public static boolean storageServiceRestore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Whether or not we sync to the storage service. */
|
||||
public static boolean storageService() {
|
||||
return getValue(STORAGE_SERVICE, false);
|
||||
}
|
||||
|
||||
/** Whether or not we use the attachments v3 form. */
|
||||
public static boolean attachmentsV3() {
|
||||
return getValue(ATTACHMENTS_V3, false);
|
||||
|
||||
@@ -159,10 +159,12 @@ public class TextSecurePreferences {
|
||||
@Deprecated
|
||||
private static final String REGISTRATION_LOCK_PIN_PREF_V1 = "pref_registration_lock_pin";
|
||||
|
||||
public static final String REGISTRATION_LOCK_PREF_V2 = "pref_registration_lock_v2";
|
||||
|
||||
private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME_POST_KBS = "pref_registration_lock_last_reminder_time_post_kbs";
|
||||
private static final String REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL = "pref_registration_lock_next_reminder_interval";
|
||||
|
||||
public static final String KBS_PIN_CHANGE = "pref_kbs_change";
|
||||
public static final String SIGNAL_PIN_CHANGE = "pref_kbs_change";
|
||||
|
||||
private static final String SERVICE_OUTAGE = "pref_service_outage";
|
||||
private static final String LAST_OUTAGE_CHECK_TIME = "pref_last_outage_check_time";
|
||||
@@ -252,7 +254,7 @@ public class TextSecurePreferences {
|
||||
return getStringPreference(context, REGISTRATION_LOCK_PIN_PREF_V1, null);
|
||||
}
|
||||
|
||||
public static void clearOldRegistrationLockPin(@NonNull Context context) {
|
||||
public static void clearRegistrationLockV1(@NonNull Context context) {
|
||||
//noinspection deprecation
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit()
|
||||
@@ -264,7 +266,7 @@ public class TextSecurePreferences {
|
||||
* @deprecated Use only for migrations to the Key Backup Store registration pinV2.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setDeprecatedRegistrationLockPin(@NonNull Context context, String pin) {
|
||||
public static void setV1RegistrationLockPin(@NonNull Context context, String pin) {
|
||||
//noinspection deprecation
|
||||
setStringPreference(context, REGISTRATION_LOCK_PIN_PREF_V1, pin);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class SimpleTask {
|
||||
|
||||
/**
|
||||
@@ -38,7 +40,15 @@ public class SimpleTask {
|
||||
* the main thread. Essentially {@link AsyncTask}, but lambda-compatible.
|
||||
*/
|
||||
public static <E> void run(@NonNull BackgroundTask<E> backgroundTask, @NonNull ForegroundTask<E> foregroundTask) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
run(SignalExecutors.BOUNDED, backgroundTask, foregroundTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task on the specified {@link Executor} and passes the result of the computation to a
|
||||
* task that is run on the main thread. Essentially {@link AsyncTask}, but lambda-compatible.
|
||||
*/
|
||||
public static <E> void run(@NonNull Executor executor, @NonNull BackgroundTask<E> backgroundTask, @NonNull ForegroundTask<E> foregroundTask) {
|
||||
executor.execute(() -> {
|
||||
final E result = backgroundTask.run();
|
||||
Util.runOnMain(() -> foregroundTask.run(result));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user