Clean up reg v1 remnants using safe delete.

This commit is contained in:
Nicholas Tinsley
2024-09-05 14:55:12 -04:00
committed by Cody Henthorne
parent 2123c642a5
commit c9746b59ed
5 changed files with 3 additions and 668 deletions

View File

@@ -91,7 +91,7 @@ public class RefreshAttributesJob extends BaseJob {
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
String registrationLockV2 = null;
SvrValues svrValues = SignalStore.svr();
int pniRegistrationId = new RegistrationRepository(AppDependencies.getApplication()).getPniRegistrationId();
int pniRegistrationId = new RegistrationRepository().getPniRegistrationId();
String recoveryPassword = svrValues.getRecoveryPassword();
if (svrValues.isRegistrationLockEnabled()) {

View File

@@ -1,12 +1,8 @@
package org.thoughtcrime.securesms.registration;
import android.app.Application;
import android.app.backup.BackupManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationManagerCompat;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKeyPair;
@@ -16,43 +12,16 @@ import org.signal.libsignal.protocol.util.KeyHelper;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.crypto.SenderKeyUtil;
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore;
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl;
import org.thoughtcrime.securesms.database.IdentityTable;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationIds;
import org.thoughtcrime.securesms.pin.SvrRepository;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.Preconditions;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckProcessor;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Operations required for finalizing the registration of an account. This is
* to be used after verifying the code and registration lock (if necessary) with
@@ -62,19 +31,7 @@ public final class RegistrationRepository {
private static final String TAG = Log.tag(RegistrationRepository.class);
private final Application context;
public RegistrationRepository(@NonNull Application context) {
this.context = context;
}
public int getRegistrationId() {
int registrationId = SignalStore.account().getRegistrationId();
if (registrationId == 0) {
registrationId = KeyHelper.generateRegistrationId(false);
SignalStore.account().setRegistrationId(registrationId);
}
return registrationId;
public RegistrationRepository() {
}
public int getPniRegistrationId() {
@@ -97,90 +54,6 @@ public final class RegistrationRepository {
return profileKey;
}
public Single<ServiceResponse<VerifyResponse>> registerAccount(@NonNull RegistrationData registrationData,
@NonNull VerifyResponse response,
boolean setRegistrationLockEnabled)
{
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
try {
registerAccountInternal(registrationData, response, setRegistrationLockEnabled);
JobManager jobManager = AppDependencies.getJobManager();
jobManager.add(new DirectoryRefreshJob(false));
jobManager.add(new RotateCertificateJob());
DirectoryRefreshListener.schedule(context);
RotateSignedPreKeyListener.schedule(context);
return ServiceResponse.forResult(response, 200, null);
} catch (IOException e) {
return ServiceResponse.forUnknownError(e);
}
}).subscribeOn(Schedulers.io());
}
@WorkerThread
private void registerAccountInternal(@NonNull RegistrationData registrationData,
@NonNull VerifyResponse response,
boolean setRegistrationLockEnabled)
throws IOException
{
Preconditions.checkNotNull(response.getAciPreKeyCollection(), "Missing ACI prekey collection!");
Preconditions.checkNotNull(response.getPniPreKeyCollection(), "Missing PNI prekey collection!");
ACI aci = ACI.parseOrThrow(response.getVerifyAccountResponse().getUuid());
PNI pni = PNI.parseOrThrow(response.getVerifyAccountResponse().getPni());
boolean hasPin = response.getVerifyAccountResponse().isStorageCapable();
SignalStore.account().setAci(aci);
SignalStore.account().setPni(pni);
AppDependencies.resetProtocolStores();
AppDependencies.getProtocolStore().aci().sessions().archiveAllSessions();
AppDependencies.getProtocolStore().pni().sessions().archiveAllSessions();
SenderKeyUtil.clearAllState();
SignalServiceAccountDataStoreImpl aciProtocolStore = AppDependencies.getProtocolStore().aci();
PreKeyMetadataStore aciMetadataStore = SignalStore.account().aciPreKeys();
SignalServiceAccountDataStoreImpl pniProtocolStore = AppDependencies.getProtocolStore().pni();
PreKeyMetadataStore pniMetadataStore = SignalStore.account().pniPreKeys();
storeSignedAndLastResortPreKeys(aciProtocolStore, aciMetadataStore, response.getAciPreKeyCollection());
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, response.getPniPreKeyCollection());
RecipientTable recipientTable = SignalDatabase.recipients();
RecipientId selfId = Recipient.trustedPush(aci, pni, registrationData.getE164()).getId();
recipientTable.setProfileSharing(selfId, true);
recipientTable.markRegisteredOrThrow(selfId, aci);
recipientTable.linkIdsForSelf(aci, pni, registrationData.getE164());
recipientTable.setProfileKey(selfId, registrationData.getProfileKey());
AppDependencies.getRecipientCache().clearSelf();
SignalStore.account().setE164(registrationData.getE164());
SignalStore.account().setFcmToken(registrationData.getFcmToken());
SignalStore.account().setFcmEnabled(registrationData.isFcm());
long now = System.currentTimeMillis();
saveOwnIdentityKey(selfId, aci, aciProtocolStore, now);
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now);
SignalStore.account().setServicePassword(registrationData.getPassword());
SignalStore.account().setRegistered(true);
TextSecurePreferences.setPromptedPushRegistration(context, true);
TextSecurePreferences.setUnauthorizedReceived(context, false);
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
SvrRepository.onRegistrationComplete(response.getMasterKey(), response.getPin(), hasPin, setRegistrationLockEnabled);
AppDependencies.resetNetwork();
AppDependencies.getIncomingMessageObserver();
PreKeysSyncJob.enqueue();
}
public static PreKeyCollection generateSignedAndLastResortPreKeys(IdentityKeyPair identity, PreKeyMetadataStore metadataStore) {
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(metadataStore.getNextSignedPreKeyId(), identity.getPrivateKey());
KyberPreKeyRecord lastResortKyberPreKey = PreKeyUtil.generateLastResortKyberPreKey(metadataStore.getNextKyberPreKeyId(), identity.getPrivateKey());
@@ -192,27 +65,6 @@ public final class RegistrationRepository {
);
}
private static void storeSignedAndLastResortPreKeys(SignalServiceAccountDataStoreImpl protocolStore, PreKeyMetadataStore metadataStore, PreKeyCollection preKeyCollection) {
PreKeyUtil.storeSignedPreKey(protocolStore, metadataStore, preKeyCollection.getSignedPreKey());
metadataStore.setSignedPreKeyRegistered(true);
metadataStore.setActiveSignedPreKeyId(preKeyCollection.getSignedPreKey().getId());
metadataStore.setLastSignedPreKeyRotationTime(System.currentTimeMillis());
PreKeyUtil.storeLastResortKyberPreKey(protocolStore, metadataStore, preKeyCollection.getLastResortKyberPreKey());
metadataStore.setLastResortKyberPreKeyId(preKeyCollection.getLastResortKyberPreKey().getId());
metadataStore.setLastResortKyberPreKeyRotationTime(System.currentTimeMillis());
}
private void saveOwnIdentityKey(@NonNull RecipientId selfId, @NonNull ServiceId serviceId, @NonNull SignalServiceAccountDataStoreImpl protocolStore, long now) {
protocolStore.identities().saveIdentityWithoutSideEffects(selfId,
serviceId,
protocolStore.getIdentityKeyPair().getPublicKey(),
IdentityTable.VerifiedStatus.VERIFIED,
true,
now,
true);
}
@WorkerThread
private static @Nullable ProfileKey findExistingProfileKey(@NonNull String e164number) {
RecipientTable recipientTable = SignalDatabase.recipients();
@@ -225,18 +77,4 @@ public final class RegistrationRepository {
return null;
}
public Single<BackupAuthCheckProcessor> getSvrAuthCredential(@NonNull RegistrationData registrationData, List<String> usernamePasswords) {
SignalServiceAccountManager accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
Log.d(TAG, "Fetching SVR backup credentials.");
return accountManager.checkBackupAuthCredentials(registrationData.getE164(), usernamePasswords)
.map(BackupAuthCheckProcessor::new)
.doOnSuccess(processor -> {
Log.d(TAG, "Received SVR backup auth credential response.");
if (SignalStore.svr().removeSvr2AuthTokens(processor.getInvalid())) {
new BackupManager(context).dataChanged();
}
});
}
}

View File

@@ -1,220 +0,0 @@
package org.thoughtcrime.securesms.registration
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException
import org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException
import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.InvalidTransportModeException
import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException
import org.whispersystems.signalservice.api.push.exceptions.MustRequestNewCodeException
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException
import org.whispersystems.signalservice.api.util.Preconditions
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
import org.whispersystems.signalservice.internal.push.RegistrationSessionState
import kotlin.time.Duration.Companion.seconds
/**
* Makes the server's response describing the state of the registration session as digestible as possible.
*/
sealed class RegistrationSessionProcessor(response: ServiceResponse<RegistrationSessionMetadataResponse>) : ServiceResponseProcessor<RegistrationSessionMetadataResponse>(response) {
companion object {
const val CAPTCHA_KEY = "captcha"
const val PUSH_CHALLENGE_KEY = "pushChallenge"
val REQUESTABLE_INFORMATION = listOf(PUSH_CHALLENGE_KEY, CAPTCHA_KEY)
}
public override fun rateLimit(): Boolean {
return error is RateLimitException
}
public override fun getError(): Throwable? {
return super.getError()
}
fun captchaRequired(excludedChallenges: List<String>): Boolean {
return response.status == 402 || (hasResult() && CAPTCHA_KEY == getChallenge(excludedChallenges))
}
fun pushChallengeTimedOut(): Boolean {
if (response.result.isEmpty) {
return false
} else {
val state: RegistrationSessionState = response.result.get().state ?: return false
return state.pushChallengeTimedOut
}
}
fun isTokenRejected(): Boolean {
return error is TokenNotAcceptedException
}
fun isImpossibleNumber(): Boolean {
return error is ImpossiblePhoneNumberException
}
fun isNonNormalizedNumber(): Boolean {
return error is NonNormalizedPhoneNumberException
}
fun getRateLimit(): Long {
Preconditions.checkState(error is RateLimitException, "This can only be called when isRateLimited()")
return (error as RateLimitException).retryAfterMilliseconds.orElse(-1L)
}
/**
* The soonest time at which the server will accept a request to send a new code via SMS.
* @return a unix timestamp in milliseconds, or 0 to represent null
*/
fun getNextCodeViaSmsAttempt(): Long {
return deriveTimestamp(result.body.nextSms)
}
/**
* The soonest time at which the server will accept a request to send a new code via a voice call.
* @return a unix timestamp in milliseconds, or 0 to represent null
*/
fun getNextCodeViaCallAttempt(): Long {
return deriveTimestamp(result.body.nextCall)
}
fun canSubmitProofImmediately(): Boolean {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
return 0 == result.body.nextVerificationAttempt
}
fun mustWaitToSubmitProof(): Boolean {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
val nextVerificationAttempt = result.body.nextVerificationAttempt
return nextVerificationAttempt != null && nextVerificationAttempt > 0
}
/**
* The soonest time at which the server will accept a submission of proof of ownership.
* @return a unix timestamp in milliseconds, or 0 to represent null
*/
fun getNextProofSubmissionAttempt(): Long {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
return deriveTimestamp(result.body.nextVerificationAttempt)
}
fun exhaustedVerificationCodeAttempts(): Boolean {
return rateLimit() && getRateLimit() == -1L
}
fun isInvalidSession(): Boolean {
return error is NoSuchSessionException
}
fun getSessionId(): String {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
return result.body.id
}
fun isAllowedToRequestCode(): Boolean {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
return result.body.allowedToRequestCode
}
/**
* Parse the response body for the server requested challenges that the client may submit.
*
* @param exclusions a collection of keys to ignore, used when they've already tried and failed
* @return the next challenge
*/
fun getChallenge(exclusions: Collection<String>): String? {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
return result.body.requestedInformation.filterNot { exclusions.contains(it) }.firstOrNull { REQUESTABLE_INFORMATION.contains(it) }
}
fun isVerified(): Boolean {
return hasResult() && result.body.verified
}
/** Should only be called if [isNonNormalizedNumber] */
fun getOriginalNumber(): String {
if (error !is NonNormalizedPhoneNumberException) {
throw IllegalStateException("This can only be called when isNonNormalizedNumber()")
}
return (error as NonNormalizedPhoneNumberException).originalNumber
}
/** Should only be called if [isNonNormalizedNumber] */
fun getNormalizedNumber(): String {
if (error !is NonNormalizedPhoneNumberException) {
throw IllegalStateException("This can only be called when isNonNormalizedNumber()")
}
return (error as NonNormalizedPhoneNumberException).normalizedNumber
}
fun cannotSubmitVerificationAttempt(): Boolean {
if (!hasResult()) {
return true
}
val body = result.body
if (body.requestedInformation.isNotEmpty()) {
return false
}
return body.nextVerificationAttempt == null
}
/**
* @param deltaSeconds the number of whole seconds to be added to the server timestamp
* @return a unix timestamp in milliseconds, or 0 to represent null
*/
private fun deriveTimestamp(deltaSeconds: Int?): Long {
Preconditions.checkState(hasResult(), "This can only be called when result is present!")
if (deltaSeconds == null) {
return 0L
}
val timestamp: Long = result.headers.timestamp
return timestamp + deltaSeconds.seconds.inWholeMilliseconds
}
abstract fun verificationCodeRequestSuccess(): Boolean
fun isMalformedRequest(): Boolean {
return error is MalformedRequestException
}
fun isRetryException(): Boolean {
return error is RegistrationRetryException
}
fun isAlreadyVerified(): Boolean {
return error is AlreadyVerifiedException
}
fun mustRequestNewCode(): Boolean {
return error is MustRequestNewCodeException
}
fun externalServiceFailure(): Boolean {
return error is ExternalServiceFailureException
}
fun invalidTransportModeFailure(): Boolean {
return error is InvalidTransportModeException
}
class RegistrationSessionProcessorForSession(response: ServiceResponse<RegistrationSessionMetadataResponse>) : RegistrationSessionProcessor(response) {
override fun verificationCodeRequestSuccess(): Boolean = false
}
class RegistrationSessionProcessorForVerification(response: ServiceResponse<RegistrationSessionMetadataResponse>) : RegistrationSessionProcessor(response) {
override fun verificationCodeRequestSuccess(): Boolean = hasResult()
}
}

View File

@@ -1,236 +1,17 @@
package org.thoughtcrime.securesms.registration
import android.app.Application
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.IdentityKeyPair
import org.thoughtcrime.securesms.AppCapabilities
import org.thoughtcrime.securesms.gcm.FcmUtil
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.registration.PushChallengeRequest.PushChallengeEvent
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.SvrNoDataException
import org.whispersystems.signalservice.api.account.AccountAttributes
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
import org.whispersystems.signalservice.internal.push.RegistrationSessionState
import java.io.IOException
import java.util.Locale
import java.util.Optional
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/**
* Request SMS/Phone verification codes to help prove ownership of a phone number.
*/
class VerifyAccountRepository(private val context: Application) {
fun validateSession(
sessionId: String?,
e164: String,
password: String
): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
return if (sessionId.isNullOrBlank()) {
Single.just(ServiceResponse.forApplicationError(NoSuchSessionException(), 409, null))
} else {
val accountManager: SignalServiceAccountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
Single.fromCallable { accountManager.getRegistrationSession(sessionId) }.subscribeOn(Schedulers.io())
}
}
fun requestValidSession(
e164: String,
password: String,
mcc: String?,
mnc: String?
): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
Log.d(TAG, "Initializing registration session.")
return Single.fromCallable {
val fcmToken: String? = FcmUtil.getToken(context).orElse(null)
val accountManager: SignalServiceAccountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
if (fcmToken == null) {
return@fromCallable accountManager.createRegistrationSession(null, mcc, mnc)
} else {
return@fromCallable createSessionAndBlockForPushChallenge(accountManager, fcmToken, mcc, mnc)
}
}
.subscribeOn(Schedulers.io())
}
private fun createSessionAndBlockForPushChallenge(accountManager: SignalServiceAccountManager, fcmToken: String, mcc: String?, mnc: String?): ServiceResponse<RegistrationSessionMetadataResponse> {
val subscriber = PushTokenChallengeSubscriber()
val eventBus = EventBus.getDefault()
eventBus.register(subscriber)
val response: ServiceResponse<RegistrationSessionMetadataResponse> = accountManager.createRegistrationSession(fcmToken, mcc, mnc)
if (!response.result.isPresent) {
return response
}
val receivedPush = subscriber.latch.await(PUSH_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
eventBus.unregister(subscriber)
if (receivedPush) {
val challenge = subscriber.challenge
if (challenge != null) {
Log.w(TAG, "Push challenge token received.")
return accountManager.submitPushChallengeToken(response.result.get().body.id, challenge)
} else {
Log.w(TAG, "Push received but challenge token was null.")
}
} else {
Log.i(TAG, "Push challenge timed out.")
}
Log.i(TAG, "Push challenge unsuccessful. Updating registration state accordingly.")
val registrationSessionState = RegistrationSessionState(pushChallengeTimedOut = true)
val rawResponse: RegistrationSessionMetadataResponse = response.result.get()
return ServiceResponse.forResult(rawResponse.copy(state = registrationSessionState), 200, null)
}
fun requestAndVerifyPushToken(
sessionId: String,
e164: String,
password: String
): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
val fcmToken: Optional<String> = FcmUtil.getToken(context)
val accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
val pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, sessionId, fcmToken, PUSH_REQUEST_TIMEOUT)
return Single.fromCallable {
return@fromCallable accountManager.submitPushChallengeToken(sessionId, pushChallenge.orElse(null))
}.subscribeOn(Schedulers.io())
}
fun verifyCaptcha(
sessionId: String,
captcha: String,
e164: String,
password: String
): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
val accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
return Single.fromCallable {
return@fromCallable accountManager.submitCaptchaToken(sessionId, captcha)
}.subscribeOn(Schedulers.io())
}
fun requestVerificationCode(
sessionId: String,
e164: String,
password: String,
mode: Mode
): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
Log.d(TAG, "SMS Verification requested")
return Single.fromCallable {
val accountManager = AccountManagerFactory.getInstance().createUnauthenticated(context, e164, SignalServiceAddress.DEFAULT_DEVICE_ID, password)
if (mode == Mode.PHONE_CALL) {
return@fromCallable accountManager.requestVoiceVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported)
} else {
return@fromCallable accountManager.requestSmsVerificationCode(sessionId, Locale.getDefault(), mode.isSmsRetrieverSupported)
}
}.subscribeOn(Schedulers.io())
}
fun verifyAccount(sessionId: String, registrationData: RegistrationData): Single<ServiceResponse<RegistrationSessionMetadataResponse>> {
val accountManager: SignalServiceAccountManager = AccountManagerFactory.getInstance().createUnauthenticated(
context,
registrationData.e164,
SignalServiceAddress.DEFAULT_DEVICE_ID,
registrationData.password
)
return Single.fromCallable {
accountManager.verifyAccount(
registrationData.code,
sessionId
)
}.subscribeOn(Schedulers.io())
}
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, masterKeyProducer: MasterKeyProducer? = null): Single<ServiceResponse<VerifyResponse>> {
val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context)
val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey)
val accountManager: SignalServiceAccountManager = AccountManagerFactory.getInstance().createUnauthenticated(
context,
registrationData.e164,
SignalServiceAddress.DEFAULT_DEVICE_ID,
registrationData.password
)
val masterKey: MasterKey? = masterKeyProducer?.produceMasterKey()
val registrationLock: String? = masterKey?.deriveRegistrationLock()
val accountAttributes = AccountAttributes(
signalingKey = null,
registrationId = registrationData.registrationId,
fetchesMessages = registrationData.isNotFcm,
registrationLock = registrationLock,
unidentifiedAccessKey = unidentifiedAccessKey,
unrestrictedUnidentifiedAccess = universalUnidentifiedAccess,
capabilities = AppCapabilities.getCapabilities(true),
discoverableByPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.DISCOVERABLE,
name = null,
pniRegistrationId = registrationData.pniRegistrationId,
recoveryPassword = registrationData.recoveryPassword
)
SignalStore.account.generateAciIdentityKeyIfNecessary()
val aciIdentity: IdentityKeyPair = SignalStore.account.aciIdentityKey
SignalStore.account.generatePniIdentityKeyIfNecessary()
val pniIdentity: IdentityKeyPair = SignalStore.account.pniIdentityKey
val aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(aciIdentity, SignalStore.account.aciPreKeys)
val pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(pniIdentity, SignalStore.account.pniPreKeys)
return Single.fromCallable {
val response = accountManager.registerAccount(sessionId, registrationData.recoveryPassword, accountAttributes, aciPreKeyCollection, pniPreKeyCollection, registrationData.fcmToken, true)
VerifyResponse.from(response, masterKey, pin, aciPreKeyCollection, pniPreKeyCollection)
}.subscribeOn(Schedulers.io())
}
fun getFcmToken(): Single<String> {
return Single.fromCallable {
return@fromCallable FcmUtil.getToken(context).orElse("")
}.subscribeOn(Schedulers.io())
}
class VerifyAccountRepository {
fun interface MasterKeyProducer {
@Throws(IOException::class, SvrWrongPinException::class, SvrNoDataException::class)
fun produceMasterKey(): MasterKey
}
enum class Mode(val isSmsRetrieverSupported: Boolean) {
SMS_WITH_LISTENER(true),
SMS_WITHOUT_LISTENER(false),
PHONE_CALL(false)
}
private class PushTokenChallengeSubscriber {
var challenge: String? = null
val latch = CountDownLatch(1)
@Subscribe
fun onChallengeEvent(pushChallengeEvent: PushChallengeEvent) {
challenge = pushChallengeEvent.challenge
latch.countDown()
}
}
companion object {
private val TAG = Log.tag(VerifyAccountRepository::class.java)
private val PUSH_REQUEST_TIMEOUT = TimeUnit.SECONDS.toMillis(5)
}
}

