Use CDSI for contact discovery in compat mode

This commit is contained in:
AsamK 2022-09-17 12:21:28 +02:00
parent 60ed2c292f
commit ed3992d993
7 changed files with 120 additions and 11 deletions

View file

@ -1668,6 +1668,27 @@
"name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged", "name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged",
"allPublicConstructors":true "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", "name":"org.signal.libsignal.protocol.state.IdentityKeyStore",
"allDeclaredMethods":true "allDeclaredMethods":true
@ -2379,6 +2400,13 @@
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":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", "name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage",
"allDeclaredFields":true, "allDeclaredFields":true,

View file

@ -95,6 +95,10 @@ class LiveConfig {
return CDS_MRENCLAVE; return CDS_MRENCLAVE;
} }
static String getCdsiMrenclave() {
return CDSI_MRENCLAVE;
}
private LiveConfig() { private LiveConfig() {
} }
} }

View file

@ -89,13 +89,15 @@ public class ServiceConfig {
LiveConfig.getUnidentifiedSenderTrustRoot(), LiveConfig.getUnidentifiedSenderTrustRoot(),
LiveConfig.createKeyBackupConfig(), LiveConfig.createKeyBackupConfig(),
LiveConfig.createFallbackKeyBackupConfigs(), LiveConfig.createFallbackKeyBackupConfigs(),
LiveConfig.getCdsMrenclave()); LiveConfig.getCdsMrenclave(),
LiveConfig.getCdsiMrenclave());
case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment, case STAGING -> new ServiceEnvironmentConfig(serviceEnvironment,
StagingConfig.createDefaultServiceConfiguration(interceptors), StagingConfig.createDefaultServiceConfiguration(interceptors),
StagingConfig.getUnidentifiedSenderTrustRoot(), StagingConfig.getUnidentifiedSenderTrustRoot(),
StagingConfig.createKeyBackupConfig(), StagingConfig.createKeyBackupConfig(),
StagingConfig.createFallbackKeyBackupConfigs(), StagingConfig.createFallbackKeyBackupConfigs(),
StagingConfig.getCdsMrenclave()); StagingConfig.getCdsMrenclave(),
StagingConfig.getCdsiMrenclave());
}; };
} }
} }

View file

@ -16,6 +16,7 @@ public class ServiceEnvironmentConfig {
private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs; private final Collection<KeyBackupConfig> fallbackKeyBackupConfigs;
private final String cdsMrenclave; private final String cdsMrenclave;
private final String cdsiMrenclave;
public ServiceEnvironmentConfig( public ServiceEnvironmentConfig(
final ServiceEnvironment type, final ServiceEnvironment type,
@ -23,7 +24,8 @@ public class ServiceEnvironmentConfig {
final ECPublicKey unidentifiedSenderTrustRoot, final ECPublicKey unidentifiedSenderTrustRoot,
final KeyBackupConfig keyBackupConfig, final KeyBackupConfig keyBackupConfig,
final Collection<KeyBackupConfig> fallbackKeyBackupConfigs, final Collection<KeyBackupConfig> fallbackKeyBackupConfigs,
final String cdsMrenclave final String cdsMrenclave,
final String cdsiMrenclave
) { ) {
this.type = type; this.type = type;
this.signalServiceConfiguration = signalServiceConfiguration; this.signalServiceConfiguration = signalServiceConfiguration;
@ -31,6 +33,7 @@ public class ServiceEnvironmentConfig {
this.keyBackupConfig = keyBackupConfig; this.keyBackupConfig = keyBackupConfig;
this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs; this.fallbackKeyBackupConfigs = fallbackKeyBackupConfigs;
this.cdsMrenclave = cdsMrenclave; this.cdsMrenclave = cdsMrenclave;
this.cdsiMrenclave = cdsiMrenclave;
} }
public ServiceEnvironment getType() { public ServiceEnvironment getType() {
@ -56,4 +59,8 @@ public class ServiceEnvironmentConfig {
public String getCdsMrenclave() { public String getCdsMrenclave() {
return cdsMrenclave; return cdsMrenclave;
} }
public String getCdsiMrenclave() {
return cdsiMrenclave;
}
} }

View file

@ -95,6 +95,10 @@ class StagingConfig {
return CDS_MRENCLAVE; return CDS_MRENCLAVE;
} }
static String getCdsiMrenclave() {
return CDSI_MRENCLAVE;
}
private StagingConfig() { private StagingConfig() {
} }
} }

View file

@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; 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.Quote;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException; import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; 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.io.IOException;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
public class RecipientHelper { public class RecipientHelper {
@ -104,15 +107,12 @@ public class RecipientHelper {
} }
public Map<String, ACI> getRegisteredUsers(final Set<String> numbers) throws IOException { public Map<String, ACI> getRegisteredUsers(final Set<String> numbers) throws IOException {
final Map<String, ACI> registeredUsers; Map<String, ACI> registeredUsers;
try { try {
registeredUsers = dependencies.getAccountManager() registeredUsers = getRegisteredUsersV2(numbers, true);
.getRegisteredUsers(ServiceConfig.getIasKeyStore(), } catch (IOException e) {
numbers, logger.warn("CDSI request failed, trying fallback to CDS", e);
serviceEnvironmentConfig.getCdsMrenclave()); registeredUsers = getRegisteredUsersV1(numbers);
} catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException |
UnauthenticatedResponseException | InvalidKeyException | NumberFormatException e) {
throw new IOException(e);
} }
// Store numbers as recipients, so we have the number/uuid association // Store numbers as recipients, so we have the number/uuid association
@ -136,6 +136,48 @@ public class RecipientHelper {
return uuid; 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 { private ACI getRegisteredUserByUsername(String username) throws IOException {
return dependencies.getAccountManager().getAciByUsername(username); return dependencies.getAccountManager().getAciByUsername(username);
} }

View file

@ -12,6 +12,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ACI; 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.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil; 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 @Override
public void deleteContact(RecipientId recipientId) { public void deleteContact(RecipientId recipientId) {
storeContact(recipientId, null); storeContact(recipientId, null);