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",
"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":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage",
"allDeclaredFields":true,

View file

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

View 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());
};
}
}

View 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;
}
}

View file

@ -95,6 +95,10 @@ class StagingConfig {
return CDS_MRENCLAVE;
}
static String getCdsiMrenclave() {
return CDSI_MRENCLAVE;
}
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.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);
}

View 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);