From 9ab275195f33ec65218dd06bca6ba7f85a0130b7 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 18 May 2022 11:42:38 -0400 Subject: [PATCH] Add support for CDSI. --- app/build.gradle | 5 +- .../sync/ContactDiscoveryRefreshV1.java | 39 +-- .../sync/ContactDiscoveryRefreshV2.kt | 35 ++- .../securesms/providers/PartProvider.java | 1 + .../push/SignalServiceNetworkAccess.kt | 10 +- .../signal/core/util/OptionalExtensions.kt | 4 + .../api/SignalServiceAccountManager.java | 53 ++--- .../api/services/CdshSocket.java | 190 --------------- .../api/services/CdshV1Service.java | 118 ---------- .../api/services/CdsiSocket.java | 222 ++++++++++++++++++ ...{CdshV2Service.java => CdsiV2Service.java} | 69 +++--- ...{SignalCdshUrl.java => SignalCdsiUrl.java} | 6 +- .../SignalServiceConfiguration.java | 10 +- ...uthResponse.java => CdsiAuthResponse.java} | 2 +- .../internal/push/PushServiceSocket.java | 9 +- .../src/main/proto/{CDSH.proto => CDSI.proto} | 39 ++- 16 files changed, 350 insertions(+), 462 deletions(-) delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshSocket.java delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV1Service.java create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java rename libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/{CdshV2Service.java => CdsiV2Service.java} (72%) rename libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/{SignalCdshUrl.java => SignalCdsiUrl.java} (63%) rename libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/{CdshAuthResponse.java => CdsiAuthResponse.java} (90%) rename libsignal/service/src/main/proto/{CDSH.proto => CDSI.proto} (64%) diff --git a/app/build.gradle b/app/build.gradle index a12a0565f..dfb66c5a1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,7 +179,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_CDSI_URL", "\"https://cdsi.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\"" @@ -196,9 +196,8 @@ android { buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" - buildConfigField "String", "CDSH_PUBLIC_KEY", "\"2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74\"" - buildConfigField "String", "CDSH_CODE_HASH", "\"2f79dc6c1599b71c70fc2d14f3ea2e3bc65134436eb87011c88845b137af673a\"" buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\"" + buildConfigField "String", "CDSI_MRENCLAVE", "\"42e36b74794abe612d698308b148ff8a7dc5fdc6ad28d99bc5024ed6ece18dfe\"" buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " + "\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " + "\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")" diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV1.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV1.java index 57af36cc4..6b27152ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV1.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV1.java @@ -111,12 +111,7 @@ class ContactDiscoveryRefreshV1 { Stopwatch stopwatch = new Stopwatch("refresh"); - ContactIntersection result; - if (FeatureFlags.cdsh()) { - result = getIntersectionWithHsm(databaseNumbers, systemNumbers); - } else { - result = getIntersection(context, databaseNumbers, systemNumbers); - } + ContactIntersection result = getIntersection(context, databaseNumbers, systemNumbers); stopwatch.split("network"); @@ -250,38 +245,6 @@ class ContactDiscoveryRefreshV1 { } } - /** - * Retrieves the contact intersection using an HSM-backed implementation of CDS that is being tested. - */ - private static ContactIntersection getIntersectionWithHsm(@NonNull Set databaseNumbers, - @NonNull Set systemNumbers) - throws IOException - { - Set allNumbers = SetUtil.union(databaseNumbers, systemNumbers); - FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers); - Set sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers()); - Set ignoredNumbers = new HashSet<>(); - - if (sanitizedNumbers.size() > MAX_NUMBERS) { - Set randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS); - - ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected); - sanitizedNumbers = randomlySelected; - } - - SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager(); - - try { - Map 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 randomlySelect(@NonNull Set numbers, int max) { List list = new ArrayList<>(numbers); Collections.shuffle(list); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt index 642392e38..553a6274d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt @@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.Stopwatch import org.whispersystems.signalservice.api.push.ServiceId -import org.whispersystems.signalservice.api.services.CdshV2Service +import org.whispersystems.signalservice.api.services.CdsiV2Service import java.io.IOException import java.util.Optional @@ -41,7 +41,12 @@ object ContactDiscoveryRefreshV2 { fun refreshAll(context: Context): ContactDiscovery.RefreshResult { val stopwatch = Stopwatch("refresh-all") - val previousE164s: Set = SignalDatabase.cds.getAllE164s() + val previousE164s: Set = if (SignalStore.misc().cdsToken != null) { + SignalDatabase.cds.getAllE164s() + } else { + Log.w(TAG, "No token set! Cannot provide previousE164s.") + emptySet() + } stopwatch.split("previous") val recipientE164s: Set = SignalDatabase.recipients.getAllE164s().sanitize() @@ -54,14 +59,15 @@ object ContactDiscoveryRefreshV2 { val newE164s: Set = newRecipientE164s + newSystemE164s - val response: CdshV2Service.Response = makeRequest( + val response: CdsiV2Service.Response = makeRequest( previousE164s = previousE164s, newE164s = newE164s, serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), + token = SignalStore.misc().cdsToken, + saveToken = true ) stopwatch.split("network") - SignalStore.misc().cdsToken = response.token SignalDatabase.cds.updateAfterCdsQuery(newE164s, recipientE164s + systemE164s) stopwatch.split("cds-db") @@ -106,10 +112,12 @@ object ContactDiscoveryRefreshV2 { Log.i(TAG, "Doing a one-off request for ${inputE164s.size} recipients.") } - val response: CdshV2Service.Response = makeRequest( + val response: CdsiV2Service.Response = makeRequest( previousE164s = emptySet(), newE164s = inputE164s, - serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs() + serviceIds = SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), + token = null, + saveToken = false ) stopwatch.split("network") @@ -125,15 +133,18 @@ object ContactDiscoveryRefreshV2 { } @Throws(IOException::class) - private fun makeRequest(previousE164s: Set, newE164s: Set, serviceIds: Map): CdshV2Service.Response { - return ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdshV2( + private fun makeRequest(previousE164s: Set, newE164s: Set, serviceIds: Map, token: ByteArray?, saveToken: Boolean): CdsiV2Service.Response { + return ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdsi( previousE164s, newE164s, serviceIds, - Optional.ofNullable(SignalStore.misc().cdsToken), - BuildConfig.CDSH_PUBLIC_KEY, - BuildConfig.CDSH_CODE_HASH - ) + Optional.ofNullable(token), + BuildConfig.CDSI_MRENCLAVE + ) { token -> + if (saveToken) { + SignalStore.misc().cdsToken = token + } + } } private fun Set.toE164s(context: Context): Set { diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java b/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java index 1c9167bcd..2663f1cff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/PartProvider.java @@ -79,6 +79,7 @@ public final class PartProvider extends BaseContentProvider { return null; } + if (uriMatcher.match(uri) == SINGLE_ROW) { Log.i(TAG, "Parting out a single row..."); try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt index 59cdd2ecb..ecb0330e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/push/SignalServiceNetworkAccess.kt @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.util.Base64 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.SignalCdsiUrl import org.whispersystems.signalservice.internal.configuration.SignalContactDiscoveryUrl import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration @@ -169,7 +169,7 @@ class SignalServiceNetworkAccess(context: Context) { fUrls.map { SignalContactDiscoveryUrl(it, F_DIRECTORY_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), fUrls.map { SignalKeyBackupServiceUrl(it, F_KBS_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), fUrls.map { SignalStorageUrl(it, F_STORAGE_HOST, fTrustStore, APP_CONNECTION_SPEC) }.toTypedArray(), - arrayOf(SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, serviceTrustStore)), + arrayOf(SignalCdsiUrl(BuildConfig.SIGNAL_CDSI_URL, serviceTrustStore)), interceptors, Optional.of(DNS), Optional.empty(), @@ -220,7 +220,7 @@ class SignalServiceNetworkAccess(context: Context) { arrayOf(SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, serviceTrustStore)), arrayOf(SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, serviceTrustStore)), arrayOf(SignalStorageUrl(BuildConfig.STORAGE_URL, serviceTrustStore)), - arrayOf(SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, serviceTrustStore)), + arrayOf(SignalCdsiUrl(BuildConfig.SIGNAL_CDSI_URL, serviceTrustStore)), interceptors, Optional.of(DNS), if (SignalStore.proxy().isProxyEnabled) Optional.ofNullable(SignalStore.proxy().proxy) else Optional.empty(), @@ -276,7 +276,7 @@ class SignalServiceNetworkAccess(context: Context) { val cdsUrls: Array = hostConfigs.map { SignalContactDiscoveryUrl("${it.baseUrl}/directory", it.host, gTrustStore, it.connectionSpec) }.toTypedArray() val kbsUrls: Array = hostConfigs.map { SignalKeyBackupServiceUrl("${it.baseUrl}/backup", it.host, gTrustStore, it.connectionSpec) }.toTypedArray() val storageUrls: Array = hostConfigs.map { SignalStorageUrl("${it.baseUrl}/storage", it.host, gTrustStore, it.connectionSpec) }.toTypedArray() - val cdshUrls: Array = listOf(SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, serviceTrustStore)).toTypedArray() + val cdsiUrls: Array = listOf(SignalCdsiUrl(BuildConfig.SIGNAL_CDSI_URL, serviceTrustStore)).toTypedArray() return SignalServiceConfiguration( serviceUrls, @@ -287,7 +287,7 @@ class SignalServiceNetworkAccess(context: Context) { cdsUrls, kbsUrls, storageUrls, - cdshUrls, + cdsiUrls, interceptors, Optional.of(DNS), Optional.empty(), diff --git a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt index 402b53971..e09de4346 100644 --- a/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt +++ b/core-util/src/main/java/org/signal/core/util/OptionalExtensions.kt @@ -8,4 +8,8 @@ fun Optional.or(other: Optional): Optional { } else { other } +} + +fun Optional.isAbsent(): Boolean { + return !isPresent } \ No newline at end of file diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java index cfbaa7716..c607b58b5 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java @@ -42,8 +42,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.CdshV1Service; -import org.whispersystems.signalservice.api.services.CdshV2Service; +import org.whispersystems.signalservice.api.services.CdsiV2Service; import org.whispersystems.signalservice.api.storage.SignalStorageCipher; import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageModels; @@ -64,7 +63,7 @@ import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequ import org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse; import org.whispersystems.signalservice.internal.crypto.PrimaryProvisioningCipher; import org.whispersystems.signalservice.internal.push.AuthCredentials; -import org.whispersystems.signalservice.internal.push.CdshAuthResponse; +import org.whispersystems.signalservice.internal.push.CdsiAuthResponse; import org.whispersystems.signalservice.internal.push.ProfileAvatarData; import org.whispersystems.signalservice.internal.push.PushServiceSocket; import org.whispersystems.signalservice.internal.push.RemoteAttestationUtil; @@ -105,6 +104,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import io.reactivex.rxjava3.core.Single; @@ -507,45 +507,20 @@ public class SignalServiceAccountManager { } } - public Map getRegisteredUsersWithCdshV1(Set e164numbers, String hexPublicKey, String hexCodeHash) + public CdsiV2Service.Response getRegisteredUsersWithCdsi(Set previousE164s, + Set newE164s, + Map serviceIds, + Optional token, + String mrEnclave, + Consumer tokenSaver) throws IOException { - CdshAuthResponse auth = pushServiceSocket.getCdshAuth(); - CdshV1Service service = new CdshV1Service(configuration, hexPublicKey, hexCodeHash); - Single>> result = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), e164numbers); + CdsiAuthResponse auth = pushServiceSocket.getCdsiAuth(); + CdsiV2Service service = new CdsiV2Service(configuration, mrEnclave); + CdsiV2Service.Request request = new CdsiV2Service.Request(previousE164s, newE164s, serviceIds, token); + Single> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request, tokenSaver); - ServiceResponse> 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 CdshV2Service.Response getRegisteredUsersWithCdshV2(Set previousE164s, - Set newE164s, - Map serviceIds, - Optional token, - String hexPublicKey, - String hexCodeHash) - throws IOException - { - CdshAuthResponse auth = pushServiceSocket.getCdshAuth(); - CdshV2Service service = new CdshV2Service(configuration, hexPublicKey, hexCodeHash); - CdshV2Service.Request request = new CdshV2Service.Request(previousE164s, newE164s, serviceIds, token); - Single> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request); - - ServiceResponse serviceResponse; + ServiceResponse serviceResponse; try { serviceResponse = single.blockingGet(); } catch (Exception e) { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshSocket.java deleted file mode 100644 index 12cf640c5..000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshSocket.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.whispersystems.signalservice.api.services; - -import org.signal.cds.ClientRequest; -import org.signal.cds.ClientResponse; -import org.signal.libsignal.hsmenclave.HsmEnclaveClient; -import org.signal.libsignal.protocol.logging.Log; -import org.signal.libsignal.protocol.util.Pair; -import org.whispersystems.signalservice.api.push.TrustStore; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.api.util.Tls12SocketFactory; -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 org.whispersystems.util.Base64; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import io.reactivex.rxjava3.core.Observable; -import okhttp3.ConnectionSpec; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; - -/** - * Handles the websocket and general lifecycle of a CDSH request. - */ -final class CdshSocket { - - private static final String TAG = CdshSocket.class.getSimpleName(); - - private final OkHttpClient client; - private final HsmEnclaveClient enclave; - private final String baseUrl; - private final String hexPublicKey; - private final String hexCodeHash; - private final Version version; - - CdshSocket(SignalServiceConfiguration configuration, String hexPublicKey, String hexCodeHash, Version version) { - this.baseUrl = configuration.getSignalCdshUrls()[0].getUrl(); - this.hexPublicKey = hexPublicKey; - this.hexCodeHash = hexCodeHash; - this.version = version; - - Pair 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); - } - } - - Observable connect(String username, String password, List requests) { - return Observable.create(emitter -> { - AtomicReference stage = new AtomicReference<>(Stage.WAITING_TO_INITIALIZE); - - String url = String.format("%s/discovery/%s/%s", baseUrl, hexPublicKey, hexCodeHash); - Request request = new Request.Builder() - .url(url) - .addHeader("Authorization", basicAuth(username, password)) - .build(); - - WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() { - @Override - public void onMessage(WebSocket webSocket, okio.ByteString bytes) { - switch (stage.get()) { - case WAITING_TO_INITIALIZE: - enclave.completeHandshake(bytes.toByteArray()); - - stage.set(Stage.WAITING_FOR_RESPONSE); - for (ClientRequest request : requests) { - byte[] plaintextBytes = requestToBytes(request, version); - byte[] ciphertextBytes = enclave.establishedSend(plaintextBytes); - webSocket.send(okio.ByteString.of(ciphertextBytes)); - } - - break; - case WAITING_FOR_RESPONSE: - byte[] rawResponse = enclave.establishedRecv(bytes.toByteArray()); - - try { - ClientResponse clientResponse = ClientResponse.parseFrom(rawResponse); - emitter.onNext(clientResponse); - } catch (IOException e) { - emitter.onError(e); - } - - break; - case FAILURE: - Log.w(TAG, "Received a message after we entered the failure state! Ignoring."); - webSocket.close(1000, "OK"); - break; - } - } - - @Override - public void onClosing(WebSocket webSocket, int code, String reason) { - if (code == 1000) { - emitter.onComplete(); - } else { - Log.w(TAG, "Remote side is closing with non-normal code " + code); - webSocket.close(1000, "Remote closed with code " + code); - stage.set(Stage.FAILURE); - emitter.onError(new NonSuccessfulResponseCodeException(code)); - } - } - - @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { - emitter.onError(t); - stage.set(Stage.FAILURE); - webSocket.close(1000, "OK"); - } - }); - - webSocket.send(okio.ByteString.of(enclave.initialRequest())); - emitter.setCancellable(() -> webSocket.close(1000, "OK")); - }); - } - - private static byte[] requestToBytes(ClientRequest request, Version version) { - ByteArrayOutputStream requestStream = new ByteArrayOutputStream(); - try { - requestStream.write(version.getValue()); - requestStream.write(request.toByteArray()); - } catch (IOException e) { - throw new AssertionError("Failed to write bytes!"); - } - return requestStream.toByteArray(); - } - - private static String basicAuth(String username, String password) { - return "Basic " + Base64.encodeBytes((username + ":" + password).getBytes(StandardCharsets.UTF_8)); - } - - private static Pair 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 - } - - enum Version { - V1(1), V2(2); - - private final int value; - - Version(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV1Service.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV1Service.java deleted file mode 100644 index 209911869..000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV1Service.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.whispersystems.signalservice.api.services; - -import com.google.protobuf.ByteString; - -import org.signal.cds.ClientRequest; -import org.signal.cds.ClientResponse; -import org.signal.libsignal.protocol.util.ByteUtil; -import org.whispersystems.signalservice.api.push.ACI; -import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.ServiceResponse; -import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -import io.reactivex.rxjava3.core.Single; - -/** - * Handles network interactions with CDSHv1, the HSM-backed CDS service. - */ -public final class CdshV1Service { - - private static final String TAG = CdshV1Service.class.getSimpleName(); - - private static final int MAX_E164S_PER_REQUEST = 5000; - private static final UUID EMPTY_ACI = new UUID(0, 0); - private static final int RESPONSE_ITEM_SIZE = 8 + 16 + 16; // 1 uint64 + 2 UUIDs - - private final CdshSocket cdshSocket; - - public CdshV1Service(SignalServiceConfiguration configuration, String hexPublicKey, String hexCodeHash) { - this.cdshSocket = new CdshSocket(configuration, hexPublicKey, hexCodeHash, CdshSocket.Version.V1); - } - - public Single>> getRegisteredUsers(String username, String password, Set e164Numbers) { - List addressBook = e164Numbers.stream().map(e -> e.substring(1)).collect(Collectors.toList()); - - return cdshSocket - .connect(username, password, buildClientRequests(addressBook)) - .map(CdshV1Service::parseEntries) - .collect(Collectors.toList()) - .flatMap(pages -> { - Map all = new HashMap<>(); - for (Map page : pages) { - all.putAll(page); - } - return Single.just(all); - }) - .map(result -> ServiceResponse.forResult(result, 200, null)) - .onErrorReturn(error -> { - if (error instanceof NonSuccessfulResponseCodeException) { - int status = ((NonSuccessfulResponseCodeException) error).getCode(); - return ServiceResponse.forApplicationError(error, status, null); - } else { - return ServiceResponse.forUnknownError(error); - } - }); - } - - private static Map parseEntries(ClientResponse clientResponse) { - Map out = new HashMap<>(); - ByteBuffer parser = clientResponse.getE164PniAciTriples().asReadOnlyByteBuffer(); - - while (parser.remaining() >= RESPONSE_ITEM_SIZE) { - String e164 = "+" + parser.getLong(); - UUID unusedPni = new UUID(parser.getLong(), parser.getLong()); - UUID aci = new UUID(parser.getLong(), parser.getLong()); - - if (!aci.equals(EMPTY_ACI)) { - out.put(e164, ACI.from(aci)); - } - } - - return out; - } - - private static List buildClientRequests(List addressBook) { - List out = new ArrayList<>((addressBook.size() / MAX_E164S_PER_REQUEST) + 1); - ByteString.Output e164Page = ByteString.newOutput(); - int pageSize = 0; - - for (String address : addressBook) { - if (pageSize >= MAX_E164S_PER_REQUEST) { - pageSize = 0; - out.add(e164sToRequest(e164Page.toByteString(), true)); - e164Page = ByteString.newOutput(); - } - - try { - e164Page.write(ByteUtil.longToByteArray(Long.parseLong(address))); - } catch (IOException e) { - throw new AssertionError("Failed to write long to ByteString", e); - } - - pageSize++; - } - - if (pageSize > 0) { - out.add(e164sToRequest(e164Page.toByteString(), false)); - } - - return out; - } - - private static ClientRequest e164sToRequest(ByteString e164s, boolean more) { - return ClientRequest.newBuilder() - .setNewE164S(e164s) - .setHasMore(more) - .build(); - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java new file mode 100644 index 000000000..8cb07ed3b --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiSocket.java @@ -0,0 +1,222 @@ +package org.whispersystems.signalservice.api.services; + +import org.signal.cdsi.proto.ClientRequest; +import org.signal.cdsi.proto.ClientResponse; +import org.signal.libsignal.cds2.AttestationDataException; +import org.signal.libsignal.cds2.Cds2Client; +import org.signal.libsignal.cds2.Cds2CommunicationFailureException; +import org.signal.libsignal.protocol.logging.Log; +import org.signal.libsignal.protocol.util.Pair; +import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; +import org.whispersystems.signalservice.api.util.Tls12SocketFactory; +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 org.whispersystems.util.Base64; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import io.reactivex.rxjava3.core.Observable; +import okhttp3.ConnectionSpec; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +/** + * Handles the websocket and general lifecycle of a CDSI request. + */ +final class CdsiSocket { + + private static final String TAG = CdsiSocket.class.getSimpleName(); + + private final OkHttpClient okhttp; + private final String baseUrl; + private final String mrEnclave; + + private Cds2Client client; + + private static final byte[] CERTIFICATE = ("-----BEGIN CERTIFICATE-----\n" + + " MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\n" + + " aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\n" + + " cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\n" + + " BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\n" + + " A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\n" + + " aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\n" + + " AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n" + + " 1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\n" + + " uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\n" + + " MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\n" + + " ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\n" + + " Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\n" + + " KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\n" + + " AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n" + + " -----END CERTIFICATE-----").getBytes(StandardCharsets.UTF_8); + + CdsiSocket(SignalServiceConfiguration configuration, String mrEnclave) { + this.baseUrl = configuration.getSignalCdsiUrls()[0].getUrl(); + this.mrEnclave = mrEnclave; + + Pair socketFactory = createTlsSocketFactory(configuration.getSignalCdsiUrls()[0].getTrustStore()); + + this.okhttp = 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(); + } + + Observable connect(String username, String password, ClientRequest clientRequest, Consumer tokenSaver) { + return Observable.create(emitter -> { + AtomicReference stage = new AtomicReference<>(Stage.WAITING_TO_INITIALIZE); + + String url = String.format("%s/v1/%s/discovery", baseUrl, mrEnclave); + Request request = new Request.Builder() + .url(url) + .addHeader("Authorization", basicAuth(username, password)) + .build(); + + WebSocket webSocket = okhttp.newWebSocket(request, new WebSocketListener() { + @Override + public void onOpen(WebSocket webSocket, Response response) { + Log.d(TAG, "onOpen"); + stage.set(Stage.WAITING_FOR_CONNECTION); + } + + @Override + public void onMessage(WebSocket webSocket, okio.ByteString bytes) { + Log.d(TAG, "[onMessage] stage: " + stage.get()); + + try { + switch (stage.get()) { + case INIT: + throw new IOException("Received a message before we were open!"); + + case WAITING_FOR_CONNECTION: + client = Cds2Client.create_NOT_FOR_PRODUCTION(Hex.fromStringCondensed(mrEnclave), + CERTIFICATE, + bytes.toByteArray(), + Instant.now().minus(Duration.ofHours(24))); + + Log.d(TAG, "[onMessage] Sending initial handshake..."); + webSocket.send(okio.ByteString.of(client.initialRequest())); + stage.set(Stage.WAITING_FOR_HANDSHAKE); + break; + + case WAITING_FOR_HANDSHAKE: + client.completeHandshake(bytes.toByteArray()); + Log.d(TAG, "[onMessage] Handshake read success."); + + Log.d(TAG, "[onMessage] Sending data..."); + byte[] ciphertextBytes = client.establishedSend(clientRequest.toByteArray()); + webSocket.send(okio.ByteString.of(ciphertextBytes)); + Log.d(TAG, "[onMessage] Data sent."); + + stage.set(Stage.WAITING_FOR_TOKEN); + break; + + case WAITING_FOR_TOKEN: + ClientResponse tokenResponse = ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray())); + if (tokenResponse.getToken().isEmpty()) { + throw new IOException("No token! Cannot continue!"); + } + + tokenSaver.accept(tokenResponse.getToken().toByteArray()); + + Log.d(TAG, "[onMessage] Sending token ack..."); + webSocket.send(okio.ByteString.of(client.establishedSend(ClientRequest.newBuilder() + .setTokenAck(true) + .build() + .toByteArray()))); + stage.set(Stage.WAITING_FOR_RESPONSE); + break; + + case WAITING_FOR_RESPONSE: + emitter.onNext(ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray()))); + break; + + case CLOSED: + Log.w(TAG, "[onMessage] Received a message after the websocket closed! Ignoring."); + break; + + case FAILED: + Log.w(TAG, "[onMessage] Received a message after we entered the failure state! Ignoring."); + webSocket.close(1000, "OK"); + break; + } + } catch (IOException | AttestationDataException | Cds2CommunicationFailureException e) { + Log.w(TAG, e); + webSocket.close(1000, "OK"); + emitter.onError(e); + } + } + + @Override + public void onClosing(WebSocket webSocket, int code, String reason) { + if (code == 1000) { + emitter.onComplete(); + stage.set(Stage.CLOSED); + } else { + Log.w(TAG, "Remote side is closing with non-normal code " + code); + webSocket.close(1000, "Remote closed with code " + code); + stage.set(Stage.FAILED); + emitter.onError(new NonSuccessfulResponseCodeException(code)); + } + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + emitter.onError(t); + stage.set(Stage.FAILED); + webSocket.close(1000, "OK"); + } + }); + + emitter.setCancellable(() -> webSocket.close(1000, "OK")); + }); + } + + private static String basicAuth(String username, String password) { + return "Basic " + Base64.encodeBytes((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + } + + private static Pair 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 { + INIT, + WAITING_FOR_CONNECTION, + WAITING_FOR_HANDSHAKE, + WAITING_FOR_TOKEN, + WAITING_TO_INITIALIZE, + WAITING_FOR_RESPONSE, + CLOSED, + FAILED + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV2Service.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java similarity index 72% rename from libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV2Service.java rename to libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java index 3f4014cb4..80c728927 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdshV2Service.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/services/CdsiV2Service.java @@ -2,8 +2,8 @@ package org.whispersystems.signalservice.api.services; import com.google.protobuf.ByteString; -import org.signal.cds.ClientRequest; -import org.signal.cds.ClientResponse; +import org.signal.cdsi.proto.ClientRequest; +import org.signal.cdsi.proto.ClientResponse; import org.signal.libsignal.protocol.util.ByteUtil; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; @@ -25,45 +25,40 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; import java.util.stream.Collectors; import io.reactivex.rxjava3.core.Single; /** - * Handles network interactions with CDSHv2, the HSM-backed CDS service. + * Handles network interactions with CDSI, the SGX-backed version of the CDSv2 API. */ -public final class CdshV2Service { +public final class CdsiV2Service { - private static final String TAG = CdshV2Service.class.getSimpleName(); + private static final String TAG = CdsiV2Service.class.getSimpleName(); private static final UUID EMPTY_UUID = new UUID(0, 0); private static final int RESPONSE_ITEM_SIZE = 8 + 16 + 16; // 1 uint64 + 2 UUIDs - private final CdshSocket cdshSocket; + private final CdsiSocket cdshSocket; - public CdshV2Service(SignalServiceConfiguration configuration, String hexPublicKey, String hexCodeHash) { - this.cdshSocket = new CdshSocket(configuration, hexPublicKey, hexCodeHash, CdshSocket.Version.V2); + public CdsiV2Service(SignalServiceConfiguration configuration, String mrEnclave) { + this.cdshSocket = new CdsiSocket(configuration, mrEnclave); } - public Single> getRegisteredUsers(String username, String password, Request request) { + public Single> getRegisteredUsers(String username, String password, Request request, Consumer tokenSaver) { return cdshSocket - .connect(username, password, buildClientRequests(request)) - .map(CdshV2Service::parseEntries) + .connect(username, password, buildClientRequest(request), tokenSaver) + .map(CdsiV2Service::parseEntries) .collect(Collectors.toList()) .flatMap(pages -> { - byte[] token = null; Map all = new HashMap<>(); for (Response page : pages) { all.putAll(page.getResults()); - token = token == null ? page.getToken() : token; } - if (token == null) { - throw new IOException("No token found in response!"); - } - - return Single.just(new Response(all, token)); + return Single.just(new Response(all)); }) .map(result -> ServiceResponse.forResult(result, 200, null)) .onErrorReturn(error -> { @@ -77,7 +72,6 @@ public final class CdshV2Service { } private static Response parseEntries(ClientResponse clientResponse) { - byte[] token = !clientResponse.getToken().isEmpty() ? clientResponse.getToken().toByteArray() : null; Map results = new HashMap<>(); ByteBuffer parser = clientResponse.getE164PniAciTriples().asReadOnlyByteBuffer(); @@ -93,22 +87,25 @@ public final class CdshV2Service { } } - return new Response(results, token); + return new Response(results); } - private static List buildClientRequests(Request request) { + private static ClientRequest buildClientRequest(Request request) { List previousE164s = parseAndSortE164Strings(request.previousE164s); List newE164s = parseAndSortE164Strings(request.newE164s); List removedE164s = parseAndSortE164Strings(request.removedE164s); - return Collections.singletonList(ClientRequest.newBuilder() - .setPrevE164S(toByteString(previousE164s)) - .setNewE164S(toByteString(newE164s)) - .setDiscardE164S(toByteString(removedE164s)) - .setAciUakPairs(toByteString(request.serviceIds)) - .setToken(ByteString.copyFrom(request.token)) - .setHasMore(false) - .build()); + ClientRequest.Builder builder = ClientRequest.newBuilder() + .setPrevE164S(toByteString(previousE164s)) + .setNewE164S(toByteString(newE164s)) + .setDiscardE164S(toByteString(removedE164s)) + .setAciUakPairs(toByteString(request.serviceIds)); + + if (request.token != null) { + builder.setToken(ByteString.copyFrom(request.token)); + } + + return builder.build(); } private static ByteString toByteString(List numbers) { @@ -158,11 +155,15 @@ public final class CdshV2Service { private final byte[] token; public Request(Set previousE164s, Set newE164s, Map serviceIds, Optional token) { + if (previousE164s.size() > 0 && !token.isPresent()) { + throw new IllegalArgumentException("You must have a token if you have previousE164s!"); + } + this.previousE164s = previousE164s; this.newE164s = newE164s; this.removedE164s = Collections.emptySet(); this.serviceIds = serviceIds; - this.token = token.isPresent() ? token.get() : new byte[32]; + this.token = token.orElse(null); } public int totalE164s() { @@ -176,20 +177,14 @@ public final class CdshV2Service { public static final class Response { private final Map results; - private final byte[] token; - public Response(Map results, byte[] token) { + public Response(Map results) { this.results = results; - this.token = token; } public Map getResults() { return results; } - - public byte[] getToken() { - return token; - } } public static final class ResponseItem { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdshUrl.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdsiUrl.java similarity index 63% rename from libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdshUrl.java rename to libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdsiUrl.java index de2f5c7cf..a4a767865 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdshUrl.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalCdsiUrl.java @@ -5,13 +5,13 @@ import org.whispersystems.signalservice.api.push.TrustStore; import okhttp3.ConnectionSpec; -public class SignalCdshUrl extends SignalUrl { +public class SignalCdsiUrl extends SignalUrl { - public SignalCdshUrl(String url, TrustStore trustStore) { + public SignalCdsiUrl(String url, TrustStore trustStore) { super(url, trustStore); } - public SignalCdshUrl(String url, String hostHeader, TrustStore trustStore, ConnectionSpec connectionSpec) { + public SignalCdsiUrl(String url, String hostHeader, TrustStore trustStore, ConnectionSpec connectionSpec) { super(url, hostHeader, trustStore, connectionSpec); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java index 1a44c1334..51e65255c 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/configuration/SignalServiceConfiguration.java @@ -14,7 +14,7 @@ public final class SignalServiceConfiguration { private final SignalServiceUrl[] signalServiceUrls; private final Map signalCdnUrlMap; private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls; - private final SignalCdshUrl[] signalCdshUrls; + private final SignalCdsiUrl[] signalCdsiUrls; private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls; private final SignalStorageUrl[] signalStorageUrls; private final List networkInterceptors; @@ -27,7 +27,7 @@ public final class SignalServiceConfiguration { SignalContactDiscoveryUrl[] signalContactDiscoveryUrls, SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls, SignalStorageUrl[] signalStorageUrls, - SignalCdshUrl[] signalCdshUrls, + SignalCdsiUrl[] signalCdsiUrls, List networkInterceptors, Optional dns, Optional proxy, @@ -36,7 +36,7 @@ public final class SignalServiceConfiguration { this.signalServiceUrls = signalServiceUrls; this.signalCdnUrlMap = signalCdnUrlMap; this.signalContactDiscoveryUrls = signalContactDiscoveryUrls; - this.signalCdshUrls = signalCdshUrls; + this.signalCdsiUrls = signalCdsiUrls; this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls; this.signalStorageUrls = signalStorageUrls; this.networkInterceptors = networkInterceptors; @@ -57,8 +57,8 @@ public final class SignalServiceConfiguration { return signalContactDiscoveryUrls; } - public SignalCdshUrl[] getSignalCdshUrls() { - return signalCdshUrls; + public SignalCdsiUrl[] getSignalCdsiUrls() { + return signalCdsiUrls; } public SignalKeyBackupServiceUrl[] getSignalKeyBackupServiceUrls() { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdshAuthResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdsiAuthResponse.java similarity index 90% rename from libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdshAuthResponse.java rename to libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdsiAuthResponse.java index e51afea3c..43f551765 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdshAuthResponse.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/CdsiAuthResponse.java @@ -2,7 +2,7 @@ package org.whispersystems.signalservice.internal.push; import com.fasterxml.jackson.annotation.JsonProperty; -public class CdshAuthResponse { +public class CdsiAuthResponse { @JsonProperty private String username; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 06b246891..f63304152 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -22,7 +22,6 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; -import org.signal.libsignal.zkgroup.profiles.PniCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; @@ -259,7 +258,7 @@ public class PushServiceSocket { private static final String BOOST_RECEIPT_CREDENTIALS = "/v1/subscription/boost/receipt_credentials"; private static final String BOOST_BADGES = "/v1/subscription/boost/badges"; - private static final String CDSH_AUTH = "/v2/directory/auth"; + private static final String CDSI_AUTH = "/v2/directory/auth"; private static final String REPORT_SPAM = "/v1/messages/report/%s/%s"; @@ -343,9 +342,9 @@ public class PushServiceSocket { } } - public CdshAuthResponse getCdshAuth() throws IOException { - String body = makeServiceRequest(CDSH_AUTH, "GET", null); - return JsonUtil.fromJsonResponse(body, CdshAuthResponse.class); + public CdsiAuthResponse getCdsiAuth() throws IOException { + String body = makeServiceRequest(CDSI_AUTH, "GET", null); + return JsonUtil.fromJsonResponse(body, CdsiAuthResponse.class); } public VerifyAccountResponse verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages, diff --git a/libsignal/service/src/main/proto/CDSH.proto b/libsignal/service/src/main/proto/CDSI.proto similarity index 64% rename from libsignal/service/src/main/proto/CDSH.proto rename to libsignal/service/src/main/proto/CDSI.proto index 1107018e0..d542cfc93 100644 --- a/libsignal/service/src/main/proto/CDSH.proto +++ b/libsignal/service/src/main/proto/CDSI.proto @@ -1,10 +1,9 @@ syntax = "proto3"; option java_multiple_files = true; -option java_package = "org.signal.cds"; -option java_outer_classname = "Cds"; +option java_package = "org.signal.cdsi.proto"; -package org.signal.cds; +package org.signal.cdsi; message ClientRequest { // Each ACI/UAK pair is a 32-byte buffer, containing the 16-byte ACI followed @@ -16,14 +15,16 @@ message ClientRequest { bytes new_e164s = 3; bytes discard_e164s = 4; - // If true, the client has more pairs or e164s to send. If false or unset, - // this is the client's last request, and processing should commence. - bool has_more = 5; + reserved /*bool has_more*/ 5; // If set, a token which allows rate limiting to discount the e164s in // the request's prev_e164s, only counting new_e164s. If not set, then // rate limiting considers both prev_e164s' and new_e164s' size. bytes token = 6; + + // After receiving a new token from the server, send back a message just + // containing a token_ack. + bool token_ack = 7; } message ClientResponse { @@ -53,3 +54,29 @@ message ClientResponse { // request's new_e164s. bytes token = 3; } + +message EnclaveLoad { + // If set, before loading any tuples entirely clear the current map, + // zero'ing out all current data. + bool clear_all = 1; + + // Each tuple is an 8-byte e164, a 16-byte PNI, a 16-byte ACI, and a + // 16-byte UAK. These should be loaded as a 48-byte value (PNI,ACI,UAK) + // associated with an 8-byte key (e164). + // ACI/PNI/UAK may all be zeros, in which case this is a delete of the e164. + bytes e164_aci_pni_uak_tuples = 2; + + // If non-empty, overwrite the shared token secret with this value. + bytes shared_token_secret = 3; +} + +message ClientHandshakeStart { + // Public key associated with this server's enclave + bytes pubkey = 1; + + // Remote-attestation evidence associated with the public key + bytes evidence = 2; + + // Endorsements of remote-attestation evidence. + bytes endorsement = 3; +} \ No newline at end of file