]> nmode's Git Repositories - signal-cli/commitdiff
Use CDSI for contact discovery in compat mode
authorAsamK <asamk@gmx.de>
Sat, 17 Sep 2022 10:21:28 +0000 (12:21 +0200)
committerAsamK <asamk@gmx.de>
Sat, 17 Sep 2022 10:21:28 +0000 (12:21 +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/RecipientHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java

index b545045e705b09392f669cad7edbd957cca324af..42affda05baec8d96f4f4f680711f2284db13725 100644 (file)
   "name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged",
   "allPublicConstructors":true
 },
+{
+  "name":"org.signal.cdsi.proto.ClientRequest",
+  "fields":[
+    {"name":"aciUakPairs_"}, 
+    {"name":"discardE164S_"}, 
+    {"name":"newE164S_"}, 
+    {"name":"prevE164S_"}, 
+    {"name":"returnAcisWithoutUaks_"}, 
+    {"name":"tokenAck_"}, 
+    {"name":"token_"}
+  ]
+},
+{
+  "name":"org.signal.cdsi.proto.ClientResponse",
+  "fields":[
+    {"name":"debugPermitsUsed_"}, 
+    {"name":"e164PniAciTriples_"}, 
+    {"name":"retryAfterSecs_"}, 
+    {"name":"token_"}
+  ]
+},
 {
   "name":"org.signal.libsignal.protocol.state.IdentityKeyStore",
   "allDeclaredMethods":true
   "allDeclaredMethods":true,
   "allDeclaredConstructors":true
 },
+{
+  "name":"org.whispersystems.signalservice.internal.push.CdsiAuthResponse",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage",
   "allDeclaredFields":true,
index 3ee4a51e35d144602066215da4a34c732f885a12..4f58190507075a321d12bb411ba9f90c10cba4a6 100644 (file)
@@ -95,6 +95,10 @@ class LiveConfig {
         return CDS_MRENCLAVE;
     }
 
+    static String getCdsiMrenclave() {
+        return CDSI_MRENCLAVE;
+    }
+
     private LiveConfig() {
     }
 }
index 0a24727998b4147548bae12774781749b9a056bc..5698d6f8d5ee1dced0633ba2756a8ed2a6b51c3f 100644 (file)
@@ -89,13 +89,15 @@ public class ServiceConfig {
                     LiveConfig.getUnidentifiedSenderTrustRoot(),
                     LiveConfig.createKeyBackupConfig(),
                     LiveConfig.createFallbackKeyBackupConfigs(),
-                    LiveConfig.getCdsMrenclave());
+                    LiveConfig.getCdsMrenclave(),
+                    LiveConfig.getCdsiMrenclave());
             case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
                     StagingConfig.createDefaultServiceConfiguration(interceptors),
                     StagingConfig.getUnidentifiedSenderTrustRoot(),
                     StagingConfig.createKeyBackupConfig(),
                     StagingConfig.createFallbackKeyBackupConfigs(),
-                    StagingConfig.getCdsMrenclave());
+                    StagingConfig.getCdsMrenclave(),
+                    StagingConfig.getCdsiMrenclave());
         };
     }
 }
