X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/c72aeed8bba4d5ca873b36b4edb2b8eda9c24ec7..d13d150fe1c6b21dd53617cf7996d2876bc5db58:/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 506948ba..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,172 +16,270 @@ */ 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; 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.signal.zkgroup.profiles.ProfileKey; -import org.whispersystems.libsignal.IdentityKeyPair; +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.KeyBackupService; 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.SleepTimer; -import org.whispersystems.signalservice.api.util.UptimeSleepTimer; -import org.whispersystems.signalservice.api.util.UuidUtil; -import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; +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; 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 { + private final static Logger logger = LoggerFactory.getLogger(RegistrationManager.class); + private SignalAccount account; private final PathConfig pathConfig; - private final SignalServiceConfiguration serviceConfiguration; + private final ServiceEnvironmentConfig serviceEnvironmentConfig; private final String userAgent; + private final Consumer newManagerListener; private final SignalServiceAccountManager accountManager; private final PinHelper pinHelper; - public RegistrationManager( + private RegistrationManager( SignalAccount account, PathConfig pathConfig, - SignalServiceConfiguration serviceConfiguration, - String userAgent + ServiceEnvironmentConfig serviceEnvironmentConfig, + String userAgent, + Consumer newManagerListener ) { this.account = account; this.pathConfig = pathConfig; - this.serviceConfiguration = serviceConfiguration; + this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; + this.newManagerListener = newManagerListener; - final SleepTimer timer = new UptimeSleepTimer(); - this.accountManager = new SignalServiceAccountManager(serviceConfiguration, new DynamicCredentialsProvider( - // Using empty UUID, because registering doesn't work otherwise - null, - account.getUsername(), - account.getPassword(), - account.getSignalingKey(), - SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, null, ServiceConfig.AUTOMATIC_NETWORK_RETRY, timer); - final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager); + GroupsV2Operations groupsV2Operations; + try { + groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())); + } 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), + userAgent, + groupsV2Operations, + ServiceConfig.AUTOMATIC_NETWORK_RETRY); + final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(), + serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(), + serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(), + serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(), + 10); this.pinHelper = new PinHelper(keyBackupService); } public static RegistrationManager init( - String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent + String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent ) throws IOException { - PathConfig pathConfig = PathConfig.createDefault(settingsPath); + return init(number, settingsPath, serviceEnvironment, userAgent, null); + } - if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) { - IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair(); - int registrationId = KeyHelper.generateRegistrationId(false); + public static RegistrationManager init( + String number, + File settingsPath, + ServiceEnvironment serviceEnvironment, + String userAgent, + Consumer newManagerListener + ) throws IOException { + var pathConfig = PathConfig.createDefault(settingsPath); - ProfileKey profileKey = KeyUtils.createProfileKey(); - SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), - username, + final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent); + if (!SignalAccount.userExists(pathConfig.dataPath(), number)) { + var identityKey = KeyUtils.generateIdentityKeyPair(); + var registrationId = KeyHelper.generateRegistrationId(false); + + var profileKey = KeyUtils.createProfileKey(); + var account = SignalAccount.create(pathConfig.dataPath(), + number, identityKey, registrationId, - profileKey); - account.save(); + profileKey, + TrustNewIdentity.ON_FIRST_USE); - return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent); + return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent, newManagerListener); } - SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username); + 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 { - if (account.getPassword() == null) { - account.setPassword(KeyUtils.createPassword()); + 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) { - accountManager.requestVoiceVerificationCode(Locale.getDefault(), + response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(), Optional.fromNullable(captcha), + Optional.absent(), Optional.absent()); } else { - accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent()); + 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); } - - account.save(); } public void verifyAccount( String verificationCode, String pin - ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException { + ) throws IOException, PinLockedException, IncorrectPinException { verificationCode = verificationCode.replace("-", ""); - if (account.getSignalingKey() == null) { - account.setSignalingKey(KeyUtils.createSignalingKey()); - } VerifyAccountResponse response; + MasterKey masterKey; try { - response = verifyAccountWithCode(verificationCode, pin, null); - account.setPinMasterKey(null); + response = verifyAccountWithCode(verificationCode, null); + + masterKey = null; + pin = null; } catch (LockedException e) { if (pin == null) { - throw e; + throw new PinLockedException(e.getTimeRemaining()); } - KbsPinData 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; } - String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock(); + var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock(); try { - response = verifyAccountWithCode(verificationCode, null, registrationLock); + response = verifyAccountWithCode(verificationCode, registrationLock); } catch (LockedException _e) { throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!"); } - account.setPinMasterKey(registrationLockData.getMasterKey()); + masterKey = registrationLockData.getMasterKey(); } - // TODO response.isStorageCapable() //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID))); + account.finishRegistration(ACI.parseOrNull(response.getUuid()), masterKey, pin); - account.setDeviceId(SignalServiceAddress.DEFAULT_DEVICE_ID); - account.setMultiDevice(false); - account.setRegistered(true); - account.setUuid(UuidUtil.parseOrNull(response.getUuid())); - account.setRegistrationLockPin(pin); - account.getSignalProtocolStore() - .saveIdentity(account.getSelfAddress(), - account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), - TrustLevel.TRUSTED_VERIFIED); - - try (Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent)) { + ManagerImpl m = null; + try { + m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent); + 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()); + } - m.close(false); + if (newManagerListener != null) { + newManagerListener.accept(m); + m = null; + } + } finally { + if (m != null) { + m.close(); + } } - - account.save(); } private VerifyAccountResponse verifyAccountWithCode( - final String verificationCode, final String legacyPin, final String registrationLock + final String verificationCode, final String registrationLock ) throws IOException { - return accountManager.verifyAccountWithCode(verificationCode, - account.getSignalingKey(), - account.getSignalProtocolStore().getLocalRegistrationId(), - true, - legacyPin, - registrationLock, - account.getSelfUnidentifiedAccessKey(), - account.isUnrestrictedUnidentifiedAccess(), - ServiceConfig.capabilities, - account.isDiscoverableByPhoneNumber()); + final ServiceResponse response; + if (registrationLock == null) { + response = accountManager.verifyAccount(verificationCode, + account.getLocalRegistrationId(), + true, + account.getSelfUnidentifiedAccessKey(), + account.isUnrestrictedUnidentifiedAccess(), + ServiceConfig.capabilities, + account.isDiscoverableByPhoneNumber()); + } else { + response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode, + account.getLocalRegistrationId(), + true, + registrationLock, + account.getSelfUnidentifiedAccessKey(), + account.isUnrestrictedUnidentifiedAccess(), + ServiceConfig.capabilities, + account.isDiscoverableByPhoneNumber()); + } + handleResponseException(response); + return response.getResult().get(); } @Override @@ -191,4 +289,15 @@ public class RegistrationManager implements Closeable { 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()); + } + } + } }