diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index d049c3cf51..78cf8d3af7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -164,7 +164,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements } private boolean userMustCreateSignalPin() { - return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed(); + return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut(); } private boolean userMustSetProfileName() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java index 94ce5112c3..e2fb451069 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/KbsValues.java @@ -62,6 +62,7 @@ public final class KbsValues extends SignalStoreValues { .putString(LOCK_LOCAL_PIN_HASH, PinHashing.localPinHash(pin)) .putString(PIN, pin) .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) + .putBoolean(OPTED_OUT, false) .commit(); } @@ -144,27 +145,16 @@ public final class KbsValues extends SignalStoreValues { return getLocalPinHash() != null; } - /** - * Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. - */ - public synchronized void optIn() { - putBoolean(OPTED_OUT, false); - } - - /** - * Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. - */ + /** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */ public synchronized void optOut() { - putBoolean(OPTED_OUT, true); - } - - /** - * Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. - */ - public synchronized void resetMasterKey() { getStore().beginWrite() - .remove(MASTER_KEY) - .apply(); + .putBoolean(OPTED_OUT, true) + .remove(TOKEN_RESPONSE) + .putBlob(MASTER_KEY, MasterKey.createNew(new SecureRandom()).serialize()) + .remove(LOCK_LOCAL_PIN_HASH) + .remove(PIN) + .putLong(LAST_CREATE_FAILED_TIMESTAMP, -1) + .commit(); } public synchronized boolean hasOptedOut() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java index 7d16bfc227..28aaf210e4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/lock/v2/BaseKbsPinFragment.java @@ -100,7 +100,8 @@ abstract class BaseKbsPinFragment extends @Override public void onPrepareOptionsMenu(@NonNull Menu menu) { if (RegistrationLockUtil.userHasRegistrationLock(requireContext()) || - SignalStore.kbsValues().hasPin()) + SignalStore.kbsValues().hasPin() || + SignalStore.kbsValues().hasOptedOut()) { menu.clear(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java index dc00b2bfa6..97a69ac09a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionPin.java @@ -22,6 +22,7 @@ public class LogSectionPin implements LogSection { .append("ReglockV1: ").append(TextSecurePreferences.isV1RegistrationLockEnabled(context)).append("\n") .append("ReglockV2: ").append(SignalStore.kbsValues().isV2RegistrationLockEnabled()).append("\n") .append("Signal PIN: ").append(SignalStore.kbsValues().hasPin()).append("\n") + .append("Opted Out: ").append(SignalStore.kbsValues().hasOptedOut()).append("\n") .append("Last Creation Failed: ").append(SignalStore.kbsValues().lastPinCreateFailed()).append("\n") .append("Needs Account Restore: ").append(SignalStore.storageServiceValues().needsAccountRestore()).append("\n") .append("PIN Required at Registration: ").append(SignalStore.registrationValues().pinWasRequiredAtRegistration()).append("\n") diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java index ae0014ff17..16a372e77c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinOptOutDialog.java @@ -34,30 +34,25 @@ public final class PinOptOutDialog { } private static void show(@NonNull Context context, - boolean skip, + boolean isForSkip, @NonNull Runnable onSuccess, @NonNull Runnable onFailed) { AlertDialog dialog = new AlertDialog.Builder(context) .setTitle(R.string.PinOptOutDialog_warning) - .setMessage(R.string.PinOptOutDialog_disabling_pins_will_create_a_hidden_high_entropy_pin) + .setMessage(R.string.PinOptOutDialog_if_you_disable_the_pin_you_will_lose_all_data) .setCancelable(true) .setPositiveButton(R.string.PinOptOutDialog_disable_pin, (d, which) -> { d.dismiss(); AlertDialog progress = SimpleProgressDialog.show(context); SimpleTask.run(() -> { - try { - if (skip) { - PinState.onPinCreationSkipped(context); - } else { - PinState.onPinOptOut(context); - } - return true; - } catch (IOException | UnauthenticatedResponseException e) { - Log.w(TAG, e); - return false; + if (isForSkip) { + PinState.onPinCreationSkipped(); + } else { + PinState.onPinOptOut(); } + return true; }, success -> { if (success) { onSuccess.run(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java index 7f055b5511..f3ed661075 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java @@ -18,9 +18,7 @@ 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.Hex; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.KbsPinData; import org.whispersystems.signalservice.api.KeyBackupService; @@ -32,6 +30,7 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import java.io.IOException; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -156,10 +155,19 @@ public final class PinState { { Log.i(TAG, "onPinChangedOrCreated()"); - boolean isFirstPin = !SignalStore.kbsValues().hasPin() || SignalStore.kbsValues().hasOptedOut(); + KbsValues kbsValues = SignalStore.kbsValues(); + boolean isFirstPin = !kbsValues.hasPin() || kbsValues.hasOptedOut(); + 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); - setPin(context, pin, keyboard); - SignalStore.kbsValues().optIn(); + kbsValues.setKbsMasterKey(kbsData, 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."); @@ -185,13 +193,11 @@ public final class PinState { * Invoked when the user has enabled the "PIN opt out" setting. */ @WorkerThread - public static synchronized void onPinOptOut(@NonNull Context context) - throws IOException, UnauthenticatedResponseException - { + public static synchronized void onPinOptOut() { Log.i(TAG, "onPinOptOutEnabled()"); assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.NO_REGISTRATION_LOCK); - optOutOfPin(context); + optOutOfPin(); updateState(buildInferredStateFromOtherFields()); } @@ -200,13 +206,11 @@ public final class PinState { * Invoked when the user has chosen to skip PIN creation. */ @WorkerThread - public static synchronized void onPinCreationSkipped(@NonNull Context context) - throws IOException, UnauthenticatedResponseException - { + public static synchronized void onPinCreationSkipped() { Log.i(TAG, "onPinCreationSkipped()"); assertState(State.NO_REGISTRATION_LOCK); - optOutOfPin(context); + optOutOfPin(); updateState(buildInferredStateFromOtherFields()); } @@ -295,6 +299,20 @@ public final class PinState { } } + @WorkerThread + private static void bestEffortForcePushStorage() { + Optional result = ApplicationDependencies.getJobManager().runSynchronously(new StorageForcePushJob(), TimeUnit.SECONDS.toMillis(10)); + + if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) { + Log.i(TAG, "Storage was force-pushed successfully."); + } else if (result.isPresent()) { + Log.w(TAG, "Storage force-pushed finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")"); + ApplicationDependencies.getJobManager().add(new RefreshAttributesJob()); + } else { + Log.w(TAG, "Storage fore push 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) { @@ -322,34 +340,13 @@ public final class PinState { } @WorkerThread - private static void setPin(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard) - throws IOException, UnauthenticatedResponseException - { - 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); - - kbsValues.setKbsMasterKey(kbsData, pin); - TextSecurePreferences.clearRegistrationLockV1(context); - SignalStore.pinValues().setKeyboardType(keyboard); - SignalStore.pinValues().resetPinReminders(); - ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL); - } - - @WorkerThread - private static void optOutOfPin(@NonNull Context context) - throws IOException, UnauthenticatedResponseException - { - SignalStore.kbsValues().resetMasterKey(); - - setPin(context, Hex.toStringCondensed(Util.getSecretBytes(32)), PinKeyboardType.ALPHA_NUMERIC); + private static void optOutOfPin() { SignalStore.kbsValues().optOut(); - ApplicationDependencies.getJobManager().add(new StorageForcePushJob()); + ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL); + bestEffortRefreshAttributes(); + bestEffortForcePushStorage(); } private static @NonNull State assertState(State... allowed) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f9d47fba93..0727baf31f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,8 +55,8 @@ Theme %1$s, Language %2$s PINs are required for registration lock. To disable PINs, please first disable registration lock. Failed to disable PINs. Try again later. - Pin created. - Pin disabled. + PIN created. + PIN disabled. @@ -959,7 +959,7 @@ Warning - Disabling PIN will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled. + If you disable the PIN, you will lose all data when you re-register Signal unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled. Disable PIN @@ -1903,7 +1903,7 @@ Theme Disable PIN Enable PIN - Disabling PIN will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled. + If you disable the PIN, you will lose all data when you re-register Signal unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled. PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You won’t need your PIN to open the app. System default Default diff --git a/app/src/main/res/xml/preferences_advanced_pin.xml b/app/src/main/res/xml/preferences_advanced_pin.xml index 74fc63781e..b72010b57d 100644 --- a/app/src/main/res/xml/preferences_advanced_pin.xml +++ b/app/src/main/res/xml/preferences_advanced_pin.xml @@ -8,7 +8,7 @@