X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/de273586b4106171a3940ce5ec2c2b3712430c8c..7f64a9812ca5bb10e8f57cacf3d22b904bd200b4:/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java diff --git a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java index 0ce43bb9..80c214f7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/ProvisioningManager.java @@ -20,24 +20,23 @@ 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.storage.SignalAccount; +import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.util.KeyUtils; -import org.signal.zkgroup.InvalidInputException; -import org.signal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.IdentityKeyPair; -import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.util.KeyHelper; 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.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.util.SleepTimer; -import org.whispersystems.signalservice.api.util.UptimeSleepTimer; +import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; +import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import java.io.File; import java.io.IOException; +import java.net.URI; import java.util.concurrent.TimeoutException; public class ProvisioningManager { @@ -49,7 +48,7 @@ public class ProvisioningManager { private final String userAgent; private final SignalServiceAccountManager accountManager; - private final IdentityKeyPair identityKey; + private final IdentityKeyPair tempIdentityKey; private final int registrationId; private final String password; @@ -58,10 +57,9 @@ public class ProvisioningManager { this.serviceEnvironmentConfig = serviceEnvironmentConfig; this.userAgent = userAgent; - identityKey = KeyUtils.generateIdentityKeyPair(); + tempIdentityKey = KeyUtils.generateIdentityKeyPair(); registrationId = KeyHelper.generateRegistrationId(false); password = KeyUtils.createPassword(); - final SleepTimer timer = new UptimeSleepTimer(); GroupsV2Operations groupsV2Operations; try { groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())); @@ -72,8 +70,7 @@ public class ProvisioningManager { new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID), userAgent, groupsV2Operations, - ServiceConfig.AUTOMATIC_NETWORK_RETRY, - timer); + ServiceConfig.AUTOMATIC_NETWORK_RETRY); } public static ProvisioningManager init( @@ -86,70 +83,107 @@ public class ProvisioningManager { return new ProvisioningManager(pathConfig, serviceConfiguration, userAgent); } - public String getDeviceLinkUri() throws TimeoutException, IOException { + public URI getDeviceLinkUri() throws TimeoutException, IOException { var deviceUuid = accountManager.getNewDeviceUuid(); - return new DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); + return new DeviceLinkInfo(deviceUuid, tempIdentityKey.getPublicKey().getPublicKey()).createDeviceLinkUri(); } - public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists { - var ret = accountManager.finishNewDeviceRegistration(identityKey, false, true, registrationId, deviceName); + public Manager finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExists { + var ret = accountManager.getNewDeviceRegistration(tempIdentityKey); + var number = ret.getNumber(); - var username = ret.getNumber(); - // TODO do this check before actually registering - if (SignalAccount.userExists(pathConfig.getDataPath(), username)) { - throw new UserAlreadyExists(username, SignalAccount.getFileName(pathConfig.getDataPath(), username)); - } + logger.info("Received link information from {}, linking in progress ...", number); - // Create new account with the synced identity - var profileKeyBytes = ret.getProfileKey(); - ProfileKey profileKey; - if (profileKeyBytes == null) { - profileKey = KeyUtils.createProfileKey(); - } else { - try { - profileKey = new ProfileKey(profileKeyBytes); - } catch (InvalidInputException e) { - throw new IOException("Received invalid profileKey", e); - } + if (SignalAccount.userExists(pathConfig.getDataPath(), number) && !canRelinkExistingAccount(number)) { + throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number)); } - try (var account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), - username, - ret.getUuid(), - password, - ret.getDeviceId(), - ret.getIdentity(), + var encryptedDeviceName = deviceName == null + ? null + : DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey()); + + var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(), + false, + true, registrationId, - profileKey)) { - account.save(); + encryptedDeviceName); - try (var m = new Manager(account, pathConfig, serviceEnvironmentConfig, userAgent)) { + // Create new account with the synced identity + var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey(); + + SignalAccount account = null; + try { + account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.getDataPath(), + number, + ret.getUuid(), + password, + encryptedDeviceName, + deviceId, + ret.getIdentity(), + registrationId, + profileKey, + TrustNewIdentity.ON_FIRST_USE); + + Manager m = null; + try { + m = new Manager(account, pathConfig, serviceEnvironmentConfig, userAgent); try { m.refreshPreKeys(); } catch (Exception e) { - logger.error("Failed to refresh prekeys."); + logger.error("Failed to check new account state."); throw e; } try { - m.requestSyncGroups(); - m.requestSyncContacts(); - m.requestSyncBlocked(); - m.requestSyncConfiguration(); - m.requestSyncKeys(); + m.requestAllSyncData(); } catch (Exception e) { logger.error("Failed to request sync messages from linked device."); throw e; } - m.close(false); + final var result = m; + account = null; + m = null; + + return result; + } finally { + if (m != null) { + m.close(); + } + } + } finally { + if (account != null) { + account.close(); } + } + } - account.save(); + private boolean canRelinkExistingAccount(final String number) throws IOException { + final SignalAccount signalAccount; + try { + signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false, TrustNewIdentity.ON_FIRST_USE); + } catch (IOException e) { + logger.debug("Account in use or failed to load.", e); + return false; } - return username; + try (signalAccount) { + if (signalAccount.isMasterDevice()) { + logger.debug("Account is a master device."); + return false; + } + + final var m = new Manager(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent); + try (m) { + m.checkAccountState(); + } catch (AuthorizationFailedException ignored) { + return true; + } + + logger.debug("Account is still successfully linked."); + return false; + } } }