Allow use of the new CDSH service in staging.
This commit is contained in:
parent
e72be42eff
commit
cc99febe32
13 changed files with 353 additions and 17 deletions
|
@ -149,6 +149,7 @@ android {
|
|||
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_CDSH_URL", "\"https://cdsh.staging.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
|
||||
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
|
||||
|
@ -157,6 +158,8 @@ android {
|
|||
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
|
||||
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
|
||||
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
|
||||
buildConfigField "String", "CDSH_PUBLIC_KEY", "\"2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74\""
|
||||
buildConfigField "String", "CDSH_CODE_HASH", "\"ec31a51880d19a5e9e0fed404740c1a3ff53a553125564b745acce475f0fded8\""
|
||||
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\""
|
||||
buildConfigField "KbsEnclave", "KBS_ENCLAVE", "new KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\"," +
|
||||
"\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
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.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
||||
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.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Uses CDS to map E164's to UUIDs.
|
||||
*/
|
||||
class ContactDiscoveryV3 {
|
||||
|
||||
private static final String TAG = Log.tag(ContactDiscoveryV3.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, UUID> results = accountManager.getRegisteredUsersWithCdsh(sanitizedNumbers, BuildConfig.CDSH_PUBLIC_KEY, BuildConfig.CDSH_CODE_HASH);
|
||||
FuzzyPhoneNumberHelper.OutputResultV2 outputResult = FuzzyPhoneNumberHelper.generateOutputV2(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));
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
|||
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
|
@ -230,7 +231,12 @@ public class DirectoryHelper {
|
|||
|
||||
Stopwatch stopwatch = new Stopwatch("refresh");
|
||||
|
||||
DirectoryResult result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
|
||||
DirectoryResult result;
|
||||
if (FeatureFlags.cdsh()) {
|
||||
result = ContactDiscoveryV3.getDirectoryResult(databaseNumbers, systemNumbers);
|
||||
} else {
|
||||
result = ContactDiscoveryV2.getDirectoryResult(context, databaseNumbers, systemNumbers);
|
||||
}
|
||||
|
||||
stopwatch.split("network");
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalCdshUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
|
@ -159,7 +160,6 @@ public class SignalServiceNetworkAccess {
|
|||
final SignalContactDiscoveryUrl omanGoogleDiscovery = new SignalContactDiscoveryUrl("https://www.google.com.om/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||
final SignalContactDiscoveryUrl qatarGoogleDiscovery = new SignalContactDiscoveryUrl("https://www.google.com.qa/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||
|
||||
|
||||
final SignalKeyBackupServiceUrl baseGoogleKbs = new SignalKeyBackupServiceUrl("https://www.google.com/backup", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
|
||||
final SignalKeyBackupServiceUrl baseAndroidKbs = new SignalKeyBackupServiceUrl("https://android.clients.google.com/backup", SERVICE_REFLECTOR_HOST, trustStore, PLAY_CONNECTION_SPEC);
|
||||
final SignalKeyBackupServiceUrl mapsOneAndroidKbs = new SignalKeyBackupServiceUrl("https://clients3.google.com/backup", SERVICE_REFLECTOR_HOST, trustStore, GMAPS_CONNECTION_SPEC);
|
||||
|
@ -204,6 +204,7 @@ public class SignalServiceNetworkAccess {
|
|||
new SignalContactDiscoveryUrl[] {egyptGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {egyptGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {egyptGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
Optional.absent(),
|
||||
|
@ -215,6 +216,7 @@ public class SignalServiceNetworkAccess {
|
|||
new SignalContactDiscoveryUrl[] {uaeGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {uaeGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {uaeGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
Optional.absent(),
|
||||
|
@ -226,6 +228,7 @@ public class SignalServiceNetworkAccess {
|
|||
new SignalContactDiscoveryUrl[] {omanGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {omanGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {omanGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
Optional.absent(),
|
||||
|
@ -238,6 +241,7 @@ public class SignalServiceNetworkAccess {
|
|||
new SignalContactDiscoveryUrl[] {qatarGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
|
||||
new SignalKeyBackupServiceUrl[] {qatarGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
|
||||
new SignalStorageUrl[] {qatarGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
Optional.absent(),
|
||||
|
@ -249,6 +253,7 @@ public class SignalServiceNetworkAccess {
|
|||
Stream.of(fastUrls).map(url -> new SignalContactDiscoveryUrl(url, DIRECTORY_FASTLY_HOST, new DomainFrontingDigicertTrustStore(context), APP_CONNECTION_SPEC)).toArray(SignalContactDiscoveryUrl[]::new),
|
||||
Stream.of(fastUrls).map(url -> new SignalKeyBackupServiceUrl(url, KBS_FASTLY_HOST, new DomainFrontingDigicertTrustStore(context), APP_CONNECTION_SPEC)).toArray(SignalKeyBackupServiceUrl[]::new),
|
||||
Stream.of(fastUrls).map(url -> new SignalStorageUrl(url, STORAGE_FASTLY_HOST, new DomainFrontingDigicertTrustStore(context), APP_CONNECTION_SPEC)).toArray(SignalStorageUrl[]::new),
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
Optional.absent(),
|
||||
|
@ -261,6 +266,7 @@ public class SignalServiceNetworkAccess {
|
|||
new SignalContactDiscoveryUrl[] {new SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, new SignalServiceTrustStore(context))},
|
||||
new SignalKeyBackupServiceUrl[] { new SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, new SignalServiceTrustStore(context)) },
|
||||
new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))},
|
||||
new SignalCdshUrl[] {new SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, new SignalServiceTrustStore(context))},
|
||||
interceptors,
|
||||
dns,
|
||||
SignalStore.proxy().isProxyEnabled() ? Optional.of(SignalStore.proxy().getProxy()) : Optional.absent(),
|
||||
|
|
|
@ -85,6 +85,7 @@ public final class FeatureFlags {
|
|||
private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging";
|
||||
private static final String CHANGE_NUMBER_ENABLED = "android.changeNumber";
|
||||
private static final String DONOR_BADGES = "android.donorBadges";
|
||||
private static final String CDSH = "android.cdsh";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -121,7 +122,8 @@ public final class FeatureFlags {
|
|||
RETRY_RECEIPTS,
|
||||
SUGGEST_SMS_BLACKLIST,
|
||||
MAX_GROUP_CALL_RING_SIZE,
|
||||
GROUP_CALL_RINGING
|
||||
GROUP_CALL_RINGING,
|
||||
CDSH
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -174,7 +176,8 @@ public final class FeatureFlags {
|
|||
RETRY_RECEIPTS,
|
||||
SENDER_KEY,
|
||||
MAX_GROUP_CALL_RING_SIZE,
|
||||
GROUP_CALL_RINGING
|
||||
GROUP_CALL_RINGING,
|
||||
CDSH
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -410,6 +413,10 @@ public final class FeatureFlags {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean cdsh() {
|
||||
return Environment.IS_STAGING && getBoolean(CDSH, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
|
@ -591,11 +591,11 @@ dependencyVerification {
|
|||
['org.threeten:threetenbp:1.3.6',
|
||||
'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'],
|
||||
|
||||
['org.whispersystems:signal-client-android:0.9.4',
|
||||
'5b4d8e0b37701caefe6089bdf09667716fea5829f105373e4bfce3041e7c6387'],
|
||||
['org.whispersystems:signal-client-android:0.9.5',
|
||||
'd63788841fe2c8d15a144c99abf188c1c3478d31701b5cdb4c19761676b9049d'],
|
||||
|
||||
['org.whispersystems:signal-client-java:0.9.4',
|
||||
'629fb84dbbf5663cbfda0cb87420b0f64ad9902088c575478b04009cce9cbf8a'],
|
||||
['org.whispersystems:signal-client-java:0.9.5',
|
||||
'f00784bf49e75744e1d6c128136a8849b42551b2075cc392f7fe237793a13f7d'],
|
||||
|
||||
['pl.tajchert:waitingdots:0.1.0',
|
||||
'2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
version('signal-client', '0.9.4')
|
||||
version('signal-client', '0.9.5')
|
||||
version('zkgroup', '0.7.0')
|
||||
version('exoplayer', '2.15.0')
|
||||
version('androidx-camera', '1.0.0-beta11')
|
||||
|
|
|
@ -81,7 +81,7 @@ dependencyVerification {
|
|||
['org.greenrobot:eventbus:3.0.0',
|
||||
'180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c'],
|
||||
|
||||
['org.whispersystems:signal-client-java:0.9.4',
|
||||
'629fb84dbbf5663cbfda0cb87420b0f64ad9902088c575478b04009cce9cbf8a'],
|
||||
['org.whispersystems:signal-client-java:0.9.5',
|
||||
'f00784bf49e75744e1d6c128136a8849b42551b2075cc392f7fe237793a13f7d'],
|
||||
]
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
|
|||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
import org.whispersystems.signalservice.api.services.CdshService;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageCipher;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageModels;
|
||||
|
@ -97,6 +98,8 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
|
||||
import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisionMessage;
|
||||
import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisioningVersion;
|
||||
|
||||
|
@ -110,10 +113,11 @@ public class SignalServiceAccountManager {
|
|||
|
||||
private static final String TAG = SignalServiceAccountManager.class.getSimpleName();
|
||||
|
||||
private final PushServiceSocket pushServiceSocket;
|
||||
private final CredentialsProvider credentials;
|
||||
private final String userAgent;
|
||||
private final GroupsV2Operations groupsV2Operations;
|
||||
private final PushServiceSocket pushServiceSocket;
|
||||
private final CredentialsProvider credentials;
|
||||
private final String userAgent;
|
||||
private final GroupsV2Operations groupsV2Operations;
|
||||
private final SignalServiceConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Construct a SignalServiceAccountManager.
|
||||
|
@ -145,6 +149,7 @@ public class SignalServiceAccountManager {
|
|||
this.pushServiceSocket = new PushServiceSocket(configuration, credentialsProvider, signalAgent, groupsV2Operations.getProfileOperations(), automaticNetworkRetry);
|
||||
this.credentials = credentialsProvider;
|
||||
this.userAgent = signalAgent;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public byte[] getSenderCertificate() throws IOException {
|
||||
|
@ -437,6 +442,7 @@ public class SignalServiceAccountManager {
|
|||
return activeTokens;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
public Map<String, UUID> getRegisteredUsers(KeyStore iasKeyStore, Set<String> e164numbers, String mrenclave)
|
||||
throws IOException, Quote.InvalidQuoteFormatException, UnauthenticatedQuoteException, SignatureException, UnauthenticatedResponseException, InvalidKeyException
|
||||
{
|
||||
|
@ -481,6 +487,31 @@ public class SignalServiceAccountManager {
|
|||
}
|
||||
}
|
||||
|
||||
public Map<String, UUID> getRegisteredUsersWithCdsh(Set<String> e164numbers, String hexPublicKey, String hexCodeHash)
|
||||
throws IOException
|
||||
{
|
||||
CdshService service = new CdshService(configuration, hexPublicKey, hexCodeHash);
|
||||
Single<ServiceResponse<Map<String, UUID>>> result = service.getRegisteredUsers(e164numbers);
|
||||
|
||||
ServiceResponse<Map<String, UUID>> response;
|
||||
try {
|
||||
response = result.blockingGet();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected exception when retrieving registered users!", e);
|
||||
}
|
||||
|
||||
if (response.getResult().isPresent()) {
|
||||
return response.getResult().get();
|
||||
} else if (response.getApplicationError().isPresent()) {
|
||||
throw new IOException(response.getApplicationError().get());
|
||||
} else if (response.getExecutionError().isPresent()) {
|
||||
throw new IOException(response.getExecutionError().get());
|
||||
} else {
|
||||
throw new IOException("Missing result!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Optional<SignalStorageManifest> getStorageManifest(StorageKey storageKey) throws IOException {
|
||||
try {
|
||||
String authToken = this.pushServiceSocket.getStorageAuth();
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package org.whispersystems.signalservice.api.services;
|
||||
|
||||
import org.signal.libsignal.hsmenclave.HsmEnclaveClient;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.core.SingleEmitter;
|
||||
import io.reactivex.rxjava3.core.SingleOnSubscribe;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* Handles network interactions with CDSH, the HSM-backed CDS service.
|
||||
*/
|
||||
public final class CdshService {
|
||||
|
||||
private static final String TAG = CdshService.class.getSimpleName();
|
||||
|
||||
private static final int VERSION = 1;
|
||||
|
||||
private final OkHttpClient client;
|
||||
private final HsmEnclaveClient enclave;
|
||||
private final String baseUrl;
|
||||
private final String hexPublicKey;
|
||||
private final String hexCodeHash;
|
||||
|
||||
public CdshService(SignalServiceConfiguration configuration, String hexPublicKey, String hexCodeHash) {
|
||||
this.baseUrl = configuration.getSignalCdshUrls()[0].getUrl();
|
||||
this.hexPublicKey = hexPublicKey;
|
||||
this.hexCodeHash = hexCodeHash;
|
||||
|
||||
Pair<SSLSocketFactory, X509TrustManager> socketFactory = createTlsSocketFactory(configuration.getSignalCdshUrls()[0].getTrustStore());
|
||||
|
||||
this.client = new OkHttpClient.Builder().sslSocketFactory(new Tls12SocketFactory(socketFactory.first()),
|
||||
socketFactory.second())
|
||||
.connectionSpecs(Util.immutableList(ConnectionSpec.RESTRICTED_TLS))
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
|
||||
try {
|
||||
this.enclave = new HsmEnclaveClient(Hex.fromStringCondensed(hexPublicKey),
|
||||
Collections.singletonList(Hex.fromStringCondensed(hexCodeHash)));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Badly-formatted public key or code hash!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<Map<String, UUID>>> getRegisteredUsers(Set<String> e164Numbers) {
|
||||
return Single.create(emitter -> {
|
||||
AtomicReference<Stage> stage = new AtomicReference<>(Stage.WAITING_TO_INITIALIZE);
|
||||
List<String> addressBook = e164Numbers.stream().map(e -> e.substring(1)).collect(Collectors.toList());
|
||||
|
||||
String url = String.format("%s/discovery/%s/%s", baseUrl, hexPublicKey, hexCodeHash);
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
switch (stage.get()) {
|
||||
case WAITING_TO_INITIALIZE:
|
||||
enclave.completeHandshake(bytes.toByteArray());
|
||||
|
||||
byte[] request = enclave.establishedSend(buildPlaintextRequest(addressBook));
|
||||
|
||||
stage.set(Stage.WAITING_FOR_RESPONSE);
|
||||
webSocket.send(ByteString.of(request));
|
||||
|
||||
break;
|
||||
case WAITING_FOR_RESPONSE:
|
||||
byte[] response = enclave.establishedRecv(bytes.toByteArray());
|
||||
|
||||
try {
|
||||
Map<String, UUID> out = parseResponse(addressBook, response);
|
||||
emitter.onSuccess(ServiceResponse.forResult(out, 200, null));
|
||||
} catch (IOException e) {
|
||||
emitter.onSuccess(ServiceResponse.forUnknownError(e));
|
||||
} finally {
|
||||
webSocket.close(1000, "OK");
|
||||
}
|
||||
|
||||
break;
|
||||
case FAILURE:
|
||||
Log.w(TAG, "Received a message after we entered the failure state! Ignoring.");
|
||||
webSocket.close(1000, "OK");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
emitter.onSuccess(ServiceResponse.forApplicationError(t, response != null ? response.code() : 0, null));
|
||||
stage.set(Stage.FAILURE);
|
||||
webSocket.close(1000, "OK");
|
||||
}
|
||||
});
|
||||
|
||||
webSocket.send(ByteString.of(enclave.initialRequest()));
|
||||
});
|
||||
}
|
||||
|
||||
private static byte[] buildPlaintextRequest(List<String> addressBook) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
outputStream.write(VERSION);
|
||||
|
||||
for (String e164 : addressBook) {
|
||||
outputStream.write(ByteUtil.longToByteArray(Long.parseLong(e164)));
|
||||
}
|
||||
|
||||
return outputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Failed to write bytes to the output stream?");
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, UUID> parseResponse(List<String> addressBook, byte[] plaintextResponse) throws IOException {
|
||||
Map<String, UUID> results = new HashMap<>();
|
||||
|
||||
try (DataInputStream uuidInputStream = new DataInputStream(new ByteArrayInputStream(plaintextResponse))) {
|
||||
for (String candidate : addressBook) {
|
||||
long candidateUuidHigh = uuidInputStream.readLong();
|
||||
long candidateUuidLow = uuidInputStream.readLong();
|
||||
if (candidateUuidHigh != 0 || candidateUuidLow != 0) {
|
||||
results.put('+' + candidate, new UUID(candidateUuidHigh, candidateUuidLow));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static Pair<SSLSocketFactory, X509TrustManager> createTlsSocketFactory(TrustStore trustStore) {
|
||||
try {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
TrustManager[] trustManagers = BlacklistingTrustManager.createFor(trustStore);
|
||||
context.init(null, trustManagers, null);
|
||||
|
||||
return new Pair<>(context.getSocketFactory(), (X509TrustManager) trustManagers[0]);
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Stage {
|
||||
WAITING_TO_INITIALIZE, WAITING_FOR_RESPONSE, FAILURE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.whispersystems.signalservice.internal.configuration;
|
||||
|
||||
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
|
||||
import okhttp3.ConnectionSpec;
|
||||
|
||||
public class SignalCdshUrl extends SignalUrl {
|
||||
|
||||
public SignalCdshUrl(String url, TrustStore trustStore) {
|
||||
super(url, trustStore);
|
||||
}
|
||||
|
||||
public SignalCdshUrl(String url, String hostHeader, TrustStore trustStore, ConnectionSpec connectionSpec) {
|
||||
super(url, hostHeader, trustStore, connectionSpec);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ public final class SignalServiceConfiguration {
|
|||
private final SignalServiceUrl[] signalServiceUrls;
|
||||
private final Map<Integer, SignalCdnUrl[]> signalCdnUrlMap;
|
||||
private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls;
|
||||
private final SignalCdshUrl[] signalCdshUrls;
|
||||
private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls;
|
||||
private final SignalStorageUrl[] signalStorageUrls;
|
||||
private final List<Interceptor> networkInterceptors;
|
||||
|
@ -25,6 +26,7 @@ public final class SignalServiceConfiguration {
|
|||
SignalContactDiscoveryUrl[] signalContactDiscoveryUrls,
|
||||
SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls,
|
||||
SignalStorageUrl[] signalStorageUrls,
|
||||
SignalCdshUrl[] signalCdshUrls,
|
||||
List<Interceptor> networkInterceptors,
|
||||
Optional<Dns> dns,
|
||||
Optional<SignalProxy> proxy,
|
||||
|
@ -33,6 +35,7 @@ public final class SignalServiceConfiguration {
|
|||
this.signalServiceUrls = signalServiceUrls;
|
||||
this.signalCdnUrlMap = signalCdnUrlMap;
|
||||
this.signalContactDiscoveryUrls = signalContactDiscoveryUrls;
|
||||
this.signalCdshUrls = signalCdshUrls;
|
||||
this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls;
|
||||
this.signalStorageUrls = signalStorageUrls;
|
||||
this.networkInterceptors = networkInterceptors;
|
||||
|
@ -53,6 +56,10 @@ public final class SignalServiceConfiguration {
|
|||
return signalContactDiscoveryUrls;
|
||||
}
|
||||
|
||||
public SignalCdshUrl[] getSignalCdshUrls() {
|
||||
return signalCdshUrls;
|
||||
}
|
||||
|
||||
public SignalKeyBackupServiceUrl[] getSignalKeyBackupServiceUrls() {
|
||||
return signalKeyBackupServiceUrls;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ dependencyVerification {
|
|||
['org.threeten:threetenbp:1.3.6',
|
||||
'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'],
|
||||
|
||||
['org.whispersystems:signal-client-java:0.9.4',
|
||||
'629fb84dbbf5663cbfda0cb87420b0f64ad9902088c575478b04009cce9cbf8a'],
|
||||
['org.whispersystems:signal-client-java:0.9.5',
|
||||
'f00784bf49e75744e1d6c128136a8849b42551b2075cc392f7fe237793a13f7d'],
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue