]> nmode's Git Repositories - signal-cli/commitdiff
Add support for SVR2
authorAsamK <asamk@gmx.de>
Fri, 14 Jul 2023 18:26:24 +0000 (20:26 +0200)
committerAsamK <asamk@gmx.de>
Fri, 14 Jul 2023 18:28:34 +0000 (20:28 +0200)
graalvm-config-dir/reflect-config.json
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/Context.java
lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java
lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java

index 34d6aea12dfcb9a9e5d13649b574cad3ee59084b..2c1fe0f0d1c28ec56473884502902d0937507621 100644 (file)
 {
   "name":"kotlin.Boolean"
 },
+{
+  "name":"kotlin.BooleanArray"
+},
+{
+  "name":"kotlin.Byte"
+},
 {
   "name":"kotlin.ByteArray"
 },
+{
+  "name":"kotlin.Char"
+},
+{
+  "name":"kotlin.CharArray"
+},
+{
+  "name":"kotlin.Double"
+},
+{
+  "name":"kotlin.DoubleArray"
+},
+{
+  "name":"kotlin.Float"
+},
+{
+  "name":"kotlin.FloatArray"
+},
 {
   "name":"kotlin.Int"
 },
+{
+  "name":"kotlin.IntArray"
+},
 {
   "name":"kotlin.Long"
 },
+{
+  "name":"kotlin.LongArray"
+},
 {
   "name":"kotlin.Metadata",
   "queryAllDeclaredMethods":true,
   "name":"kotlin.SafePublicationLazyImpl",
   "fields":[{"name":"_value"}]
 },
+{
+  "name":"kotlin.Short"
+},
+{
+  "name":"kotlin.ShortArray"
+},
 {
   "name":"kotlin.String"
 },
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
   "allDeclaredConstructors":true,
+  "allDeclaredClasses":true,
   "methods":[{"name":"getData","parameterTypes":[] }, {"name":"getIv","parameterTypes":[] }, {"name":"getMac","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }]
 },
 {
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
   "allDeclaredConstructors":true,
+  "allDeclaredClasses":true,
   "methods":[{"name":"getClientPublic","parameterTypes":[] }]
 },
 {
index 80322b8ba713c80e54da871f0f24c1d3e3bc2ec5..285b7c9307abbd37b2a6cbf6d179953dc13307fb 100644 (file)
@@ -28,6 +28,7 @@ class LiveConfig {
     private final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder()
             .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF");
     private final static String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
+    private final static String SVR2_MRENCLAVE = "6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094";
 
     private final static String KEY_BACKUP_ENCLAVE_NAME = "e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5";
     private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
@@ -44,7 +45,7 @@ class LiveConfig {
     private final static String SIGNAL_KEY_BACKUP_URL = "https://api.backup.signal.org";
     private final static String STORAGE_URL = "https://storage.signal.org";
     private final static String SIGNAL_CDSI_URL = "https://cdsi.signal.org";
-    private final static String SIGNAL_SVR2_URL = "https://svr2.staging.signal.org";
+    private final static String SIGNAL_SVR2_URL = "https://svr2.signal.org";
     private final static TrustStore TRUST_STORE = new WhisperTrustStore();
 
     private final static Optional<Dns> dns = Optional.empty();
@@ -96,6 +97,10 @@ class LiveConfig {
         return CDSI_MRENCLAVE;
     }
 
+    static String getSvr2Mrenclave() {
+        return SVR2_MRENCLAVE;
+    }
+
     private LiveConfig() {
     }
 }
index f97e8f68c3da15a3b8b02513d28d775db4e6b1a6..5b6bce475ee03aeb54b550a75885481b5a81fdf6 100644 (file)
@@ -66,13 +66,15 @@ public class ServiceConfig {
                     LiveConfig.getUnidentifiedSenderTrustRoot(),
                     LiveConfig.createKeyBackupConfig(),
                     LiveConfig.createFallbackKeyBackupConfigs(),
-                    LiveConfig.getCdsiMrenclave());
+                    LiveConfig.getCdsiMrenclave(),
+                    LiveConfig.getSvr2Mrenclave());
             case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
                     StagingConfig.createDefaultServiceConfiguration(interceptors),
                     StagingConfig.getUnidentifiedSenderTrustRoot(),
                     StagingConfig.createKeyBackupConfig(),
                     StagingConfig.createFallbackKeyBackupConfigs(),
-                    StagingConfig.getCdsiMrenclave());
+                    StagingConfig.getCdsiMrenclave(),
+                    StagingConfig.getSvr2Mrenclave());
         };
     }
 }
