Use both envelope.type and ciphertextMessageType in the validator.

This commit is contained in:
Greyson Parrelli
2026-01-28 12:43:59 -05:00
parent 0a572153f0
commit d9dba89781
7 changed files with 179 additions and 33 deletions

View File

@@ -4,6 +4,7 @@ import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.core.models.ServiceId
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.message.CiphertextMessage
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.groups
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupChangeBusyException
@@ -47,7 +48,8 @@ class PushProcessMessageJob private constructor(
sourceDeviceId = metadata.sourceDeviceId,
sealedSender = metadata.sealedSender,
groupId = if (metadata.groupId != null) metadata.groupId!!.toByteString() else null,
destinationServiceId = ByteString.of(*metadata.destinationServiceId.toByteArray())
destinationServiceId = ByteString.of(*metadata.destinationServiceId.toByteArray()),
ciphertextMessageType = metadata.ciphertextMessageType
),
serverDeliveredTimestamp = serverDeliveredTimestamp
).encode()
@@ -84,7 +86,8 @@ class PushProcessMessageJob private constructor(
sourceDeviceId = completeMessage.metadata.sourceDeviceId,
sealedSender = completeMessage.metadata.sealedSender,
groupId = completeMessage.metadata.groupId?.toByteArray(),
destinationServiceId = ServiceId.parseOrThrow(completeMessage.metadata.destinationServiceId.toByteArray())
destinationServiceId = ServiceId.parseOrThrow(completeMessage.metadata.destinationServiceId.toByteArray()),
ciphertextMessageType = completeMessage.metadata.ciphertextMessageType ?: CiphertextMessage.WHISPER_TYPE
),
serverDeliveredTimestamp = completeMessage.serverDeliveredTimestamp
)

View File

