Rename some CDS-related classes.

This commit is contained in:
Greyson Parrelli 2022-04-05 12:47:37 -04:00 committed by Cody Henthorne
parent c56ef33833
commit 612c6db6db
4 changed files with 115 additions and 200 deletions

View file

@ -69,7 +69,7 @@ object ContactDiscovery {
context = context,
descriptor = "refresh-all",
refresh = {
DirectoryHelper.refreshAll(context)
ContactDiscoveryRefreshV1.refreshAll(context)
},
removeSystemContactLinksIfMissing = true,
notifyOfNewUsers = notifyOfNewUsers
@ -86,7 +86,7 @@ object ContactDiscovery {
context = context,
descriptor = "refresh-multiple",
refresh = {
DirectoryHelper.refresh(context, recipients)
ContactDiscoveryRefreshV1.refresh(context, recipients)
},
removeSystemContactLinksIfMissing = false,
notifyOfNewUsers = notifyOfNewUsers
@ -101,7 +101,7 @@ object ContactDiscovery {
context = context,
descriptor = "refresh-single",
refresh = {
DirectoryHelper.refresh(context, listOf(recipient))
ContactDiscoveryRefreshV1.refresh(context, listOf(recipient))
},
removeSystemContactLinksIfMissing = false,
notifyOfNewUsers = notifyOfNewUsers

View file

@ -1,75 +0,0 @@
package org.thoughtcrime.securesms.contacts.sync;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper.DirectoryResult;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.signal.core.util.SetUtil;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.ACI;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Uses CDSHv1 to map E164's to UUIDs.
*/
class ContactDiscoveryHsmV1 {
private static final String TAG = Log.tag(ContactDiscoveryHsmV1.class);
private static final int MAX_NUMBERS = 20_500;
@WorkerThread
static DirectoryResult getDirectoryResult(@NonNull Set<String> databaseNumbers, @NonNull Set<String> systemNumbers) throws IOException {
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers);
Set<String> sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers());
Set<String> ignoredNumbers = new HashSet<>();
if (sanitizedNumbers.size() > MAX_NUMBERS) {
Set<String> randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS);
ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected);
sanitizedNumbers = randomlySelected;
}
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
try {
Map<String, ACI> results = accountManager.getRegisteredUsersWithCdshV1(sanitizedNumbers, BuildConfig.CDSH_PUBLIC_KEY, BuildConfig.CDSH_CODE_HASH);
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
return new DirectoryResult(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
} catch (IOException e) {
Log.w(TAG, "Attestation error.", e);
throw new IOException(e);
}
}
private static Set<String> sanitizeNumbers(@NonNull Set<String> numbers) {
return numbers.stream().filter(number -> {
try {
return number.startsWith("+") && number.length() > 1 && number.charAt(1) != '0' && Long.parseLong(number.substring(1)) > 0;
} catch (NumberFormatException e) {
return false;
}
}).collect(Collectors.toSet());
}
private static @NonNull Set<String> randomlySelect(@NonNull Set<String> numbers, int max) {
List<String> list = new ArrayList<>(numbers);
Collections.shuffle(list);
return new HashSet<>(list.subList(0, max));
}
}

View file