index 2a95f34f1258635088a5a4c34cd74690f7448f36..64f175890626c97476bd41afb1bd58c59c57fd7f 100644 (file)
@@ -17,6 +17,7 @@ public class ServiceEnvironmentConfig {
     private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
 
     private final String cdsiMrenclave;
+    private final String svr2Mrenclave;
 
     public ServiceEnvironmentConfig(
             final ServiceEnvironment type,
@@ -24,7 +25,8 @@ public class ServiceEnvironmentConfig {
             final ECPublicKey unidentifiedSenderTrustRoot,
             final KeyBackupConfig keyBackupConfig,
             final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
-            final String cdsiMrenclave
+            final String cdsiMrenclave,
+            final String svr2Mrenclave
     ) {
         this.type = type;
         this.signalServiceConfiguration = signalServiceConfiguration;
@@ -32,6 +34,7 @@ public class ServiceEnvironmentConfig {
         this.keyBackupConfig = keyBackupConfig;
         this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
         this.cdsiMrenclave = cdsiMrenclave;
+        this.svr2Mrenclave = svr2Mrenclave;
     }
 
     public ServiceEnvironment getType() {
@@ -57,4 +60,8 @@ public class ServiceEnvironmentConfig {
     public String getCdsiMrenclave() {
         return cdsiMrenclave;
     }
+
+    public String getSvr2Mrenclave() {
+        return svr2Mrenclave;
+    }
 }
index 95a3dd96c4d1052fbfc9f485b31dd54a9c30bbc7..f573e5057f170d6b6eb3926c39abfaf839243c99 100644 (file)
@@ -28,6 +28,7 @@ class StagingConfig {
     private final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder()
             .decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx");
     private final static String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
+    private final static String SVR2_MRENCLAVE = "a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95";
 
     private final static String KEY_BACKUP_ENCLAVE_NAME = "39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b";
     private final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
@@ -96,6 +97,10 @@ class StagingConfig {
         return CDSI_MRENCLAVE;
     }
 
+    static String getSvr2Mrenclave() {
+        return SVR2_MRENCLAVE;
+    }
+
     private StagingConfig() {
     }
 }
index 2caeb0fb5dfafc096834527d34bd57fed2a680bb..a11ea470320b08f594e1a0fc663ddec9c8034e38 100644 (file)
@@ -116,7 +116,8 @@ public class Context {
     PinHelper getPinHelper() {
         return getOrCreate(() -> pinHelper,
                 () -> pinHelper = new PinHelper(dependencies.getKeyBackupService(),
-                        dependencies.getFallbackKeyBackupServices()));
+                        dependencies.getFallbackKeyBackupServices(),
+                        dependencies.getSecureValueRecoveryV2()));
     }
 
     public PreKeyHelper getPreKeyHelper() {
index c734323ff092c94a44a8313ff11956176bd96faa..2c56092eddb9640ae0ba50c304fcc934e8cc50fe 100644 (file)
@@ -1,37 +1,39 @@
 package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.api.IncorrectPinException;
-import org.asamk.signal.manager.api.Pair;
-import org.signal.libsignal.protocol.InvalidKeyException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.KeyBackupService;
-import org.whispersystems.signalservice.api.KeyBackupServicePinException;
-import org.whispersystems.signalservice.api.SvrNoDataException;
-import org.whispersystems.signalservice.api.SvrPinData;
 import org.whispersystems.signalservice.api.kbs.MasterKey;
 import org.whispersystems.signalservice.api.kbs.PinHashUtil;
+import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
+import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1;
+import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2;
 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
-import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
 import org.whispersystems.signalservice.internal.push.AuthCredentials;
 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 SecureValueRecoveryV1 secureValueRecoveryV1;
+    private final SecureValueRecoveryV2 secureValueRecoveryV2;
     private final Collection<KeyBackupService> fallbackKeyBackupServices;
 
     public PinHelper(
-            final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
+            final KeyBackupService keyBackupService,
+            final Collection<KeyBackupService> fallbackKeyBackupServices,
+            SecureValueRecoveryV2 secureValueRecoveryV2
     ) {
         this.keyBackupService = keyBackupService;
         this.fallbackKeyBackupServices = fallbackKeyBackupServices;
+        this.secureValueRecoveryV1 = new SecureValueRecoveryV1(keyBackupService);
+        this.secureValueRecoveryV2 = secureValueRecoveryV2;
     }
 
     public void setRegistrationLockPin(
@@ -46,6 +48,22 @@ public class PinHelper {
             throw new IOException(e);
         }
         pinChangeSession.enableRegistrationLock(masterKey);
+
+        final var backupResponse = secureValueRecoveryV2.setPin(pin, masterKey).execute();
+        if (backupResponse instanceof SecureValueRecovery.BackupResponse.Success) {
+        } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ServerRejected) {
+            logger.warn("Backup svr2 failed: ServerRejected");
+        } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.EnclaveNotFound) {
+            logger.warn("Backup svr2 failed: EnclaveNotFound");
+        } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ExposeFailure) {
+            logger.warn("Backup svr2 failed: ExposeFailure");
+        } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ApplicationError error) {
+            throw new IOException(error.getException());
+        } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.NetworkError error) {
+            throw error.getException();
+        } else {
+            throw new AssertionError("Unexpected response");
+        }
     }
 
     public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
@@ -69,75 +87,53 @@ public class PinHelper {
         } catch (UnauthenticatedResponseException e) {
             throw new IOException(e);
         }
