Refactor Manager to always have a valid SignalAccount instance

Extract ProvisioningManager to link new devices
This commit is contained in:
AsamK 2020-05-11 18:07:37 +02:00
parent 8163a42d3a
commit a02031aa80
8 changed files with 239 additions and 149 deletions

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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 {

View 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;
}
}

View 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;
}
}

View file

@ -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;
} }