]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java
Add more logging to register
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / internal / RegistrationManagerImpl.java
index ec200966080bd3ff8f2029d86dd53c65f64bb53d..0d20cc0bd5cfdd92c256525c6b87f31d5b2e27d3 100644 (file)
@@ -24,6 +24,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
 import org.asamk.signal.manager.api.PinLockedException;
 import org.asamk.signal.manager.api.RateLimitException;
 import org.asamk.signal.manager.api.UpdateProfile;
+import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
 import org.asamk.signal.manager.config.ServiceConfig;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.helper.AccountFileUpdater;
@@ -31,14 +32,12 @@ import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.util.KeyUtils;
 import org.asamk.signal.manager.util.NumberVerificationUtils;
-import org.asamk.signal.manager.util.Utils;
 import org.signal.libsignal.usernames.BaseUsernameException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 import org.whispersystems.signalservice.api.account.PreKeyCollection;
-import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
-import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
+import org.whispersystems.signalservice.api.kbs.MasterKey;
 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 import org.whispersystems.signalservice.api.push.ServiceIdType;
@@ -47,11 +46,13 @@ import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedExcep
 import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
 import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
-import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
 
 import java.io.IOException;
 import java.util.function.Consumer;
 
+import static org.asamk.signal.manager.util.KeyUtils.generatePreKeysForType;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
+
 public class RegistrationManagerImpl implements RegistrationManager {
 
     private static final Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
@@ -61,9 +62,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
     private final ServiceEnvironmentConfig serviceEnvironmentConfig;
     private final String userAgent;
     private final Consumer<Manager> newManagerListener;
-    private final GroupsV2Operations groupsV2Operations;
 
-    private final SignalServiceAccountManager accountManager;
+    private final SignalServiceAccountManager unauthenticatedAccountManager;
     private final PinHelper pinHelper;
     private final AccountFileUpdater accountFileUpdater;
 
@@ -82,26 +82,30 @@ public class RegistrationManagerImpl implements RegistrationManager {
         this.userAgent = userAgent;
         this.newManagerListener = newManagerListener;
 
-        groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()),
-                ServiceConfig.GROUP_MAX_SIZE);
-        this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.signalServiceConfiguration(),
-                new DynamicCredentialsProvider(
-                        // Using empty UUID, because registering doesn't work otherwise
-                        null, null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
+        this.unauthenticatedAccountManager = SignalServiceAccountManager.createWithStaticCredentials(
+                serviceEnvironmentConfig.signalServiceConfiguration(),
+                // Using empty UUID, because registering doesn't work otherwise
+                null,
+                null,
+                account.getNumber(),
+                SignalServiceAddress.DEFAULT_DEVICE_ID,
+                account.getPassword(),
                 userAgent,
-                groupsV2Operations,
-                ServiceConfig.AUTOMATIC_NETWORK_RETRY);
-        final var secureValueRecoveryV2 = serviceEnvironmentConfig.svr2Mrenclaves()
+                ServiceConfig.AUTOMATIC_NETWORK_RETRY,
+                ServiceConfig.GROUP_MAX_SIZE);
+        final var secureValueRecovery = serviceEnvironmentConfig.svr2Mrenclaves()
                 .stream()
-                .map(mr -> (SecureValueRecovery) accountManager.getSecureValueRecoveryV2(mr))
+                .map(mr -> (SecureValueRecovery) this.unauthenticatedAccountManager.getSecureValueRecoveryV2(mr))
                 .toList();
