]> nmode's Git Repositories - signal-cli/commitdiff
Refactor register and verify
authorAsamK <asamk@gmx.de>
Sun, 10 Jan 2021 16:07:06 +0000 (17:07 +0100)
committerAsamK <asamk@gmx.de>
Sun, 10 Jan 2021 22:06:13 +0000 (23:06 +0100)
33 files changed:
src/main/java/org/asamk/signal/Main.java
src/main/java/org/asamk/signal/commands/AddDeviceCommand.java
src/main/java/org/asamk/signal/commands/BlockCommand.java
src/main/java/org/asamk/signal/commands/DaemonCommand.java
src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java
src/main/java/org/asamk/signal/commands/JoinGroupCommand.java
src/main/java/org/asamk/signal/commands/ListContactsCommand.java
src/main/java/org/asamk/signal/commands/ListDevicesCommand.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java
src/main/java/org/asamk/signal/commands/QuitGroupCommand.java
src/main/java/org/asamk/signal/commands/ReceiveCommand.java
src/main/java/org/asamk/signal/commands/RegisterCommand.java
src/main/java/org/asamk/signal/commands/RegistrationCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java
src/main/java/org/asamk/signal/commands/RemovePinCommand.java
src/main/java/org/asamk/signal/commands/SendContactsCommand.java
src/main/java/org/asamk/signal/commands/SendReactionCommand.java
src/main/java/org/asamk/signal/commands/SetPinCommand.java
src/main/java/org/asamk/signal/commands/TrustCommand.java
src/main/java/org/asamk/signal/commands/UnblockCommand.java
src/main/java/org/asamk/signal/commands/UnregisterCommand.java
src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java
src/main/java/org/asamk/signal/commands/UpdateContactCommand.java
src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java
src/main/java/org/asamk/signal/commands/VerifyCommand.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/NotRegisteredException.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/ProvisioningManager.java
src/main/java/org/asamk/signal/manager/RegistrationManager.java [new file with mode: 0644]
src/main/java/org/asamk/signal/manager/ServiceConfig.java
src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java
src/main/java/org/asamk/signal/manager/storage/SignalAccount.java

index 6204778dd593469c43a6c69f9d8d831214e0056c..b920963184ed7c038f9fd280fd489aebbb449869 100644 (file)
@@ -32,9 +32,12 @@ import org.asamk.signal.commands.DbusCommand;
 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;
@@ -43,7 +46,6 @@ import org.freedesktop.dbus.connections.impl.DBusConnection;
 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;
 
@@ -75,8 +77,14 @@ public class Main {
     }
 
     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");
