]> nmode's Git Repositories - signal-cli/commitdiff
Extract number verification code logic
authorAsamK <asamk@gmx.de>
Fri, 11 Feb 2022 15:25:35 +0000 (16:25 +0100)
committerAsamK <asamk@gmx.de>
Fri, 11 Feb 2022 20:03:54 +0000 (21:03 +0100)
lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java
lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java [new file with mode: 0644]

index 81602122158f4abe94ea7f8ae2a1ebfab6da7c81..97c0adf2e56ff520b2a26b2ae79a076a190e16d3 100644 (file)
@@ -24,22 +24,15 @@ import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.helper.AccountFileUpdater;
 import org.asamk.signal.manager.helper.PinHelper;
 import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.util.Utils;
+import org.asamk.signal.manager.util.NumberVerificationUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.api.KbsPinData;
-import org.whispersystems.signalservice.api.KeyBackupServicePinException;
-import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
 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.ACI;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.internal.ServiceResponse;
-import org.whispersystems.signalservice.internal.push.LockedException;
-import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
 
@@ -100,98 +93,25 @@ class RegistrationManagerImpl implements RegistrationManager {
 
     @Override
     public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
-        captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
-        if (account.getAci() != null) {
-            try {
-                final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
-                        new DynamicCredentialsProvider(account.getAci(),
-                                account.getNumber(),
-                                account.getPassword(),
-                                account.getDeviceId()),
-                        userAgent,
-                        null,
-                        ServiceConfig.AUTOMATIC_NETWORK_RETRY);
-                accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
-                        null,
-                        account.getLocalRegistrationId(),
-                        true,
-                        null,
-                        account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
-                        account.getSelfUnidentifiedAccessKey(),
-                        account.isUnrestrictedUnidentifiedAccess(),
-                        capabilities,
-                        account.isDiscoverableByPhoneNumber());
-                account.setRegistered(true);
-                logger.info("Reactivated existing account, verify is not necessary.");
-                if (newManagerListener != null) {
-                    final var m = new ManagerImpl(account,
-                            pathConfig,
-                            accountFileUpdater,
-                            serviceEnvironmentConfig,
-                            userAgent);
-                    account = null;
-                    newManagerListener.accept(m);
-                }
-                return;
-            } catch (IOException e) {
-                logger.debug("Failed to reactivate account");
-            }
-        }
-        final ServiceResponse<RequestVerificationCodeResponse> response;
-        if (voiceVerification) {
-            response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(null),
-                    Optional.fromNullable(captcha),
-                    Optional.absent(),
-                    Optional.absent());
-        } else {
-            response = accountManager.requestSmsVerificationCode(false,
-                    Optional.fromNullable(captcha),
-                    Optional.absent(),
-                    Optional.absent());
-        }
-        try {
-            handleResponseException(response);
-        } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
-            throw new CaptchaRequiredException(e.getMessage(), e);
+        if (account.getAci() != null && attemptReactivateAccount()) {
+            return;
         }
