diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java index a75a05d3dd..4f1c6f87ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RefreshAttributesJob.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java index 9972f3e671..780717d30e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationRepository.java @@ -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> registerAccount(@NonNull RegistrationData registrationData, - @NonNull VerifyResponse response, - boolean setRegistrationLockEnabled) - { - return Single.>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 getSvrAuthCredential(@NonNull RegistrationData registrationData, List 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(); - } - }); - } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt deleted file mode 100644 index 14a1e126ed..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/RegistrationSessionProcessor.kt +++ /dev/null @@ -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) : ServiceResponseProcessor(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): 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? { - 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) : RegistrationSessionProcessor(response) { - - override fun verificationCodeRequestSuccess(): Boolean = false - } - - class RegistrationSessionProcessorForVerification(response: ServiceResponse) : RegistrationSessionProcessor(response) { - override fun verificationCodeRequestSuccess(): Boolean = hasResult() - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt index e028a7ae94..2d0b5a735f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountRepository.kt @@ -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> { - 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> { - 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 { - val subscriber = PushTokenChallengeSubscriber() - val eventBus = EventBus.getDefault() - eventBus.register(subscriber) - - val response: ServiceResponse = 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> { - val fcmToken: Optional = 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> { - 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> { - 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> { - 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> { - 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 { - 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) - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt index 44be9a2311..3769a913f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyResponseProcessor.kt @@ -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) : ServiceResponseProcessor(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) 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) : Veri } } -/** - * Verify processor indicating we cannot register until registration lock has been resolved. - */ -class VerifyResponseHitRegistrationLock(response: ServiceResponse) : 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, 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): 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 - } -}