mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Allow use of the new CDSH service in staging.
This commit is contained in:
committed by
Cody Henthorne
parent
e72be42eff
commit
cc99febe32
@@ -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'],
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user