index c2013eac242fa078395e64aa297ea221b76e9d58..94169804638e821fcc9ee7d37b7d208a36410549 100644 (file)
@@ -16,6 +16,7 @@ public class ServiceEnvironmentConfig {
     private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
 
     private final String cdsMrenclave;
+    private final String cdsiMrenclave;
 
     public ServiceEnvironmentConfig(
             final ServiceEnvironment type,
@@ -23,7 +24,8 @@ public class ServiceEnvironmentConfig {
             final ECPublicKey unidentifiedSenderTrustRoot,
             final KeyBackupConfig keyBackupConfig,
             final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
-            final String cdsMrenclave
+            final String cdsMrenclave,
+            final String cdsiMrenclave
     ) {
         this.type = type;
         this.signalServiceConfiguration = signalServiceConfiguration;
@@ -31,6 +33,7 @@ public class ServiceEnvironmentConfig {
         this.keyBackupConfig = keyBackupConfig;
         this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
         this.cdsMrenclave = cdsMrenclave;
+        this.cdsiMrenclave = cdsiMrenclave;
     }
 
     public ServiceEnvironment getType() {
@@ -56,4 +59,8 @@ public class ServiceEnvironmentConfig {
     public String getCdsMrenclave() {
         return cdsMrenclave;
     }
+
+    public String getCdsiMrenclave() {
+        return cdsiMrenclave;
+    }
 }
index 9d83eb0ccc8e59f39a10b1aee347051d528afbbc..2f2cd8e37acb5db182a12850d0496744c971c312 100644 (file)
@@ -95,6 +95,10 @@ class StagingConfig {
         return CDS_MRENCLAVE;
     }
 
+    static String getCdsiMrenclave() {
+        return CDSI_MRENCLAVE;
+    }
+
     private StagingConfig() {
     }
 }
index 5498aef92da5dc15652b221d979fe2557dc7a6f6..c8e5859ad3ef476750b4ec4056fa849dd13a4722 100644 (file)
@@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.push.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.services.CdsiV2Service;
 import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
@@ -21,8 +22,10 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated
 import java.io.IOException;
 import java.security.SignatureException;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 public class RecipientHelper {
@@ -104,15 +107,12 @@ public class RecipientHelper {
     }
 
     public Map<String, ACI> getRegisteredUsers(final Set<String> numbers) throws IOException {
-        final Map<String, ACI> registeredUsers;
+        Map<String, ACI> registeredUsers;
         try {
-            registeredUsers = dependencies.getAccountManager()
-                    .getRegisteredUsers(ServiceConfig.getIasKeyStore(),
-                            numbers,
-                            serviceEnvironmentConfig.getCdsMrenclave());
-        } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException |
-                 UnauthenticatedResponseException | InvalidKeyException | NumberFormatException e) {
-            throw new IOException(e);
+            registeredUsers = getRegisteredUsersV2(numbers, true);
+        } catch (IOException e) {
+            logger.warn("CDSI request failed, trying fallback to CDS", e);
+            registeredUsers = getRegisteredUsersV1(numbers);
         }
 
         // Store numbers as recipients, so we have the number/uuid association
@@ -136,6 +136,48 @@ public class RecipientHelper {
         return uuid;
     }
 
+    private Map<String, ACI> getRegisteredUsersV1(final Set<String> numbers) throws IOException {
+        final Map<String, ACI> registeredUsers;
+        try {
+            registeredUsers = dependencies.getAccountManager()
+                    .getRegisteredUsers(ServiceConfig.getIasKeyStore(),
+                            numbers,
+                            serviceEnvironmentConfig.getCdsMrenclave());
+        } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException |
+                 UnauthenticatedResponseException | InvalidKeyException | NumberFormatException e) {
+            throw new IOException(e);
+        }
+        return registeredUsers;
+    }
+
+    private Map<String, ACI> getRegisteredUsersV2(final Set<String> numbers, boolean useCompat) throws IOException {
+        // Only partial refresh is implemented here
+        final CdsiV2Service.Response response;
+        try {
+            response = dependencies.getAccountManager()
+                    .getRegisteredUsersWithCdsi(Set.of(),
+                            numbers,
+                            account.getRecipientStore().getServiceIdToProfileKeyMap(),
+                            useCompat,
+                            Optional.empty(),
+                            serviceEnvironmentConfig.getCdsiMrenclave(),
+                            token -> {
+                                // Not storing for partial refresh
+                            });
+        } catch (NumberFormatException e) {
+            throw new IOException(e);
+        }
+        logger.debug("CDSI request successful, quota used by this request: {}", response.getQuotaUsedDebugOnly());
+
+        final var registeredUsers = new HashMap<String, ACI>();
+        response.getResults().forEach((key, value) -> {
+            if (value.getAci().isPresent()) {
+                registeredUsers.put(key, value.getAci().get());
+            }
+        });
+        return registeredUsers;
+    }
+
     private ACI getRegisteredUserByUsername(String username) throws IOException {
         return dependencies.getAccountManager().getAciByUsername(username);
     }
index 45da42b12a59e642b1f3ea9a7375ff4fffb69279..1a42846751dffc3fd6f6a88a48082fcfa958cfa5 100644 (file)
@@ -12,6 +12,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.push.ACI;
+import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.util.UuidUtil;
 
@@ -274,6 +275,27 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         }
     }
 
+    public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() {
+        final var sql = (
+                """
+                SELECT r.uuid, r.profile_key
+                FROM %s r
+                WHERE r.uuid IS NOT NULL AND r.profile_key IS NOT NULL
+                """
+        ).formatted(TABLE_RECIPIENT);
+        try (final var connection = database.getConnection()) {
+            try (final var statement = connection.prepareStatement(sql)) {
+                return Utils.executeQueryForStream(statement, resultSet -> {
+                    final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
+                    final var profileKey = getProfileKeyFromResultSet(resultSet);
+                    return new Pair<>(serviceId, profileKey);
+                }).filter(Objects::nonNull).collect(Collectors.toMap(Pair::first, Pair::second));
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException("Failed read from recipient store", e);
+        }
+    }
+
     @Override
     public void deleteContact(RecipientId recipientId) {
         storeContact(recipientId, null);