-        this.pinHelper = new PinHelper(secureValueRecoveryV2);
+        this.pinHelper = new PinHelper(secureValueRecovery);
     }
 
     @Override
     public void register(
-            boolean voiceVerification, String captcha
-    ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException {
+            boolean voiceVerification,
+            String captcha,
+            final boolean forceRegister
+    ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException {
         if (account.isRegistered()
                 && account.getServiceEnvironment() != null
                 && account.getServiceEnvironment() != serviceEnvironmentConfig.type()) {
@@ -109,16 +113,32 @@ public class RegistrationManagerImpl implements RegistrationManager {
         }
 
         try {
-            if (account.getAci() != null && attemptReactivateAccount()) {
+            if (!forceRegister) {
+                if (account.isRegistered()) {
+                    throw new IOException("Account is already registered");
+                }
+
+                if (account.getAci() != null && attemptReactivateAccount()) {
+                    return;
+                }
+            }
+
+            final var recoveryPassword = account.getRecoveryPassword();
+            if (recoveryPassword != null && account.isPrimaryDevice() && attemptReregisterAccount(recoveryPassword)) {
                 return;
             }
 
-            String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager,
+            final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+            logger.trace("Creating verification session");
+            String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
                     account.getSessionId(account.getNumber()),
                     id -> account.setSessionId(account.getNumber(), id),
                     voiceVerification,
                     captcha);
-            NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification);
+            logger.trace("Requesting verification code");
+            NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
+            logger.debug("Successfully requested verification code");
+            account.setRegistered(false);
         } catch (DeprecatedVersionException e) {
             logger.debug("Signal-Server returned deprecated version exception", e);
             throw e;
@@ -127,7 +147,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
 
     @Override
     public void verifyAccount(
-            String verificationCode, String pin
+            String verificationCode,
+            String pin
     ) throws IOException, PinLockedException, IncorrectPinException {
         if (account.isRegistered()) {
             throw new IOException("Account is already registered");
@@ -137,8 +158,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
             account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
         }
 
-        final var aciPreKeys = generatePreKeysForType(ServiceIdType.ACI);
-        final var pniPreKeys = generatePreKeysForType(ServiceIdType.PNI);
+        final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
+        final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
         final var result = NumberVerificationUtils.verifyNumber(account.getSessionId(account.getNumber()),
                 verificationCode,
                 pin,
@@ -154,43 +175,7 @@ public class RegistrationManagerImpl implements RegistrationManager {
             pin = null;
         }
 
-        //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
-        final var aci = ACI.parseOrThrow(response.getUuid());
-        final var pni = PNI.parseOrThrow(response.getPni());
-        account.finishRegistration(aci, pni, masterKey, pin, aciPreKeys, pniPreKeys);
-        accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
-
-        ManagerImpl m = null;
-        try {
-            m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent);
-            account = null;
-
-            m.refreshPreKeys();
-            if (response.isStorageCapable()) {
-                m.retrieveRemoteStorage();
-            }
-            // Set an initial empty profile so user can be added to groups
-            try {
-                m.updateProfile(UpdateProfile.newBuilder().build());
-            } catch (NoClassDefFoundError e) {
-                logger.warn("Failed to set default profile: {}", e.getMessage());
-            }
-
-            try {
-                m.refreshCurrentUsername();
-            } catch (IOException | BaseUsernameException e) {
-                logger.warn("Failed to refresh current username", e);
-            }
-
-            if (newManagerListener != null) {
-                newManagerListener.accept(m);
-                m = null;
-            }
-        } finally {
-            if (m != null) {
-                m.close();
-            }
-        }
+        finishAccountRegistration(response, pin, masterKey, aciPreKeys, pniPreKeys);
     }
 
     @Override
@@ -205,14 +190,45 @@ public class RegistrationManagerImpl implements RegistrationManager {
         return account.isRegistered();
     }
 
+    private boolean attemptReregisterAccount(final String recoveryPassword) {
+        try {
+            if (account.getPniIdentityKeyPair() == null) {
+                account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
+            }
+
+            final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI));
+            final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI));
+            final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
+            final var response = handleResponseException(registrationApi.registerAccount(null,
+                    recoveryPassword,
+                    account.getAccountAttributes(null),
+                    aciPreKeys,
+                    pniPreKeys,
+                    null,
+                    true));
+            finishAccountRegistration(response,
+                    account.getRegistrationLockPin(),
+                    account.getPinBackedMasterKey(),
+                    aciPreKeys,
+                    pniPreKeys);
+            logger.info("Reregistered existing account, verify is not necessary.");
+            return true;
+        } catch (IOException e) {
+            logger.debug("Failed to reregister account with recovery password", e);
+        }
+        return false;
+    }
+
     private boolean attemptReactivateAccount() {
         try {
-            final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.signalServiceConfiguration(),
-                    account.getCredentialsProvider(),
+            final var dependencies = new SignalDependencies(serviceEnvironmentConfig,
                     userAgent,
-                    groupsV2Operations,
-                    ServiceConfig.AUTOMATIC_NETWORK_RETRY);
-            accountManager.setAccountAttributes(account.getAccountAttributes(null));
+                    account.getCredentialsProvider(),
+                    account.getSignalServiceDataStore(),
+                    null,
+                    new ReentrantSignalSessionLock());
+            handleResponseException(dependencies.getAccountApi()
+                    .setAccountAttributes(account.getAccountAttributes(null)));
             account.setRegistered(true);
             logger.info("Reactivated existing account, verify is not necessary.");
             if (newManagerListener != null) {
@@ -238,12 +254,13 @@ public class RegistrationManagerImpl implements RegistrationManager {
             final PreKeyCollection aciPreKeys,
             final PreKeyCollection pniPreKeys
     ) throws IOException {
+        final var registrationApi = unauthenticatedAccountManager.getRegistrationApi();
         try {
-            Utils.handleResponseException(accountManager.verifyAccount(verificationCode, sessionId));
+            handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode));
         } catch (AlreadyVerifiedException e) {
             // Already verified so can continue registering
         }
-        return Utils.handleResponseException(accountManager.registerAccount(sessionId,
+        return handleResponseException(registrationApi.registerAccount(sessionId,
                 null,
                 account.getAccountAttributes(registrationLock),
                 aciPreKeys,
@@ -252,19 +269,50 @@ public class RegistrationManagerImpl implements RegistrationManager {
                 true));
     }
 
-    private PreKeyCollection generatePreKeysForType(ServiceIdType serviceIdType) {
-        final var accountData = account.getAccountData(serviceIdType);
-        final var keyPair = accountData.getIdentityKeyPair();
-        final var preKeyMetadata = accountData.getPreKeyMetadata();
+    private void finishAccountRegistration(
+            final VerifyAccountResponse response,
+            final String pin,
+            final MasterKey masterKey,
+            final PreKeyCollection aciPreKeys,
+            final PreKeyCollection pniPreKeys
+    ) throws IOException {
+        //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
+        final var aci = ACI.parseOrThrow(response.getUuid());
+        final var pni = PNI.parseOrThrow(response.getPni());
+        account.finishRegistration(aci, pni, masterKey, pin, aciPreKeys, pniPreKeys);
+        accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
 
-        final var nextSignedPreKeyId = preKeyMetadata.getNextSignedPreKeyId();
-        final var signedPreKey = KeyUtils.generateSignedPreKeyRecord(nextSignedPreKeyId, keyPair.getPrivateKey());
+        ManagerImpl m = null;
+        try {
+            m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent);
+            account = null;
 
-        final var privateKey = keyPair.getPrivateKey();
-        final var kyberPreKeyIdOffset = preKeyMetadata.getNextKyberPreKeyId();
-        final var lastResortKyberPreKey = KeyUtils.generateKyberPreKeyRecord(kyberPreKeyIdOffset, privateKey);
+            m.refreshPreKeys();
+            if (response.isStorageCapable()) {
+                m.syncRemoteStorage();
+            }
+            // Set an initial empty profile so user can be added to groups
+            try {
+                m.updateProfile(UpdateProfile.newBuilder().build());
+            } catch (NoClassDefFoundError e) {
+                logger.warn("Failed to set default profile: {}", e.getMessage());
+            }
+
+            try {
+                m.refreshCurrentUsername();
+            } catch (IOException | BaseUsernameException e) {
+                logger.warn("Failed to refresh current username", e);
+            }
 
-        return new PreKeyCollection(keyPair.getPublicKey(), signedPreKey, lastResortKyberPreKey);
+            if (newManagerListener != null) {
+                newManagerListener.accept(m);
+                m = null;
+            }
+        } finally {
+            if (m != null) {
+                m.close();
+            }
+        }
     }
 
     @Override