Convert CDSI, KBS, and WebSocket protos to wire.

This commit is contained in:
Cody Henthorne
2023-08-23 13:15:45 -04:00
parent dcbf4b8faf
commit 7909703f4c
18 changed files with 225 additions and 229 deletions

View File

@@ -38,6 +38,10 @@ wire {
sourcePath {
srcDir 'src/main/protowire'
}
protoPath {
srcDir "${project.rootDir}/libsignal/service/src/main/protowire"
}
}
ktlint {

View File

@@ -27,8 +27,8 @@ import org.thoughtcrime.securesms.testing.FakeClientHelpers
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
@@ -179,32 +179,19 @@ class MessageProcessingPerformanceTest {
}
private fun webSocketTombstone(): ByteString {
return WebSocketMessage
.newBuilder()
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/queue/empty")
)
.build()
.toByteArray()
.toByteString()
return WebSocketMessage(request = WebSocketRequestMessage(verb = "PUT", path = "/api/v1/queue/empty")).encodeByteString()
}
private fun Envelope.toWebSocketPayload(): ByteString {
return WebSocketMessage
.newBuilder()
.setType(WebSocketMessage.Type.REQUEST)
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/message")
.setId(Random(System.currentTimeMillis()).nextLong())
.addHeaders("X-Signal-Timestamp: ${this.timestamp}")
.setBody(this.toByteString())
return WebSocketMessage(
type = WebSocketMessage.Type.REQUEST,
request = WebSocketRequestMessage(
verb = "PUT",
path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.toByteArray().toByteString()
)
.build()
.toByteArray()
.toByteString()
).encodeByteString()
}
}

View File

@@ -2,6 +2,7 @@ plugins {
id 'signal-library'
id 'com.google.protobuf'
id 'kotlin-kapt'
id 'com.squareup.wire'
}
android {
@@ -33,3 +34,13 @@ dependencies {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
}
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir 'src/main/protowire'
}
}

View File

