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;
private final String userAgent;
private final SignalServiceAccountManager accountManager;
- private final IdentityKeyPair identityKey;
+ private final IdentityKeyPair tempIdentityKey;
private final int registrationId;
private final String password;
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()));
new DynamicCredentialsProvider(null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID),
userAgent,
groupsV2Operations,
- ServiceConfig.AUTOMATIC_NETWORK_RETRY,
- timer);
+ ServiceConfig.AUTOMATIC_NETWORK_RETRY);
}
public static ProvisioningManager init(
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 Manager 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);
+
+ if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
+ throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
}
+ var encryptedDeviceName = deviceName == null
+ ? null
+ : DeviceNameUtil.encryptDeviceName(deviceName, ret.getIdentity().getPrivateKey());
+
+ logger.debug("Finishing new device registration");
+ var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(),
+ false,
+ true,
+ registrationId,
+ encryptedDeviceName);
+
// 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);
- }
- }
+ var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey();
SignalAccount account = null;
try {
- account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(),
- username,
+ account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
+ number,
ret.getUuid(),
password,
- ret.getDeviceId(),
+ encryptedDeviceName,
+ deviceId,
ret.getIdentity(),
registrationId,
- profileKey);
+ profileKey,
+ TrustNewIdentity.ON_FIRST_USE);
- Manager m = null;
+ ManagerImpl m = null;
try {
- m = new Manager(account, pathConfig, serviceEnvironmentConfig, userAgent);
+ m = new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
+ logger.debug("Refreshing pre keys");
try {
m.refreshPreKeys();
} catch (Exception e) {
- logger.error("Failed to refresh prekeys.");
- throw e;
+ logger.error("Failed to refresh pre keys.");
}
+ logger.debug("Requesting sync data");
try {
m.requestAllSyncData();
} catch (Exception e) {
- logger.error("Failed to request sync messages from linked device.");
- throw e;
+ logger.error(
+ "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
}
final var result = m;
}
}
}
+
+ private boolean canRelinkExistingAccount(final String number) throws IOException {
+ final SignalAccount signalAccount;
+ try {
+ signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
+ } catch (IOException e) {
+ logger.debug("Account in use or failed to load.", e);
+ return false;
+ }
+
+ try (signalAccount) {
+ if (signalAccount.isMasterDevice()) {
+ logger.debug("Account is a master device.");
+ return false;
+ }
+
+ final var m = new ManagerImpl(signalAccount, pathConfig, serviceEnvironmentConfig, userAgent);
+ try (m) {
+ m.checkAccountState();
+ } catch (AuthorizationFailedException ignored) {
+ return true;
+ }
+
+ logger.debug("Account is still successfully linked.");
+ return false;
+ }
+ }
}