Allow use of the new CDSH service in staging.

This commit is contained in:
Greyson Parrelli 2021-09-27 16:28:24 -04:00 committed by Cody Henthorne
parent e72be42eff
commit cc99febe32
13 changed files with 353 additions and 17 deletions

View file

@ -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\", " +

View file

@ -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));
}
}

View file

@ -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");

View file

@ -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(),

View file

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

View file

@ -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'],

View file

@ -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')

View file

@ -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'],
]
}

View file

@ -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();

View file

@ -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
}
}

View file

@ -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);
}
}

View file

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

View file

@ -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'],
]
}