Add support for CDSI.

fork-5.53.8
Greyson Parrelli 2022-05-18 11:42:38 -04:00 zatwierdzone przez Cody Henthorne
rodzic 8407f2ff69
commit 9ab275195f
16 zmienionych plików z 350 dodań i 462 usunięć

Wyświetl plik

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

Wyświetl plik

@ -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<String> databaseNumbers,
@NonNull Set<String> systemNumbers)
throws IOException
{
Set<String> allNumbers = SetUtil.union(databaseNumbers, systemNumbers);
FuzzyPhoneNumberHelper.InputResult inputResult = FuzzyPhoneNumberHelper.generateInput(allNumbers, databaseNumbers);
Set<String> sanitizedNumbers = sanitizeNumbers(inputResult.getNumbers());
Set<String> ignoredNumbers = new HashSet<>();
if (sanitizedNumbers.size() > MAX_NUMBERS) {
Set<String> randomlySelected = randomlySelect(sanitizedNumbers, MAX_NUMBERS);
ignoredNumbers = SetUtil.difference(sanitizedNumbers, randomlySelected);
sanitizedNumbers = randomlySelected;
}
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
try {
Map<String, ACI> results = accountManager.getRegisteredUsersWithCdshV1(sanitizedNumbers, BuildConfig.CDSH_PUBLIC_KEY, BuildConfig.CDSH_CODE_HASH);
FuzzyPhoneNumberHelper.OutputResult outputResult = FuzzyPhoneNumberHelper.generateOutput(results, inputResult);
return new ContactIntersection(outputResult.getNumbers(), outputResult.getRewrites(), ignoredNumbers);
} catch (IOException e) {
Log.w(TAG, "Attestation error.", e);
throw new IOException(e);
}
}
private static @NonNull Set<String> randomlySelect(@NonNull Set<String> numbers, int max) {
List<String> list = new ArrayList<>(numbers);
Collections.shuffle(list);

Wyświetl plik

@ -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<String> = SignalDatabase.cds.getAllE164s()
val previousE164s: Set<String> = 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<String> = SignalDatabase.recipients.getAllE164s().sanitize()
@ -54,14 +59,15 @@ object ContactDiscoveryRefreshV2 {
val newE164s: Set<String> = 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<String>, newE164s: Set<String>, serviceIds: Map<ServiceId, ProfileKey>): CdshV2Service.Response {
return ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdshV2(
private fun makeRequest(previousE164s: Set<String>, newE164s: Set<String>, serviceIds: Map<ServiceId, ProfileKey>, 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<String>.toE164s(context: Context): Set<String> {

Wyświetl plik

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

Wyświetl plik

@ -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<SignalContactDiscoveryUrl> = hostConfigs.map { SignalContactDiscoveryUrl("${it.baseUrl}/directory", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
val kbsUrls: Array<SignalKeyBackupServiceUrl> = hostConfigs.map { SignalKeyBackupServiceUrl("${it.baseUrl}/backup", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
val storageUrls: Array<SignalStorageUrl> = hostConfigs.map { SignalStorageUrl("${it.baseUrl}/storage", it.host, gTrustStore, it.connectionSpec) }.toTypedArray()
val cdshUrls: Array<SignalCdshUrl> = listOf(SignalCdshUrl(BuildConfig.SIGNAL_CDSH_URL, serviceTrustStore)).toTypedArray()
val cdsiUrls: Array<SignalCdsiUrl> = 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(),

Wyświetl plik

@ -8,4 +8,8 @@ fun <E> Optional<E>.or(other: Optional<E>): Optional<E> {
} else {
other
}
}
fun <E> Optional<E>.isAbsent(): Boolean {
return !isPresent
}

Wyświetl plik

@ -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<String, ACI> getRegisteredUsersWithCdshV1(Set<String> e164numbers, String hexPublicKey, String hexCodeHash)
public CdsiV2Service.Response getRegisteredUsersWithCdsi(Set<String> previousE164s,
Set<String> newE164s,
Map<ServiceId, ProfileKey> serviceIds,
Optional<byte[]> token,
String mrEnclave,
Consumer<byte[]> tokenSaver)
throws IOException
{
CdshAuthResponse auth = pushServiceSocket.getCdshAuth();
CdshV1Service service = new CdshV1Service(configuration, hexPublicKey, hexCodeHash);
Single<ServiceResponse<Map<String, ACI>>> 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<ServiceResponse<CdsiV2Service.Response>> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request, tokenSaver);
ServiceResponse<Map<String, ACI>> 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<String> previousE164s,
Set<String> newE164s,
Map<ServiceId, ProfileKey> serviceIds,
Optional<byte[]> 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<ServiceResponse<CdshV2Service.Response>> single = service.getRegisteredUsers(auth.getUsername(), auth.getPassword(), request);
ServiceResponse<CdshV2Service.Response> serviceResponse;
ServiceResponse<CdsiV2Service.Response> serviceResponse;
try {
serviceResponse = single.blockingGet();
} catch (Exception e) {

Wyświetl plik

@ -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<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);
}
}
Observable<ClientResponse> connect(String username, String password, List<ClientRequest> requests) {
return Observable.create(emitter -> {
AtomicReference<Stage> 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<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
}
enum Version {
V1(1), V2(2);
private final int value;
Version(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
}

Wyświetl plik

@ -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<ServiceResponse<Map<String, ACI>>> getRegisteredUsers(String username, String password, Set<String> e164Numbers) {
List<String> 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<String, ACI> all = new HashMap<>();
for (Map<String, ACI> 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<String, ACI> parseEntries(ClientResponse clientResponse) {
Map<String, ACI> 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<ClientRequest> buildClientRequests(List<String> addressBook) {
List<ClientRequest> 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();
}
}

Wyświetl plik

@ -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<SSLSocketFactory, X509TrustManager> 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<ClientResponse> connect(String username, String password, ClientRequest clientRequest, Consumer<byte[]> tokenSaver) {
return Observable.create(emitter -> {
AtomicReference<Stage> 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<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 {
INIT,
WAITING_FOR_CONNECTION,
WAITING_FOR_HANDSHAKE,
WAITING_FOR_TOKEN,
WAITING_TO_INITIALIZE,
WAITING_FOR_RESPONSE,
CLOSED,
FAILED
}
}

Wyświetl plik

@ -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<ServiceResponse<Response>> getRegisteredUsers(String username, String password, Request request) {
public Single<ServiceResponse<Response>> getRegisteredUsers(String username, String password, Request request, Consumer<byte[]> 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<String, ResponseItem> 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<String, ResponseItem> 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<ClientRequest> buildClientRequests(Request request) {
private static ClientRequest buildClientRequest(Request request) {
List<Long> previousE164s = parseAndSortE164Strings(request.previousE164s);
List<Long> newE164s = parseAndSortE164Strings(request.newE164s);
List<Long> 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<Long> numbers) {
@ -158,11 +155,15 @@ public final class CdshV2Service {
private final byte[] token;
public Request(Set<String> previousE164s, Set<String> newE164s, Map<ServiceId, ProfileKey> serviceIds, Optional<byte[]> 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<String, ResponseItem> results;
private final byte[] token;
public Response(Map<String, ResponseItem> results, byte[] token) {
public Response(Map<String, ResponseItem> results) {
this.results = results;
this.token = token;
}
public Map<String, ResponseItem> getResults() {
return results;
}
public byte[] getToken() {
return token;
}
}
public static final class ResponseItem {

Wyświetl plik

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

Wyświetl plik

@ -14,7 +14,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 SignalCdsiUrl[] signalCdsiUrls;
private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls;
private final SignalStorageUrl[] signalStorageUrls;
private final List<Interceptor> networkInterceptors;
@ -27,7 +27,7 @@ public final class SignalServiceConfiguration {
SignalContactDiscoveryUrl[] signalContactDiscoveryUrls,
SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls,
SignalStorageUrl[] signalStorageUrls,
SignalCdshUrl[] signalCdshUrls,
SignalCdsiUrl[] signalCdsiUrls,
List<Interceptor> networkInterceptors,
Optional<Dns> dns,
Optional<SignalProxy> 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() {

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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