]> nmode's Git Repositories - signal-cli/commitdiff
Add fallback KBS and migrate to current version
authorAsamK <asamk@gmx.de>
Sun, 28 Aug 2022 13:35:02 +0000 (15:35 +0200)
committerAsamK <asamk@gmx.de>
Sun, 28 Aug 2022 13:35:15 +0000 (15:35 +0200)
lib/src/main/java/org/asamk/signal/manager/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/SignalDependencies.java
lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java
lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java
lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java
lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java
lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/Context.java
lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java

index 337aa348ae3811449d83b7d43843dcd4a7d7de37..4b7c836201525532c482e605bba11133a66de36d 100644 (file)
@@ -92,7 +92,15 @@ class RegistrationManagerImpl implements RegistrationManager {
                 serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
                 serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
                 10);
-        this.pinHelper = new PinHelper(keyBackupService);
+        final var fallbackKeyBackupServices = serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
+                .stream()
+                .map(config -> accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
+                        config.getEnclaveName(),
+                        config.getServiceId(),
+                        config.getMrenclave(),
+                        10))
+                .toList();
+        this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices);
     }
 
     @Override
index aeef7f15e9e190887dc70f52021209a6fb58b30c..b892e935940b779cbcfd5446ab53bcaeba0212db 100644 (file)
@@ -22,6 +22,7 @@ import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
 import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
 import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
 
+import java.util.Collection;
 import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Supplier;
@@ -183,6 +184,17 @@ public class SignalDependencies {
                         10));
     }
 
+    public Collection<KeyBackupService> getFallbackKeyBackupServices() {
+        return serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
+                .stream()
+                .map(config -> getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(),
+                        config.getEnclaveName(),
+                        config.getServiceId(),
+                        config.getMrenclave(),
+                        10))
+                .toList();
+    }
+
     public ProfileService getProfileService() {
         return getOrCreate(() -> profileService,
                 () -> profileService = new ProfileService(getClientZkProfileOperations(),
index 018a859953418e5cb9bff82d3609fc6f1a204718..3ee4a51e35d144602066215da4a34c732f885a12 100644 (file)
@@ -15,6 +15,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
 import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
 
 import java.util.Base64;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -33,6 +34,10 @@ class LiveConfig {
     private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
             "3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89");
     private final static String KEY_BACKUP_MRENCLAVE = "45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c";
+    private final static String FALLBACK_KEY_BACKUP_ENCLAVE_NAME = "0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f";
+    private final static byte[] FALLBACK_KEY_BACKUP_SERVICE_ID = Hex.decode(
+            "187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39");
+    private final static String FALLBACK_KEY_BACKUP_MRENCLAVE = "ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba";
 
     private final static String URL = "https://chat.signal.org";
     private final static String CDN_URL = "https://cdn.signal.org";
@@ -80,6 +85,12 @@ class LiveConfig {
         return new KeyBackupConfig(KEY_BACKUP_ENCLAVE_NAME, KEY_BACKUP_SERVICE_ID, KEY_BACKUP_MRENCLAVE);
     }
 
+    static Collection<KeyBackupConfig> createFallbackKeyBackupConfigs() {
+        return List.of(new KeyBackupConfig(FALLBACK_KEY_BACKUP_ENCLAVE_NAME,
+                FALLBACK_KEY_BACKUP_SERVICE_ID,
+                FALLBACK_KEY_BACKUP_MRENCLAVE));
+    }
+
     static String getCdsMrenclave() {
         return CDS_MRENCLAVE;
     }
index 91b07ce3c1e7e62b61d5d214cfcf8ab173dde2f0..c5db130dcfab9cda645020887f2f35249ec7f0ad 100644 (file)
@@ -79,11 +79,13 @@ public class ServiceConfig {
                     LiveConfig.createDefaultServiceConfiguration(interceptors),
                     LiveConfig.getUnidentifiedSenderTrustRoot(),
                     LiveConfig.createKeyBackupConfig(),
+                    LiveConfig.createFallbackKeyBackupConfigs(),
                     LiveConfig.getCdsMrenclave());
             case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
                     StagingConfig.createDefaultServiceConfiguration(interceptors),
                     StagingConfig.getUnidentifiedSenderTrustRoot(),
                     StagingConfig.createKeyBackupConfig(),
+                    StagingConfig.createFallbackKeyBackupConfigs(),
                     StagingConfig.getCdsMrenclave());
         };
     }
index b6a6af1d3b05d4ba54ad5b500168716212c4973e..c2013eac242fa078395e64aa297ea221b76e9d58 100644 (file)
@@ -3,6 +3,8 @@ package org.asamk.signal.manager.config;
 import org.signal.libsignal.protocol.ecc.ECPublicKey;
 import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
 
