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:
Greyson Parrelli
2020-04-02 17:09:25 -04:00
parent 3c6a7b76ca
commit 8e13403cca
48 changed files with 905 additions and 523 deletions

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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());
}};
}

View File

@@ -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));

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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}.

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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");
}

View File

@@ -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);
}
}
}

View 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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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());
}
});
}
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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));
});