+
+        final var deleteResponse = secureValueRecoveryV2.deleteData();
+        if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.Success) {
+        } else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.ServerRejected) {
+            logger.warn("Delete svr2 failed: ServerRejected");
+        } else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.EnclaveNotFound) {
+            logger.warn("Delete svr2 failed: EnclaveNotFound");
+        } else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.ApplicationError error) {
+            throw new IOException(error.getException());
+        } else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.NetworkError error) {
+            throw error.getException();
+        } else {
+            throw new AssertionError("Unexpected response");
+        }
     }
 
-    public SvrPinData getRegistrationLockData(
+    public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData(
             String pin, LockedException e
     ) throws IOException, IncorrectPinException {
-        var basicStorageCredentials = e.getSvr1Credentials();
-        if (basicStorageCredentials == null) {
-            return null;
-        }
-
-        try {
-            return getRegistrationLockData(pin, basicStorageCredentials);
-        } catch (SvrNoDataException ex) {
-            throw new IOException(e);
-        } catch (KeyBackupServicePinException ex) {
-            throw new IncorrectPinException(ex.getTriesRemaining());
+        var svr1Credentials = e.getSvr1Credentials();
+        if (svr1Credentials != null) {
+            return getRegistrationLockData(secureValueRecoveryV1, svr1Credentials, pin);
         }
-    }
 
-    private SvrPinData getRegistrationLockData(
-            String pin, AuthCredentials authCredentials
-    ) throws IOException, SvrNoDataException, KeyBackupServicePinException {
-        final var basicStorageCredentials = authCredentials.asBasic();
-        var tokenResponsePair = getTokenResponse(basicStorageCredentials);
-        final var tokenResponse = tokenResponsePair.first();
-        final var keyBackupService = tokenResponsePair.second();
-
-        var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService);
-        if (registrationLockData == null) {
-            throw new AssertionError("Failed to restore master key");
+        var svr2Credentials = e.getSvr2Credentials();
+        if (svr2Credentials != null) {
+            return getRegistrationLockData(secureValueRecoveryV2, svr2Credentials, pin);
         }
-        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.");
+        return null;
     }
 
-    private SvrPinData restoreMasterKey(
-            String pin,
-            String basicStorageCredentials,
-            TokenResponse tokenResponse,
-            final KeyBackupService keyBackupService
-    ) throws IOException, SvrNoDataException, KeyBackupServicePinException {
-        if (pin == null) return null;
-
-        if (basicStorageCredentials == null) {
-            throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
-        }
-
-        var session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
-
-        try {
-            var hashedPin = PinHashUtil.hashPin(pin, session.hashSalt());
-            var kbsData = session.restorePin(hashedPin);
-            if (kbsData == null) {
-                throw new AssertionError("Null not expected");
-            }
-            return kbsData;
-        } catch (UnauthenticatedResponseException | InvalidKeyException e) {
-            throw new IOException(e);
+    public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData(
+            SecureValueRecovery secureValueRecovery, AuthCredentials authCredentials, String pin
+    ) throws IOException, IncorrectPinException {
+        final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, pin);
+
+        if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.Success s) {
+            return s;
+        } else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.PinMismatch pinMismatch) {
+            throw new IncorrectPinException(pinMismatch.getTriesRemaining());
+        } else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.ApplicationError error) {
+            throw new IOException(error.getException());
+        } else if (restoreResponse instanceof SecureValueRecovery.RestoreResponse.NetworkError error) {
+            throw error.getException();
+        } else {
+            throw new AssertionError("Unexpected response");
         }
     }
 }
index ff8076e0aa96c341c67bdc5bc868eaef0f2d84e2..bf53a9b88ccf001e1eb07761988d3aa5f46e7eb9 100644 (file)
@@ -107,7 +107,8 @@ public class RegistrationManagerImpl implements RegistrationManager {
                         config.getMrenclave(),
                         10))
                 .toList();
-        this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices);
+        final var secureValueRecoveryV2 = accountManager.getSecureValueRecoveryV2(serviceEnvironmentConfig.getSvr2Mrenclave());
+        this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices, secureValueRecoveryV2);
     }
 
     @Override
index bf2f76dfd103021521cdeec8049c8b928617d280..7b3a5a54e3e756e48393b644cbd431f229dea321 100644 (file)
@@ -17,6 +17,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.services.ProfileService;
+import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2;
 import org.whispersystems.signalservice.api.util.CredentialsProvider;
 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
 import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
@@ -50,6 +51,7 @@ public class SignalDependencies {
     private SignalServiceMessageSender messageSender;
 
     private KeyBackupService keyBackupService;
+    private SecureValueRecoveryV2 secureValueRecoveryV2;
     private ProfileService profileService;
     private SignalServiceCipher cipher;
 
@@ -194,6 +196,11 @@ public class SignalDependencies {
                         10));
     }
 
+    public SecureValueRecoveryV2 getSecureValueRecoveryV2() {
+        return getOrCreate(() -> secureValueRecoveryV2,
+                () -> secureValueRecoveryV2 = getAccountManager().getSecureValueRecoveryV2(serviceEnvironmentConfig.getSvr2Mrenclave()));
+    }
+
     public Collection<KeyBackupService> getFallbackKeyBackupServices() {
         return serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
                 .stream()