@@ -15,6 +15,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.ByteString
import okio.ByteString.Companion.encodeUtf8
import org.json.JSONObject
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
@@ -263,7 +264,7 @@ class StripeApi(
private fun getRequestBuilder(endpoint: String): Request.Builder {
return Request.Builder()
.url("${configuration.baseUrl}/$endpoint")
.addHeader("Authorization", "Basic ${ByteString.encodeUtf8("${configuration.publishableKey}:").base64()}")
.addHeader("Authorization", "Basic ${"${configuration.publishableKey}:".encodeUtf8().base64()}")
}
private fun checkResponseForErrors(response: Response): Response {

View File

@@ -175,14 +175,13 @@ public class KeyBackupService {
final KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
final RestoreResponse status = KeyBackupCipher.getKeyRestoreResponse(response, remoteAttestation);
TokenResponse nextToken = status.hasToken()
? new TokenResponse(token.getBackupId(), status.getToken().toByteArray(), status.getTries())
: token;
TokenResponse nextToken = status.token != null ? new TokenResponse(token.getBackupId(), status.token.toByteArray(), status.tries)
: token;
Log.i(TAG, "Restore " + status.getStatus());
switch (status.getStatus()) {
Log.i(TAG, "Restore " + status.status);
switch (status.status) {
case OK:
KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, status.getData().toByteArray());
KbsData kbsData = PinHashUtil.decryptSvrDataIVCipherText(hashedPin, status.data_.toByteArray());
MasterKey masterKey = kbsData.getMasterKey();
return new SvrPinData(masterKey, nextToken);
case PIN_MISMATCH:
@@ -191,8 +190,8 @@ public class KeyBackupService {
case TOKEN_MISMATCH:
Log.i(TAG, "Restore TOKEN_MISMATCH");
// if the number of tries has not fallen, the pin is correct we're just using an out of date token
boolean canRetry = remainingTries == status.getTries();
Log.i(TAG, String.format(Locale.US, "Token MISMATCH remainingTries: %d, status.getTries(): %d", remainingTries, status.getTries()));
boolean canRetry = remainingTries == status.tries;
Log.i(TAG, String.format(Locale.US, "Token MISMATCH remainingTries: %d, status.getTries(): %d", remainingTries, status.tries));
throw new TokenException(nextToken, canRetry);
case MISSING:
Log.i(TAG, "Restore OK! No data though");
@@ -259,11 +258,11 @@ public class KeyBackupService {
KeyBackupRequest request = KeyBackupCipher.createKeyBackupRequest(kbsAccessKey, kbsData, token, remoteAttestation, serviceId, maxTries);
KeyBackupResponse response = pushServiceSocket.putKbsData(authorization, request, remoteAttestation.getCookies(), enclaveName);
BackupResponse backupResponse = KeyBackupCipher.getKeyBackupResponse(response, remoteAttestation);
BackupResponse.Status status = backupResponse.getStatus();
BackupResponse.Status status = backupResponse.status;
switch (status) {
case OK:
return backupResponse.hasToken() ? new TokenResponse(token.getBackupId(), backupResponse.getToken().toByteArray(), maxTries) : token;
return backupResponse.token != null ? new TokenResponse(token.getBackupId(), backupResponse.token.toByteArray(), maxTries) : token;
case ALREADY_EXISTS:
throw new UnauthenticatedResponseException("Already exists");
case NOT_YET_VALID:

View File

@@ -1,7 +1,5 @@
package org.whispersystems.signalservice.api;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.libsignal.protocol.logging.Log;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.messages.EnvelopeResponse;
@@ -10,8 +8,8 @@ import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketResponseMessage;
import org.whispersystems.signalservice.internal.websocket.WebsocketResponse;
import org.whispersystems.util.Base64;
@@ -202,9 +200,11 @@ public final class SignalWebSocket {
public Single<WebsocketResponse> request(WebSocketRequestMessage requestMessage, Optional<UnidentifiedAccess> unidentifiedAccess) {
if (unidentifiedAccess.isPresent()) {
WebSocketRequestMessage message = WebSocketRequestMessage.newBuilder(requestMessage)
.addHeaders("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()))
.build();
List<String> headers = new ArrayList<>(requestMessage.headers);
headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
WebSocketRequestMessage message = requestMessage.newBuilder()
.headers(headers)
.build();
try {
return getUnidentifiedWebSocket().sendRequest(message)
.flatMap(r -> {
@@ -299,7 +299,7 @@ public final class SignalWebSocket {
}
private static EnvelopeResponse requestToEnvelopeResponse(WebSocketRequestMessage request)
throws InvalidProtocolBufferException
throws IOException
{
Optional<String> timestampHeader = findHeader(request);
long timestamp = 0;
@@ -312,41 +312,41 @@ public final class SignalWebSocket {
}
}
SignalServiceProtos.Envelope envelope = SignalServiceProtos.Envelope.parseFrom(request.getBody().toByteArray());
SignalServiceProtos.Envelope envelope = SignalServiceProtos.Envelope.parseFrom(request.body.toByteArray());
return new EnvelopeResponse(envelope, timestamp, request);
}
private static boolean isSignalServiceEnvelope(WebSocketRequestMessage message) {
return "PUT".equals(message.getVerb()) && "/api/v1/message".equals(message.getPath());
return "PUT".equals(message.verb) && "/api/v1/message".equals(message.path);
}
private static boolean isSocketEmptyRequest(WebSocketRequestMessage message) {
return "PUT".equals(message.getVerb()) && "/api/v1/queue/empty".equals(message.getPath());
return "PUT".equals(message.verb) && "/api/v1/queue/empty".equals(message.path);
}
private static WebSocketResponseMessage createWebSocketResponse(WebSocketRequestMessage request) {
if (isSignalServiceEnvelope(request)) {
return WebSocketResponseMessage.newBuilder()
.setId(request.getId())
.setStatus(200)
.setMessage("OK")
.build();
return new WebSocketResponseMessage.Builder()
.id(request.id)
.status(200)
.message("OK")
.build();
} else {
return WebSocketResponseMessage.newBuilder()
.setId(request.getId())
.setStatus(400)
.setMessage("Unknown")
.build();
return new WebSocketResponseMessage.Builder()
.id(request.id)
.status(400)
.message("Unknown")
.build();
}
}
private static Optional<String> findHeader(WebSocketRequestMessage message) {
if (message.getHeadersCount() == 0) {
if (message.headers.isEmpty()) {
return Optional.empty();
}
for (String header : message.getHeadersList()) {
for (String header : message.headers) {
if (header.startsWith(SERVER_DELIVERED_TIMESTAMP_HEADER)) {
String[] split = header.split(":");
if (split.length == 2 && split[0].trim().toLowerCase().equals(SERVER_DELIVERED_TIMESTAMP_HEADER.toLowerCase())) {

View File

@@ -1,7 +1,7 @@
package org.whispersystems.signalservice.api.messages
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
/**
* Represents an envelope off the wire, paired with the metadata needed to process it.

View File

@@ -6,7 +6,7 @@ import org.whispersystems.signalservice.internal.ServiceResponseProcessor;
import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
import org.whispersystems.signalservice.internal.push.AttachmentV3UploadAttributes;
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
import java.security.SecureRandom;
@@ -25,11 +25,11 @@ public final class AttachmentService {
}
public Single<ServiceResponse<AttachmentV2UploadAttributes>> getAttachmentV2UploadAttributes() {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("GET")
.setPath("/v2/attachments/form/upload")
.build();
WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder()
.id(new SecureRandom().nextLong())
.verb("GET")
.path("/v2/attachments/form/upload")
.build();
return signalWebSocket.request(requestMessage)
.map(DefaultResponseMapper.getDefault(AttachmentV2UploadAttributes.class)::map)
@@ -37,11 +37,11 @@ public final class AttachmentService {
}
public Single<ServiceResponse<AttachmentV3UploadAttributes>> getAttachmentV3UploadAttributes() {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("GET")
.setPath("/v3/attachments/form/upload")
.build();
WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder()
.id(new SecureRandom().nextLong())
.verb("GET")
.path("/v3/attachments/form/upload")
.build();
return signalWebSocket.request(requestMessage)
.map(DefaultResponseMapper.getDefault(AttachmentV3UploadAttributes.class)::map)

View File

@@ -128,7 +128,7 @@ final class CdsiSocket {
Log.d(TAG, "[onMessage] Handshake read success.");
Log.d(TAG, "[onMessage] Sending data...");
byte[] ciphertextBytes = client.establishedSend(clientRequest.toByteArray());
byte[] ciphertextBytes = client.establishedSend(clientRequest.encode());
webSocket.send(okio.ByteString.of(ciphertextBytes));
Log.d(TAG, "[onMessage] Data sent.");
@@ -136,24 +136,24 @@ final class CdsiSocket {
break;
case WAITING_FOR_TOKEN:
ClientResponse tokenResponse = ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray()));
ClientResponse tokenResponse = ClientResponse.ADAPTER.decode(client.establishedRecv(bytes.toByteArray()));
if (tokenResponse.getToken().isEmpty()) {
if (tokenResponse.token.size() == 0) {
throw new IOException("No token! Cannot continue!");
}
tokenSaver.accept(tokenResponse.getToken().toByteArray());
tokenSaver.accept(tokenResponse.token.toByteArray());
Log.d(TAG, "[onMessage] Sending token ack...");
webSocket.send(okio.ByteString.of(client.establishedSend(ClientRequest.newBuilder()
.setTokenAck(true)
.build()
.toByteArray())));
webSocket.send(okio.ByteString.of(client.establishedSend(new ClientRequest.Builder()
.tokenAck(true)
.build()
.encode())));
stage.set(Stage.WAITING_FOR_RESPONSE);
break;
case WAITING_FOR_RESPONSE:
emitter.onNext(ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray())));
emitter.onNext(ClientResponse.ADAPTER.decode(client.establishedRecv(bytes.toByteArray())));
break;
case CLOSED:

View File

@@ -1,20 +1,19 @@
package org.whispersystems.signalservice.api.services;
import com.google.protobuf.ByteString;
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;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
@@ -29,6 +28,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import io.reactivex.rxjava3.core.Single;
import okio.ByteString;
/**
* Handles network interactions with CDSI, the SGX-backed version of the CDSv2 API.
@@ -75,7 +75,7 @@ public final class CdsiV2Service {
private static Response parseEntries(ClientResponse clientResponse) {
Map<String, ResponseItem> results = new HashMap<>();
ByteBuffer parser = clientResponse.getE164PniAciTriples().asReadOnlyByteBuffer();
ByteBuffer parser = clientResponse.e164PniAciTriples.asByteBuffer();
while (parser.remaining() >= RESPONSE_ITEM_SIZE) {
String e164 = "+" + parser.getLong();
@@ -89,7 +89,7 @@ public final class CdsiV2Service {
}
}
return new Response(results, clientResponse.getDebugPermitsUsed());
return new Response(results, clientResponse.debugPermitsUsed);
}
private static ClientRequest buildClientRequest(Request request) {
@@ -97,22 +97,22 @@ public final class CdsiV2Service {
List<Long> newE164s = parseAndSortE164Strings(request.newE164s);
List<Long> removedE164s = parseAndSortE164Strings(request.removedE164s);
ClientRequest.Builder builder = ClientRequest.newBuilder()
.setPrevE164S(toByteString(previousE164s))
.setNewE164S(toByteString(newE164s))
.setDiscardE164S(toByteString(removedE164s))
.setAciUakPairs(toByteString(request.serviceIds))
.setReturnAcisWithoutUaks(request.requireAcis);
ClientRequest.Builder builder = new ClientRequest.Builder()
.prevE164s(toByteString(previousE164s))
.newE164s(toByteString(newE164s))
.discardE164s(toByteString(removedE164s))
.aciUakPairs(toByteString(request.serviceIds))
.returnAcisWithoutUaks(request.requireAcis);
if (request.token != null) {
builder.setToken(ByteString.copyFrom(request.token));
builder.token(ByteString.of(request.token));
}
return builder.build();
}
private static ByteString toByteString(List<Long> numbers) {
ByteString.Output os = ByteString.newOutput();
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (long number : numbers) {
try {
@@ -122,11 +122,11 @@ public final class CdsiV2Service {
}
}
return os.toByteString();
return ByteString.of(os.toByteArray());
}
private static ByteString toByteString(Map<ServiceId, ProfileKey> serviceIds) {
ByteString.Output os = ByteString.newOutput();
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (Map.Entry<ServiceId, ProfileKey> entry : serviceIds.entrySet()) {
try {
@@ -137,7 +137,7 @@ public final class CdsiV2Service {
}
}
return os.toByteString();
return ByteString.of(os.toByteArray());
}
private static List<Long> parseAndSortE164Strings(Collection<String> e164s) {

View File

@@ -1,7 +1,5 @@
package org.whispersystems.signalservice.api.services;
import com.google.protobuf.ByteString;
import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
@@ -20,7 +18,7 @@ import org.whispersystems.signalservice.internal.util.JsonUtil;
import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
import org.whispersystems.util.Base64;
import java.security.SecureRandom;
@@ -30,6 +28,7 @@ import java.util.Locale;
import java.util.Optional;
import io.reactivex.rxjava3.core.Single;
import okio.ByteString;
/**
* Provide WebSocket based interface to message sending endpoints.
@@ -48,13 +47,13 @@ public class MessagingService {
add("content-type:application/json");
}};
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("PUT")
.setPath(String.format("/v1/messages/%s?story=%s", list.getDestination(), story ? "true" : "false"))
.addAllHeaders(headers)
.setBody(ByteString.copyFrom(JsonUtil.toJson(list).getBytes()))
.build();
WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder()
.id(new SecureRandom().nextLong())
.verb("PUT")
.path(String.format("/v1/messages/%s?story=%s", list.getDestination(), story ? "true" : "false"))
.headers(headers)
.body(ByteString.of(JsonUtil.toJson(list).getBytes()))
.build();
ResponseMapper<SendMessageResponse> responseMapper = DefaultResponseMapper.extend(SendMessageResponse.class)
.withResponseMapper((status, body, getHeader, unidentified) -> {
@@ -80,13 +79,13 @@ public class MessagingService {
String path = String.format(Locale.US, "/v1/messages/multi_recipient?ts=%s&online=%s&urgent=%s&story=%s", timestamp, online, urgent, story);
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("PUT")
.setPath(path)
.addAllHeaders(headers)
.setBody(ByteString.copyFrom(body))
.build();
WebSocketRequestMessage requestMessage = new WebSocketRequestMessage.Builder()
.id(new SecureRandom().nextLong())
.verb("PUT")
.path(path)
.headers(headers)
.body(ByteString.of(body))
.build();
return signalWebSocket.request(requestMessage)
.map(DefaultResponseMapper.extend(SendGroupMessageResponse.class)

View File

@@ -29,7 +29,7 @@ import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import org.whispersystems.signalservice.internal.websocket.DefaultResponseMapper;
import org.whispersystems.signalservice.internal.websocket.ResponseMapper;
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage;
import java.security.SecureRandom;
import java.util.Collections;
@@ -78,9 +78,9 @@ public final class ProfileService {
SecureRandom random = new SecureRandom();
ProfileKeyCredentialRequestContext requestContext = null;
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
.setId(random.nextLong())
.setVerb("GET");
WebSocketRequestMessage.Builder builder = new WebSocketRequestMessage.Builder()
.id(random.nextLong())
.verb("GET");
if (profileKey.isPresent()) {
if (!(serviceId instanceof ACI)) {
@@ -98,15 +98,15 @@ public final class ProfileService {
ProfileKeyCredentialRequest request = requestContext.getRequest();
String credentialRequest = Hex.toStringCondensed(request.serialize());
builder.setPath(String.format("/v1/profile/%s/%s/%s?credentialType=expiringProfileKey", serviceId, version, credentialRequest));
builder.path(String.format("/v1/profile/%s/%s/%s?credentialType=expiringProfileKey", serviceId, version, credentialRequest));
} else {
builder.setPath(String.format("/v1/profile/%s/%s", serviceId, version));
builder.path(String.format("/v1/profile/%s/%s", serviceId, version));
}
} else {
builder.setPath(String.format("/v1/profile/%s", address.getIdentifier()));
builder.path(String.format("/v1/profile/%s", address.getIdentifier()));
}
builder.addHeaders(AcceptLanguagesUtil.getAcceptLanguageHeader(locale));
builder.headers(Collections.singletonList(AcceptLanguagesUtil.getAcceptLanguageHeader(locale)));
WebSocketRequestMessage requestMessage = builder.build();
@@ -128,12 +128,12 @@ public final class ProfileService {
IdentityCheckRequest request = new IdentityCheckRequest(serviceIdKeyPairs);
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("POST")
.setPath("/v1/profile/identity_check/batch")
.addAllHeaders(Collections.singleton("content-type:application/json"))
.setBody(JsonUtil.toJsonByteString(request));
WebSocketRequestMessage.Builder builder = new WebSocketRequestMessage.Builder()
.id(new SecureRandom().nextLong())
.verb("POST")
.path("/v1/profile/identity_check/batch")
.headers(Collections.singletonList("content-type:application/json"))
.body(JsonUtil.toJsonByteString(request));
ResponseMapper<IdentityCheckResponse> responseMapper = DefaultResponseMapper.getDefault(IdentityCheckResponse.class);

View File

@@ -1,8 +1,5 @@
package org.whispersystems.signalservice.internal.contacts.crypto;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest;
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse;
@@ -16,8 +13,11 @@ import org.whispersystems.signalservice.internal.keybackup.protos.Response;
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest;
import org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okio.ByteString;
public final class KeyBackupCipher {
private KeyBackupCipher() {
@@ -34,17 +34,17 @@ public final class KeyBackupCipher {
{
long now = System.currentTimeMillis();
BackupRequest backupRequest = BackupRequest.newBuilder()
.setServiceId(ByteString.copyFrom(serviceId))
.setBackupId(ByteString.copyFrom(token.getBackupId()))
.setToken(ByteString.copyFrom(token.getToken()))
.setValidFrom(getValidFromSeconds(now))
.setData(ByteString.copyFrom(kbsData))
.setPin(ByteString.copyFrom(kbsAccessKey))
.setTries(tries)
.build();
BackupRequest backupRequest = new BackupRequest.Builder()
.serviceId(ByteString.of(serviceId))
.backupId(ByteString.of(token.getBackupId()))
.token(ByteString.of(token.getToken()))
.validFrom(getValidFromSeconds(now))
.data_(ByteString.of(kbsData))
.pin(ByteString.of(kbsAccessKey))
.tries(tries)
.build();
Request requestData = Request.newBuilder().setBackup(backupRequest).build();
Request requestData = new Request.Builder().backup(backupRequest).build();
return createKeyBackupRequest(requestData, remoteAttestation, "backup");
}
@@ -56,15 +56,15 @@ public final class KeyBackupCipher {
{
long now = System.currentTimeMillis();
RestoreRequest restoreRequest = RestoreRequest.newBuilder()
.setServiceId(ByteString.copyFrom(serviceId))
.setBackupId(ByteString.copyFrom(token.getBackupId()))
.setToken(ByteString.copyFrom(token.getToken()))
.setValidFrom(getValidFromSeconds(now))
.setPin(ByteString.copyFrom(kbsAccessKey))
.build();
RestoreRequest restoreRequest = new RestoreRequest.Builder()
.serviceId(ByteString.of(serviceId))
.backupId(ByteString.of(token.getBackupId()))
.token(ByteString.of(token.getToken()))
.validFrom(getValidFromSeconds(now))
.pin(ByteString.of(kbsAccessKey))
.build();
Request request = Request.newBuilder().setRestore(restoreRequest).build();
Request request = new Request.Builder().restore(restoreRequest).build();
return createKeyBackupRequest(request, remoteAttestation, "restore");
}
@@ -73,47 +73,47 @@ public final class KeyBackupCipher {
RemoteAttestation remoteAttestation,
byte[] serviceId)
{
DeleteRequest deleteRequest = DeleteRequest.newBuilder()
.setServiceId(ByteString.copyFrom(serviceId))
.setBackupId(ByteString.copyFrom(token.getBackupId()))
.build();
DeleteRequest deleteRequest = new DeleteRequest.Builder()
.serviceId(ByteString.of(serviceId))
.backupId(ByteString.of(token.getBackupId()))
.build();
Request request = Request.newBuilder().setDelete(deleteRequest).build();
Request request = new Request.Builder().delete(deleteRequest).build();
return createKeyBackupRequest(request, remoteAttestation, "delete");
}
public static BackupResponse getKeyBackupResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation)
throws InvalidCiphertextException, InvalidProtocolBufferException
throws InvalidCiphertextException, IOException
{
byte[] data = decryptData(response, remoteAttestation);
Response backupResponse = Response.parseFrom(data);
Response backupResponse = Response.ADAPTER.decode(data);
return backupResponse.getBackup();
return backupResponse.backup;
}
public static RestoreResponse getKeyRestoreResponse(KeyBackupResponse response, RemoteAttestation remoteAttestation)
throws InvalidCiphertextException, InvalidProtocolBufferException
throws InvalidCiphertextException, IOException
{
byte[] data = decryptData(response, remoteAttestation);
return Response.parseFrom(data).getRestore();
return Response.ADAPTER.decode(data).restore;
}
public static DeleteResponse getKeyDeleteResponseStatus(KeyBackupResponse response, RemoteAttestation remoteAttestation)
throws InvalidCiphertextException, InvalidProtocolBufferException
throws InvalidCiphertextException, IOException
{
byte[] data = decryptData(response, remoteAttestation);
return DeleteResponse.parseFrom(data);
return DeleteResponse.ADAPTER.decode(data);
}
private static KeyBackupRequest createKeyBackupRequest(Request requestData, RemoteAttestation remoteAttestation, String type) {
byte[] clientKey = remoteAttestation.getKeys().getClientKey();
byte[] aad = remoteAttestation.getRequestId();
AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData.toByteArray());
AESCipher.AESEncryptedResult aesEncryptedResult = AESCipher.encrypt(clientKey, aad, requestData.encode());
return new KeyBackupRequest(aesEncryptedResult.aad, aesEncryptedResult.iv, aesEncryptedResult.data, aesEncryptedResult.mac, type);
}

View File

@@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.module.kotlin.KotlinModule;
import com.google.protobuf.ByteString;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
@@ -35,6 +34,8 @@ import java.util.UUID;
import javax.annotation.Nonnull;
import okio.ByteString;
@SuppressWarnings("unused")
public class JsonUtil {
@@ -57,7 +58,7 @@ public class JsonUtil {
}
public static @Nonnull ByteString toJsonByteString(@Nonnull Object object) {
return ByteString.copyFrom(toJson(object).getBytes());
return ByteString.of(toJson(object).getBytes());
}
public static <T> T fromJson(String json, Class<T> clazz)

View File

@@ -1,7 +1,5 @@
package org.whispersystems.signalservice.internal.websocket;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.libsignal.protocol.logging.Log;
import org.signal.libsignal.protocol.util.Pair;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -52,10 +50,6 @@ import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage;
import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
import static org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
public class WebSocketConnection extends WebSocketListener {
private static final String TAG = WebSocketConnection.class.getSimpleName();
@@ -228,16 +222,16 @@ public class WebSocketConnection extends WebSocketListener {
throw new IOException("No connection!");
}
WebSocketMessage message = WebSocketMessage.newBuilder()
.setType(WebSocketMessage.Type.REQUEST)
.setRequest(request)
.build();
WebSocketMessage message = new WebSocketMessage.Builder()
.type(WebSocketMessage.Type.REQUEST)
.request(request)
.build();
SingleSubject<WebsocketResponse> single = SingleSubject.create();
outgoingRequests.put(request.getId(), new OutgoingRequest(single));
outgoingRequests.put(request.id, new OutgoingRequest(single));
if (!client.send(ByteString.of(message.toByteArray()))) {
if (!client.send(ByteString.of(message.encode()))) {
throw new IOException("Write failed!");
}
@@ -251,12 +245,12 @@ public class WebSocketConnection extends WebSocketListener {
throw new IOException("Connection closed!");
}
WebSocketMessage message = WebSocketMessage.newBuilder()
.setType(WebSocketMessage.Type.RESPONSE)
.setResponse(response)
.build();
WebSocketMessage message = new WebSocketMessage.Builder()
.type(WebSocketMessage.Type.RESPONSE)
.response(response)
.build();
if (!client.send(ByteString.of(message.toByteArray()))) {
if (!client.send(ByteString.of(message.encode()))) {
throw new IOException("Write failed!");
}
}
@@ -265,15 +259,15 @@ public class WebSocketConnection extends WebSocketListener {
if (client != null) {
log( "Sending keep alive...");
long id = System.currentTimeMillis();
byte[] message = WebSocketMessage.newBuilder()
.setType(WebSocketMessage.Type.REQUEST)
.setRequest(WebSocketRequestMessage.newBuilder()
.setId(id)
.setPath("/v1/keepalive")
.setVerb("GET")
.build())
.build()
.toByteArray();
byte[] message = new WebSocketMessage.Builder()
.type(WebSocketMessage.Type.REQUEST)
.request(new WebSocketRequestMessage.Builder()
.id(id)
.path("/v1/keepalive")
.verb("GET")
.build())
.build()
.encode();
keepAlives.add(id);
if (!client.send(ByteString.of(message))) {
throw new IOException("Write failed!");
@@ -292,27 +286,27 @@ public class WebSocketConnection extends WebSocketListener {
@Override
public synchronized void onMessage(WebSocket webSocket, ByteString payload) {
try {
WebSocketMessage message = WebSocketMessage.parseFrom(payload.toByteArray());
WebSocketMessage message = WebSocketMessage.ADAPTER.decode(payload.toByteArray());
if (message.getType().getNumber() == WebSocketMessage.Type.REQUEST_VALUE) {
incomingRequests.add(message.getRequest());
} else if (message.getType().getNumber() == WebSocketMessage.Type.RESPONSE_VALUE) {
OutgoingRequest listener = outgoingRequests.remove(message.getResponse().getId());
if (message.type == WebSocketMessage.Type.REQUEST) {
incomingRequests.add(message.request);
} else if (message.type == WebSocketMessage.Type.RESPONSE) {
OutgoingRequest listener = outgoingRequests.remove(message.response.id);
if (listener != null) {
listener.onSuccess(new WebsocketResponse(message.getResponse().getStatus(),
new String(message.getResponse().getBody().toByteArray()),
message.getResponse().getHeadersList(),
listener.onSuccess(new WebsocketResponse(message.response.status,
new String(message.response.body.toByteArray()),
message.response.headers,
!credentialsProvider.isPresent()));
if (message.getResponse().getStatus() >= 400) {
healthMonitor.onMessageError(message.getResponse().getStatus(), credentialsProvider.isPresent());
if (message.response.status >= 400) {
healthMonitor.onMessageError(message.response.status, credentialsProvider.isPresent());
}
} else if (keepAlives.remove(message.getResponse().getId())) {
healthMonitor.onKeepAliveResponse(message.getResponse().getId(), credentialsProvider.isPresent());
} else if (keepAlives.remove(message.response.id)) {
healthMonitor.onKeepAliveResponse(message.response.id, credentialsProvider.isPresent());
}
}
notifyAll();
} catch (InvalidProtocolBufferException e) {
} catch (IOException e) {
warn(e);
}
}

View File

@@ -8,12 +8,12 @@ package org.signal.cdsi;
message ClientRequest {
// Each ACI/UAK pair is a 32-byte buffer, containing the 16-byte ACI followed
// by its 16-byte UAK.
bytes aci_uak_pairs = 1;
bytes aciUakPairs = 1;
// Each E164 is an 8-byte big-endian number, as 8 bytes.
bytes prev_e164s = 2;
bytes new_e164s = 3;
bytes discard_e164s = 4;
bytes prevE164s = 2;
bytes newE164s = 3;
bytes discardE164s = 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.
@@ -27,11 +27,11 @@ message ClientRequest {
// After receiving a new token from the server, send back a message just
// containing a token_ack.
bool token_ack = 7;
bool tokenAck = 7;
// Request that, if the server allows, both ACI and PNI be returned even
// if the aci_uak_pairs don't match.
bool return_acis_without_uaks = 8;
bool returnAcisWithoutUaks = 8;
}
message ClientResponse {
@@ -47,14 +47,14 @@ message ClientResponse {
// where the additional 2 bytes are the id/type/length additions of the
// protobuf marshaling added to each byte array. This avoids any data
// leakage based on the size of the encrypted output.
bytes e164_pni_aci_triples = 1;
bytes e164PniAciTriples = 1;
// If the user has run out of quota for lookups, they will receive
// a response with just the following field set, followed by a websocket
// closure of type 4008 (RESOURCE_EXHAUSTED). Should they retry exactly
// the same request after the provided number of seconds has passed,
// we expect it should work.
int32 retry_after_secs = 2;
int32 retryAfterSecs = 2;
// A token which allows subsequent calls' rate limiting to discount the
// e164s sent up in this request, only counting those in the next
@@ -64,28 +64,28 @@ message ClientResponse {
// On a successful response to a token_ack request, the number of permits
// that were deducted from the user's rate-limit in order to process the
// request
int32 debug_permits_used = 4;
int32 debugPermitsUsed = 4;
}
message EnclaveLoad {
// If set, before loading any tuples entirely clear the current map,
// zero'ing out all current data.
bool clear_all = 1;
bool clearAll = 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;
bytes e164AciPniUakTuples = 2;
// If non-empty, overwrite the shared token secret with this value.
bytes shared_token_secret = 3;
bytes sharedTokenSecret = 3;
}
message ClientHandshakeStart {
// Public key associated with this server's enclave. For use in test-only
// contexts where attestation is not available
bytes test_only_pubkey = 1;
bytes testonlyPubkey = 1;
// Remote-attestation evidence associated with the public key
bytes evidence = 2;

View File

@@ -23,13 +23,13 @@ message Response {
}
message BackupRequest {
optional bytes service_id = 1;
optional bytes backup_id = 2;
optional bytes token = 3;
optional uint64 valid_from = 4;
optional bytes data = 5;
optional bytes pin = 6;
optional uint32 tries = 7;
optional bytes serviceId = 1;
optional bytes backupId = 2;
optional bytes token = 3;
optional uint64 validFrom = 4;
optional bytes data = 5;
optional bytes pin = 6;
optional uint32 tries = 7;
}
message BackupResponse {
@@ -44,11 +44,11 @@ message BackupResponse {
}
message RestoreRequest {
optional bytes service_id = 1;
optional bytes backup_id = 2;
optional bytes token = 3;
optional uint64 valid_from = 4;
optional bytes pin = 5;
optional bytes serviceId = 1;
optional bytes backupId = 2;
optional bytes token = 3;
optional uint64 validFrom = 4;
optional bytes pin = 5;
}
message RestoreResponse {
@@ -67,8 +67,8 @@ message RestoreResponse {
}
message DeleteRequest {
optional bytes service_id = 1;
optional bytes backup_id = 2;
optional bytes serviceId = 1;
optional bytes backupId = 2;
}
message DeleteResponse {