+
+        NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
     }
 
     @Override
     public void verifyAccount(
             String verificationCode, String pin
     ) throws IOException, PinLockedException, IncorrectPinException {
-        verificationCode = verificationCode.replace("-", "");
-        VerifyAccountResponse response;
-        MasterKey masterKey;
-        try {
-            response = verifyAccountWithCode(verificationCode, null);
-
-            masterKey = null;
+        final var result = NumberVerificationUtils.verifyNumber(verificationCode,
+                pin,
+                pinHelper,
+                this::verifyAccountWithCode);
+        final var response = result.first();
+        final var masterKey = result.second();
+        if (masterKey == null) {
             pin = null;
-        } catch (LockedException e) {
-            if (pin == null) {
-                throw new PinLockedException(e.getTimeRemaining());
-            }
-
-            KbsPinData registrationLockData;
-            try {
-                registrationLockData = pinHelper.getRegistrationLockData(pin, e);
-            } catch (KeyBackupSystemNoDataException ex) {
-                throw new IOException(e);
-            } catch (KeyBackupServicePinException ex) {
-                throw new IncorrectPinException(ex.getTriesRemaining());
-            }
-            if (registrationLockData == null) {
-                throw e;
-            }
-
-            var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
-            try {
-                response = verifyAccountWithCode(verificationCode, registrationLock);
-            } catch (LockedException _e) {
-                throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
-            }
-            masterKey = registrationLockData.getMasterKey();
         }
 
         //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
@@ -226,12 +146,46 @@ class RegistrationManagerImpl implements RegistrationManager {
         }
     }
 
-    private VerifyAccountResponse verifyAccountWithCode(
+    private boolean attemptReactivateAccount() {
+        try {
+            final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
+                    account.getCredentialsProvider(),
+                    userAgent,
+                    null,
+                    ServiceConfig.AUTOMATIC_NETWORK_RETRY);
+            accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
+                    null,
+                    account.getLocalRegistrationId(),
+                    true,
+                    null,
+                    account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
+                    account.getSelfUnidentifiedAccessKey(),
+                    account.isUnrestrictedUnidentifiedAccess(),
+                    capabilities,
+                    account.isDiscoverableByPhoneNumber());
+            account.setRegistered(true);
+            logger.info("Reactivated existing account, verify is not necessary.");
+            if (newManagerListener != null) {
+                final var m = new ManagerImpl(account,
+                        pathConfig,
+                        accountFileUpdater,
+                        serviceEnvironmentConfig,
+                        userAgent);
+                account = null;
+                newManagerListener.accept(m);
+            }
+            return true;
+        } catch (IOException e) {
+            logger.debug("Failed to reactivate account");
+        }
+        return false;
+    }
+
+    private ServiceResponse<VerifyAccountResponse> verifyAccountWithCode(
             final String verificationCode, final String registrationLock
-    ) throws IOException {
-        final ServiceResponse<VerifyAccountResponse> response;
+    ) {
         if (registrationLock == null) {
-            response = accountManager.verifyAccount(verificationCode,
+            return accountManager.verifyAccount(verificationCode,
                     account.getLocalRegistrationId(),
                     true,
                     account.getSelfUnidentifiedAccessKey(),
@@ -239,7 +193,7 @@ class RegistrationManagerImpl implements RegistrationManager {
                     ServiceConfig.capabilities,
                     account.isDiscoverableByPhoneNumber());
         } else {
-            response = accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
+            return accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
                     account.getLocalRegistrationId(),
                     true,
                     registrationLock,
@@ -248,8 +202,6 @@ class RegistrationManagerImpl implements RegistrationManager {
                     ServiceConfig.capabilities,
                     account.isDiscoverableByPhoneNumber());
         }
-        handleResponseException(response);
-        return response.getResult().get();
     }
 
     @Override
@@ -259,15 +211,4 @@ class RegistrationManagerImpl implements RegistrationManager {
             account = null;
         }
     }
-
-    private void handleResponseException(final ServiceResponse<?> response) throws IOException {
-        final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
-        if (throwableOptional.isPresent()) {
-            if (throwableOptional.get() instanceof IOException) {
-                throw (IOException) throwableOptional.get();
-            } else {
-                throw new IOException(throwableOptional.get());
-            }
-        }
-    }
 }
index 888b86ce603fd221f71c958e3eb684e4d731030f..accdbc0f8b9022fb0625951c04ec3a236d3a74fc 100644 (file)
@@ -1,5 +1,6 @@
 package org.asamk.signal.manager.helper;
 
+import org.asamk.signal.manager.api.IncorrectPinException;
 import org.asamk.signal.manager.util.PinHashing;
 import org.whispersystems.libsignal.InvalidKeyException;
 import org.whispersystems.signalservice.api.KbsPinData;
