Skip to content

Commit

Permalink
Add skip SMS flow.
Browse files Browse the repository at this point in the history
  • Loading branch information
cody-signal authored and greyson-signal committed Feb 23, 2023
1 parent a47e390 commit 4f458a0
Show file tree
Hide file tree
Showing 19 changed files with 657 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.multidex.MultiDexApplication;

import com.google.android.gms.security.ProviderInstaller;
Expand Down Expand Up @@ -63,6 +62,7 @@
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
Expand All @@ -75,7 +75,6 @@
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
Expand Down Expand Up @@ -104,13 +103,10 @@
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.Security;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import io.reactivex.rxjava3.core.CompletableObserver;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
Expand Down Expand Up @@ -202,6 +198,7 @@ public void onCreate() {
.addPostRender(this::initializeExpiringMessageManager)
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
.addPostRender(this::initializeTrimThreadsByDateManager)
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public static Map<String, Job.Factory> getJobFactories(@NonNull Application appl
put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory());
put(ReactionSendJob.KEY, new ReactionSendJob.Factory());
put(RefreshAttributesJob.KEY, new RefreshAttributesJob.Factory());
put(RefreshKbsCredentialsJob.KEY, new RefreshKbsCredentialsJob.Factory());
put(RefreshOwnProfileJob.KEY, new RefreshOwnProfileJob.Factory());
put(RemoteConfigRefreshJob.KEY, new RemoteConfigRefreshJob.Factory());
put(RemoteDeleteSendJob.KEY, new RemoteDeleteSendJob.Factory());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
import org.whispersystems.util.Base64;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -83,15 +84,15 @@ public void onRun() throws IOException {
return;
}

int registrationId = SignalStore.account().getRegistrationId();
boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced();
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
String registrationLockV1 = null;
String registrationLockV2 = null;
KbsValues kbsValues = SignalStore.kbsValues();
int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId();
String registrationRecoveryPassword = kbsValues.getRegistrationRecoveryPassword();
int registrationId = SignalStore.account().getRegistrationId();
boolean fetchesMessages = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced();
byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
String registrationLockV1 = null;
String registrationLockV2 = null;
KbsValues kbsValues = SignalStore.kbsValues();
int pniRegistrationId = new RegistrationRepository(ApplicationDependencies.getApplication()).getPniRegistrationId();
String recoveryPassword = kbsValues.getRecoveryPassword();

if (kbsValues.isV2RegistrationLockEnabled()) {
registrationLockV2 = kbsValues.getRegistrationLockToken();
Expand All @@ -107,23 +108,27 @@ public void onRun() throws IOException {

AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut());
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() +
"\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) +
"\n Phone number discoverable : " + phoneNumberDiscoverable +
"\n Device Name : " + (encryptedDeviceName != null) +
"\n Capabilities: " + capabilities);

SignalServiceAccountManager signalAccountManager = ApplicationDependencies.getSignalServiceAccountManager();
signalAccountManager.setAccountAttributes(null,
registrationId,
fetchesMessages,
registrationLockV1,
registrationLockV2,
unidentifiedAccessKey,
universalUnidentifiedAccess,
capabilities,
phoneNumberDiscoverable,
encryptedDeviceName,
pniRegistrationId,
registrationRecoveryPassword);
AccountAttributes accountAttributes = new AccountAttributes(
null,
registrationId,
fetchesMessages,
registrationLockV1,
registrationLockV2,
unidentifiedAccessKey,
universalUnidentifiedAccess,
capabilities,
phoneNumberDiscoverable,
(encryptedDeviceName == null) ? null : Base64.encodeBytes(encryptedDeviceName),
pniRegistrationId,
recoveryPassword
);

ApplicationDependencies.getSignalServiceAccountManager().setAccountAttributes(accountAttributes);

hasRefreshedThisAppCycle = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.jobs

import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Data
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import java.io.IOException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days

/**
* Refresh KBS authentication credentials for talking to KBS during re-registration.
*/
class RefreshKbsCredentialsJob private constructor(parameters: Parameters) : BaseJob(parameters) {

companion object {
const val KEY = "RefreshKbsCredentialsJob"

private val TAG = Log.tag(RefreshKbsCredentialsJob::class.java)
private val FREQUENCY: Duration = 15.days

@JvmStatic
fun enqueueIfNecessary() {
val lastTimestamp = SignalStore.kbsValues().lastRefreshAuthTimestamp
if (lastTimestamp + FREQUENCY.inWholeMilliseconds < System.currentTimeMillis() || lastTimestamp > System.currentTimeMillis()) {
ApplicationDependencies.getJobManager().add(RefreshKbsCredentialsJob())
} else {
Log.d(TAG, "Do not need to refresh credentials. Last refresh: $lastTimestamp")
}
}
}

private constructor() : this(
parameters = Parameters.Builder()
.setQueue("RefreshKbsCredentials")
.addConstraint(NetworkConstraint.KEY)
.setMaxInstancesForQueue(2)
.setLifespan(1.days.inWholeMilliseconds)
.build()
)

override fun serialize(): Data = Data.Builder().build()

override fun getFactoryKey(): String = KEY

override fun onRun() {
KbsRepository().refreshAuthorization()
}

override fun onShouldRetry(e: Exception): Boolean {
return e is IOException && e !is NonSuccessfulResponseCodeException
}

override fun onFailure() = Unit

class Factory : Job.Factory<RefreshKbsCredentialsJob> {
override fun create(parameters: Parameters, data: Data): RefreshKbsCredentialsJob {
return RefreshKbsCredentialsJob(parameters)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@

import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class KbsValues extends SignalStoreValues {

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 PIN = "kbs.pin";
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
public static final String OPTED_OUT = "kbs.opted_out";
private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
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 PIN = "kbs.pin";
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
public static final String OPTED_OUT = "kbs.opted_out";
private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
private static final String KBS_LAST_AUTH_REFRESH_TIMESTAMP = "kbs.kbs_auth_tokens.last_refresh_timestamp";

KbsValues(KeyValueStore store) {
super(store);
Expand Down Expand Up @@ -55,6 +57,8 @@ public void clearRegistrationLockAndPin() {
.remove(PIN)
.remove(LAST_CREATE_FAILED_TIMESTAMP)
.remove(OPTED_OUT)
.remove(KBS_AUTH_TOKENS)
.remove(KBS_LAST_AUTH_REFRESH_TIMESTAMP)
.commit();
}

Expand Down Expand Up @@ -149,12 +153,12 @@ public synchronized boolean lastPinCreateFailed() {
}
}

public synchronized @Nullable String getRegistrationRecoveryPassword() {
MasterKey masterKey = getPinBackedMasterKey();
if (masterKey == null) {
return null;
} else {
public synchronized @Nullable String getRecoveryPassword() {
MasterKey masterKey = getMasterKey();
if (masterKey != null && hasPin()) {
return masterKey.deriveRegistrationRecoveryPassword();
} else {
return null;
}
}

Expand All @@ -180,6 +184,7 @@ public synchronized void setPinForgottenOrSkipped(boolean value) {

public synchronized void putAuthTokenList(List<String> tokens) {
putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
setLastRefreshAuthTimestamp(System.currentTimeMillis());
}

public synchronized List<String> getKbsAuthTokenList() {
Expand All @@ -202,6 +207,16 @@ public synchronized boolean appendAuthTokenToList(String token) {
}
}

public boolean removeAuthTokens(@NonNull List<String> invalid) {
List<String> tokens = new ArrayList<>(getKbsAuthTokenList());
if (tokens.removeAll(invalid)) {
putAuthTokenList(tokens);
return true;
}

return false;
}

/** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */
public synchronized void optOut() {
getStore().beginWrite()
Expand Down Expand Up @@ -229,4 +244,12 @@ public synchronized boolean hasOptedOut() {
throw new AssertionError(e);
}
}

private void setLastRefreshAuthTimestamp(long timestamp) {
putLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, timestamp);
}

public long getLastRefreshAuthTimestamp() {
return getLong(KBS_LAST_AUTH_REFRESH_TIMESTAMP, 0L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ public Single<ServiceResponse<TokenData>> getToken(@Nullable String authorizatio
}).subscribeOn(Schedulers.io());
}

/**
* Fetch and store a new KBS authorization.
*/
public void refreshAuthorization() throws IOException {
for (KbsEnclave enclave : KbsEnclaves.all()) {
KeyBackupService kbs = ApplicationDependencies.getKeyBackupService(enclave);

try {
String authorization = kbs.getAuthorization();
backupAuthToken(authorization);
} catch (NonSuccessfulResponseCodeException e) {
if (e.getCode() == 404) {
Log.i(TAG, "Enclave decommissioned, skipping", e);
} else {
throw e;
}
}
}
}

private @NonNull TokenData getTokenSync(@Nullable String authorization) throws IOException {
TokenData firstKnownTokenData = null;

Expand Down Expand Up @@ -101,7 +121,7 @@ public Single<ServiceResponse<TokenData>> getToken(@Nullable String authorizatio

private static void backupAuthToken(String token) {
final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token);
if (tokenIsNew) {
if (tokenIsNew && SignalStore.kbsValues().hasPin()) {
new BackupManager(ApplicationDependencies.getApplication()).dataChanged();
}
}
Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/org/thoughtcrime/securesms/pin/PinState.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
Expand All @@ -44,7 +43,8 @@ public final class PinState {
public static synchronized void onRegistration(@NonNull Context context,
@Nullable KbsPinData kbsData,
@Nullable String pin,
boolean hasPinToRestore)
boolean hasPinToRestore,
boolean setRegistrationLockEnabled)
{
Log.i(TAG, "onRegistration()");

Expand All @@ -57,9 +57,13 @@ public static synchronized void onRegistration(@NonNull Context context,
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
} else if (kbsData != null && pin != null) {
Log.i(TAG, "Registration Lock V2");
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
if (setRegistrationLockEnabled) {
Log.i(TAG, "Registration Lock V2");
TextSecurePreferences.setV1RegistrationLockEnabled(context, false);
SignalStore.kbsValues().setV2RegistrationLockEnabled(true);
} else {
Log.i(TAG, "ReRegistration Skip SMS");
}
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
SignalStore.pinValues().resetPinReminders();
resetPinRetryCount(context, pin);
Expand Down Expand Up @@ -130,6 +134,7 @@ public static synchronized void onPinChangedOrCreated(@NonNull Context context,
bestEffortRefreshAttributes();
} else {
Log.i(TAG, "Not the first time setting a PIN. Enclave: " + kbsEnclave.getEnclaveName());
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
}
}

Expand Down

0 comments on commit 4f458a0

Please sign in to comment.