import org.asamk.signal.commands.ExtendedDbusCommand;
import org.asamk.signal.commands.LocalCommand;
import org.asamk.signal.commands.ProvisioningCommand;
+import org.asamk.signal.commands.RegistrationCommand;
import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.NotRegisteredException;
import org.asamk.signal.manager.ProvisioningManager;
+import org.asamk.signal.manager.RegistrationManager;
import org.asamk.signal.manager.ServiceConfig;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.SecurityProvider;
import org.freedesktop.dbus.exceptions.DBusException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
}
public static int init(Namespace ns) {
+ Command command = getCommand(ns);
+ if (command == null) {
+ logger.error("Command not implemented!");
+ return 3;
+ }
+
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
- return initDbusClient(ns, ns.getBoolean("dbus_system"));
+ return initDbusClient(command, ns, ns.getBoolean("dbus_system"));
}
final String username = ns.getString("username");
if (username == null) {
ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
- return handleCommands(ns, pm);
+ return handleCommand(command, ns, pm);
+ }
+
+ if (command instanceof RegistrationCommand) {
+ final RegistrationManager manager;
+ try {
+ manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
+ } catch (Throwable e) {
+ logger.error("Error loading or creating state file: {}", e.getMessage());
+ return 2;
+ }
+ try (RegistrationManager m = manager) {
+ return handleCommand(command, ns, m);
+ } catch (Exception e) {
+ logger.error("Cleanup failed", e);
+ return 3;
+ }
}
Manager manager;
try {
manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
+ } catch (NotRegisteredException e) {
+ System.err.println("User is not registered.");
+ return 1;
} catch (Throwable e) {
logger.error("Error loading state file: {}", e.getMessage());
return 2;
try (Manager m = manager) {
try {
m.checkAccountState();
- } catch (AuthorizationFailedException e) {
- 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 (IOException e) {
logger.error("Error while checking account: {}", e.getMessage());
return 2;
}
- return handleCommands(ns, m);
+ return handleCommand(command, ns, m);
} catch (IOException e) {
logger.error("Cleanup failed", e);
return 3;
}
}
- private static int initDbusClient(final Namespace ns, final boolean systemBus) {
+ private static int initDbusClient(final Command command, final Namespace ns, final boolean systemBus) {
try {
DBusConnection.DBusBusType busType;
if (systemBus) {
DbusConfig.SIGNAL_OBJECTPATH,
Signal.class);
- return handleCommands(ns, ts, dBusConn);
+ return handleCommand(command, ns, ts, dBusConn);
}
} catch (DBusException | IOException e) {
logger.error("Dbus client failed", e);
}
}
- private static int handleCommands(Namespace ns, Signal ts, DBusConnection dBusConn) {
+ private static Command getCommand(Namespace ns) {
String commandKey = ns.getString("command");
final Map<String, Command> commands = Commands.getCommands();
- if (commands.containsKey(commandKey)) {
- Command command = commands.get(commandKey);
+ if (!commands.containsKey(commandKey)) {
+ return null;
+ }
+ return commands.get(commandKey);
+ }
- if (command instanceof ExtendedDbusCommand) {
- return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
- } else if (command instanceof DbusCommand) {
- return ((DbusCommand) command).handleCommand(ns, ts);
- } else {
- System.err.println(commandKey + " is not yet implemented via dbus");
- return 1;
- }
+ private static int handleCommand(Command command, Namespace ns, Signal ts, DBusConnection dBusConn) {
+ if (command instanceof ExtendedDbusCommand) {
+ return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
+ } else if (command instanceof DbusCommand) {
+ return ((DbusCommand) command).handleCommand(ns, ts);
+ } else {
+ System.err.println("Command is not yet implemented via dbus");
+ return 1;
}
- return 0;
}
- private static int handleCommands(Namespace ns, ProvisioningManager pm) {
- String commandKey = ns.getString("command");
- final Map<String, Command> commands = Commands.getCommands();
- if (commands.containsKey(commandKey)) {
- Command command = commands.get(commandKey);
+ private static int handleCommand(Command command, Namespace ns, ProvisioningManager pm) {
+ if (command instanceof ProvisioningCommand) {
+ return ((ProvisioningCommand) command).handleCommand(ns, pm);
+ } else {
+ System.err.println("Command only works with a username");
+ return 1;
+ }
+ }
- if (command instanceof ProvisioningCommand) {
- return ((ProvisioningCommand) command).handleCommand(ns, pm);
- } else {
- System.err.println(commandKey + " only works with a username");
- return 1;
- }
+ private static int handleCommand(Command command, Namespace ns, RegistrationManager m) {
+ if (command instanceof RegistrationCommand) {
+ return ((RegistrationCommand) command).handleCommand(ns, m);
}
- return 0;
+ return 1;
}
- private static int handleCommands(Namespace ns, Manager m) {
- String commandKey = ns.getString("command");
- final Map<String, Command> commands = Commands.getCommands();
- if (commands.containsKey(commandKey)) {
- Command command = commands.get(commandKey);
-
- if (command instanceof LocalCommand) {
- return ((LocalCommand) command).handleCommand(ns, m);
- } else if (command instanceof DbusCommand) {
- return ((DbusCommand) command).handleCommand(ns, new DbusSignalImpl(m));
- } else if (command instanceof ExtendedDbusCommand) {
- System.err.println(commandKey + " only works via dbus");
- }
+ private static int handleCommand(Command command, Namespace ns, Manager m) {
+ if (command instanceof LocalCommand) {
+ return ((LocalCommand) command).handleCommand(ns, m);
+ } else if (command instanceof DbusCommand) {
+ return ((DbusCommand) command).handleCommand(ns, new DbusSignalImpl(m));
+ } else {
+ System.err.println("Command only works via dbus");
return 1;
}
- return 0;
}
/**
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
m.addDeviceLink(new URI(ns.getString("uri")));
return 0;
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
for (String contact_number : ns.<String>getList("contact")) {
try {
m.setContactBlocked(contact_number, true);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
DBusConnection conn = null;
try {
try {
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
// Setup the json object mapper
ObjectMapper jsonProcessor = new ObjectMapper();
jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
final GroupInviteLinkUrl linkUrl;
String uri = ns.getString("uri");
try {
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
List<ContactInfo> contacts = m.getContacts();
for (ContactInfo c : contacts) {
System.out.println(String.format("Number: %s Name: %s Blocked: %b", c.number, c.name, c.blocked));
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
List<DeviceInfo> devices = m.getLinkedDevices();
for (DeviceInfo d : devices) {
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
List<GroupInfo> groups = m.getGroups();
boolean detailed = ns.getBoolean("detailed");
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
if (ns.get("number") == null) {
for (IdentityInfo identity : m.getIdentities()) {
printIdentityFingerprint(m, identity);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
try {
final GroupId groupId = Util.decodeGroupId(ns.getString("group"));
final Pair<Long, List<SendMessageResult>> results = m.sendQuitGroupMessage(groupId);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
double timeout = 5;
if (ns.getDouble("timeout") != null) {
timeout = ns.getDouble("timeout");
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
-import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.RegistrationManager;
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
import java.io.IOException;
-public class RegisterCommand implements LocalCommand {
+public class RegisterCommand implements RegistrationCommand {
@Override
public void attachToSubparser(final Subparser subparser) {
}
@Override
- public int handleCommand(final Namespace ns, final Manager m) {
+ public int handleCommand(final Namespace ns, final RegistrationManager m) {
try {
final boolean voiceVerification = ns.getBoolean("voice");
final String captcha = ns.getString("captcha");
--- /dev/null
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+
+import org.asamk.signal.manager.RegistrationManager;
+
+public interface RegistrationCommand extends Command {
+
+ int handleCommand(Namespace ns, RegistrationManager m);
+}
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
int deviceId = ns.getInt("deviceId");
m.removeLinkedDevices(deviceId);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
m.setRegistrationLockPin(Optional.absent());
return 0;
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
m.sendContacts();
return 0;
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
if ((ns.getList("recipient") == null || ns.getList("recipient").size() == 0) && ns.getString("group") == null) {
System.err.println("No recipients given");
System.err.println("Aborting sending.");
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
String registrationLockPin = ns.getString("registrationLockPin");
m.setRegistrationLockPin(Optional.of(registrationLockPin));
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
String number = ns.getString("number");
if (ns.getBoolean("trust_all_known_keys")) {
boolean res = m.trustIdentityAllKeys(number);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
for (String contact_number : ns.<String>getList("contact")) {
try {
m.setContactBlocked(contact_number, false);
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
m.unregister();
return 0;
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
try {
m.updateAccountAttributes();
return 0;
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
String number = ns.getString("number");
String name = ns.getString("name");
@Override
public int handleCommand(final Namespace ns, final Manager m) {
- if (!m.isRegistered()) {
- System.err.println("User is not registered.");
- return 1;
- }
-
String name = ns.getString("name");
String avatarPath = ns.getString("avatar");
boolean removeAvatar = ns.getBoolean("remove_avatar");
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
-import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.RegistrationManager;
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
import org.whispersystems.signalservice.internal.push.LockedException;
import java.io.IOException;
-public class VerifyCommand implements LocalCommand {
+public class VerifyCommand implements RegistrationCommand {
@Override
public void attachToSubparser(final Subparser subparser) {
}
@Override
- public int handleCommand(final Namespace ns, final Manager m) {
- if (m.isRegistered()) {
- System.err.println("User registration is already verified");
- return 1;
- }
+ public int handleCommand(final Namespace ns, final RegistrationManager m) {
try {
String verificationCode = ns.getString("verificationCode");
String pin = ns.getString("pin");
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.KeyHelper;
import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.Pair;
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.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
-import org.whispersystems.signalservice.internal.push.LockedException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
-import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.util.Base64;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
-import java.security.KeyStore;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
private final SignalServiceConfiguration serviceConfiguration;
private final String userAgent;
- // TODO make configurable
- private final boolean discoverableByPhoneNumber = true;
- private final boolean unrestrictedUnidentifiedAccess = false;
-
- private final SignalAccount account;
+ private SignalAccount account;
private final PathConfig pathConfig;
- private SignalServiceAccountManager accountManager;
- private GroupsV2Api groupsV2Api;
+ private final SignalServiceAccountManager accountManager;
+ private final GroupsV2Api groupsV2Api;
private final GroupsV2Operations groupsV2Operations;
+ private final SignalServiceMessageReceiver messageReceiver;
+ private final ClientZkProfileOperations clientZkProfileOperations;
- private SignalServiceMessageReceiver messageReceiver = null;
private SignalServiceMessagePipe messagePipe = null;
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
private final GroupHelper groupHelper;
- private PinHelper pinHelper;
+ private final PinHelper pinHelper;
Manager(
SignalAccount account,
this.userAgent = userAgent;
this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
serviceConfiguration)) : null;
- createSignalServiceAccountManager();
+ this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
+ new DynamicCredentialsProvider(account.getUuid(),
+ account.getUsername(),
+ account.getPassword(),
+ account.getSignalingKey(),
+ account.getDeviceId()),
+ userAgent,
+ groupsV2Operations,
+ timer);
+ this.groupsV2Api = accountManager.getGroupsV2Api();
+ final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager);
+ this.pinHelper = new PinHelper(keyBackupService);
+ this.clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(serviceConfiguration)
+ .getProfileOperations() : null;
+ this.messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration,
+ account.getUuid(),
+ account.getUsername(),
+ account.getPassword(),
+ account.getDeviceId(),
+ account.getSignalingKey(),
+ userAgent,
+ null,
+ timer,
+ clientZkProfileOperations);
this.account.setResolver(this::resolveSignalServiceAddress);
this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey,
unidentifiedAccessHelper::getAccessFor,
unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
- this::getOrCreateMessageReceiver);
+ () -> messageReceiver);
this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential,
this::getRecipientProfile,
account::getSelfAddress,
return account.getSelfAddress();
}
- private void createSignalServiceAccountManager() {
- this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
- new DynamicCredentialsProvider(account.getUuid(),
- account.getUsername(),
- account.getPassword(),
- null,
- account.getDeviceId()),
- userAgent,
- groupsV2Operations,
- timer);
- this.groupsV2Api = accountManager.getGroupsV2Api();
- this.pinHelper = new PinHelper(createKeyBackupService());
- }
-
- private KeyBackupService createKeyBackupService() {
- KeyStore keyStore = ServiceConfig.getIasKeyStore();
-
- return accountManager.getKeyBackupService(keyStore,
- ServiceConfig.KEY_BACKUP_ENCLAVE_NAME,
- ServiceConfig.KEY_BACKUP_SERVICE_ID,
- ServiceConfig.KEY_BACKUP_MRENCLAVE,
- 10);
- }
-
private IdentityKeyPair getIdentityKeyPair() {
return account.getSignalProtocolStore().getIdentityKeyPair();
}
public static Manager init(
String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
- ) throws IOException {
+ ) throws IOException, NotRegisteredException {
PathConfig pathConfig = PathConfig.createDefault(settingsPath);
if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
- IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair();
- 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);
+ throw new NotRegisteredException();
}
SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
- Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
-
- m.migrateLegacyConfigs();
-
- return m;
- }
-
- private void migrateLegacyConfigs() {
- if (account.getProfileKey() == null && isRegistered()) {
- // Old config file, creating new profile key
- account.setProfileKey(KeyUtils.createProfileKey());
- account.save();
- }
- // Store profile keys only in profile store
- for (ContactInfo contact : account.getContactStore().getContacts()) {
- String profileKeyString = contact.profileKey;
- if (profileKeyString == null) {
- continue;
- }
- final ProfileKey profileKey;
- try {
- profileKey = new ProfileKey(Base64.decode(profileKeyString));
- } catch (InvalidInputException | IOException e) {
- continue;
- }
- contact.profileKey = null;
- account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
+ if (!account.isRegistered()) {
+ throw new NotRegisteredException();
}
- // Ensure our profile key is stored in profile store
- account.getProfileStore().storeProfileKey(getSelfAddress(), account.getProfileKey());
+
+ return new Manager(account, pathConfig, serviceConfiguration, userAgent);
}
public void checkAccountState() throws IOException {
return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
}
- public void register(boolean voiceVerification, String captcha) throws IOException {
- account.setPassword(KeyUtils.createPassword());
-
- // Resetting UUID, because registering doesn't work otherwise
- account.setUuid(null);
- createSignalServiceAccountManager();
-
- if (voiceVerification) {
- accountManager.requestVoiceVerificationCode(Locale.getDefault(),
- Optional.fromNullable(captcha),
- Optional.absent());
- } else {
- accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent());
- }
-
- account.setRegistered(false);
- account.save();
- }
-
public void updateAccountAttributes() throws IOException {
accountManager.setAccountAttributes(account.getSignalingKey(),
account.getSignalProtocolStore().getLocalRegistrationId(),
// set legacy pin only if no KBS master key is set
account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null,
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
- unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
- unrestrictedUnidentifiedAccess,
+ account.getSelfUnidentifiedAccessKey(),
+ account.isUnrestrictedUnidentifiedAccess(),
capabilities,
- discoverableByPhoneNumber);
+ account.isDiscoverableByPhoneNumber());
}
public void setProfile(String name, File avatar) throws IOException {
}
}
- public void verifyAccount(
- String verificationCode, String pin
- ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
- verificationCode = verificationCode.replace("-", "");
- account.setSignalingKey(KeyUtils.createSignalingKey());
- VerifyAccountResponse response;
- try {
- response = verifyAccountWithCode(verificationCode, pin, null);
- } catch (LockedException e) {
- if (pin == null) {
- throw e;
- }
-
- KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e);
- if (registrationLockData == null) {
- throw e;
- }
-
- String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
- try {
- response = verifyAccountWithCode(verificationCode, null, registrationLock);
- } catch (LockedException _e) {
- throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
- }
- account.setPinMasterKey(registrationLockData.getMasterKey());
- }
-
- // TODO response.isStorageCapable()
- //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
-
- account.setRegistered(true);
- account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
- account.setRegistrationLockPin(pin);
- account.getSignalProtocolStore()
- .saveIdentity(account.getSelfAddress(),
- getIdentityKeyPair().getPublicKey(),
- TrustLevel.TRUSTED_VERIFIED);
-
- refreshPreKeys();
- account.save();
- }
-
- private VerifyAccountResponse verifyAccountWithCode(
- final String verificationCode, final String legacyPin, final String registrationLock
- ) throws IOException {
- return accountManager.verifyAccountWithCode(verificationCode,
- account.getSignalingKey(),
- account.getSignalProtocolStore().getLocalRegistrationId(),
- true,
- legacyPin,
- registrationLock,
- unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
- unrestrictedUnidentifiedAccess,
- capabilities,
- discoverableByPhoneNumber);
- }
-
public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
if (pin.isPresent()) {
final MasterKey masterKey = account.getPinMasterKey() != null
accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
}
- private SignalServiceMessageReceiver createMessageReceiver() {
- final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(
- serviceConfiguration).getProfileOperations() : null;
- return new SignalServiceMessageReceiver(serviceConfiguration,
- account.getUuid(),
- account.getUsername(),
- account.getPassword(),
- account.getDeviceId(),
- account.getSignalingKey(),
- userAgent,
- null,
- timer,
- clientZkProfileOperations);
- }
-
- private SignalServiceMessageReceiver getOrCreateMessageReceiver() {
- if (messageReceiver == null) {
- messageReceiver = createMessageReceiver();
- }
- return messageReceiver;
- }
-
private SignalServiceMessagePipe getOrCreateMessagePipe() {
if (messagePipe == null) {
- messagePipe = getOrCreateMessageReceiver().createMessagePipe();
+ messagePipe = messageReceiver.createMessagePipe();
}
return messagePipe;
}
private SignalServiceMessagePipe getOrCreateUnidentifiedMessagePipe() {
if (unidentifiedMessagePipe == null) {
- unidentifiedMessagePipe = getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
+ unidentifiedMessagePipe = messageReceiver.createUnidentifiedMessagePipe();
}
return unidentifiedMessagePipe;
}
private SignalServiceMessageSender createMessageSender() {
- final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(
- serviceConfiguration).getProfileOperations() : null;
final ExecutorService executor = null;
return new SignalServiceMessageSender(serviceConfiguration,
account.getUuid(),
GroupId groupId, GroupSecretParams groupSecretParams, String cdnKey
) throws IOException {
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
- SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver();
File outputFile = getGroupAvatarFile(groupId);
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
File tmpFile = IOUtils.createTempFile();
tmpFile.deleteOnExit();
- try (InputStream input = receiver.retrieveGroupsV2ProfileAvatar(cdnKey,
+ try (InputStream input = messageReceiver.retrieveGroupsV2ProfileAvatar(cdnKey,
tmpFile,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
byte[] encryptedData = IOUtils.readFully(input);
SignalServiceAddress address, String avatarPath, ProfileKey profileKey
) throws IOException {
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
- SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver();
File outputFile = getProfileAvatarFile(address);
File tmpFile = IOUtils.createTempFile();
- try (InputStream input = receiver.retrieveProfileAvatar(avatarPath,
+ try (InputStream input = messageReceiver.retrieveProfileAvatar(avatarPath,
tmpFile,
profileKey,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
}
}
- final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
-
File tmpFile = IOUtils.createTempFile();
try (InputStream input = messageReceiver.retrieveAttachment(pointer,
tmpFile,
private InputStream retrieveAttachmentAsStream(
SignalServiceAttachmentPointer pointer, File tmpFile
) throws IOException, InvalidMessageException, MissingConfigurationException {
- final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
}
@Override
public void close() throws IOException {
+ close(true);
+ }
+
+ void close(boolean closeAccount) throws IOException {
if (messagePipe != null) {
messagePipe.shutdown();
messagePipe = null;
unidentifiedMessagePipe = null;
}
- account.close();
+ if (closeAccount && account != null) {
+ account.close();
+ }
+ account = null;
}
public interface ReceiveMessageHandler {
--- /dev/null
+package org.asamk.signal.manager;
+
+public class NotRegisteredException extends Exception {
+
+ public NotRegisteredException() {
+ super("User is not registered.");
+ }
+}
m.requestSyncBlocked();
m.requestSyncConfiguration();
- m.saveAccount();
+ m.close(false);
}
+
+ account.save();
}
return username;
--- /dev/null
+/*
+ Copyright (C) 2015-2021 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.manager.helper.PinHelper;
+import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.util.KeyUtils;
+import org.signal.zkgroup.profiles.ProfileKey;
+import org.whispersystems.libsignal.IdentityKeyPair;
+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.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.push.LockedException;
+import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
+import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+
+public class RegistrationManager implements AutoCloseable {
+
+ private SignalAccount account;
+ private final PathConfig pathConfig;
+ private final SignalServiceConfiguration serviceConfiguration;
+ private final String userAgent;
+
+ private final SignalServiceAccountManager accountManager;
+ private final PinHelper pinHelper;
+
+ public RegistrationManager(
+ SignalAccount account,
+ PathConfig pathConfig,
+ SignalServiceConfiguration serviceConfiguration,
+ String userAgent
+ ) {
+ this.account = account;
+ this.pathConfig = pathConfig;
+ this.serviceConfiguration = serviceConfiguration;
+ this.userAgent = userAgent;
+
+ 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, timer);
+ final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager);
+ this.pinHelper = new PinHelper(keyBackupService);
+ }
+
+ public static RegistrationManager init(
+ String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
+ ) throws IOException {
+ PathConfig pathConfig = PathConfig.createDefault(settingsPath);
+
+ if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+ IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair();
+ int registrationId = KeyHelper.generateRegistrationId(false);
+
+ ProfileKey profileKey = KeyUtils.createProfileKey();
+ SignalAccount account = SignalAccount.create(pathConfig.getDataPath(),
+ username,
+ identityKey,
+ registrationId,
+ profileKey);
+ account.save();
+
+ return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
+ }
+
+ SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
+
+ return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
+ }
+
+ public void register(boolean voiceVerification, String captcha) throws IOException {
+ if (account.getPassword() == null) {
+ account.setPassword(KeyUtils.createPassword());
+ }
+
+ if (voiceVerification) {
+ accountManager.requestVoiceVerificationCode(Locale.getDefault(),
+ Optional.fromNullable(captcha),
+ Optional.absent());
+ } else {
+ accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent());
+ }
+
+ account.setRegistered(false);
+ account.save();
+ }
+
+ public void verifyAccount(
+ String verificationCode, String pin
+ ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
+ verificationCode = verificationCode.replace("-", "");
+ if (account.getSignalingKey() == null) {
+ account.setSignalingKey(KeyUtils.createSignalingKey());
+ }
+ VerifyAccountResponse response;
+ try {
+ response = verifyAccountWithCode(verificationCode, pin, null);
+ account.setPinMasterKey(null);
+ } catch (LockedException e) {
+ if (pin == null) {
+ throw e;
+ }
+
+ KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e);
+ if (registrationLockData == null) {
+ throw e;
+ }
+
+ String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
+ try {
+ response = verifyAccountWithCode(verificationCode, null, registrationLock);
+ } catch (LockedException _e) {
+ throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
+ }
+ account.setPinMasterKey(registrationLockData.getMasterKey());
+ }
+
+ // TODO response.isStorageCapable()
+ //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
+
+ 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)) {
+
+ m.refreshPreKeys();
+
+ m.close(false);
+ }
+
+ account.save();
+ }
+
+ private VerifyAccountResponse verifyAccountWithCode(
+ final String verificationCode, final String legacyPin, 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());
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (account != null) {
+ account.close();
+ account = null;
+ }
+ }
+}
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.KeyBackupService;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
}
}
+ static KeyBackupService createKeyBackupService(SignalServiceAccountManager accountManager) {
+ KeyStore keyStore = ServiceConfig.getIasKeyStore();
+
+ return accountManager.getKeyBackupService(keyStore,
+ ServiceConfig.KEY_BACKUP_ENCLAVE_NAME,
+ ServiceConfig.KEY_BACKUP_SERVICE_ID,
+ ServiceConfig.KEY_BACKUP_MRENCLAVE,
+ 10);
+ }
+
static ECPublicKey getUnidentifiedSenderTrustRoot() {
try {
return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0);
this.senderCertificateProvider = senderCertificateProvider;
}
- public byte[] getSelfUnidentifiedAccessKey() {
+ private byte[] getSelfUnidentifiedAccessKey() {
return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
}
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
import org.asamk.signal.manager.storage.threads.ThreadInfo;
import org.asamk.signal.manager.util.IOUtils;
+import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.Pair;
+import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.util.Base64;
try {
SignalAccount account = new SignalAccount(pair.first(), pair.second());
account.load(dataPath);
+ account.migrateLegacyConfigs();
+
return account;
} catch (Throwable e) {
pair.second().close();
return account;
}
+ public void migrateLegacyConfigs() {
+ if (getProfileKey() == null && isRegistered()) {
+ // Old config file, creating new profile key
+ setProfileKey(KeyUtils.createProfileKey());
+ save();
+ }
+ // Store profile keys only in profile store
+ for (ContactInfo contact : getContactStore().getContacts()) {
+ String profileKeyString = contact.profileKey;
+ if (profileKeyString == null) {
+ continue;
+ }
+ final ProfileKey profileKey;
+ try {
+ profileKey = new ProfileKey(Base64.decode(profileKeyString));
+ } catch (InvalidInputException | IOException e) {
+ continue;
+ }
+ contact.profileKey = null;
+ getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
+ }
+ // Ensure our profile key is stored in profile store
+ getProfileStore().storeProfileKey(getSelfAddress(), getProfileKey());
+ }
+
public static File getFileName(File dataPath, String username) {
return new File(dataPath, username);
}
return deviceId;
}
+ public void setDeviceId(final int deviceId) {
+ this.deviceId = deviceId;
+ }
+
public String getPassword() {
return password;
}
this.profileKey = profileKey;
}
+ public byte[] getSelfUnidentifiedAccessKey() {
+ return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
+ }
+
public int getPreKeyIdOffset() {
return preKeyIdOffset;
}
isMultiDevice = multiDevice;
}
+ public boolean isUnrestrictedUnidentifiedAccess() {
+ // TODO make configurable
+ return false;
+ }
+
+ public boolean isDiscoverableByPhoneNumber() {
+ // TODO make configurable
+ return true;
+ }
+
@Override
public void close() throws IOException {
+ save();
synchronized (fileChannel) {
try {
lock.close();