mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Initial refactor of the message decryption flow.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package org.whispersystems.signalservice.api.crypto
|
||||
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
|
||||
class EnvelopeMetadata(
|
||||
val sourceServiceId: ServiceId,
|
||||
val sourceE164: String?,
|
||||
val sourceDeviceId: Int,
|
||||
val sealedSender: Boolean,
|
||||
val groupId: ByteArray?,
|
||||
val destinationServiceId: ServiceId
|
||||
)
|
||||
@@ -49,10 +49,12 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
|
||||
import org.whispersystems.signalservice.api.push.ACI;
|
||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
|
||||
import org.whispersystems.signalservice.internal.push.PushTransportDetails;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
|
||||
@@ -144,7 +146,7 @@ public class SignalServiceCipher {
|
||||
{
|
||||
try {
|
||||
if (envelope.hasContent()) {
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getContent());
|
||||
Plaintext plaintext = decryptInternal(envelope.getProto(), envelope.getServerDeliveredTimestamp());
|
||||
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
|
||||
|
||||
SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder()
|
||||
@@ -162,7 +164,39 @@ public class SignalServiceCipher {
|
||||
}
|
||||
}
|
||||
|
||||
private Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext)
|
||||
public SignalServiceCipherResult decrypt(Envelope envelope, long serverDeliveredTimestamp)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
|
||||
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
|
||||
SelfSendException, InvalidMessageStructureException
|
||||
{
|
||||
try {
|
||||
if (envelope.hasContent()) {
|
||||
Plaintext plaintext = decryptInternal(envelope, serverDeliveredTimestamp);
|
||||
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
|
||||
|
||||
return new SignalServiceCipherResult(
|
||||
content,
|
||||
new EnvelopeMetadata(
|
||||
plaintext.metadata.getSender().getServiceId(),
|
||||
plaintext.metadata.getSender().getNumber().orElse(null),
|
||||
plaintext.metadata.getSenderDevice(),
|
||||
plaintext.metadata.isNeedsReceipt(),
|
||||
plaintext.metadata.getGroupId().orElse(null),
|
||||
localAddress.getServiceId()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new InvalidMetadataMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Plaintext decryptInternal(Envelope envelope, long serverDeliveredTimestamp)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException,
|
||||
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
|
||||
@@ -175,30 +209,30 @@ public class SignalServiceCipher {
|
||||
byte[] paddedMessage;
|
||||
SignalServiceMetadata metadata;
|
||||
|
||||
if (!envelope.hasSourceUuid() && !envelope.isUnidentifiedSender()) {
|
||||
if (!envelope.hasSourceUuid() && envelope.getType().getNumber() != Envelope.Type.UNIDENTIFIED_SENDER_VALUE) {
|
||||
throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!");
|
||||
}
|
||||
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice());
|
||||
if (envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE) {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.getContent().toByteArray()));
|
||||
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
|
||||
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress));
|
||||
} else if (envelope.isSignalMessage()) {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid().get(), envelope.getSourceDevice());
|
||||
} else if (envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE) {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
} else if (envelope.isPlaintextContent()) {
|
||||
paddedMessage = new PlaintextContent(ciphertext).getBody();
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
} else if (envelope.isUnidentifiedSender()) {
|
||||
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.getContent().toByteArray()));
|
||||
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
} else if (envelope.getType().getNumber() == Envelope.Type.PLAINTEXT_CONTENT_VALUE) {
|
||||
paddedMessage = new PlaintextContent(envelope.getContent().toByteArray()).getBody();
|
||||
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, false, envelope.getServerGuid(), Optional.empty(), envelope.getDestinationUuid());
|
||||
} else if (envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE) {
|
||||
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().uuid(), localAddress.getNumber().orElse(null), localDeviceId));
|
||||
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp());
|
||||
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.getContent().toByteArray(), envelope.getServerTimestamp());
|
||||
SignalServiceAddress resultAddress = new SignalServiceAddress(ACI.parseOrThrow(result.getSenderUuid()), result.getSenderE164());
|
||||
Optional<byte[]> groupId = result.getGroupId();
|
||||
boolean needsReceipt = true;
|
||||
@@ -213,7 +247,7 @@ public class SignalServiceCipher {
|
||||
}
|
||||
|
||||
paddedMessage = result.getPaddedMessage();
|
||||
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid());
|
||||
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerTimestamp(), serverDeliveredTimestamp, needsReceipt, envelope.getServerGuid(), groupId, envelope.getDestinationUuid());
|
||||
} else {
|
||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||
}
|
||||
@@ -223,24 +257,28 @@ public class SignalServiceCipher {
|
||||
|
||||
return new Plaintext(metadata, data);
|
||||
} catch (DuplicateMessageException e) {
|
||||
throw new ProtocolDuplicateMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolDuplicateMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (LegacyMessageException e) {
|
||||
throw new ProtocolLegacyMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolLegacyMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (InvalidMessageException e) {
|
||||
throw new ProtocolInvalidMessageException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolInvalidMessageException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (InvalidKeyIdException e) {
|
||||
throw new ProtocolInvalidKeyIdException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolInvalidKeyIdException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProtocolInvalidKeyException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolInvalidKeyException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (UntrustedIdentityException e) {
|
||||
throw new ProtocolUntrustedIdentityException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolUntrustedIdentityException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (InvalidVersionException e) {
|
||||
throw new ProtocolInvalidVersionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolInvalidVersionException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
} catch (NoSessionException e) {
|
||||
throw new ProtocolNoSessionException(e, envelope.getSourceIdentifier(), envelope.getSourceDevice());
|
||||
throw new ProtocolNoSessionException(e, envelope.getSourceUuid(), envelope.getSourceDevice());
|
||||
}
|
||||
}
|
||||
|
||||
private static SignalServiceAddress getSourceAddress(Envelope envelope) {
|
||||
return new SignalServiceAddress(ServiceId.parseOrNull(envelope.getSourceUuid()));
|
||||
}
|
||||
|
||||
private static class Plaintext {
|
||||
private final SignalServiceMetadata metadata;
|
||||
private final byte[] data;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.whispersystems.signalservice.api.crypto
|
||||
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||
|
||||
/**
|
||||
* Represents the output of decrypting a [SignalServiceProtos.Envelope] via [SignalServiceCipher.decrypt]
|
||||
*
|
||||
* @param content The [SignalServiceProtos.Content] that was decrypted from the envelope.
|
||||
* @param metadata The decrypted metadata of the envelope. Represents sender information that may have
|
||||
* been encrypted with sealed sender.
|
||||
*/
|
||||
data class SignalServiceCipherResult(
|
||||
val content: SignalServiceProtos.Content,
|
||||
val metadata: EnvelopeMetadata
|
||||
)
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.whispersystems.signalservice.api.messages
|
||||
|
||||
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
|
||||
import org.whispersystems.signalservice.api.InvalidMessageStructureException
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.ReceiptMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.StoryMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage
|
||||
|
||||
/**
|
||||
* Validates an [Envelope] and its decrypted [Content] so that we know the message can be processed safely
|
||||
* down the line.
|
||||
*
|
||||
* Mostly makes sure that UUIDs are valid, required fields are presents, etc.
|
||||
*/
|
||||
object EnvelopeContentValidator {
|
||||
|
||||
fun validate(envelope: Envelope, content: Content): Result {
|
||||
return when {
|
||||
envelope.story && !content.meetsStoryFlagCriteria() -> Result.Invalid("Envelope was flagged as a story, but it did not have any story-related content!")
|
||||
content.hasDataMessage() -> validateDataMessage(envelope, content.dataMessage)
|
||||
content.hasSyncMessage() -> validateSyncMessage(envelope, content.syncMessage)
|
||||
content.hasCallMessage() -> Result.Valid
|
||||
content.hasReceiptMessage() -> validateReceiptMessage(content.receiptMessage)
|
||||
content.hasTypingMessage() -> validateTypingMessage(envelope, content.typingMessage)
|
||||
content.hasDecryptionErrorMessage() -> validateDecryptionErrorMessage(content.decryptionErrorMessage.toByteArray())
|
||||
content.hasStoryMessage() -> validateStoryMessage(content.storyMessage)
|
||||
content.hasPniSignatureMessage() -> Result.Valid
|
||||
content.hasSenderKeyDistributionMessage() -> Result.Valid
|
||||
else -> Result.Invalid("Content is empty!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateDataMessage(envelope: Envelope, dataMessage: DataMessage): Result {
|
||||
if (dataMessage.requiredProtocolVersion > DataMessage.ProtocolVersion.CURRENT_VALUE) {
|
||||
return Result.UnsupportedDataMessage(
|
||||
ourVersion = DataMessage.ProtocolVersion.CURRENT_VALUE,
|
||||
theirVersion = dataMessage.requiredProtocolVersion
|
||||
)
|
||||
}
|
||||
|
||||
if (!dataMessage.hasTimestamp()) {
|
||||
return Result.Invalid("[DataMessage] Missing timestamp!")
|
||||
}
|
||||
|
||||
if (dataMessage.timestamp != envelope.timestamp) {
|
||||
Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}")
|
||||
}
|
||||
|
||||
if (dataMessage.hasQuote() && dataMessage.quote.authorUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[DataMessage] Invalid UUID on quote!")
|
||||
}
|
||||
|
||||
if (dataMessage.contactList.any { it.hasAvatar() && it.avatar.avatar.isPresentAndInvalid() }) {
|
||||
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!")
|
||||
}
|
||||
|
||||
if (dataMessage.previewList.any { it.hasImage() && it.image.isPresentAndInvalid() }) {
|
||||
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!")
|
||||
}
|
||||
|
||||
if (dataMessage.bodyRangesList.any { it.hasMentionUuid() && it.mentionUuid.isNullOrInvalidUuid() }) {
|
||||
return Result.Invalid("[DataMessage] Invalid UUID on body range!")
|
||||
}
|
||||
|
||||
if (dataMessage.hasSticker() && dataMessage.sticker.data.isNullOrInvalid()) {
|
||||
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.sticker!")
|
||||
}
|
||||
|
||||
if (dataMessage.hasReaction()) {
|
||||
if (!dataMessage.reaction.hasTargetSentTimestamp()) {
|
||||
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!")
|
||||
}
|
||||
if (dataMessage.reaction.targetAuthorUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.reaction!")
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMessage.hasDelete() && !dataMessage.delete.hasTargetSentTimestamp()) {
|
||||
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!")
|
||||
}
|
||||
|
||||
if (dataMessage.hasStoryContext() && dataMessage.storyContext.authorUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[DataMessage] Invalid UUID on DataMessage.storyContext!")
|
||||
}
|
||||
|
||||
if (dataMessage.hasGiftBadge()) {
|
||||
if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) {
|
||||
return Result.Invalid("[DataMessage] Missing DataMessage.giftBadge.receiptCredentialPresentation!")
|
||||
}
|
||||
if (!dataMessage.giftBadge.hasReceiptCredentialPresentation()) {
|
||||
try {
|
||||
ReceiptCredentialPresentation(dataMessage.giftBadge.receiptCredentialPresentation.toByteArray())
|
||||
} catch (e: InvalidInputException) {
|
||||
return Result.Invalid("[DataMessage] Invalid DataMessage.giftBadge.receiptCredentialPresentation!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMessage.attachmentsList.any { it.isNullOrInvalid() }) {
|
||||
return Result.Invalid("[DataMessage] Invalid attachments!")
|
||||
}
|
||||
|
||||
return Result.Valid
|
||||
}
|
||||
|
||||
private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage): Result {
|
||||
if (syncMessage.hasSent()) {
|
||||
val validAddress = syncMessage.sent.destinationUuid.isValidUuid()
|
||||
val hasDataGroup = syncMessage.sent.message?.hasGroupV2() ?: false
|
||||
val hasStoryGroup = syncMessage.sent.storyMessage?.hasGroup() ?: false
|
||||
val hasStoryManifest = syncMessage.sent.storyMessageRecipientsList.isNotEmpty()
|
||||
|
||||
if (hasDataGroup) {
|
||||
validateGroupContextV2(syncMessage.sent.message.groupV2, "[SyncMessage.Sent.Message]")?.let { return it }
|
||||
}
|
||||
|
||||
if (hasStoryGroup) {
|
||||
validateGroupContextV2(syncMessage.sent.storyMessage.group, "[SyncMessage.Sent.StoryMessage]")?.let { return it }
|
||||
}
|
||||
|
||||
if (!validAddress && !hasDataGroup && !hasStoryGroup && !hasStoryManifest) {
|
||||
return Result.Invalid("[SyncMessage] No valid destination! Checked the destination, DataMessage.group, StoryMessage.group, and storyMessageRecipientList")
|
||||
}
|
||||
|
||||
for (status in syncMessage.sent.unidentifiedStatusList) {
|
||||
if (status.destinationUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.sent.unidentifiedStatusList!")
|
||||
}
|
||||
}
|
||||
|
||||
return if (syncMessage.sent.hasMessage()) {
|
||||
validateDataMessage(envelope, syncMessage.sent.message)
|
||||
} else if (syncMessage.sent.hasStoryMessage()) {
|
||||
validateStoryMessage(syncMessage.sent.storyMessage)
|
||||
} else {
|
||||
Result.Invalid("[SyncMessage] Empty SyncMessage.sent!")
|
||||
}
|
||||
}
|
||||
|
||||
if (syncMessage.readList.any { it.senderUuid.isNullOrInvalidUuid() }) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.readList!")
|
||||
}
|
||||
|
||||
if (syncMessage.viewedList.any { it.senderUuid.isNullOrInvalidUuid() }) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewList!")
|
||||
}
|
||||
|
||||
if (syncMessage.hasViewOnceOpen() && syncMessage.viewOnceOpen.senderUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.viewOnceOpen!")
|
||||
}
|
||||
|
||||
if (syncMessage.hasVerified() && syncMessage.verified.destinationUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.verified!")
|
||||
}
|
||||
|
||||
if (syncMessage.stickerPackOperationList.any { !it.hasPackId() }) {
|
||||
return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!")
|
||||
}
|
||||
|
||||
if (syncMessage.hasBlocked() && syncMessage.blocked.uuidsList.any { it.isNullOrInvalidUuid() }) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.blocked!")
|
||||
}
|
||||
|
||||
if (syncMessage.hasMessageRequestResponse() && !syncMessage.messageRequestResponse.hasGroupId() && syncMessage.messageRequestResponse.threadUuid.isNullOrInvalidUuid()) {
|
||||
return Result.Invalid("[SyncMessage] Invalid UUID in SyncMessage.messageRequestResponse!")
|
||||
}
|
||||
|
||||
return Result.Valid
|
||||
}
|
||||
|
||||
private fun validateReceiptMessage(receiptMessage: ReceiptMessage): Result {
|
||||
return if (!receiptMessage.hasType()) {
|
||||
Result.Invalid("[ReceiptMessage] Missing type!")
|
||||
} else {
|
||||
Result.Valid
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateTypingMessage(envelope: Envelope, typingMessage: TypingMessage): Result {
|
||||
return if (!typingMessage.hasTimestamp()) {
|
||||
return Result.Invalid("[TypingMessage] Missing timestamp!")
|
||||
} else if (typingMessage.hasTimestamp() && typingMessage.timestamp != envelope.timestamp) {
|
||||
Result.Invalid("[TypingMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${typingMessage.timestamp}")
|
||||
} else if (!typingMessage.hasAction()) {
|
||||
Result.Invalid("[TypingMessage] Missing action!")
|
||||
} else {
|
||||
Result.Valid
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateDecryptionErrorMessage(serializedDecryptionErrorMessage: ByteArray): Result {
|
||||
return try {
|
||||
DecryptionErrorMessage(serializedDecryptionErrorMessage)
|
||||
Result.Valid
|
||||
} catch (e: InvalidMessageStructureException) {
|
||||
Result.Invalid("[DecryptionErrorMessage] Bad decryption error message!", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateStoryMessage(storyMessage: StoryMessage): Result {
|
||||
if (storyMessage.hasGroup()) {
|
||||
validateGroupContextV2(storyMessage.group, "[StoryMessage]")?.let { return it }
|
||||
}
|
||||
|
||||
return Result.Valid
|
||||
}
|
||||
|
||||
private fun AttachmentPointer?.isNullOrInvalid(): Boolean {
|
||||
return this == null || this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET
|
||||
}
|
||||
|
||||
private fun AttachmentPointer?.isPresentAndInvalid(): Boolean {
|
||||
return this != null && this.attachmentIdentifierCase == AttachmentPointer.AttachmentIdentifierCase.ATTACHMENTIDENTIFIER_NOT_SET
|
||||
}
|
||||
|
||||
private fun String?.isValidUuid(): Boolean {
|
||||
return UuidUtil.isUuid(this)
|
||||
}
|
||||
|
||||
private fun String?.isNullOrInvalidUuid(): Boolean {
|
||||
return !UuidUtil.isUuid(this)
|
||||
}
|
||||
|
||||
private fun Content?.meetsStoryFlagCriteria(): Boolean {
|
||||
return when {
|
||||
this == null -> false
|
||||
this.hasSenderKeyDistributionMessage() -> true
|
||||
this.hasStoryMessage() -> true
|
||||
this.hasDataMessage() && this.dataMessage.hasStoryContext() && this.dataMessage.hasGroupV2() -> true
|
||||
this.hasDataMessage() && this.dataMessage.hasDelete() -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateGroupContextV2(groupContext: GroupContextV2, prefix: String): Result.Invalid? {
|
||||
return if (!groupContext.hasMasterKey()) {
|
||||
Result.Invalid("$prefix Missing GV2 master key!")
|
||||
} else if (!groupContext.hasRevision()) {
|
||||
Result.Invalid("$prefix Missing GV2 revision!")
|
||||
} else {
|
||||
try {
|
||||
GroupMasterKey(groupContext.masterKey.toByteArray())
|
||||
null
|
||||
} catch (e: InvalidInputException) {
|
||||
Result.Invalid("$prefix Bad GV2 master key!", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Result {
|
||||
/** Content is valid. */
|
||||
object Valid : Result()
|
||||
|
||||
/** The [DataMessage.requiredProtocolVersion_] is newer than the one we support. */
|
||||
class UnsupportedDataMessage(val ourVersion: Int, val theirVersion: Int) : Result()
|
||||
|
||||
/** The contents of the proto do not match our expectations, e.g. invalid UUIDs, missing required fields, etc. */
|
||||
class Invalid(val reason: String, val throwable: Throwable = Throwable()) : Result()
|
||||
}
|
||||
}
|
||||
@@ -271,6 +271,10 @@ public class SignalServiceEnvelope {
|
||||
return envelope.getReportingToken().toByteArray();
|
||||
}
|
||||
|
||||
public Envelope getProto() {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
private SignalServiceEnvelopeProto.Builder serializeToProto() {
|
||||
SignalServiceEnvelopeProto.Builder builder = SignalServiceEnvelopeProto.newBuilder()
|
||||
.setType(getType())
|
||||
|
||||
@@ -23,6 +23,10 @@ public final class PNI extends ServiceId {
|
||||
return from(UUID.fromString(raw));
|
||||
}
|
||||
|
||||
public static PNI parseOrThrow(byte[] raw) {
|
||||
return from(UuidUtil.parseOrThrow(raw));
|
||||
}
|
||||
|
||||
public static PNI parseOrNull(byte[] raw) {
|
||||
UUID uuid = UuidUtil.parseOrNull(raw);
|
||||
return uuid != null ? from(uuid) : null;
|
||||
|
||||
Reference in New Issue
Block a user