@@ -47,13 +48,19 @@ public class PinHelper {
 
     public KbsPinData getRegistrationLockData(
             String pin, LockedException e
-    ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
+    ) throws IOException, IncorrectPinException {
         var basicStorageCredentials = e.getBasicStorageCredentials();
         if (basicStorageCredentials == null) {
             return null;
         }
 
-        return getRegistrationLockData(pin, basicStorageCredentials);
+        try {
+            return getRegistrationLockData(pin, basicStorageCredentials);
+        } catch (KeyBackupSystemNoDataException ex) {
+            throw new IOException(e);
+        } catch (KeyBackupServicePinException ex) {
+            throw new IncorrectPinException(ex.getTriesRemaining());
+        }
     }
 
     private KbsPinData getRegistrationLockData(
diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java
new file mode 100644 (file)
index 0000000..1874bab
--- /dev/null
@@ -0,0 +1,101 @@
+package org.asamk.signal.manager.util;
+
+import org.asamk.signal.manager.api.CaptchaRequiredException;
+import org.asamk.signal.manager.api.IncorrectPinException;
+import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.PinLockedException;
+import org.asamk.signal.manager.helper.PinHelper;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.KbsPinData;
+import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.kbs.MasterKey;
+import org.whispersystems.signalservice.internal.ServiceResponse;
+import org.whispersystems.signalservice.internal.push.LockedException;
+import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
+import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
+
+import java.io.IOException;
+
+public class NumberVerificationUtils {
+
+    public static void requestVerificationCode(
+            SignalServiceAccountManager accountManager, String captcha, boolean voiceVerification
+    ) throws IOException, CaptchaRequiredException {
+        captcha = captcha == null ? null : captcha.replace("signalcaptcha://", "");
+
+        final ServiceResponse<RequestVerificationCodeResponse> response;
+        if (voiceVerification) {
+            response = accountManager.requestVoiceVerificationCode(Utils.getDefaultLocale(null),
+                    Optional.fromNullable(captcha),
+                    Optional.absent(),
+                    Optional.absent());
+        } else {
+            response = accountManager.requestSmsVerificationCode(false,
+                    Optional.fromNullable(captcha),
+                    Optional.absent(),
+                    Optional.absent());
+        }
+        try {
+            handleResponseException(response);
+        } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
+            throw new CaptchaRequiredException(e.getMessage(), e);
+        }
+    }
+
+    public static Pair<VerifyAccountResponse, MasterKey> verifyNumber(
+            String verificationCode, String pin, PinHelper pinHelper, Verifier verifier
+    ) throws IOException, PinLockedException, IncorrectPinException {
+        verificationCode = verificationCode.replace("-", "");
+        try {
+            final var response = verifyAccountWithCode(verificationCode, null, verifier);
+
+            return new Pair<>(response, null);
+        } catch (LockedException e) {
+            if (pin == null) {
+                throw new PinLockedException(e.getTimeRemaining());
+            }
+
+            KbsPinData registrationLockData;
+            registrationLockData = pinHelper.getRegistrationLockData(pin, e);
+            if (registrationLockData == null) {
+                throw e;
+            }
+
+            var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
+            VerifyAccountResponse response;
+            try {
+                response = verifyAccountWithCode(verificationCode, registrationLock, verifier);
+            } catch (LockedException _e) {
+                throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
+            }
+
+            return new Pair<>(response, registrationLockData.getMasterKey());
+        }
+    }
+
+    private static VerifyAccountResponse verifyAccountWithCode(
+            final String verificationCode, final String registrationLock, final Verifier verifier
+    ) throws IOException {
+        final var response = verifier.verify(verificationCode, registrationLock);
+        handleResponseException(response);
+        return response.getResult().get();
+    }
+
+    private static void handleResponseException(final ServiceResponse<?> response) throws IOException {
+        final var throwableOptional = response.getExecutionError().or(response.getApplicationError());
+        if (throwableOptional.isPresent()) {
+            if (throwableOptional.get() instanceof IOException) {
+                throw (IOException) throwableOptional.get();
+            } else {
+                throw new IOException(throwableOptional.get());
+            }
+        }
+    }
+
+    public interface Verifier {
+
+        ServiceResponse<VerifyAccountResponse> verify(
+                String verificationCode, String registrationLock
+        );
+    }
+}