mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Clean up reg v1 remnants using safe delete.
This commit is contained in:
committed by
Cody Henthorne
parent
2123c642a5
commit
c9746b59ed
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user