@@ -177,7 +177,7 @@ object MessageDecryptor {
Log.d(TAG, "${logPrefix(envelope, cipherResult)} Successfully decrypted the envelope in ${(endTimeNanos - startTimeNanos).nanoseconds.toDouble(DurationUnit.MILLISECONDS).roundedString(2)} ms (GUID ${UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary)}). Delivery latency: ${serverDeliveredTimestamp - envelope.serverTimestamp!!} ms, Urgent: ${envelope.urgent}")
val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content, SignalStore.account.aci!!)
val validationResult: EnvelopeContentValidator.Result = EnvelopeContentValidator.validate(envelope, cipherResult.content, SignalStore.account.aci!!, cipherResult.metadata.ciphertextMessageType)
if (validationResult is EnvelopeContentValidator.Result.Invalid) {
Log.w(TAG, "${logPrefix(envelope, cipherResult)} Invalid content! ${validationResult.reason}", validationResult.throwable)

View File

@@ -8,5 +8,6 @@ class EnvelopeMetadata(
val sourceDeviceId: Int,
val sealedSender: Boolean,
val groupId: ByteArray?,
val destinationServiceId: ServiceId
val destinationServiceId: ServiceId,
val ciphertextMessageType: Int
)

View File

@@ -153,7 +153,8 @@ public class SignalServiceCipher {
plaintext.metadata.getSenderDevice(),
plaintext.metadata.isNeedsReceipt(),
plaintext.metadata.getGroupId().orElse(null),
localAddress.getServiceId()
localAddress.getServiceId(),
plaintext.getCiphertextMessageType()
)
);
} else {
@@ -180,6 +181,7 @@ public class SignalServiceCipher {
byte[] paddedMessage;
SignalServiceMetadata metadata;
int ciphertextMessageType;
if (sourceServiceId == null && envelope.type != Envelope.Type.UNIDENTIFIED_SENDER) {
throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!");
@@ -189,19 +191,22 @@ public class SignalServiceCipher {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.PREKEY_TYPE;
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress));
} else if (envelope.type == Envelope.Type.CIPHERTEXT) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.WHISPER_TYPE;
} else if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) {
paddedMessage = new PlaintextContent(envelope.content.toByteArray()).getBody();
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
paddedMessage = new PlaintextContent(envelope.content.toByteArray()).getBody();
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
ciphertextMessageType = CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
} else if (envelope.type == Envelope.Type.UNIDENTIFIED_SENDER) {
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId));
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.content.toByteArray(), envelope.serverTimestamp);
@@ -214,7 +219,9 @@ public class SignalServiceCipher {
needsReceipt = false;
}
if (result.getCiphertextMessageType() == CiphertextMessage.PREKEY_TYPE) {
ciphertextMessageType = result.getCiphertextMessageType();
if (ciphertextMessageType == CiphertextMessage.PREKEY_TYPE) {
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(new SignalProtocolAddress(result.getSenderUuid(), result.getDeviceId())));
}
@@ -227,7 +234,7 @@ public class SignalServiceCipher {
PushTransportDetails transportDetails = new PushTransportDetails();
byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage);
return new Plaintext(metadata, data);
return new Plaintext(metadata, data, ciphertextMessageType);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (LegacyMessageException e) {
@@ -253,11 +260,13 @@ public class SignalServiceCipher {
private static class Plaintext {
private final SignalServiceMetadata metadata;
private final byte[] data;
private final byte[] data;
private final int ciphertextMessageType;
private Plaintext(SignalServiceMetadata metadata, byte[] data) {
this.metadata = metadata;
this.data = data;
private Plaintext(SignalServiceMetadata metadata, byte[] data, int ciphertextMessageType) {
this.metadata = metadata;
this.data = data;
this.ciphertextMessageType = ciphertextMessageType;
}
public SignalServiceMetadata getMetadata() {
@@ -267,5 +276,9 @@ public class SignalServiceCipher {
public byte[] getData() {
return data;
}
public int getCiphertextMessageType() {
return ciphertextMessageType;
}
}
}

View File

@@ -3,6 +3,7 @@ package org.whispersystems.signalservice.api.messages
import okio.ByteString
import org.signal.core.models.ServiceId
import org.signal.core.models.ServiceId.ACI
import org.signal.libsignal.protocol.message.CiphertextMessage
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
import org.signal.libsignal.protocol.message.SenderKeyDistributionMessage
import org.signal.libsignal.zkgroup.InvalidInputException
@@ -33,8 +34,8 @@ object EnvelopeContentValidator {
private const val MAX_POLL_CHARACTER_LENGTH = 100
private const val MIN_POLL_OPTIONS = 2
fun validate(envelope: Envelope, content: Content, localAci: ACI): Result {
if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) {
fun validate(envelope: Envelope, content: Content, localAci: ACI, ciphertextMessageType: Int): Result {
if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT || ciphertextMessageType == CiphertextMessage.PLAINTEXT_CONTENT_TYPE) {
validatePlaintextContent(content)?.let { return it }
}

View File

@@ -11,12 +11,13 @@ package signalservice;
option java_package = "org.whispersystems.signalservice.api.crypto.protos";
message EnvelopeMetadata {
required bytes sourceServiceId = 1;
optional string sourceE164 = 2;
required int32 sourceDeviceId = 3;
required bool sealedSender = 4;
optional bytes groupId = 5;
required bytes destinationServiceId = 6;
required bytes sourceServiceId = 1;
optional string sourceE164 = 2;
required int32 sourceDeviceId = 3;
required bool sealedSender = 4;
optional bytes groupId = 5;
required bytes destinationServiceId = 6;
optional int32 ciphertextMessageType = 7;
}
message CompleteMessage {

View File

@@ -8,6 +8,8 @@ package org.whispersystems.signalservice.api.messages
import okio.ByteString.Companion.toByteString
import org.junit.Test
import org.signal.core.models.ServiceId
import org.signal.libsignal.protocol.message.CiphertextMessage
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
@@ -30,7 +32,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -45,7 +47,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -61,7 +63,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -77,7 +79,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -93,7 +95,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -108,7 +110,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -120,7 +122,7 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@@ -134,7 +136,132 @@ class EnvelopeContentValidatorTest {
)
)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI)
val result = EnvelopeContentValidator.validate(Envelope(), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - plaintext content via envelope type with valid decryption error message is valid`() {
val envelope = Envelope(
type = Envelope.Type.PLAINTEXT_CONTENT
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage()
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
@Test
fun `validate - plaintext content via ciphertext message type (sealed sender) with valid decryption error message is valid`() {
val envelope = Envelope(
type = Envelope.Type.UNIDENTIFIED_SENDER
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage()
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
@Test
fun `validate - plaintext content via envelope type with unexpected DataMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.PLAINTEXT_CONTENT,
timestamp = 1234
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
dataMessage = DataMessage(timestamp = 1234)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - plaintext content via ciphertext message type (sealed sender) with unexpected DataMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.UNIDENTIFIED_SENDER,
timestamp = 1234
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
dataMessage = DataMessage(timestamp = 1234)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - plaintext content via envelope type without DecryptionErrorMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.PLAINTEXT_CONTENT
)
val content = Content()
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - plaintext content via ciphertext message type (sealed sender) without DecryptionErrorMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.UNIDENTIFIED_SENDER
)
val content = Content()
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - plaintext content with SyncMessage is invalid`() {
val envelope = Envelope(
type = Envelope.Type.PLAINTEXT_CONTENT
)
val content = Content(
decryptionErrorMessage = createValidDecryptionErrorMessage(),
syncMessage = org.whispersystems.signalservice.internal.push.SyncMessage()
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.PLAINTEXT_CONTENT_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - regular encrypted message is not subject to plaintext validation`() {
val envelope = Envelope(
type = Envelope.Type.CIPHERTEXT,
timestamp = 1234
)
val content = Content(
dataMessage = DataMessage(timestamp = 1234)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
private fun createValidDecryptionErrorMessage(): okio.ByteString {
val minimalSenderKeyContent = ByteArray(64)
val decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(
minimalSenderKeyContent,
CiphertextMessage.SENDERKEY_TYPE,
System.currentTimeMillis(),
1
)
return decryptionErrorMessage.serialize().toByteString()
}
}