diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt index 502c24434f..8314b89b4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.kt @@ -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 ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt index 32adc67be5..9c3e15fa83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -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) diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt index b1d5b9801f..9388017c35 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/EnvelopeMetadata.kt @@ -8,5 +8,6 @@ class EnvelopeMetadata( val sourceDeviceId: Int, val sealedSender: Boolean, val groupId: ByteArray?, - val destinationServiceId: ServiceId + val destinationServiceId: ServiceId, + val ciphertextMessageType: Int ) diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index 6cf3df2b16..b6b5939d14 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -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; + } } } diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt index 9376b490c8..41abbace5b 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -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 } } diff --git a/lib/libsignal-service/src/main/protowire/MessageProcessing.proto b/lib/libsignal-service/src/main/protowire/MessageProcessing.proto index a5cf412e3d..9589c6818d 100644 --- a/lib/libsignal-service/src/main/protowire/MessageProcessing.proto +++ b/lib/libsignal-service/src/main/protowire/MessageProcessing.proto @@ -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 { diff --git a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt index 57f080829c..27275ecd5f 100644 --- a/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt +++ b/lib/libsignal-service/src/test/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidatorTest.kt @@ -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() + } }