mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
Extract number verification code logic
This commit is contained in:
parent
292ef0f2da
commit
7a06d3959e
3 changed files with 161 additions and 112 deletions
|
@ -24,22 +24,15 @@ import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||||
import org.asamk.signal.manager.helper.AccountFileUpdater;
|
import org.asamk.signal.manager.helper.AccountFileUpdater;
|
||||||
import org.asamk.signal.manager.helper.PinHelper;
|
import org.asamk.signal.manager.helper.PinHelper;
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
import org.asamk.signal.manager.util.Utils;
|
import org.asamk.signal.manager.util.NumberVerificationUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
|
||||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
|
||||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
|
||||||
import org.whispersystems.signalservice.api.push.ACI;
|
import org.whispersystems.signalservice.api.push.ACI;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
|
||||||
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
|
|
||||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||||
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
|
||||||
|
|
||||||
|
@ -100,98 +93,25 @@ class RegistrationManagerImpl implements RegistrationManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
|
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
|
||||||
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
if (account.getAci() != null && attemptReactivateAccount()) {
|
||||||
if (account.getAci() != null) {
|
return;
|
||||||
try {
|
|
||||||
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
|
||||||
new DynamicCredentialsProvider(account.getAci(),
|
|
||||||
account.getNumber(),
|
|
||||||
account.getPassword(),
|
|
||||||
account.getDeviceId()),
|
|
||||||
userAgent,
|
|
||||||
null,
|
|
||||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
|
||||||
accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
|
|
||||||
null,
|
|
||||||
account.getLocalRegistrationId(),
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
|
|
||||||
account.getSelfUnidentifiedAccessKey(),
|
|
||||||
account.isUnrestrictedUnidentifiedAccess(),
|
|
||||||
capabilities,
|
|
||||||
account.isDiscoverableByPhoneNumber());
|
|
||||||
account.setRegistered(true);
|
|
||||||
logger.info("Reactivated existing account, verify is not necessary.");
|
|
||||||
if (newManagerListener != null) {
|
|
||||||
final var m = new ManagerImpl(account,
|
|
||||||
pathConfig,
|
|
||||||
accountFileUpdater,
|
|
||||||
serviceEnvironmentConfig,
|
|
||||||
userAgent);
|
|
||||||
account = null;
|
|
||||||
newManagerListener.accept(m);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Failed to reactivate account");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final ServiceResponse<RequestVerificationCodeResponse> response;
|
|
||||||
if (voiceVerification) {
|
|
||||||
response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(null),
|
|
||||||
Optional.fromNullable(captcha),
|
|
||||||
Optional.absent(),
|
|
||||||
Optional.absent());
|
|
||||||
} else {
|
|
||||||
response = accountManager.requestSmsVerificationCode(false,
|
|
||||||
Optional.fromNullable(captcha),
|
|
||||||
Optional.absent(),
|
|
||||||
Optional.absent());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
handleResponseException(response);
|
|
||||||
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
|
||||||
throw new CaptchaRequiredException(e.getMessage(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verifyAccount(
|
public void verifyAccount(
|
||||||
String verificationCode, String pin
|
String verificationCode, String pin
|
||||||
) throws IOException, PinLockedException, IncorrectPinException {
|
) throws IOException, PinLockedException, IncorrectPinException {
|
||||||
verificationCode = verificationCode.replace("-", "");
|
final var result = NumberVerificationUtils.verifyNumber(verificationCode,
|
||||||
VerifyAccountResponse response;
|
pin,
|
||||||
MasterKey masterKey;
|
pinHelper,
|
||||||
try {
|
this::verifyAccountWithCode);
|
||||||
response = verifyAccountWithCode(verificationCode, null);
|
final var response = result.first();
|
||||||
|
final var masterKey = result.second();
|
||||||
masterKey = null;
|
if (masterKey == null) {
|
||||||
pin = null;
|
pin = null;
|
||||||
} catch (LockedException e) {
|
|
||||||
if (pin == null) {
|
|
||||||
throw new PinLockedException(e.getTimeRemaining());
|
|
||||||
}
|
|
||||||
|
|
||||||
KbsPinData registrationLockData;
|
|
||||||
try {
|
|
||||||
registrationLockData = pinHelper.getRegistrationLockData(pin, e);
|
|
||||||
} catch (KeyBackupSystemNoDataException ex) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} catch (KeyBackupServicePinException ex) {
|
|
||||||
throw new IncorrectPinException(ex.getTriesRemaining());
|
|
||||||
}
|
|
||||||
if (registrationLockData == null) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
|
||||||
try {
|
|
||||||
response = verifyAccountWithCode(verificationCode, registrationLock);
|
|
||||||
} catch (LockedException _e) {
|
|
||||||
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
|
||||||
}
|
|
||||||
masterKey = registrationLockData.getMasterKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
|
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
|
||||||
|
@ -226,12 +146,46 @@ class RegistrationManagerImpl implements RegistrationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VerifyAccountResponse verifyAccountWithCode(
|
private boolean attemptReactivateAccount() {
|
||||||
|
try {
|
||||||
|
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
|
account.getCredentialsProvider(),
|
||||||
|
userAgent,
|
||||||
|
null,
|
||||||
|
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||||
|
accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
|
||||||
|
null,
|
||||||
|
account.getLocalRegistrationId(),
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
|
||||||
|
account.getSelfUnidentifiedAccessKey(),
|
||||||
|
account.isUnrestrictedUnidentifiedAccess(),
|
||||||
|
capabilities,
|
||||||
|
account.isDiscoverableByPhoneNumber());
|
||||||
|
account.setRegistered(true);
|
||||||
|
logger.info("Reactivated existing account, verify is not necessary.");
|
||||||
|
if (newManagerListener != null) {
|
||||||
|
final var m = new ManagerImpl(account,
|
||||||
|
pathConfig,
|
||||||
|
accountFileUpdater,
|
||||||
|
serviceEnvironmentConfig,
|
||||||
|
userAgent);
|
||||||
|
account = null;
|
||||||
|
newManagerListener.accept(m);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Failed to reactivate account");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceResponse<VerifyAccountResponse> verifyAccountWithCode(
|
||||||
final String verificationCode, final String registrationLock
|
final String verificationCode, final String registrationLock
|
||||||
) throws IOException {
|
) {
|
||||||
final ServiceResponse<VerifyAccountResponse> response;
|
|
||||||
if (registrationLock == null) {
|
if (registrationLock == null) {
|
||||||
response = accountManager.verifyAccount(verificationCode,
|
return accountManager.verifyAccount(verificationCode,
|
||||||
account.getLocalRegistrationId(),
|
account.getLocalRegistrationId(),
|
||||||
true,
|
true,
|
||||||
account.getSelfUnidentifiedAccessKey(),
|
account.getSelfUnidentifiedAccessKey(),
|
||||||
|
@ -239,7 +193,7 @@ class RegistrationManagerImpl implements RegistrationManager {
|
||||||
ServiceConfig.capabilities,
|
ServiceConfig.capabilities,
|
||||||
account.isDiscoverableByPhoneNumber());
|
account.isDiscoverableByPhoneNumber());
|
||||||
} else {
|
} else {
|
||||||
response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
|
return accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
|
||||||
account.getLocalRegistrationId(),
|
account.getLocalRegistrationId(),
|
||||||
true,
|
true,
|
||||||
registrationLock,
|
registrationLock,
|
||||||
|
@ -248,8 +202,6 @@ class RegistrationManagerImpl implements RegistrationManager {
|
||||||
ServiceConfig.capabilities,
|
ServiceConfig.capabilities,
|
||||||
account.isDiscoverableByPhoneNumber());
|
account.isDiscoverableByPhoneNumber());
|
||||||
}
|
}
|
||||||
handleResponseException(response);
|
|
||||||
return response.getResult().get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -259,15 +211,4 @@ class RegistrationManagerImpl implements RegistrationManager {
|
||||||
account = null;
|
account = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleResponseException(final ServiceResponse<?> response) throws IOException {
|
|
||||||
final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
|
|
||||||
if (throwableOptional.isPresent()) {
|
|
||||||
if (throwableOptional.get() instanceof IOException) {
|
|
||||||
throw (IOException) throwableOptional.get();
|
|
||||||
} else {
|
|
||||||
throw new IOException(throwableOptional.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.asamk.signal.manager.helper;
|
package org.asamk.signal.manager.helper;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||||
import org.asamk.signal.manager.util.PinHashing;
|
import org.asamk.signal.manager.util.PinHashing;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
import org.whispersystems.signalservice.api.KbsPinData;
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
|
@ -47,13 +48,19 @@ public class PinHelper {
|
||||||
|
|
||||||
public KbsPinData getRegistrationLockData(
|
public KbsPinData getRegistrationLockData(
|
||||||
String pin, LockedException e
|
String pin, LockedException e
|
||||||
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
|
) throws IOException, IncorrectPinException {
|
||||||
var basicStorageCredentials = e.getBasicStorageCredentials();
|
var basicStorageCredentials = e.getBasicStorageCredentials();
|
||||||
if (basicStorageCredentials == null) {
|
if (basicStorageCredentials == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getRegistrationLockData(pin, basicStorageCredentials);
|
try {
|
||||||
|
return getRegistrationLockData(pin, basicStorageCredentials);
|
||||||
|
} catch (KeyBackupSystemNoDataException ex) {
|
||||||
|
throw new IOException(e);
|
||||||
|
} catch (KeyBackupServicePinException ex) {
|
||||||
|
throw new IncorrectPinException(ex.getTriesRemaining());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private KbsPinData getRegistrationLockData(
|
private KbsPinData getRegistrationLockData(
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.asamk.signal.manager.util;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.CaptchaRequiredException;
|
||||||
|
import org.asamk.signal.manager.api.IncorrectPinException;
|
||||||
|
import org.asamk.signal.manager.api.Pair;
|
||||||
|
import org.asamk.signal.manager.api.PinLockedException;
|
||||||
|
import org.asamk.signal.manager.helper.PinHelper;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.KbsPinData;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||||
|
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||||
|
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
|
||||||
|
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class NumberVerificationUtils {
|
||||||
|
|
||||||
|
public static void requestVerificationCode(
|
||||||
|
SignalServiceAccountManager accountManager, String captcha, boolean voiceVerification
|
||||||
|
) throws IOException, CaptchaRequiredException {
|
||||||
|
captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
|
||||||
|
|
||||||
|
final ServiceResponse<RequestVerificationCodeResponse> response;
|
||||||
|
if (voiceVerification) {
|
||||||
|
response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(null),
|
||||||
|
Optional.fromNullable(captcha),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent());
|
||||||
|
} else {
|
||||||
|
response = accountManager.requestSmsVerificationCode(false,
|
||||||
|
Optional.fromNullable(captcha),
|
||||||
|
Optional.absent(),
|
||||||
|
Optional.absent());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
handleResponseException(response);
|
||||||
|
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
|
||||||
|
throw new CaptchaRequiredException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pair<VerifyAccountResponse, MasterKey> verifyNumber(
|
||||||
|
String verificationCode, String pin, PinHelper pinHelper, Verifier verifier
|
||||||
|
) throws IOException, PinLockedException, IncorrectPinException {
|
||||||
|
verificationCode = verificationCode.replace("-", "");
|
||||||
|
try {
|
||||||
|
final var response = verifyAccountWithCode(verificationCode, null, verifier);
|
||||||
|
|
||||||
|
return new Pair<>(response, null);
|
||||||
|
} catch (LockedException e) {
|
||||||
|
if (pin == null) {
|
||||||
|
throw new PinLockedException(e.getTimeRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
KbsPinData registrationLockData;
|
||||||
|
registrationLockData = pinHelper.getRegistrationLockData(pin, e);
|
||||||
|
if (registrationLockData == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
||||||
|
VerifyAccountResponse response;
|
||||||
|
try {
|
||||||
|
response = verifyAccountWithCode(verificationCode, registrationLock, verifier);
|
||||||
|
} catch (LockedException _e) {
|
||||||
|
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pair<>(response, registrationLockData.getMasterKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VerifyAccountResponse verifyAccountWithCode(
|
||||||
|
final String verificationCode, final String registrationLock, final Verifier verifier
|
||||||
|
) throws IOException {
|
||||||
|
final var response = verifier.verify(verificationCode, registrationLock);
|
||||||
|
handleResponseException(response);
|
||||||
|
return response.getResult().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleResponseException(final ServiceResponse<?> response) throws IOException {
|
||||||
|
final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
|
||||||
|
if (throwableOptional.isPresent()) {
|
||||||
|
if (throwableOptional.get() instanceof IOException) {
|
||||||
|
throw (IOException) throwableOptional.get();
|
||||||
|
} else {
|
||||||
|
throw new IOException(throwableOptional.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Verifier {
|
||||||
|
|
||||||
|
ServiceResponse<VerifyAccountResponse> verify(
|
||||||
|
String verificationCode, String registrationLock
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue