X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/b91c162159c7c28d049ceb8889c419791573d3bb..a7ecb9e10fd45fde4a4707bae99ef0b9aad33283:/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 978f1fd5..24f0d5ba 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -16,6 +16,9 @@ */ 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.PinLockedException; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; @@ -23,18 +26,20 @@ import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.util.KeyUtils; +import org.asamk.signal.manager.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.util.KeyHelper; 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.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.ServiceResponse; import org.whispersystems.signalservice.internal.push.LockedException; import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse; @@ -44,7 +49,9 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.util.Locale; +import java.util.function.Consumer; + +import static org.asamk.signal.manager.config.ServiceConfig.capabilities; public class RegistrationManager implements Closeable { @@ -54,6 +61,7 @@ public class RegistrationManager implements Closeable { private final PathConfig pathConfig; private final ServiceEnvironmentConfig serviceEnvironmentConfig; private final String userAgent; + private final Consumer newManagerListener; private final SignalServiceAccountManager accountManager; private final PinHelper pinHelper; @@ -62,12 +70,14 @@ public class RegistrationManager implements Closeable { SignalAccount account, PathConfig pathConfig, ServiceEnvironmentConfig serviceEnvironmentConfig, - String userAgent + String userAgent, + Consumer newManagerListener ) { this.account = account; this.pathConfig = pathConfig; this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; + this.newManagerListener = newManagerListener; GroupsV2Operations groupsV2Operations; try { @@ -78,7 +88,7 @@ public class RegistrationManager implements Closeable { this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(), new DynamicCredentialsProvider( // Using empty UUID, because registering doesn't work otherwise - null, account.getUsername(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), + null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, groupsV2Operations, ServiceConfig.AUTOMATIC_NETWORK_RETRY); @@ -91,35 +101,78 @@ public class RegistrationManager implements Closeable { } public static RegistrationManager init( - String username, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent + String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent + ) throws IOException { + return init(number, settingsPath, serviceEnvironment, userAgent, null); + } + + public static RegistrationManager init( + String number, + File settingsPath, + ServiceEnvironment serviceEnvironment, + String userAgent, + Consumer newManagerListener ) throws IOException { var pathConfig = PathConfig.createDefault(settingsPath); final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); - if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) { + if (!SignalAccount.userExists(pathConfig.dataPath(), number)) { var identityKey = KeyUtils.generateIdentityKeyPair(); var registrationId = KeyHelper.generateRegistrationId(false); var profileKey = KeyUtils.createProfileKey(); - var account = SignalAccount.create(pathConfig.getDataPath(), - username, + var account = SignalAccount.create(pathConfig.dataPath(), + number, identityKey, registrationId, profileKey, TrustNewIdentity.ON_FIRST_USE); - return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent); + return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener); } - var account = SignalAccount.load(pathConfig.getDataPath(), username, true, TrustNewIdentity.ON_FIRST_USE); + var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE); - return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent); + return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener); } - public void register(boolean voiceVerification, String captcha) throws IOException { + 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"); + } + } final ServiceResponse response; if (voiceVerification) { - response = accountManager.requestVoiceVerificationCode(getDefaultLocale(), + response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(), Optional.fromNullable(captcha), Optional.absent(), Optional.absent()); @@ -129,24 +182,16 @@ public class RegistrationManager implements Closeable { Optional.absent(), Optional.absent()); } - handleResponseException(response); - } - - private Locale getDefaultLocale() { - final var locale = Locale.getDefault(); try { - Locale.LanguageRange.parse(locale.getLanguage() + "-" + locale.getCountry()); - } catch (IllegalArgumentException e) { - logger.debug("Invalid locale, ignoring: {}", locale); - return null; + handleResponseException(response); + } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) { + throw new CaptchaRequiredException(e.getMessage(), e); } - - return locale; } - public Manager verifyAccount( + public void verifyAccount( String verificationCode, String pin - ) throws IOException, LockedException, KeyBackupSystemNoDataException, KeyBackupServicePinException { + ) throws IOException, PinLockedException, IncorrectPinException { verificationCode = verificationCode.replace("-", ""); VerifyAccountResponse response; MasterKey masterKey; @@ -157,10 +202,17 @@ public class RegistrationManager implements Closeable { pin = null; } catch (LockedException e) { if (pin == null) { - throw e; + throw new PinLockedException(e.getTimeRemaining()); } - var registrationLockData = pinHelper.getRegistrationLockData(pin, e); + 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; } @@ -175,7 +227,7 @@ public class RegistrationManager implements Closeable { } //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); - account.finishRegistration(UuidUtil.parseOrNull(response.getUuid()), masterKey, pin); + account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin); ManagerImpl m = null; try { @@ -183,20 +235,20 @@ public class RegistrationManager implements Closeable { account = null; m.refreshPreKeys(); + if (response.isStorageCapable()) { + m.retrieveRemoteStorage(); + } // Set an initial empty profile so user can be added to groups try { m.setProfile(null, null, null, null, null); } catch (NoClassDefFoundError e) { logger.warn("Failed to set default profile: {}", e.getMessage()); } - if (response.isStorageCapable()) { - m.retrieveRemoteStorage(); - } - final var result = m; - m = null; - - return result; + if (newManagerListener != null) { + newManagerListener.accept(m); + m = null; + } } finally { if (m != null) { m.close();