View File

@@ -1,10 +1,5 @@
package org.thoughtcrime.securesms.registration
import org.thoughtcrime.securesms.pin.SvrWrongPinException
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
import org.whispersystems.signalservice.api.SvrNoDataException
import org.whispersystems.signalservice.api.push.exceptions.IncorrectRegistrationRecoveryPasswordException
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.LockedException
@@ -14,20 +9,6 @@ import org.whispersystems.signalservice.internal.push.LockedException
*/
sealed class VerifyResponseProcessor(response: ServiceResponse<VerifyResponse>) : ServiceResponseProcessor<VerifyResponse>(response) {
open val svrTriesRemaining: Int?
get() = (error as? SvrWrongPinException)?.triesRemaining
open val svrAuthCredentials: SvrAuthCredentialSet?
get() {
return error?.let {
if (it is LockedException) {
SvrAuthCredentialSet(svr2Credentials = it.svr2Credentials, svr3Credentials = it.svr3Credentials)
} else {
null
}
}
}
public override fun authorizationFailed(): Boolean {
return super.authorizationFailed()
}
@@ -48,14 +29,6 @@ sealed class VerifyResponseProcessor(response: ServiceResponse<VerifyResponse>)
return error as LockedException
}
open fun isServerSentError(): Boolean {
return error is NonSuccessfulResponseCodeException
}
fun isIncorrectRegistrationRecoveryPassword(): Boolean {
return error is IncorrectRegistrationRecoveryPasswordException
}
/** True if the account has reglock enabled but all guesses have been exhausted, otherwise false. */
abstract fun isRegistrationLockPresentAndSvrExhausted(): Boolean
}
@@ -69,40 +42,3 @@ class VerifyResponseWithoutKbs(response: ServiceResponse<VerifyResponse>) : Veri
}
}
/**
* Verify processor indicating we cannot register until registration lock has been resolved.
*/
class VerifyResponseHitRegistrationLock(response: ServiceResponse<VerifyResponse>) : VerifyResponseProcessor(response) {
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
return false
}
}
/**
* Process responses from attempting to verify an account with registration lock for use in
* account registration.
*/
class VerifyResponseWithRegistrationLockProcessor(response: ServiceResponse<VerifyResponse>, override val svrAuthCredentials: SvrAuthCredentialSet?) : VerifyResponseProcessor(response) {
fun wrongPin(): Boolean {
return error is SvrWrongPinException
}
override fun isRegistrationLockPresentAndSvrExhausted(): Boolean {
return error is SvrNoDataException
}
fun updatedIfRegistrationFailed(response: ServiceResponse<VerifyResponse>): VerifyResponseWithRegistrationLockProcessor {
if (response.result.isPresent) {
return this
}
return VerifyResponseWithRegistrationLockProcessor(ServiceResponse.coerceError(response), svrAuthCredentials)
}
override fun isServerSentError(): Boolean {
return super.isServerSentError() ||
error is SvrWrongPinException ||
error is SvrNoDataException
}
}