mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add the ability to migrate to new KBS enclaves.
This commit is contained in:
committed by
Alan Evans
parent
e22384b6b4
commit
474963dcf1
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
||||
@@ -30,7 +31,6 @@ import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -107,7 +107,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
|
||||
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null, null,
|
||||
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null,
|
||||
new CodeVerificationRequest.VerifyCallback() {
|
||||
|
||||
@Override
|
||||
@@ -133,10 +133,9 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) {
|
||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull PinRestoreRepository.TokenData tokenData, @NonNull String kbsStorageCredentials) {
|
||||
model.setLockedTimeRemaining(timeRemaining);
|
||||
model.setStorageCredentials(kbsStorageCredentials);
|
||||
model.setKeyBackupCurrentToken(tokenResponse);
|
||||
model.setKeyBackupTokenData(tokenData);
|
||||
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean r) {
|
||||
@@ -147,7 +146,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull PinRestoreRepository.TokenData tokenData) {
|
||||
throw new AssertionError("Unexpected, user has made no pin guesses");
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -106,10 +106,10 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
getModel().getLockedTimeRemaining()
|
||||
.observe(getViewLifecycleOwner(), t -> timeRemaining = t);
|
||||
|
||||
TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken();
|
||||
TokenData keyBackupCurrentToken = getModel().getKeyBackupCurrentToken();
|
||||
|
||||
if (keyBackupCurrentToken != null) {
|
||||
int triesRemaining = keyBackupCurrentToken.getTries();
|
||||
int triesRemaining = keyBackupCurrentToken.getTriesRemaining();
|
||||
if (triesRemaining <= 3) {
|
||||
int daysRemaining = getLockoutDays(timeRemaining);
|
||||
|
||||
@@ -158,8 +158,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
|
||||
String basicStorageCredentials = model.getBasicStorageCredentials();
|
||||
TokenData tokenData = model.getKeyBackupCurrentToken();
|
||||
|
||||
setSpinning(pinButton);
|
||||
|
||||
@@ -167,8 +166,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
model.getFcmToken(),
|
||||
model.getTextCodeEntered(),
|
||||
pin,
|
||||
basicStorageCredentials,
|
||||
tokenResponse,
|
||||
tokenData,
|
||||
|
||||
new CodeVerificationRequest.VerifyCallback() {
|
||||
|
||||
@@ -189,19 +187,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials) {
|
||||
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials) {
|
||||
throw new AssertionError("Not expected after a pin guess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
|
||||
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenData tokenData) {
|
||||
cancelSpinning(pinButton);
|
||||
pinEntry.getText().clear();
|
||||
enableAndFocusPinEntry();
|
||||
|
||||
model.setKeyBackupCurrentToken(tokenResponse);
|
||||
model.setKeyBackupTokenData(tokenData);
|
||||
|
||||
int triesRemaining = tokenResponse.getTries();
|
||||
int triesRemaining = tokenData.getTriesRemaining();
|
||||
|
||||
if (triesRemaining == 0) {
|
||||
Log.w(TAG, "Account locked. User out of attempts on KBS.");
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||
import org.thoughtcrime.securesms.pin.PinState;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -65,41 +67,40 @@ public final class CodeVerificationRequest {
|
||||
/**
|
||||
* Asynchronously verify the account via the code.
|
||||
*
|
||||
* @param fcmToken The FCM token for the device.
|
||||
* @param code The code that was delivered to the user.
|
||||
* @param pin The users registration pin.
|
||||
* @param callback Exactly one method on this callback will be called.
|
||||
* @param kbsTokenResponse By keeping the token, on failure, a newly returned token will be reused in subsequent pin
|
||||
* attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot.
|
||||
* @param fcmToken The FCM token for the device.
|
||||
* @param code The code that was delivered to the user.
|
||||
* @param pin The users registration pin.
|
||||
* @param callback Exactly one method on this callback will be called.
|
||||
* @param kbsTokenData By keeping the token, on failure, a newly returned token will be reused in subsequent pin
|
||||
* attempts, preventing certain attacks, we can also track the attempts making missing replies easier to spot.
|
||||
*/
|
||||
static void verifyAccount(@NonNull Context context,
|
||||
@NonNull Credentials credentials,
|
||||
@Nullable String fcmToken,
|
||||
@NonNull String code,
|
||||
@Nullable String pin,
|
||||
@Nullable String basicStorageCredentials,
|
||||
@Nullable TokenResponse kbsTokenResponse,
|
||||
@Nullable TokenData kbsTokenData,
|
||||
@NonNull VerifyCallback callback)
|
||||
{
|
||||
new AsyncTask<Void, Void, Result>() {
|
||||
|
||||
private volatile LockedException lockedException;
|
||||
private volatile TokenResponse kbsToken;
|
||||
private volatile TokenData tokenData;
|
||||
|
||||
@Override
|
||||
protected Result doInBackground(Void... voids) {
|
||||
final boolean pinSupplied = pin != null;
|
||||
final boolean tryKbs = kbsTokenResponse != null;
|
||||
final boolean tryKbs = tokenData != null;
|
||||
|
||||
try {
|
||||
kbsToken = kbsTokenResponse;
|
||||
verifyAccount(context, credentials, code, pin, kbsTokenResponse, basicStorageCredentials, fcmToken);
|
||||
this.tokenData = kbsTokenData;
|
||||
verifyAccount(context, credentials, code, pin, tokenData, fcmToken);
|
||||
return Result.SUCCESS;
|
||||
} catch (KeyBackupSystemNoDataException e) {
|
||||
Log.w(TAG, "No data found on KBS");
|
||||
return Result.KBS_ACCOUNT_LOCKED;
|
||||
} catch (KeyBackupSystemWrongPinException e) {
|
||||
kbsToken = e.getTokenResponse();
|
||||
tokenData = TokenData.withResponse(tokenData, e.getTokenResponse());
|
||||
return Result.KBS_WRONG_PIN;
|
||||
} catch (LockedException e) {
|
||||
if (pinSupplied && tryKbs) {
|
||||
@@ -110,8 +111,8 @@ public final class CodeVerificationRequest {
|
||||
lockedException = e;
|
||||
if (e.getBasicStorageCredentials() != null) {
|
||||
try {
|
||||
kbsToken = getToken(e.getBasicStorageCredentials());
|
||||
if (kbsToken == null || kbsToken.getTries() == 0) {
|
||||
tokenData = getToken(e.getBasicStorageCredentials());
|
||||
if (tokenData == null || tokenData.getTriesRemaining() == 0) {
|
||||
return Result.KBS_ACCOUNT_LOCKED;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
@@ -137,12 +138,12 @@ public final class CodeVerificationRequest {
|
||||
callback.onSuccessfulRegistration();
|
||||
break;
|
||||
case PIN_LOCKED:
|
||||
if (kbsToken != null) {
|
||||
if (tokenData != null) {
|
||||
if (lockedException.getBasicStorageCredentials() == null) {
|
||||
throw new AssertionError("KBS Token set, but no storage credentials supplied.");
|
||||
}
|
||||
Log.w(TAG, "Reg Locked: V2 pin needed for registration");
|
||||
callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), kbsToken, lockedException.getBasicStorageCredentials());
|
||||
callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), tokenData, lockedException.getBasicStorageCredentials());
|
||||
} else {
|
||||
Log.w(TAG, "Reg Locked: V1 pin needed for registration");
|
||||
callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining());
|
||||
@@ -156,7 +157,7 @@ public final class CodeVerificationRequest {
|
||||
break;
|
||||
case KBS_WRONG_PIN:
|
||||
Log.w(TAG, "KBS Pin was wrong");
|
||||
callback.onIncorrectKbsRegistrationLockPin(kbsToken);
|
||||
callback.onIncorrectKbsRegistrationLockPin(tokenData);
|
||||
break;
|
||||
case KBS_ACCOUNT_LOCKED:
|
||||
Log.w(TAG, "KBS Account is locked");
|
||||
@@ -167,9 +168,9 @@ public final class CodeVerificationRequest {
|
||||
}.executeOnExecutor(SignalExecutors.UNBOUNDED);
|
||||
}
|
||||
|
||||
private static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
|
||||
private static TokenData getToken(@Nullable String basicStorageCredentials) throws IOException {
|
||||
if (basicStorageCredentials == null) return null;
|
||||
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
|
||||
return new PinRestoreRepository().getTokenSync(basicStorageCredentials);
|
||||
}
|
||||
|
||||
private static void handleSuccessfulRegistration(@NonNull Context context) {
|
||||
@@ -185,12 +186,11 @@ public final class CodeVerificationRequest {
|
||||
@NonNull Credentials credentials,
|
||||
@NonNull String code,
|
||||
@Nullable String pin,
|
||||
@Nullable TokenResponse kbsTokenResponse,
|
||||
@Nullable String kbsStorageCredentials,
|
||||
@Nullable TokenData kbsTokenData,
|
||||
@Nullable String fcmToken)
|
||||
throws IOException, KeyBackupSystemWrongPinException, KeyBackupSystemNoDataException
|
||||
{
|
||||
boolean isV2RegistrationLock = kbsTokenResponse != null;
|
||||
boolean isV2RegistrationLock = kbsTokenData != null;
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
ProfileKey profileKey = findExistingProfileKey(context, credentials.getE164number());
|
||||
@@ -206,7 +206,7 @@ public final class CodeVerificationRequest {
|
||||
SessionUtil.archiveAllSessions(context);
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
|
||||
KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null;
|
||||
KbsPinData kbsData = isV2RegistrationLock ? PinState.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse()) : null;
|
||||
String registrationLockV2 = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
|
||||
String registrationLockV1 = isV2RegistrationLock ? null : pin;
|
||||
boolean hasFcm = fcmToken != null;
|
||||
@@ -292,14 +292,14 @@ public final class CodeVerificationRequest {
|
||||
/**
|
||||
* The account is locked with a V2 (KBS) pin. Called before any user pin guesses.
|
||||
*/
|
||||
void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials);
|
||||
void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenData kbsTokenData, @NonNull String kbsStorageCredentials);
|
||||
|
||||
/**
|
||||
* The account is locked with a V2 (KBS) pin. Called after a user pin guess.
|
||||
* <p>
|
||||
* i.e. an attempt has likely been used.
|
||||
*/
|
||||
void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse kbsTokenResponse);
|
||||
void onIncorrectKbsRegistrationLockPin(@NonNull TokenData kbsTokenResponse);
|
||||
|
||||
/**
|
||||
* V2 (KBS) pin is set, but there is no data on KBS.
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.Activity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,10 +40,9 @@ public final class RegistrationService {
|
||||
@Nullable String fcmToken,
|
||||
@NonNull String code,
|
||||
@Nullable String pin,
|
||||
@Nullable String basicStorageCredentials,
|
||||
@Nullable TokenResponse tokenResponse,
|
||||
@Nullable PinRestoreRepository.TokenData tokenData,
|
||||
@NonNull CodeVerificationRequest.VerifyCallback callback)
|
||||
{
|
||||
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, basicStorageCredentials, tokenResponse, callback);
|
||||
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, tokenData, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.lifecycle.SavedStateHandle;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.pin.PinRestoreRepository.TokenData;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
@@ -28,11 +29,10 @@ public final class RegistrationViewModel extends ViewModel {
|
||||
private final MutableLiveData<String> textCodeEntered;
|
||||
private final MutableLiveData<String> captchaToken;
|
||||
private final MutableLiveData<String> fcmToken;
|
||||
private final MutableLiveData<String> basicStorageCredentials;
|
||||
private final MutableLiveData<Boolean> restoreFlowShown;
|
||||
private final MutableLiveData<Integer> successfulCodeRequestAttempts;
|
||||
private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter;
|
||||
private final MutableLiveData<String> keyBackupCurrentTokenJson;
|
||||
private final MutableLiveData<TokenData> kbsTokenData;
|
||||
private final MutableLiveData<Long> lockedTimeRemaining;
|
||||
private final MutableLiveData<Long> canCallAtTime;
|
||||
|
||||
@@ -43,11 +43,10 @@ public final class RegistrationViewModel extends ViewModel {
|
||||
textCodeEntered = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", "");
|
||||
captchaToken = savedStateHandle.getLiveData("CAPTCHA");
|
||||
fcmToken = savedStateHandle.getLiveData("FCM_TOKEN");
|
||||
basicStorageCredentials = savedStateHandle.getLiveData("BASIC_STORAGE_CREDENTIALS");
|
||||
restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
|
||||
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
|
||||
requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
|
||||
keyBackupCurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN");
|
||||
kbsTokenData = savedStateHandle.getLiveData("KBS_TOKEN");
|
||||
lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
|
||||
canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L);
|
||||
}
|
||||
@@ -158,28 +157,12 @@ public final class RegistrationViewModel extends ViewModel {
|
||||
requestLimiter.setValue(requestLimiter.getValue());
|
||||
}
|
||||
|
||||
public void setStorageCredentials(@Nullable String storageCredentials) {
|
||||
basicStorageCredentials.setValue(storageCredentials);
|
||||
public @Nullable TokenData getKeyBackupCurrentToken() {
|
||||
return kbsTokenData.getValue();
|
||||
}
|
||||
|
||||
public @Nullable String getBasicStorageCredentials() {
|
||||
return basicStorageCredentials.getValue();
|
||||
}
|
||||
|
||||
public @Nullable TokenResponse getKeyBackupCurrentToken() {
|
||||
String json = keyBackupCurrentTokenJson.getValue();
|
||||
if (json == null) return null;
|
||||
try {
|
||||
return JsonUtil.fromJson(json, TokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setKeyBackupCurrentToken(TokenResponse tokenResponse) {
|
||||
String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse);
|
||||
keyBackupCurrentTokenJson.setValue(json);
|
||||
public void setKeyBackupTokenData(TokenData tokenData) {
|
||||
kbsTokenData.setValue(tokenData);
|
||||
}
|
||||
|
||||
public LiveData<Long> getLockedTimeRemaining() {
|
||||
|
||||
Reference in New Issue
Block a user