+import java.util.Collection;
+
 public class ServiceEnvironmentConfig {
 
     private final ServiceEnvironment type;
@@ -11,6 +13,7 @@ public class ServiceEnvironmentConfig {
     private final ECPublicKey unidentifiedSenderTrustRoot;
 
     private final KeyBackupConfig keyBackupConfig;
+    private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
 
     private final String cdsMrenclave;
 
@@ -19,12 +22,14 @@ public class ServiceEnvironmentConfig {
             final SignalServiceConfiguration signalServiceConfiguration,
             final ECPublicKey unidentifiedSenderTrustRoot,
             final KeyBackupConfig keyBackupConfig,
+            final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
             final String cdsMrenclave
     ) {
         this.type = type;
         this.signalServiceConfiguration = signalServiceConfiguration;
         this.unidentifiedSenderTrustRoot = unidentifiedSenderTrustRoot;
         this.keyBackupConfig = keyBackupConfig;
+        this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
         this.cdsMrenclave = cdsMrenclave;
     }
 
@@ -44,6 +49,10 @@ public class ServiceEnvironmentConfig {
         return keyBackupConfig;
     }
 
+    public Collection<KeyBackupConfig> getFallbackKeyBackupConfigs() {
+        return fallbackKeyBackupConfigs;
+    }
+
     public String getCdsMrenclave() {
         return cdsMrenclave;
     }
index b5a146dd0674ac054ae8b66de86187519c24718c..9d83eb0ccc8e59f39a10b1aee347051d528afbbc 100644 (file)
@@ -15,6 +15,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
 import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
 
 import java.util.Base64;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -33,6 +34,10 @@ class StagingConfig {
     private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
             "9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef");
     private final static String KEY_BACKUP_MRENCLAVE = "45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c";
+    private final static String FALLBACK_KEY_BACKUP_ENCLAVE_NAME = "dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99";
+    private final static byte[] FALLBACK_KEY_BACKUP_SERVICE_ID = Hex.decode(
+            "4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a");
+    private final static String FALLBACK_KEY_BACKUP_MRENCLAVE = "ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba";
 
     private final static String URL = "https://chat.staging.signal.org";
     private final static String CDN_URL = "https://cdn-staging.signal.org";
@@ -80,6 +85,12 @@ class StagingConfig {
         return new KeyBackupConfig(KEY_BACKUP_ENCLAVE_NAME, KEY_BACKUP_SERVICE_ID, KEY_BACKUP_MRENCLAVE);
     }
 
+    static Collection<KeyBackupConfig> createFallbackKeyBackupConfigs() {
+        return List.of(new KeyBackupConfig(FALLBACK_KEY_BACKUP_ENCLAVE_NAME,
+                FALLBACK_KEY_BACKUP_SERVICE_ID,
+                FALLBACK_KEY_BACKUP_MRENCLAVE));
+    }
+
     static String getCdsMrenclave() {
         return CDS_MRENCLAVE;
     }
index 78cffa23e670fa0728a31817c958482c4514d0a7..99a0834e32b98674ee782f78fd099f3e062f846d 100644 (file)
@@ -60,6 +60,7 @@ public class AccountHelper {
             }
         }
         try {
+            updateAccountAttributes();
             context.getPreKeyHelper().refreshPreKeysIfNecessary();
             if (account.getAci() == null || account.getPni() == null) {
                 checkWhoAmiI();
@@ -67,7 +68,11 @@ public class AccountHelper {
             if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
                 context.getSyncHelper().requestSyncPniIdentity();
             }
-            updateAccountAttributes();
+            if (account.getPreviousStorageVersion() < 4
+                    && account.isPrimaryDevice()
+                    && account.getRegistrationLockPin() != null) {
+                migrateRegistrationPin();
+            }
         } catch (AuthorizationFailedException e) {
             account.setRegistered(false);
             throw e;
@@ -171,6 +176,12 @@ public class AccountHelper {
         account.setMultiDevice(devices.size() > 1);
     }
 
+    public void migrateRegistrationPin() throws IOException {
+        var masterKey = account.getOrCreatePinMasterKey();
+
+        context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
+    }
+
     public void setRegistrationPin(String pin) throws IOException {
         var masterKey = account.getOrCreatePinMasterKey();
 
index 522143ee71182fed03425a1263e53ea45d49c7b3..7ff8bd6426daafe0c7b86a9f742054bf12c3f535 100644 (file)
@@ -114,7 +114,9 @@ public class Context {
     }
 
     PinHelper getPinHelper() {
-        return getOrCreate(() -> pinHelper, () -> pinHelper = new PinHelper(dependencies.getKeyBackupService()));
+        return getOrCreate(() -> pinHelper,
+                () -> pinHelper = new PinHelper(dependencies.getKeyBackupService(),
+                        dependencies.getFallbackKeyBackupServices()));
     }
 
     public PreKeyHelper getPreKeyHelper() {
index 4ebc4d05c5d2bd556520470121613515406abcc1..6b925c692233ac27c41a0b50e10d0b5f39153a90 100644 (file)
@@ -1,8 +1,11 @@
 package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.api.IncorrectPinException;
+import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.util.PinHashing;
 import org.signal.libsignal.protocol.InvalidKeyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.KbsPinData;
 import org.whispersystems.signalservice.api.KeyBackupService;
 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
@@ -13,13 +16,21 @@ import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
 import org.whispersystems.signalservice.internal.push.LockedException;
 
 import java.io.IOException;
+import java.util.Collection;
+import java.util.stream.Stream;
 
 public class PinHelper {
 
+    private final static Logger logger = LoggerFactory.getLogger(PinHelper.class);
+
     private final KeyBackupService keyBackupService;
+    private final Collection<KeyBackupService> fallbackKeyBackupServices;
 
-    public PinHelper(final KeyBackupService keyBackupService) {
+    public PinHelper(
+            final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
+    ) {
         this.keyBackupService = keyBackupService;
+        this.fallbackKeyBackupServices = fallbackKeyBackupServices;
     }
 
     public void setRegistrationLockPin(
@@ -36,6 +47,19 @@ public class PinHelper {
         pinChangeSession.enableRegistrationLock(masterKey);
     }
 
+    public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
+        setRegistrationLockPin(pin, masterKey);
+
+        for (final var keyBackupService : fallbackKeyBackupServices) {
+            try {
+                final var pinChangeSession = keyBackupService.newPinChangeSession();
+                pinChangeSession.removePin();
+            } catch (Exception e) {
+                logger.warn("Failed to remove PIN from fallback KBS: {}", e.getMessage());
+            }
+        }
+    }
+
     public void removeRegistrationLockPin() throws IOException {
         final var pinChangeSession = keyBackupService.newPinChangeSession();
         pinChangeSession.disableRegistrationLock();
@@ -66,20 +90,34 @@ public class PinHelper {
     private KbsPinData getRegistrationLockData(
             String pin, String basicStorageCredentials
     ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
-        var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
-        if (tokenResponse == null || tokenResponse.getTries() == 0) {
-            throw new IOException("KBS Account locked, maximum pin attempts reached.");
-        }
+        var tokenResponsePair = getTokenResponse(basicStorageCredentials);
+        final var tokenResponse = tokenResponsePair.first();
+        final var keyBackupService = tokenResponsePair.second();
 
-        var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse);
+        var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService);
         if (registrationLockData == null) {
             throw new AssertionError("Failed to restore master key");
         }
         return registrationLockData;
     }
 
+    private Pair<TokenResponse, KeyBackupService> getTokenResponse(String basicStorageCredentials) throws IOException {
+        final var keyBackupServices = Stream.concat(Stream.of(keyBackupService), fallbackKeyBackupServices.stream())
+                .toList();
+        for (final var keyBackupService : keyBackupServices) {
+            var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
+            if (tokenResponse != null && tokenResponse.getTries() > 0) {
+                return new Pair<>(tokenResponse, keyBackupService);
+            }
+        }
+        throw new IOException("KBS Account locked, maximum pin attempts reached.");
+    }
+
     private KbsPinData restoreMasterKey(
-            String pin, String basicStorageCredentials, TokenResponse tokenResponse
+            String pin,
+            String basicStorageCredentials,
+            TokenResponse tokenResponse,
+            final KeyBackupService keyBackupService
     ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
         if (pin == null) return null;
 
index b3345153bff4864ce692ac3ab289d1e69ec5569e..a246c3a7178fc82de2ecefe138d1890af033bcc1 100644 (file)
@@ -93,7 +93,7 @@ public class SignalAccount implements Closeable {
     private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
 
     private static final int MINIMUM_STORAGE_VERSION = 1;
-    private static final int CURRENT_STORAGE_VERSION = 3;
+    private static final int CURRENT_STORAGE_VERSION = 4;
 
     private final Object LOCK = new Object();
 
@@ -997,6 +997,10 @@ public class SignalAccount implements Closeable {
         save();
     }
 
+    public int getPreviousStorageVersion() {
+        return previousStorageVersion;
+    }
+
     public SignalServiceDataStore getSignalServiceDataStore() {
         return new SignalServiceDataStore() {
             @Override
@@ -1277,6 +1281,10 @@ public class SignalAccount implements Closeable {
         save();
     }
 
+    public String getRegistrationLockPin() {
+        return registrationLockPin;
+    }
+
     public String getRegistrationLock() {
         final var masterKey = getPinBackedMasterKey();
         if (masterKey == null) {