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.
master
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

@ -122,9 +122,9 @@ android {
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
buildConfigField "String", "KEY_BACKUP_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
buildConfigField "String", "CDS_MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
@ -198,8 +198,8 @@ android {
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"a1e9c1d3f352b5c4f0fc7a421b98119e60e5ff703c28fbea85c66bfa7306deab\""
buildConfigField "String", "CDS_MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"\""
}

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

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:text="@string/preferences_app_protection__change" />

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:text="@string/preferences_app_protection__create" />

View File

@ -2029,10 +2029,12 @@
<string name="preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint">Lock Signal access with Android screen lock or fingerprint</string>
<string name="preferences_app_protection__screen_lock_inactivity_timeout">Screen lock inactivity timeout</string>
<string name="preferences_app_protection__signal_pin">Signal PIN</string>
<string name="preferences_app_protection__pin">PIN</string>
<string name="preferences_app_protection__change">Change</string>
<string name="preferences_app_protection__create">Create</string>
<string name="preferences_app_protection__your_pin_adds_an_extra_layer_of_security_to_your_account">Your PIN adds an extra layer of security to your account. You\'ll be asked for it when you register your phone number with Signal. If you forget your PIN, you\'ll be locked out of your account for 7 days.</string>
<string name="preferences_app_protection__create_a_pin">Create a PIN</string>
<string name="preferences_app_protection__change_your_pin">Change your PIN</string>
<string name="preferences_app_protection__your_pin_backs_up_profile_settings_and_preferences">Your PIN backs up profile settings and preferences to save you time when re-registering your account.</string>
<string name="preferences_app_protection__registration_lock_adds_and_extra_layer_of_security">Registration Lock adds an extra layer of security to your account. You\'ll be asked for your Signal PIN when you register your phone number with Signal. If you forget your PIN, you\'ll be locked out of your account for 7 days.</string>
<string name="preferences_app_protection__failed_to_enable_registration_lock">Failed to enable registration lock.</string>
<string name="preferences_app_protection__failed_to_disable_registration_lock">Failed to disable registration lock.</string>
<string name="AppProtectionPreferenceFragment_none">None</string>
<string name="registration_activity__the_registration_lock_pin_is_not_the_same_as_the_sms_verification_code_you_just_received_please_enter_the_pin_you_previously_configured_in_the_application">The Registration Lock PIN is not the same as the SMS verification code you just received. Please enter the PIN you previously configured in the application.</string>
<string name="registration_activity__registration_lock_pin">Registration Lock PIN</string>

View File

@ -1,121 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_app_protection__app_access">
<PreferenceCategory android:title="@string/preferences_app_protection__app_access">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:key="pref_android_screen_lock"
android:defaultValue="false"
android:title="@string/preferences_app_protection__screen_lock"
android:summary="@string/preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_android_screen_lock"
android:summary="@string/preferences_app_protection__lock_signal_access_with_android_screen_lock_or_fingerprint"
android:title="@string/preferences_app_protection__screen_lock" />
<Preference android:title="@string/preferences_app_protection__screen_lock_inactivity_timeout"
android:key="pref_android_screen_lock_timeout"
android:dependency="pref_android_screen_lock"/>
<Preference
android:dependency="pref_android_screen_lock"
android:key="pref_android_screen_lock_timeout"
android:title="@string/preferences_app_protection__screen_lock_inactivity_timeout" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:key="pref_enable_passphrase_temporary"
android:defaultValue="true"
android:title="@string/preferences__enable_passphrase"
android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_enable_passphrase_temporary"
android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase"
android:title="@string/preferences__enable_passphrase" />
<Preference android:key="pref_change_passphrase"
android:title="@string/preferences__change_passphrase"
android:summary="@string/preferences__change_your_passphrase"
android:dependency="pref_enable_passphrase_temporary"/>
<Preference
android:dependency="pref_enable_passphrase_temporary"
android:key="pref_change_passphrase"
android:summary="@string/preferences__change_your_passphrase"
android:title="@string/preferences__change_passphrase" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_timeout_passphrase"
android:title="@string/preferences__inactivity_timeout_passphrase"
android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"
android:dependency="pref_enable_passphrase_temporary"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="pref_enable_passphrase_temporary"
android:key="pref_timeout_passphrase"
android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"
android:title="@string/preferences__inactivity_timeout_passphrase" />
<Preference android:title="@string/preferences__inactivity_timeout_interval"
android:key="pref_timeout_interval"
android:dependency="pref_timeout_passphrase"/>
<Preference
android:dependency="pref_timeout_passphrase"
android:key="pref_timeout_interval"
android:title="@string/preferences__inactivity_timeout_interval" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_screen_security"
android:title="@string/preferences__screen_security"
android:summary="@string/preferences__disable_screen_security_to_allow_screen_shots" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_screen_security"
android:summary="@string/preferences__disable_screen_security_to_allow_screen_shots"
android:title="@string/preferences__screen_security" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_incognito_keyboard"
android:title="@string/preferences__incognito_keyboard"
android:summary="@string/preferences__request_keyboard_to_disable_personalized_learning"/>
</PreferenceCategory>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_incognito_keyboard"
android:summary="@string/preferences__request_keyboard_to_disable_personalized_learning"
android:title="@string/preferences__incognito_keyboard" />
</PreferenceCategory>
<PreferenceCategory android:layout="@layout/preference_divider"/>
<PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:title="@string/preferences_app_protection__communication">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_turn_only"
android:title="@string/preferences_advanced__always_relay_calls"
android:summary="@string/preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address"/>
<PreferenceCategory android:title="@string/preferences_app_protection__communication">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_turn_only"
android:summary="@string/preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address"
android:title="@string/preferences_advanced__always_relay_calls" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_read_receipts"
android:title="@string/preferences__read_receipts"
android:summary="@string/preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_read_receipts"
android:summary="@string/preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts"
android:title="@string/preferences__read_receipts" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_typing_indicators"
android:title="@string/preferences__typing_indicators"
android:summary="@string/preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_typing_indicators"
android:summary="@string/preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators"
android:title="@string/preferences__typing_indicators" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_link_previews"
android:summary="@string/preferences__previews_are_supported_for"
android:title="@string/preferences__send_link_previews"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_link_previews"
android:summary="@string/preferences__previews_are_supported_for"
android:title="@string/preferences__send_link_previews" />
<Preference android:key="preference_category_blocked"
android:title="@string/preferences_app_protection__blocked_contacts" />
</PreferenceCategory>
<Preference
android:key="preference_category_blocked"
android:title="@string/preferences_app_protection__blocked_contacts" />
</PreferenceCategory>
<PreferenceCategory android:layout="@layout/preference_divider"/>
<PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:title="@string/preferences_communication__category_sealed_sender">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_show_unidentifed_delivery_indicators"
android:title="@string/preferences_communication__sealed_sender_display_indicators"
android:summary="@string/preferences_communication__sealed_sender_display_indicators_description"/>
<PreferenceCategory android:title="@string/preferences_communication__category_sealed_sender">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_show_unidentifed_delivery_indicators"
android:summary="@string/preferences_communication__sealed_sender_display_indicators_description"
android:title="@string/preferences_communication__sealed_sender_display_indicators" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_universal_unidentified_access"
android:title="@string/preferences_communication__sealed_sender_allow_from_anyone"
android:summary="@string/preferences_communication__sealed_sender_allow_from_anyone_description"/>
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_universal_unidentified_access"
android:summary="@string/preferences_communication__sealed_sender_allow_from_anyone_description"
android:title="@string/preferences_communication__sealed_sender_allow_from_anyone" />
<Preference
android:key="pref_unidentified_learn_more"
android:layout="@layout/unidentified_delivery_learn_more" />
<Preference
android:key="pref_unidentified_learn_more"
android:layout="@layout/unidentified_delivery_learn_more" />
</PreferenceCategory>
</PreferenceCategory>
<PreferenceCategory android:layout="@layout/preference_divider"/>
<PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:key="prefs_lock_v1" android:title="@string/preferences_app_protection__registration_lock">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_registration_lock"
android:title="@string/preferences_app_protection__registration_lock_pin"
android:summary="@string/preferences_app_protection__enable_a_registration_lock_pin_that_will_be_required"/>
</PreferenceCategory>
<PreferenceCategory
android:key="prefs_signal_pin"
android:title="@string/preferences_app_protection__signal_pin">
<Preference
android:key="pref_kbs_change"
android:summary="@string/preferences_app_protection__your_pin_backs_up_profile_settings_and_preferences"
android:title="@string/preferences_app_protection__change_your_pin" />
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:enabled="false"
android:key="kbs.v2_lock_enabled"
android:summary="@string/preferences_app_protection__registration_lock_adds_and_extra_layer_of_security"
android:title="@string/preferences_app_protection__registration_lock" />
</PreferenceCategory>
<PreferenceCategory
android:key="prefs_lock_v1"
android:title="@string/preferences_app_protection__registration_lock">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_registration_lock"
android:summary="@string/preferences_app_protection__enable_a_registration_lock_pin_that_will_be_required"
android:title="@string/preferences_app_protection__registration_lock_pin" />
</PreferenceCategory>
<PreferenceCategory android:key="prefs_lock_v2" android:title="@string/preferences_app_protection__signal_pin">
<Preference
android:widgetLayout="@layout/kbs_pin_change_preference"
android:key="pref_kbs_change"
android:title="@string/preferences_app_protection__pin"
android:summary="@string/preferences_app_protection__your_pin_adds_an_extra_layer_of_security_to_your_account" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -3,12 +3,12 @@ package org.whispersystems.signalservice.api;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
public final class RegistrationLockData {
public final class KbsPinData {
private final MasterKey masterKey;
private final TokenResponse tokenResponse;
RegistrationLockData(MasterKey masterKey, TokenResponse tokenResponse) {
KbsPinData(MasterKey masterKey, TokenResponse tokenResponse) {
this.masterKey = masterKey;
this.tokenResponse = tokenResponse;
}

View File

@ -112,7 +112,7 @@ public final class KeyBackupService {
}
@Override
public RegistrationLockData restorePin(HashedPin hashedPin)
public KbsPinData restorePin(HashedPin hashedPin)
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException
{
int attempt = 0;
@ -145,7 +145,7 @@ public final class KeyBackupService {
}
}
private RegistrationLockData restorePin(HashedPin hashedPin, TokenResponse token)
private KbsPinData restorePin(HashedPin hashedPin, TokenResponse token)
throws UnauthenticatedResponseException, IOException, TokenException, KeyBackupSystemNoDataException
{
try {
@ -165,7 +165,7 @@ public final class KeyBackupService {
Log.i(TAG, String.format(Locale.US,"Restore OK! data: %s tries: %d", Hex.toStringCondensed(status.getData().toByteArray()), status.getTries()));
KbsData kbsData = hashedPin.decryptKbsDataIVCipherText(status.getData().toByteArray());
MasterKey masterKey = kbsData.getMasterKey();
return new RegistrationLockData(masterKey, nextToken);
return new KbsPinData(masterKey, nextToken);
case PIN_MISMATCH:
Log.i(TAG, "Restore PIN_MISMATCH");
throw new KeyBackupServicePinException(nextToken);
@ -199,24 +199,24 @@ public final class KeyBackupService {
}
@Override
public RegistrationLockData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException {
KbsData newKbsData = hashedPin.createNewKbsData(masterKey);
public KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException {
KbsData newKbsData = hashedPin.createNewKbsData(masterKey);
TokenResponse tokenResponse = putKbsData(newKbsData.getKbsAccessKey(),
newKbsData.getCipherText(),
enclaveName,
currentToken);
pushServiceSocket.setRegistrationLock(masterKey.deriveRegistrationLock());
return new RegistrationLockData(masterKey, tokenResponse);
return new KbsPinData(masterKey, tokenResponse);
}
@Override
public void removePin() throws IOException, UnauthenticatedResponseException {
deleteKbsData();
public void enableRegistrationLock(MasterKey masterKey) throws IOException {
pushServiceSocket.setRegistrationLockV2(masterKey.deriveRegistrationLock());
}
pushServiceSocket.removePinV2();
@Override
public void disableRegistrationLock() throws IOException {
pushServiceSocket.disableRegistrationLockV2();
}
private TokenResponse putKbsData(byte[] kbsAccessKey, byte[] kbsData, String enclaveName, TokenResponse token)
@ -243,20 +243,6 @@ public final class KeyBackupService {
throw new UnauthenticatedResponseException(e);
}
}
private void deleteKbsData()
throws IOException, UnauthenticatedResponseException
{
try {
RemoteAttestation remoteAttestation = getAndVerifyRemoteAttestation();
KeyBackupRequest request = KeyBackupCipher.createKeyDeleteRequest(currentToken, remoteAttestation, Hex.fromStringCondensed(enclaveName));
KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
KeyBackupCipher.getKeyDeleteResponseStatus(response, remoteAttestation);
} catch (InvalidCiphertextException e) {
throw new UnauthenticatedResponseException(e);
}
}
}
public interface HashSession {
@ -266,16 +252,18 @@ public final class KeyBackupService {
public interface RestoreSession extends HashSession {
RegistrationLockData restorePin(HashedPin hashedPin)
KbsPinData restorePin(HashedPin hashedPin)
throws UnauthenticatedResponseException, IOException, KeyBackupServicePinException, KeyBackupSystemNoDataException;
}
public interface PinChangeSession extends HashSession {
/** Creates a PIN. Does nothing to registration lock. */
KbsPinData setPin(HashedPin hashedPin, MasterKey masterKey) throws IOException, UnauthenticatedResponseException;
RegistrationLockData setPin(HashedPin hashedPin, MasterKey masterKey)
throws IOException, UnauthenticatedResponseException;
/** Enables registration lock. This assumes a PIN is set. */
void enableRegistrationLock(MasterKey masterKey) throws IOException;
void removePin()
throws IOException, UnauthenticatedResponseException;
/** Disables registration lock. The user keeps their PIN. */
void disableRegistrationLock() throws IOException;
}
}

View File

@ -10,7 +10,6 @@ package org.whispersystems.signalservice.api;
import com.google.protobuf.ByteString;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.whispersystems.libsignal.IdentityKey;
@ -29,6 +28,8 @@ import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Authorization;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.kbs.HashedPin;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
@ -147,11 +148,14 @@ public class SignalServiceAccountManager {
}
/**
* V1 Pin setting has been replaced by KeyBackupService.
* Now you can only remove the old pin but there is no need to remove the old pin if setting a KBS Pin.
* V1 PINs are no longer used in favor of V2 PINs stored on KBS.
*
* You can remove a V1 PIN, but typically this is unnecessary, as setting a V2 PIN via
* {@link KeyBackupService.Session#enableRegistrationLock(MasterKey)}} will automatically clear the
* V1 PIN on the service.
*/
public void removeV1Pin() throws IOException {
this.pushServiceSocket.removePin();
public void removeRegistrationLockV1() throws IOException {
this.pushServiceSocket.removeRegistrationLockV1();
}
public UUID getOwnUuid() throws IOException {

View File

@ -105,12 +105,16 @@ public class SignalServiceProfile {
@JsonProperty
private boolean gv2;
@JsonProperty
private boolean storage;
@JsonCreator
public Capabilities() {}
public Capabilities(boolean uuid, boolean gv2) {
this.uuid = uuid;
this.gv2 = gv2;
public Capabilities(boolean uuid, boolean gv2, boolean storage) {
this.uuid = uuid;
this.gv2 = gv2;
this.storage = storage;
}
public boolean isUuid() {
@ -120,6 +124,10 @@ public class SignalServiceProfile {
public boolean isGv2() {
return gv2;
}
public boolean isStorage() {
return storage;
}
}
public ProfileKeyCredentialResponse getProfileKeyCredentialResponse() {

View File

@ -41,7 +41,7 @@ public class SignalStorageManifest {
public Optional<StorageId> getAccountStorageId() {
List<StorageId> list = storageIdsByType.get(ManifestRecord.Identifier.Type.ACCOUNT_VALUE);
if (list.size() > 0) {
if (list != null && list.size() > 0) {
return Optional.of(list.get(0));
} else {
return Optional.absent();

View File

@ -338,16 +338,16 @@ public class PushServiceSocket {
}
/** Note: Setting a KBS Pin will clear this */
public void removePin() throws IOException {
public void removeRegistrationLockV1() throws IOException {
makeServiceRequest(PIN_PATH, "DELETE", null);
}
public void setRegistrationLock(String registrationLock) throws IOException {
public void setRegistrationLockV2(String registrationLock) throws IOException {
RegistrationLockV2 accountLock = new RegistrationLockV2(registrationLock);
makeServiceRequest(REGISTRATION_LOCK_PATH, "PUT", JsonUtil.toJson(accountLock));
}
public void removePinV2() throws IOException {
public void disableRegistrationLockV2() throws IOException {
makeServiceRequest(REGISTRATION_LOCK_PATH, "DELETE", null);
}