@@ -99,12 +107,31 @@ public class Main {
 
         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;
@@ -113,25 +140,19 @@ public class Main {
         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) {
@@ -144,7 +165,7 @@ public class Main {
                         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);
@@ -152,56 +173,51 @@ public class Main {
         }
     }
 
-    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;
     }
 
     /**
index dab886d79ee0cf88cba835c1620bb14ebb4c8a8a..c5d18ab196b29b5c92de54d1a539211277c2e4f2 100644 (file)
@@ -23,10 +23,6 @@ public class AddDeviceCommand implements LocalCommand {
 
     @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;
index 627be5a9f2f62d1275e6256a4726fa1249ed0baa..60009cfbbdd8a04b20ba1b088da2c276b06c521e 100644 (file)
@@ -21,11 +21,6 @@ public class BlockCommand implements LocalCommand {
 
     @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);
index 3caaaa378161fcce12038e6653ece32f5b9bf215..c5ee2edce8304a8975432437c38186d3eda531c1 100644 (file)
@@ -35,10 +35,6 @@ public class DaemonCommand implements LocalCommand {
 
     @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 {
index 882f4a5c0131e1214e75618d2141462b64240f76..0a1ddc4c33c6760306a39c35314dca180ea9d464 100644 (file)
@@ -28,11 +28,6 @@ public class GetUserStatusCommand implements LocalCommand {
 
     @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);
index 305bf55b85461b437969fc0be434cf00cb4e9f50..c5975b0cfe0b7d1a2851d778fa1a5efcfa42b06a 100644 (file)
@@ -29,11 +29,6 @@ public class JoinGroupCommand implements LocalCommand {
 
     @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 {
index 2c98ec6bb0efd18bf3df42263253bc09b2eebb1c..1a14e8df26da631063b453fa26efac99a8ba18b4 100644 (file)
@@ -16,10 +16,6 @@ public class ListContactsCommand implements LocalCommand {
 
     @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));
index 4b9dac5cec8a857956e6f2647ae255cc5c63940e..a03b078fa1189d0a4fc75c38558671a46070d6af 100644 (file)
@@ -18,10 +18,6 @@ public class ListDevicesCommand implements LocalCommand {
 
     @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) {
index b4be4ad074297a642b283d946e452fe8ea72efe7..97af502e8525beb952dd2292f5d56b66ef5e33c1 100644 (file)
@@ -64,11 +64,6 @@ public class ListGroupsCommand implements LocalCommand {
 
     @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");
 
index 3f422cbd0753109fef0a93adbde1cfcd25ec52e7..4caeca29ad81ac50a8f10f120d33d69fdb76fbc8 100644 (file)
@@ -30,10 +30,6 @@ public class ListIdentitiesCommand implements LocalCommand {
 
     @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);
index c1f6bfbfe047e1c91b368c95e68e357de378f8dc..f258ae248e4430760e159534f8e15e35e948a300 100644 (file)
@@ -31,11 +31,6 @@ public class QuitGroupCommand implements LocalCommand {
 
     @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);
index 84a918fbc021470eeca0a1560c600ddc643228b3..7dc9dcafb3fa3fd090e0f9321a533e79b5e2a782 100644 (file)
@@ -146,10 +146,6 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
 
     @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");
index f69e0844b8673650c91dd2398414c8731b49c022..da652f7d58bac5b11e08564a1871714c17b05f54 100644 (file)
@@ -4,12 +4,12 @@ import net.sourceforge.argparse4j.impl.Arguments;
 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) {
@@ -21,7 +21,7 @@ public class RegisterCommand implements LocalCommand {
     }
 
     @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");
diff --git a/src/main/java/org/asamk/signal/commands/RegistrationCommand.java b/src/main/java/org/asamk/signal/commands/RegistrationCommand.java
new file mode 100644 (file)
index 0000000..8683570
--- /dev/null
@@ -0,0 +1,10 @@
+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);
+}
index 1e2343e7ab938f388a3547d707b42bd58774980f..78d14bbdeb1e295b8f87a70d4506c90e52f63a6b 100644 (file)
@@ -19,10 +19,6 @@ public class RemoveDeviceCommand implements LocalCommand {
 
     @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);
index 95531249663eeaff55d1d4a8eff16f414eee3dec..ada9c446699ebc6787ef612de11d13cff56d8585 100644 (file)
@@ -17,10 +17,6 @@ public class RemovePinCommand implements LocalCommand {
 
     @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;
index 20e81a60fa839a7014651755fd677f689a2d67e9..aaca283ab344b541a91fe457d86573243e576437 100644 (file)
@@ -17,10 +17,6 @@ public class SendContactsCommand implements LocalCommand {
 
     @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;
index c680bfd7b1784433bc4647bfd962fca563d14dcc..2a9afa74d8debf54a0debe7112d6967d26c27572 100644 (file)
@@ -47,11 +47,6 @@ public class SendReactionCommand implements LocalCommand {
 
     @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.");
index c68ea3a989817279b29be217dabd5b88d8d436b7..ac601b3b11a7797c465cd4a7aebd51af7db2dfd8 100644 (file)
@@ -19,10 +19,6 @@ public class SetPinCommand implements LocalCommand {
 
     @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));
index 076a86db4f138d6ba353e39a36b36a208220a7ec..58c7371fd322be498544e49111308c85b1104bd4 100644 (file)
@@ -27,10 +27,6 @@ public class TrustCommand implements LocalCommand {
 
     @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);
index 14ea2996ba6df565f262e83ccf202e9be67e638f..d191ef223cd3e4620326dad056dc41c4bcbee0a4 100644 (file)
@@ -21,11 +21,6 @@ public class UnblockCommand implements LocalCommand {
 
     @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);
index 7a7616bd070df653e55bb22daac6785781cb3f8c..079070e6f8548ab84ff457cb3204aa911f7cca69 100644 (file)
@@ -16,10 +16,6 @@ public class UnregisterCommand implements LocalCommand {
 
     @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;
index 79459fe6d9a2b7c624728171e6451daabf0eb9a5..8211e190e5ee03bc364ba740de8c7ebd764843f9 100644 (file)
@@ -16,10 +16,6 @@ public class UpdateAccountCommand implements LocalCommand {
 
     @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;
index da090209c47f5090de8e95f001a11ebb2289dba2..c4da94a2c834f7130aac36c3a5d840c79646c509 100644 (file)
@@ -23,11 +23,6 @@ public class UpdateContactCommand implements LocalCommand {
 
     @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");
 
index 1e332fb411fffa736356980ebfbf005210d81f7e..968a8733385cb6b9079931c3d513551d5b5fa930 100644 (file)
@@ -25,11 +25,6 @@ public class UpdateProfileCommand implements LocalCommand {
 
     @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");
index d4b0a7cb3169f0bfc806c9a1fabafbfac9169f52..7fa10b6aaab8c768274845b1357588d24eb890df 100644 (file)
@@ -3,14 +3,14 @@ package org.asamk.signal.commands;
 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) {
@@ -19,11 +19,7 @@ public class VerifyCommand implements LocalCommand {
     }
 
     @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");
index 576dbb5a68a5b61ede1d391aaba91ee9820a6b7d..9d090a5873bbdc0576001a9be0865034fee5b82c 100644 (file)
@@ -79,14 +79,10 @@ import org.whispersystems.libsignal.ecc.ECKeyPair;
 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;
@@ -144,10 +140,8 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
 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;
@@ -167,7 +161,6 @@ import java.nio.charset.StandardCharsets;
 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;
@@ -176,7 +169,6 @@ import java.util.Date;
 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;
@@ -202,24 +194,21 @@ public class Manager implements Closeable {
     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,
@@ -233,7 +222,30 @@ public class Manager implements Closeable {
         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);
 
@@ -244,7 +256,7 @@ public class Manager implements Closeable {
         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,
@@ -261,30 +273,6 @@ public class Manager implements Closeable {
         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();
     }
@@ -313,56 +301,20 @@ public class Manager implements Closeable {
 
     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 {
@@ -401,25 +353,6 @@ public class Manager implements Closeable {
         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(),
@@ -427,10 +360,10 @@ public class Manager implements Closeable {
                 // 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 {
@@ -519,63 +452,6 @@ public class Manager implements Closeable {
         }
     }
 
-    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
@@ -607,45 +483,21 @@ public class Manager implements Closeable {
         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(),
@@ -2349,13 +2201,12 @@ public class Manager implements Closeable {
             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);
@@ -2384,11 +2235,10 @@ public class Manager implements Closeable {
             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)) {
@@ -2429,8 +2279,6 @@ public class Manager implements Closeable {
             }
         }
 
-        final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
-
         File tmpFile = IOUtils.createTempFile();
         try (InputStream input = messageReceiver.retrieveAttachment(pointer,
                 tmpFile,
@@ -2451,7 +2299,6 @@ public class Manager implements Closeable {
     private InputStream retrieveAttachmentAsStream(
             SignalServiceAttachmentPointer pointer, File tmpFile
     ) throws IOException, InvalidMessageException, MissingConfigurationException {
-        final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
         return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
     }
 
@@ -2737,6 +2584,10 @@ public class Manager implements Closeable {
 
     @Override
     public void close() throws IOException {
+        close(true);
+    }
+
+    void close(boolean closeAccount) throws IOException {
         if (messagePipe != null) {
             messagePipe.shutdown();
             messagePipe = null;
@@ -2747,7 +2598,10 @@ public class Manager implements Closeable {
             unidentifiedMessagePipe = null;
         }
 
-        account.close();
+        if (closeAccount && account != null) {
+            account.close();
+        }
+        account = null;
     }
 
     public interface ReceiveMessageHandler {
diff --git a/src/main/java/org/asamk/signal/manager/NotRegisteredException.java b/src/main/java/org/asamk/signal/manager/NotRegisteredException.java
new file mode 100644 (file)
index 0000000..c1b35a1
--- /dev/null
@@ -0,0 +1,8 @@
+package org.asamk.signal.manager;
+
+public class NotRegisteredException extends Exception {
+
+    public NotRegisteredException() {
+        super("User is not registered.");
+    }
+}
index 4195e8af8f4d05428c623e732937da2d04ea2186..475c90b807b8459aecb11020afb93f154c68468e 100644 (file)
@@ -124,8 +124,10 @@ public class ProvisioningManager {
                 m.requestSyncBlocked();
                 m.requestSyncConfiguration();
 
-                m.saveAccount();
+                m.close(false);
             }
+
+            account.save();
         }
 
         return username;
diff --git a/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/src/main/java/org/asamk/signal/manager/RegistrationManager.java
new file mode 100644 (file)
index 0000000..e740bb9
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+  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;
+        }
+    }
+}
index 55935ced840266dbf0e159561561d9f9336889d6..b6d4f4fd9b0be29637e80cfb41ccd9f895669de6 100644 (file)
@@ -6,6 +6,8 @@ import org.whispersystems.libsignal.InvalidKeyException;
 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;
@@ -109,6 +111,16 @@ public class ServiceConfig {
         }
     }
 
+    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);
index a994c40a8af373c8b2bf38dcbf41452bc89addcf..3930154cf0ba4c43d8527f6996c8448a09e89b21 100644 (file)
@@ -36,7 +36,7 @@ public class UnidentifiedAccessHelper {
         this.senderCertificateProvider = senderCertificateProvider;
     }
 
-    public byte[] getSelfUnidentifiedAccessKey() {
+    private byte[] getSelfUnidentifiedAccessKey() {
         return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
     }
 
index 1c35b1fb65b14fc50190fb304534f9d6cc1ced96..a030af3f1769701d922ce8da23bdeb6ba033aa3f 100644 (file)
@@ -26,6 +26,7 @@ import org.asamk.signal.manager.storage.stickers.StickerStore;
 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;
@@ -36,6 +37,7 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
 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;
@@ -98,6 +100,8 @@ public class SignalAccount implements Closeable {
         try {
             SignalAccount account = new SignalAccount(pair.first(), pair.second());
             account.load(dataPath);
+            account.migrateLegacyConfigs();
+
             return account;
         } catch (Throwable e) {
             pair.second().close();
@@ -169,6 +173,31 @@ public class SignalAccount implements Closeable {
         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);
     }
@@ -451,6 +480,10 @@ public class SignalAccount implements Closeable {
         return deviceId;
     }
 
+    public void setDeviceId(final int deviceId) {
+        this.deviceId = deviceId;
+    }
+
     public String getPassword() {
         return password;
     }
@@ -491,6 +524,10 @@ public class SignalAccount implements Closeable {
         this.profileKey = profileKey;
     }
 
+    public byte[] getSelfUnidentifiedAccessKey() {
+        return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
+    }
+
     public int getPreKeyIdOffset() {
         return preKeyIdOffset;
     }
@@ -515,8 +552,19 @@ public class SignalAccount implements Closeable {
         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();