mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Refactor Manager to always have a valid SignalAccount instance
Extract ProvisioningManager to link new devices
This commit is contained in:
parent
8163a42d3a
commit
a02031aa80
8 changed files with 239 additions and 149 deletions
|
@ -31,7 +31,9 @@ import org.asamk.signal.commands.Commands;
|
||||||
import org.asamk.signal.commands.DbusCommand;
|
import org.asamk.signal.commands.DbusCommand;
|
||||||
import org.asamk.signal.commands.ExtendedDbusCommand;
|
import org.asamk.signal.commands.ExtendedDbusCommand;
|
||||||
import org.asamk.signal.commands.LocalCommand;
|
import org.asamk.signal.commands.LocalCommand;
|
||||||
|
import org.asamk.signal.commands.ProvisioningCommand;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.manager.ProvisioningManager;
|
||||||
import org.asamk.signal.manager.ServiceConfig;
|
import org.asamk.signal.manager.ServiceConfig;
|
||||||
import org.asamk.signal.util.IOUtils;
|
import org.asamk.signal.util.IOUtils;
|
||||||
import org.asamk.signal.util.SecurityProvider;
|
import org.asamk.signal.util.SecurityProvider;
|
||||||
|
@ -69,13 +71,13 @@ public class Main {
|
||||||
|
|
||||||
private static int handleCommands(Namespace ns) {
|
private static int handleCommands(Namespace ns) {
|
||||||
final String username = ns.getString("username");
|
final String username = ns.getString("username");
|
||||||
Manager m;
|
Manager m = null;
|
||||||
|
ProvisioningManager pm = null;
|
||||||
Signal ts;
|
Signal ts;
|
||||||
DBusConnection dBusConn = null;
|
DBusConnection dBusConn = null;
|
||||||
try {
|
try {
|
||||||
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
|
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
|
||||||
try {
|
try {
|
||||||
m = null;
|
|
||||||
DBusConnection.DBusBusType busType;
|
DBusConnection.DBusBusType busType;
|
||||||
if (ns.getBoolean("dbus_system")) {
|
if (ns.getBoolean("dbus_system")) {
|
||||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||||
|
@ -102,19 +104,23 @@ public class Main {
|
||||||
dataPath = getDefaultDataPath();
|
dataPath = getDefaultDataPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
m = new Manager(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
|
if (username == null) {
|
||||||
ts = m;
|
pm = new ProvisioningManager(dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
|
||||||
try {
|
ts = null;
|
||||||
m.init();
|
} else {
|
||||||
} catch (AuthorizationFailedException e) {
|
try {
|
||||||
if (!"register".equals(ns.getString("command"))) {
|
m = Manager.init(username, dataPath, ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT), BaseConfig.USER_AGENT);
|
||||||
// Register command should still be possible, if current authorization fails
|
} catch (AuthorizationFailedException e) {
|
||||||
System.err.println("Authorization failed, was the number registered elsewhere?");
|
if (!"register".equals(ns.getString("command"))) {
|
||||||
|
// Register command should still be possible, if current authorization fails
|
||||||
|
System.err.println("Authorization failed, was the number registered elsewhere?");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.err.println("Error loading state file: " + e.getMessage());
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
ts = m;
|
||||||
System.err.println("Error loading state file: " + e.getMessage());
|
|
||||||
return 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +141,8 @@ public class Main {
|
||||||
} else {
|
} else {
|
||||||
if (command instanceof LocalCommand) {
|
if (command instanceof LocalCommand) {
|
||||||
return ((LocalCommand) command).handleCommand(ns, m);
|
return ((LocalCommand) command).handleCommand(ns, m);
|
||||||
|
} else if (command instanceof ProvisioningCommand) {
|
||||||
|
return ((ProvisioningCommand) command).handleCommand(ns, pm);
|
||||||
} else if (command instanceof DbusCommand) {
|
} else if (command instanceof DbusCommand) {
|
||||||
return ((DbusCommand) command).handleCommand(ns, ts);
|
return ((DbusCommand) command).handleCommand(ns, ts);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
import org.asamk.signal.UserAlreadyExists;
|
import org.asamk.signal.UserAlreadyExists;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.ProvisioningManager;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -12,7 +12,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||||
|
|
||||||
public class LinkCommand implements LocalCommand {
|
public class LinkCommand implements ProvisioningCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachToSubparser(final Subparser subparser) {
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
|
@ -21,15 +21,15 @@ public class LinkCommand implements LocalCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleCommand(final Namespace ns, final Manager m) {
|
public int handleCommand(final Namespace ns, final ProvisioningManager m) {
|
||||||
String deviceName = ns.getString("name");
|
String deviceName = ns.getString("name");
|
||||||
if (deviceName == null) {
|
if (deviceName == null) {
|
||||||
deviceName = "cli";
|
deviceName = "cli";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
System.out.println(m.getDeviceLinkUri());
|
System.out.println(m.getDeviceLinkUri());
|
||||||
m.finishDeviceLink(deviceName);
|
String username = m.finishDeviceLink(deviceName);
|
||||||
System.out.println("Associated with: " + m.getUsername());
|
System.out.println("Associated with: " + username);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
System.err.println("Link request timed out, please try again.");
|
System.err.println("Link request timed out, please try again.");
|
||||||
return 3;
|
return 3;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.ProvisioningManager;
|
||||||
|
|
||||||
|
public interface ProvisioningCommand extends Command {
|
||||||
|
|
||||||
|
int handleCommand(Namespace ns, ProvisioningManager m);
|
||||||
|
}
|
|
@ -20,10 +20,6 @@ public class VerifyCommand implements LocalCommand {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int handleCommand(final Namespace ns, final Manager m) {
|
public int handleCommand(final Namespace ns, final Manager m) {
|
||||||
if (!m.userHasKeys()) {
|
|
||||||
System.err.println("User has no keys, first call register.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (m.isRegistered()) {
|
if (m.isRegistered()) {
|
||||||
System.err.println("User registration is already verified");
|
System.err.println("User registration is already verified");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -24,7 +24,6 @@ import org.asamk.signal.GroupNotFoundException;
|
||||||
import org.asamk.signal.NotAGroupMemberException;
|
import org.asamk.signal.NotAGroupMemberException;
|
||||||
import org.asamk.signal.StickerPackInvalidException;
|
import org.asamk.signal.StickerPackInvalidException;
|
||||||
import org.asamk.signal.TrustLevel;
|
import org.asamk.signal.TrustLevel;
|
||||||
import org.asamk.signal.UserAlreadyExists;
|
|
||||||
import org.asamk.signal.storage.SignalAccount;
|
import org.asamk.signal.storage.SignalAccount;
|
||||||
import org.asamk.signal.storage.contacts.ContactInfo;
|
import org.asamk.signal.storage.contacts.ContactInfo;
|
||||||
import org.asamk.signal.storage.groups.GroupInfo;
|
import org.asamk.signal.storage.groups.GroupInfo;
|
||||||
|
@ -149,44 +148,40 @@ import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class Manager implements Signal {
|
public class Manager implements Signal {
|
||||||
|
|
||||||
private final String settingsPath;
|
|
||||||
private final String dataPath;
|
|
||||||
private final String attachmentsPath;
|
|
||||||
private final String avatarsPath;
|
|
||||||
private final SleepTimer timer = new UptimeSleepTimer();
|
private final SleepTimer timer = new UptimeSleepTimer();
|
||||||
private final SignalServiceConfiguration serviceConfiguration;
|
private final SignalServiceConfiguration serviceConfiguration;
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
|
|
||||||
private SignalAccount account;
|
private final SignalAccount account;
|
||||||
private String username;
|
private final PathConfig pathConfig;
|
||||||
private SignalServiceAccountManager accountManager;
|
private SignalServiceAccountManager accountManager;
|
||||||
private SignalServiceMessagePipe messagePipe = null;
|
private SignalServiceMessagePipe messagePipe = null;
|
||||||
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
|
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
|
||||||
|
|
||||||
public Manager(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) {
|
public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) {
|
||||||
this.username = username;
|
this.account = account;
|
||||||
this.settingsPath = settingsPath;
|
this.pathConfig = pathConfig;
|
||||||
this.dataPath = this.settingsPath + "/data";
|
|
||||||
this.attachmentsPath = this.settingsPath + "/attachments";
|
|
||||||
this.avatarsPath = this.settingsPath + "/avatars";
|
|
||||||
this.serviceConfiguration = serviceConfiguration;
|
this.serviceConfiguration = serviceConfiguration;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
|
this.accountManager = createSignalServiceAccountManager();
|
||||||
|
|
||||||
|
this.account.setResolver(this::resolveSignalServiceAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return account.getUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceAddress getSelfAddress() {
|
public SignalServiceAddress getSelfAddress() {
|
||||||
return account.getSelfAddress();
|
return account.getSelfAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SignalServiceAccountManager getSignalServiceAccountManager() {
|
private SignalServiceAccountManager createSignalServiceAccountManager() {
|
||||||
return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
|
return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityKey getIdentity() {
|
private IdentityKeyPair getIdentityKeyPair() {
|
||||||
return account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
|
return account.getSignalProtocolStore().getIdentityKeyPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDeviceId() {
|
public int getDeviceId() {
|
||||||
|
@ -194,7 +189,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMessageCachePath() {
|
private String getMessageCachePath() {
|
||||||
return this.dataPath + "/" + username + ".d/msg-cache";
|
return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMessageCachePath(String sender) {
|
private String getMessageCachePath(String sender) {
|
||||||
|
@ -211,30 +206,28 @@ public class Manager implements Signal {
|
||||||
return new File(cachePath + "/" + now + "_" + timestamp);
|
return new File(cachePath + "/" + now + "_" + timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean userHasKeys() {
|
public static Manager init(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) throws IOException {
|
||||||
return account != null && account.getSignalProtocolStore() != null;
|
PathConfig pathConfig = PathConfig.createDefault(settingsPath);
|
||||||
}
|
|
||||||
|
|
||||||
public void init() throws IOException {
|
if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
|
||||||
if (!SignalAccount.userExists(dataPath, username)) {
|
IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
|
||||||
return;
|
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||||
|
|
||||||
|
ProfileKey profileKey = KeyUtils.createProfileKey();
|
||||||
|
SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), username, identityKey, registrationId, profileKey);
|
||||||
|
account.save();
|
||||||
|
|
||||||
|
return new Manager(account, pathConfig, serviceConfiguration, userAgent);
|
||||||
}
|
}
|
||||||
account = SignalAccount.load(dataPath, username);
|
|
||||||
account.setResolver(this::resolveSignalServiceAddress);
|
|
||||||
|
|
||||||
migrateLegacyConfigs();
|
SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
|
||||||
|
|
||||||
accountManager = getSignalServiceAccountManager();
|
Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
|
||||||
if (account.isRegistered()) {
|
|
||||||
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
|
m.migrateLegacyConfigs();
|
||||||
refreshPreKeys();
|
m.checkAccountState();
|
||||||
account.save();
|
|
||||||
}
|
return m;
|
||||||
if (account.getUuid() == null) {
|
|
||||||
account.setUuid(accountManager.getOwnUuid());
|
|
||||||
account.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateLegacyConfigs() {
|
private void migrateLegacyConfigs() {
|
||||||
|
@ -246,7 +239,7 @@ public class Manager implements Signal {
|
||||||
File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId()));
|
File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId()));
|
||||||
if (!avatarFile.exists() && attachmentFile.exists()) {
|
if (!avatarFile.exists() && attachmentFile.exists()) {
|
||||||
try {
|
try {
|
||||||
IOUtils.createPrivateDirectories(avatarsPath);
|
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
|
||||||
Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Ignore
|
// Ignore
|
||||||
|
@ -263,31 +256,29 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewIdentity() throws IOException {
|
private void checkAccountState() throws IOException {
|
||||||
IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
|
if (account.isRegistered()) {
|
||||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
|
||||||
if (username == null) {
|
refreshPreKeys();
|
||||||
account = SignalAccount.createTemporaryAccount(identityKey, registrationId);
|
account.save();
|
||||||
account.setResolver(this::resolveSignalServiceAddress);
|
}
|
||||||
} else {
|
if (account.getUuid() == null) {
|
||||||
ProfileKey profileKey = KeyUtils.createProfileKey();
|
account.setUuid(accountManager.getOwnUuid());
|
||||||
account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey);
|
account.save();
|
||||||
account.setResolver(this::resolveSignalServiceAddress);
|
}
|
||||||
account.save();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRegistered() {
|
public boolean isRegistered() {
|
||||||
return account != null && account.isRegistered();
|
return account.isRegistered();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register(boolean voiceVerification) throws IOException {
|
public void register(boolean voiceVerification) throws IOException {
|
||||||
if (account == null) {
|
|
||||||
createNewIdentity();
|
|
||||||
}
|
|
||||||
account.setPassword(KeyUtils.createPassword());
|
account.setPassword(KeyUtils.createPassword());
|
||||||
|
|
||||||
|
// Resetting UUID, because registering doesn't work otherwise
|
||||||
account.setUuid(null);
|
account.setUuid(null);
|
||||||
accountManager = getSignalServiceAccountManager();
|
accountManager = createSignalServiceAccountManager();
|
||||||
|
|
||||||
if (voiceVerification) {
|
if (voiceVerification) {
|
||||||
accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent());
|
accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent());
|
||||||
|
@ -327,52 +318,6 @@ public class Manager implements Signal {
|
||||||
account.save();
|
account.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceLinkUri() throws TimeoutException, IOException {
|
|
||||||
if (account == null) {
|
|
||||||
createNewIdentity();
|
|
||||||
}
|
|
||||||
account.setPassword(KeyUtils.createPassword());
|
|
||||||
accountManager = getSignalServiceAccountManager();
|
|
||||||
String uuid = accountManager.getNewDeviceUuid();
|
|
||||||
|
|
||||||
return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
|
|
||||||
account.setSignalingKey(KeyUtils.createSignalingKey());
|
|
||||||
SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(account.getSignalProtocolStore().getIdentityKeyPair(), account.getSignalingKey(), false, true, account.getSignalProtocolStore().getLocalRegistrationId(), deviceName);
|
|
||||||
|
|
||||||
username = ret.getNumber();
|
|
||||||
// TODO do this check before actually registering
|
|
||||||
if (SignalAccount.userExists(dataPath, username)) {
|
|
||||||
throw new UserAlreadyExists(username, SignalAccount.getFileName(dataPath, username));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new account with the synced identity
|
|
||||||
byte[] 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
|
|
||||||
account.setResolver(this::resolveSignalServiceAddress);
|
|
||||||
|
|
||||||
refreshPreKeys();
|
|
||||||
|
|
||||||
requestSyncGroups();
|
|
||||||
requestSyncContacts();
|
|
||||||
requestSyncBlocked();
|
|
||||||
requestSyncConfiguration();
|
|
||||||
|
|
||||||
account.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DeviceInfo> getLinkedDevices() throws IOException {
|
public List<DeviceInfo> getLinkedDevices() throws IOException {
|
||||||
List<DeviceInfo> devices = accountManager.getDevices();
|
List<DeviceInfo> devices = accountManager.getDevices();
|
||||||
account.setMultiDevice(devices.size() > 1);
|
account.setMultiDevice(devices.size() > 1);
|
||||||
|
@ -394,7 +339,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
|
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
|
||||||
IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
|
IdentityKeyPair identityKeyPair = getIdentityKeyPair();
|
||||||
String verificationCode = accountManager.getNewDeviceVerificationCode();
|
String verificationCode = accountManager.getNewDeviceVerificationCode();
|
||||||
|
|
||||||
accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.of(account.getProfileKey().serialize()), verificationCode);
|
accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.of(account.getProfileKey().serialize()), verificationCode);
|
||||||
|
@ -447,7 +392,7 @@ public class Manager implements Signal {
|
||||||
account.setRegistered(true);
|
account.setRegistered(true);
|
||||||
account.setUuid(uuid);
|
account.setUuid(uuid);
|
||||||
account.setRegistrationLockPin(pin);
|
account.setRegistrationLockPin(pin);
|
||||||
account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
|
account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
|
||||||
|
|
||||||
refreshPreKeys();
|
refreshPreKeys();
|
||||||
account.save();
|
account.save();
|
||||||
|
@ -464,12 +409,12 @@ public class Manager implements Signal {
|
||||||
account.save();
|
account.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshPreKeys() throws IOException {
|
void refreshPreKeys() throws IOException {
|
||||||
List<PreKeyRecord> oneTimePreKeys = generatePreKeys();
|
List<PreKeyRecord> oneTimePreKeys = generatePreKeys();
|
||||||
final IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
|
final IdentityKeyPair identityKeyPair = getIdentityKeyPair();
|
||||||
SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
|
SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
|
||||||
|
|
||||||
accountManager.setPreKeys(getIdentity(), signedPreKeyRecord, oneTimePreKeys);
|
accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SignalServiceMessageReceiver getMessageReceiver() {
|
private SignalServiceMessageReceiver getMessageReceiver() {
|
||||||
|
@ -629,7 +574,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avatarFile != null) {
|
if (avatarFile != null) {
|
||||||
IOUtils.createPrivateDirectories(avatarsPath);
|
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
|
||||||
File aFile = getGroupAvatarFile(g.groupId);
|
File aFile = getGroupAvatarFile(g.groupId);
|
||||||
Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
}
|
}
|
||||||
|
@ -984,7 +929,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestSyncGroups() throws IOException {
|
void requestSyncGroups() throws IOException {
|
||||||
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
|
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
|
||||||
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
||||||
try {
|
try {
|
||||||
|
@ -994,7 +939,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestSyncContacts() throws IOException {
|
void requestSyncContacts() throws IOException {
|
||||||
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build();
|
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build();
|
||||||
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
||||||
try {
|
try {
|
||||||
|
@ -1004,7 +949,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestSyncBlocked() throws IOException {
|
void requestSyncBlocked() throws IOException {
|
||||||
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED).build();
|
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED).build();
|
||||||
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
||||||
try {
|
try {
|
||||||
|
@ -1014,7 +959,7 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestSyncConfiguration() throws IOException {
|
void requestSyncConfiguration() throws IOException {
|
||||||
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION).build();
|
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION).build();
|
||||||
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
||||||
try {
|
try {
|
||||||
|
@ -1750,11 +1695,11 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getContactAvatarFile(String number) {
|
private File getContactAvatarFile(String number) {
|
||||||
return new File(avatarsPath, "contact-" + number);
|
return new File(pathConfig.getAvatarsPath(), "contact-" + number);
|
||||||
}
|
}
|
||||||
|
|
||||||
private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException {
|
private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||||
IOUtils.createPrivateDirectories(avatarsPath);
|
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
|
||||||
if (attachment.isPointer()) {
|
if (attachment.isPointer()) {
|
||||||
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||||
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
|
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
|
||||||
|
@ -1765,11 +1710,11 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getGroupAvatarFile(byte[] groupId) {
|
private File getGroupAvatarFile(byte[] groupId) {
|
||||||
return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
|
return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException {
|
private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||||
IOUtils.createPrivateDirectories(avatarsPath);
|
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
|
||||||
if (attachment.isPointer()) {
|
if (attachment.isPointer()) {
|
||||||
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||||
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
|
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
|
||||||
|
@ -1780,11 +1725,11 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
|
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
|
||||||
return new File(attachmentsPath, attachmentId.toString());
|
return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException {
|
private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||||
IOUtils.createPrivateDirectories(attachmentsPath);
|
IOUtils.createPrivateDirectories(pathConfig.getAttachmentsPath());
|
||||||
return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true);
|
return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2054,7 +1999,11 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
|
public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
|
||||||
return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey);
|
return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress, theirIdentityKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveAccount() {
|
||||||
|
account.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
|
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
|
||||||
|
|
34
src/main/java/org/asamk/signal/manager/PathConfig.java
Normal file
34
src/main/java/org/asamk/signal/manager/PathConfig.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
public class PathConfig {
|
||||||
|
|
||||||
|
private final String dataPath;
|
||||||
|
private final String attachmentsPath;
|
||||||
|
private final String avatarsPath;
|
||||||
|
|
||||||
|
public static PathConfig createDefault(final String settingsPath) {
|
||||||
|
return new PathConfig(
|
||||||
|
settingsPath + "/data",
|
||||||
|
settingsPath + "/attachments",
|
||||||
|
settingsPath + "/avatars"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PathConfig(final String dataPath, final String attachmentsPath, final String avatarsPath) {
|
||||||
|
this.dataPath = dataPath;
|
||||||
|
this.attachmentsPath = attachmentsPath;
|
||||||
|
this.avatarsPath = avatarsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataPath() {
|
||||||
|
return dataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttachmentsPath() {
|
||||||
|
return attachmentsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAvatarsPath() {
|
||||||
|
return avatarsPath;
|
||||||
|
}
|
||||||
|
}
|
102
src/main/java/org/asamk/signal/manager/ProvisioningManager.java
Normal file
102
src/main/java/org/asamk/signal/manager/ProvisioningManager.java
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2015-2020 AsamK and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import org.asamk.signal.UserAlreadyExists;
|
||||||
|
import org.asamk.signal.storage.SignalAccount;
|
||||||
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
|
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.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||||
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public class ProvisioningManager {
|
||||||
|
|
||||||
|
private final PathConfig pathConfig;
|
||||||
|
private final SignalServiceConfiguration serviceConfiguration;
|
||||||
|
private final String userAgent;
|
||||||
|
|
||||||
|
private final SignalServiceAccountManager accountManager;
|
||||||
|
private final IdentityKeyPair identityKey;
|
||||||
|
private final int registrationId;
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
public ProvisioningManager(String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) {
|
||||||
|
this.pathConfig = PathConfig.createDefault(settingsPath);
|
||||||
|
this.serviceConfiguration = serviceConfiguration;
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
|
||||||
|
identityKey = KeyHelper.generateIdentityKeyPair();
|
||||||
|
registrationId = KeyHelper.generateRegistrationId(false);
|
||||||
|
password = KeyUtils.createPassword();
|
||||||
|
final SleepTimer timer = new UptimeSleepTimer();
|
||||||
|
accountManager = new SignalServiceAccountManager(serviceConfiguration, null, null, password, SignalServiceAddress.DEFAULT_DEVICE_ID, userAgent, timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceLinkUri() throws TimeoutException, IOException {
|
||||||
|
String deviceUuid = accountManager.getNewDeviceUuid();
|
||||||
|
|
||||||
|
return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(deviceUuid, identityKey.getPublicKey().getPublicKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
|
||||||
|
String signalingKey = KeyUtils.createSignalingKey();
|
||||||
|
SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(identityKey, signalingKey, false, true, registrationId, deviceName);
|
||||||
|
|
||||||
|
String 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new account with the synced identity
|
||||||
|
byte[] 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SignalAccount account = SignalAccount.createLinkedAccount(pathConfig.getDataPath(), username, ret.getUuid(), password, ret.getDeviceId(), ret.getIdentity(), registrationId, signalingKey, profileKey);
|
||||||
|
account.save();
|
||||||
|
|
||||||
|
Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
|
||||||
|
|
||||||
|
m.refreshPreKeys();
|
||||||
|
|
||||||
|
m.requestSyncGroups();
|
||||||
|
m.requestSyncContacts();
|
||||||
|
m.requestSyncBlocked();
|
||||||
|
m.requestSyncConfiguration();
|
||||||
|
|
||||||
|
m.saveAccount();
|
||||||
|
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
}
|
|
@ -121,15 +121,6 @@ public class SignalAccount {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalAccount createTemporaryAccount(IdentityKeyPair identityKey, int registrationId) {
|
|
||||||
SignalAccount account = new SignalAccount();
|
|
||||||
|
|
||||||
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
|
|
||||||
account.registered = false;
|
|
||||||
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileName(String dataPath, String username) {
|
public static String getFileName(String dataPath, String username) {
|
||||||
return dataPath + "/" + username;
|
return dataPath + "/" + username;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue