X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/5cccf521032954d7ad8e3f70e3cbef2ce1293e85..b9eee539bdaa6eb27b9acd4e829e1eaeeb6e1c6c:/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java index c48a2e99..4b7c8362 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java @@ -18,27 +18,24 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; +import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; +import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.PinHelper; 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.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.groupsv2.ClientZkOperations; 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.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.util.DynamicCredentialsProvider; @@ -59,30 +56,34 @@ class RegistrationManagerImpl implements RegistrationManager { private final SignalServiceAccountManager accountManager; private final PinHelper pinHelper; + private final AccountFileUpdater accountFileUpdater; RegistrationManagerImpl( SignalAccount account, PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent, - Consumer newManagerListener + Consumer newManagerListener, + AccountFileUpdater accountFileUpdater ) { this.account = account; this.pathConfig = pathConfig; + this.accountFileUpdater = accountFileUpdater; this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; this.newManagerListener = newManagerListener; GroupsV2Operations groupsV2Operations; try { - groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())); + groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()), + ServiceConfig.GROUP_MAX_SIZE); } catch (Throwable ignored) { groupsV2Operations = null; } this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), new DynamicCredentialsProvider( // Using empty UUID, because registering doesn't work otherwise - null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), + null, null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, groupsV2Operations, ServiceConfig.AUTOMATIC_NETWORK_RETRY); @@ -91,107 +92,57 @@ class RegistrationManagerImpl implements RegistrationManager { serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(), serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(), 10); - this.pinHelper = new PinHelper(keyBackupService); + final var fallbackKeyBackupServices = serviceEnvironmentConfig.getFallbackKeyBackupConfigs() + .stream() + .map(config -> accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(), + config.getEnclaveName(), + config.getServiceId(), + config.getMrenclave(), + 10)) + .toList(); + this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices); } @Override - public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException { - captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); - if (account.getAci() != null) { - try { - final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), - new DynamicCredentialsProvider(account.getAci(), - account.getAccount(), - 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, serviceEnvironmentConfig, userAgent); - account = null; - newManagerListener.accept(m); - } - return; - } catch (IOException e) { - logger.debug("Failed to reactivate account"); - } + public void register( + boolean voiceVerification, String captcha + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { + if (account.isRegistered() + && account.getServiceEnvironment() != null + && account.getServiceEnvironment() != serviceEnvironmentConfig.getType()) { + throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment()); } - final ServiceResponse 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); + + if (account.getAci() != null && attemptReactivateAccount()) { + return; } + + NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification); } @Override public void verifyAccount( String verificationCode, String pin ) throws IOException, PinLockedException, IncorrectPinException { - verificationCode = verificationCode.replace("-", ""); - VerifyAccountResponse response; - MasterKey masterKey; - try { - response = verifyAccountWithCode(verificationCode, null); - - masterKey = null; + final var result = NumberVerificationUtils.verifyNumber(verificationCode, + pin, + pinHelper, + this::verifyAccountWithCode); + final var response = result.first(); + final var masterKey = result.second(); + if (masterKey == 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))); - account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin); + final var aci = ACI.parseOrNull(response.getUuid()); + final var pni = PNI.parseOrNull(response.getPni()); + account.finishRegistration(aci, pni, masterKey, pin); + accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci); ManagerImpl m = null; try { - m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); + m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent); account = null; m.refreshPreKeys(); @@ -200,7 +151,7 @@ class RegistrationManagerImpl implements RegistrationManager { } // Set an initial empty profile so user can be added to groups try { - m.setProfile(null, null, null, null, null); + m.updateProfile(UpdateProfile.newBuilder().build()); } catch (NoClassDefFoundError e) { logger.warn("Failed to set default profile: {}", e.getMessage()); } @@ -216,30 +167,77 @@ class RegistrationManagerImpl implements RegistrationManager { } } - private VerifyAccountResponse verifyAccountWithCode( + @Override + public void deleteLocalAccountData() throws IOException { + account.deleteAccountData(); + accountFileUpdater.removeAccount(); + account = null; + } + + @Override + public boolean isRegistered() { + return account.isRegistered(); + } + + private boolean attemptReactivateAccount() { + try { + final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), + account.getCredentialsProvider(), + userAgent, + null, + ServiceConfig.AUTOMATIC_NETWORK_RETRY); + accountManager.setAccountAttributes(null, + account.getLocalRegistrationId(), + true, + null, + account.getRegistrationLock(), + account.getSelfUnidentifiedAccessKey(), + account.isUnrestrictedUnidentifiedAccess(), + capabilities, + account.isDiscoverableByPhoneNumber(), + account.getEncryptedDeviceName(), + account.getLocalPniRegistrationId()); + 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 verifyAccountWithCode( final String verificationCode, final String registrationLock - ) throws IOException { - final ServiceResponse response; + ) { if (registrationLock == null) { - response = accountManager.verifyAccount(verificationCode, + return accountManager.verifyAccount(verificationCode, account.getLocalRegistrationId(), true, account.getSelfUnidentifiedAccessKey(), account.isUnrestrictedUnidentifiedAccess(), ServiceConfig.capabilities, - account.isDiscoverableByPhoneNumber()); + account.isDiscoverableByPhoneNumber(), + account.getLocalPniRegistrationId()); } else { - response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode, + return accountManager.verifyAccountWithRegistrationLockPin(verificationCode, account.getLocalRegistrationId(), true, registrationLock, account.getSelfUnidentifiedAccessKey(), account.isUnrestrictedUnidentifiedAccess(), ServiceConfig.capabilities, - account.isDiscoverableByPhoneNumber()); + account.isDiscoverableByPhoneNumber(), + account.getLocalPniRegistrationId()); } - handleResponseException(response); - return response.getResult().get(); } @Override @@ -249,15 +247,4 @@ class RegistrationManagerImpl implements RegistrationManager { 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()); - } - } - } }