@ -10,7 +10,9 @@ import com.annimon.stream.Stream;
import org.signal.contacts.SystemContactsRepository;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery.RefreshResult;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
@ -19,6 +21,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.push.IasTrustStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
@ -26,13 +29,24 @@ import org.thoughtcrime.securesms.util.ProfileUtil;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.TrustStore;
import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.internal.ServiceResponse;
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.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -46,9 +60,11 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Manages all the stuff around determining if a user is registered or not.
*/
class DirectoryHelper {
class ContactDiscoveryRefreshV1 {
private static final String TAG = Log.tag(DirectoryHelper.class);
private static final String TAG = Log.tag(ContactDiscoveryRefreshV1.class);
private static final int MAX_NUMBERS = 20_500;
@WorkerThread
static @NonNull RefreshResult refreshAll(@NonNull Context context) throws IOException {
@ -95,11 +111,11 @@ class DirectoryHelper {
Stopwatch stopwatch = new Stopwatch("refresh");
DirectoryResult result;
ContactIntersection result;
if (FeatureFlags.cdsh()) {
result = ContactDiscoveryHsmV1.getDirectoryResult(databaseNumbers, systemNumbers);
result = getIntersectionWithHsm(databaseNumbers, systemNumbers);
} else {
result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
result = getIntersection(context, databaseNumbers, systemNumbers);
}
stopwatch.split("network");
@ -165,7 +181,7 @@ class DirectoryHelper {
.map(Recipient::resolved)
.filter(Recipient::isRegistered)
.filter(Recipient::hasServiceId)
.filter(DirectoryHelper::hasCommunicatedWith)
.filter(ContactDiscoveryRefreshV1::hasCommunicatedWith)
.toList();
ProfileService profileService = new ProfileService(ApplicationDependencies.getGroupsV2Operations().getProfileOperations(),
@ -204,14 +220,100 @@ class DirectoryHelper {
return SignalDatabase.threads().hasThread(recipient.getId()) || (recipient.hasServiceId() && SignalDatabase.sessions().hasSessionFor(localAci, recipient.requireServiceId().toString()));
}
static class DirectoryResult {
/**
* Retrieves the contact intersection using the current production CDS.
*/
private static ContactIntersection getIntersection(@NonNull Context context,
@NonNull Set<String> databaseNumbers,
@NonNull Set<String> systemNumbers)
throws IOException
{
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers);
Set<String> sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers());
Set<String> ignoredNumbers = new HashSet<>();
if (sanitizedNumbers.size() > MAX_NUMBERS) {
Set<String> randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS);
ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected);
sanitizedNumbers = randomlySelected;
}
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
KeyStore iasKeyStore = getIasKeyStore(context);
try {
Map<String, ACI> results = accountManager.getRegisteredUsers(iasKeyStore, sanitizedNumbers, BuildConfig.CDS_MRENCLAVE);
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
return new ContactIntersection(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
} catch (SignatureException | UnauthenticatedQuoteException | UnauthenticatedResponseException | Quote.InvalidQuoteFormatException | InvalidKeyException e) {
Log.w(TAG, "Attestation error.", e);
throw new IOException(e);
}
}
/**
* Retrieves the contact intersection using an HSM-backed implementation of CDS that is being tested.
*/
private static ContactIntersection getIntersectionWithHsm(@NonNull Set<String> databaseNumbers,
@NonNull Set<String> systemNumbers)
throws IOException
{
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers);
Set<String> sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers());
Set<String> ignoredNumbers = new HashSet<>();
if (sanitizedNumbers.size() > MAX_NUMBERS) {
Set<String> randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS);
ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected);
sanitizedNumbers = randomlySelected;
}
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
try {
Map<String, ACI> results = accountManager.getRegisteredUsersWithCdshV1(sanitizedNumbers, BuildConfig.CDSH_PUBLIC_KEY, BuildConfig.CDSH_CODE_HASH);
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
return new ContactIntersection(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
} catch (IOException e) {
Log.w(TAG, "Attestation error.", e);
throw new IOException(e);
}
}
private static @NonNull Set<String> randomlySelect(@NonNull Set<String> numbers, int max) {
List<String> list = new ArrayList<>(numbers);
Collections.shuffle(list);
return new HashSet<>(list.subList(0, max));
}
private static KeyStore getIasKeyStore(@NonNull Context context) {
try {
TrustStore contactTrustStore = new IasTrustStore(context);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(contactTrustStore.getKeyStoreInputStream(), contactTrustStore.getKeyStorePassword().toCharArray());
return keyStore;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
static class ContactIntersection {
private final Map<String, ACI> registeredNumbers;
private final Map<String, String> numberRewrites;
private final Set<String> ignoredNumbers;
DirectoryResult(@NonNull Map<String, ACI> registeredNumbers,
@NonNull Map<String, String> numberRewrites,
@NonNull Set<String> ignoredNumbers)
ContactIntersection(@NonNull Map<String, ACI> registeredNumbers,
@NonNull Map<String, String> numberRewrites,
@NonNull Set<String> ignoredNumbers)
{
this.registeredNumbers = registeredNumbers;
this.numberRewrites = numberRewrites;

View file

@ -1,112 +0,0 @@
package org.thoughtcrime.securesms.contacts.sync;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper.DirectoryResult;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.push.IasTrustStore;
import org.signal.core.util.SetUtil;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.TrustStore;
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.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Uses CDS to map E164's to UUIDs.
*/
class ContactDiscoveryV2 {
private static final String TAG = Log.tag(ContactDiscoveryV2.class);
private static final int MAX_NUMBERS = 20_500;
@WorkerThread
static DirectoryResult getDirectoryResult(@NonNull Context context,
@NonNull Set<String> databaseNumbers,
@NonNull Set<String> systemNumbers)
throws IOException
{
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers);
Set<String> sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers());
Set<String> ignoredNumbers = new HashSet<>();
if (sanitizedNumbers.size() > MAX_NUMBERS) {
Set<String> randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS);
ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected);
sanitizedNumbers = randomlySelected;
}
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
KeyStore iasKeyStore = getIasKeyStore(context);
try {
Map<String, ACI> results = accountManager.getRegisteredUsers(iasKeyStore, sanitizedNumbers, BuildConfig.CDS_MRENCLAVE);
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
return new DirectoryResult(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
} catch (SignatureException | UnauthenticatedQuoteException | UnauthenticatedResponseException | Quote.InvalidQuoteFormatException |InvalidKeyException e) {
Log.w(TAG, "Attestation error.", e);
throw new IOException(e);
}
}
static @NonNull DirectoryResult getDirectoryResult(@NonNull Context context, @NonNull String number) throws IOException {
return getDirectoryResult(context, Collections.singleton(number), Collections.singleton(number));
}
private static Set<String> sanitizeNumbers(@NonNull Set<String> numbers) {
return Stream.of(numbers).filter(number -> {
try {
return number.startsWith("+") && number.length() > 1 && number.charAt(1) != '0' && Long.parseLong(number.substring(1)) > 0;
} catch (NumberFormatException e) {
return false;
}
}).collect(Collectors.toSet());
}
private static @NonNull Set<String> randomlySelect(@NonNull Set<String> numbers, int max) {
List<String> list = new ArrayList<>(numbers);
Collections.shuffle(list);
return new HashSet<>(list.subList(0, max));
}
private static KeyStore getIasKeyStore(@NonNull Context context) {
try {
TrustStore contactTrustStore = new IasTrustStore(context);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(contactTrustStore.getKeyStoreInputStream(), contactTrustStore.getKeyStorePassword().toCharArray());
return keyStore;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
}