Add support for endpoint checking prekey consistency.

This commit is contained in:
Greyson Parrelli
2024-02-26 15:59:05 -05:00
committed by Alex Hart
parent 09b0f15294
commit e1067e30de
8 changed files with 191 additions and 3 deletions

View File

@@ -30,6 +30,7 @@ import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.keys.KeysApi;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.payments.CurrencyConversions;
@@ -864,6 +865,10 @@ public class SignalServiceAccountManager {
return ArchiveApi.create(pushServiceSocket, configuration.getBackupServerPublicParams(), credentials.getAci());
}
public KeysApi getKeysApi() {
return KeysApi.create(pushServiceSocket);
}
public AuthCredentials getPaymentsAuthorization() throws IOException {
return pushServiceSocket.getPaymentsAuthorization();
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.keys
import org.signal.core.util.toByteArray
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.protocol.kem.KEMPublicKey
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.push.ServiceIdType
import org.whispersystems.signalservice.internal.push.PushServiceSocket
import java.security.MessageDigest
/**
* Contains APIs for interacting with /keys endpoints on the service.
*/
class KeysApi(private val pushServiceSocket: PushServiceSocket) {
companion object {
@JvmStatic
fun create(pushServiceSocket: PushServiceSocket): KeysApi {
return KeysApi(pushServiceSocket)
}
}
/**
* Checks to see if our local view of our repeated-use prekeys matches the server's view. It's an all-or-nothing match, and no details can be given beyond
* whether or not everything perfectly matches or not.
*
* Status codes:
* - 200: Everything matches
* - 409: Something doesn't match
*/
fun checkRepeatedUseKeys(
serviceIdType: ServiceIdType,
identityKey: IdentityKey,
signedPreKeyId: Int,
signedPreKey: ECPublicKey,
lastResortKyberKeyId: Int,
lastResortKyberKey: KEMPublicKey
): NetworkResult<Unit> {
val digest: MessageDigest = MessageDigest.getInstance("SHA-256").apply {
update(identityKey.serialize())
update(signedPreKeyId.toLong().toByteArray())
update(signedPreKey.serialize())
update(lastResortKyberKeyId.toLong().toByteArray())
update(lastResortKyberKey.serialize())
}
return NetworkResult.fromFetch {
pushServiceSocket.checkRepeatedUsePreKeys(serviceIdType, digest.digest())
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import org.signal.core.util.Base64
/**
* Deserializes any valid base64 (regardless of padding or url-safety) into a ByteArray.
*/
class ByteArrayDeserializerBase64 : JsonDeserializer<ByteArray>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ByteArray {
return Base64.decode(p.valueAsString)
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.signal.core.util.Base64
/**
* JSON serializer to encode a ByteArray as a base64 string without padding.
*/
class ByteArraySerializerBase64NoPadding : JsonSerializer<ByteArray>() {
override fun serialize(value: ByteArray, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(Base64.encodeWithoutPadding(value))
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonSerialize
/**
* Request body to check if our prekeys match what's on the service.
*/
class CheckRepeatedUsedPreKeysRequest(
@JsonProperty
val identityType: String,
@JsonProperty
@JsonSerialize(using = ByteArraySerializerBase64NoPadding::class)
val digest: ByteArray
)

View File

@@ -231,6 +231,8 @@ public class PushServiceSocket {
private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s";
private static final String PREKEY_PATH = "/v2/keys?identity=%s";
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s?pq=true";
private static final String PREKEY_CHECK_PATH = "/v2/keys/check";
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s";
@@ -874,6 +876,17 @@ public class PushServiceSocket {
}
}
public void checkRepeatedUsePreKeys(ServiceIdType serviceIdType, byte[] digest) throws IOException {
String body = JsonUtil.toJson(new CheckRepeatedUsedPreKeysRequest(serviceIdType.toString(), digest));
makeServiceRequest(PREKEY_CHECK_PATH, "POST", body, NO_HEADERS, (responseCode, body1) -> {
// Must override this handling because otherwise code assumes a device mismatch error
if (responseCode == 409) {
throw new NonSuccessfulResponseCodeException(409);
}
}, Optional.empty());
}
public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
throws IOException, MissingConfigurationException
{