From: AsamK Date: Sat, 17 Sep 2022 10:21:28 +0000 (+0200) Subject: Use CDSI for contact discovery in compat mode X-Git-Tag: v0.11.0~3 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/ed3992d9935afd0d9fbad61134ce2ec52dc9529b Use CDSI for contact discovery in compat mode --- diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index b545045e..42affda0 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1668,6 +1668,27 @@ "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 @@ -2379,6 +2400,13 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"org.whispersystems.signalservice.internal.push.CdsiAuthResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage", "allDeclaredFields":true, diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 3ee4a51e..4f581905 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -95,6 +95,10 @@ class LiveConfig { return CDS_MRENCLAVE; } + static String getCdsiMrenclave() { + return CDSI_MRENCLAVE; + } + private LiveConfig() { } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index 0a247279..5698d6f8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -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()); }; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java index c2013eac..94169804 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java @@ -16,6 +16,7 @@ public class ServiceEnvironmentConfig { private final Collection 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 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; + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 9d83eb0c..2f2cd8e3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -95,6 +95,10 @@ class StagingConfig { return CDS_MRENCLAVE; } + static String getCdsiMrenclave() { + return CDSI_MRENCLAVE; + } + private StagingConfig() { } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 5498aef9..c8e5859a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -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 getRegisteredUsers(final Set numbers) throws IOException { - final Map registeredUsers; + Map 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 getRegisteredUsersV1(final Set numbers) throws IOException { + final Map 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 getRegisteredUsersV2(final Set 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(); + 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); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 45da42b1..1a428467 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -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 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);