"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,
return CDS_MRENCLAVE;
}
+ static String getCdsiMrenclave() {
+ return CDSI_MRENCLAVE;
+ }
+
private LiveConfig() {
}
}
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());
};
}
}
private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
private final String cdsMrenclave;
+ private final String cdsiMrenclave;
public ServiceEnvironmentConfig(
final ServiceEnvironment type,
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;
this.keyBackupConfig = keyBackupConfig;
this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
this.cdsMrenclave = cdsMrenclave;
+ this.cdsiMrenclave = cdsiMrenclave;
}
public ServiceEnvironment getType() {
public String getCdsMrenclave() {
return cdsMrenclave;
}
+
+ public String getCdsiMrenclave() {
+ return cdsiMrenclave;
+ }
}
return CDS_MRENCLAVE;
}
+ static String getCdsiMrenclave() {
+ return CDSI_MRENCLAVE;
+ }
+
private StagingConfig() {
}
}
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;
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 {
}
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
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);
}
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;
}
}
+ 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);