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 42a1bff159..915d5cb155 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageDecryptor.kt @@ -133,6 +133,11 @@ object MessageDecryptor { if (validationResult is EnvelopeContentValidator.Result.Invalid) { Log.w(TAG, "${logPrefix(envelope, cipherResult)} Invalid content! ${validationResult.reason}", validationResult.throwable) + + if (FeatureFlags.internalUser()) { + postInvalidMessageNotification(context, validationResult.reason) + } + return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList()) } @@ -182,7 +187,7 @@ object MessageDecryptor { Log.w(TAG, "${logPrefix(envelope, e)} Decryption error!", e, true) if (FeatureFlags.internalUser()) { - postErrorNotification(context) + postDecryptionErrorNotification(context) } if (FeatureFlags.retryReceipts()) { @@ -324,11 +329,22 @@ object MessageDecryptor { } } - private fun postErrorNotification(context: Context) { + private fun postDecryptionErrorNotification(context: Context) { val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(context.getString(R.string.MessageDecryptionUtil_failed_to_decrypt_message)) - .setContentText(context.getString(R.string.MessageDecryptionUtil_tap_to_send_a_debug_log)) + .setContentTitle("[Internal-only] Failed to decrypt a message!") + .setContentText("Tap to send a debug log") + .setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable())) + .build() + + NotificationManagerCompat.from(context).notify(NotificationIds.INTERNAL_ERROR, notification) + } + + private fun postInvalidMessageNotification(context: Context, message: String) { + val notification: Notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().FAILURES) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle("[Internal-only] Received an invalid message!") + .setContentText("$message Tap to send a debug log.") .setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, SubmitDebugLogActivity::class.java), PendingIntentFlags.mutable())) .build() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43df93a065..b890bc3e38 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1223,10 +1223,6 @@ All media Camera - - Failed to decrypt message - Tap to send a debug log - Unknown Received a message encrypted using an old version of Signal that is no longer supported. Please ask the sender to update to the most recent version and resend the message. diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt index 49641756e3..de3acb60c0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/EnvelopeContentValidator.kt @@ -26,6 +26,14 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Typing object EnvelopeContentValidator { fun validate(envelope: Envelope, content: Content): Result { + if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) { + val result: Result? = createPlaintextResultIfInvalid(content) + + if (result != null) { + return 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) @@ -288,6 +296,47 @@ object EnvelopeContentValidator { } } + private fun createPlaintextResultIfInvalid(content: Content): Result? { + val errors: MutableList = mutableListOf() + + if (!content.hasDecryptionErrorMessage()) { + errors += "Missing DecryptionErrorMessage" + } + if (content.hasStoryMessage()) { + errors += "Unexpected StoryMessage" + } + if (content.hasSenderKeyDistributionMessage()) { + errors += "Unexpected SenderKeyDistributionMessage" + } + if (content.hasCallMessage()) { + errors += "Unexpected CallMessage" + } + if (content.hasEditMessage()) { + errors += "Unexpected EditMessage" + } + if (content.hasNullMessage()) { + errors += "Unexpected NullMessage" + } + if (content.hasPniSignatureMessage()) { + errors += "Unexpected PniSignatureMessage" + } + if (content.hasReceiptMessage()) { + errors += "Unexpected ReceiptMessage" + } + if (content.hasSyncMessage()) { + errors += "Unexpected SyncMessage" + } + if (content.hasTypingMessage()) { + errors += "Unexpected TypingMessage" + } + + return if (errors.isNotEmpty()) { + Result.Invalid("Invalid PLAINTEXT_CONTENT! Errors: $errors") + } else { + null + } + } + private fun validateGroupContextV2(groupContext: GroupContextV2, prefix: String): Result.Invalid? { return if (!groupContext.hasMasterKey()) { Result.Invalid("$prefix Missing GV2 master key!")