]> nmode's Git Repositories - signal-cli/commitdiff
Request message resend if incoming message can't be decrypted
authorAsamK <asamk@gmx.de>
Sun, 12 Sep 2021 11:13:45 +0000 (13:13 +0200)
committerAsamK <asamk@gmx.de>
Sun, 12 Sep 2021 11:21:48 +0000 (13:21 +0200)
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java [new file with mode: 0644]
lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
lib/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java
lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientId.java
src/main/java/org/asamk/signal/ReceiveMessageHandler.java

index b74e866008b5ca09950f0421d0a2f15b406b0bf9..0570037964adf38d4a3ee957ee09282f7adb9ac7 100644 (file)
@@ -239,6 +239,7 @@ public class Manager implements Closeable {
                 contactHelper,
                 attachmentHelper,
                 syncHelper,
+                this::getRecipientProfile,
                 jobExecutor);
     }
 
@@ -876,11 +877,11 @@ public class Manager implements Closeable {
                     // store message on disk, before acknowledging receipt to the server
                     cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
                 });
-                logger.debug("New message received from server");
                 if (result.isPresent()) {
                     envelope = result.get();
+                    logger.debug("New message received from server");
                 } else {
-                    // Received indicator that server queue is empty
+                    logger.debug("Received indicator that server queue is empty");
                     handleQueuedActions(queuedActions);
                     queuedActions.clear();
 
diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java
new file mode 100644 (file)
index 0000000..ecd5597
--- /dev/null
@@ -0,0 +1,89 @@
+package org.asamk.signal.manager.actions;
+
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.jobs.Context;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.signal.libsignal.metadata.ProtocolException;
+import org.whispersystems.libsignal.protocol.CiphertextMessage;
+import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
+
+public class SendRetryMessageRequestAction implements HandleAction {
+
+    private final RecipientId recipientId;
+    private final ProtocolException protocolException;
+    private final SignalServiceEnvelope envelope;
+
+    public SendRetryMessageRequestAction(
+            final RecipientId recipientId,
+            final ProtocolException protocolException,
+            final SignalServiceEnvelope envelope
+    ) {
+        this.recipientId = recipientId;
+        this.protocolException = protocolException;
+        this.envelope = envelope;
+    }
+
+    @Override
+    public void execute(Context context) throws Throwable {
+        context.getAccount().getSessionStore().archiveSessions(recipientId);
+
+        int senderDevice = protocolException.getSenderDevice();
+        Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
+                protocolException.getGroupId().get())) : Optional.absent();
+
+        byte[] originalContent;
+        int envelopeType;
+        if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) {
+            final var messageContent = protocolException.getUnidentifiedSenderMessageContent().get();
+            originalContent = messageContent.getContent();
+            envelopeType = messageContent.getType();
+        } else {
+            originalContent = envelope.getContent();
+            envelopeType = envelopeTypeToCiphertextMessageType(envelope.getType());
+        }
+
+        DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent,
+                envelopeType,
+                envelope.getTimestamp(),
+                senderDevice);
+
+        context.getSendHelper().sendRetryReceipt(decryptionErrorMessage, recipientId, groupId);
+    }
+
+    private static int envelopeTypeToCiphertextMessageType(int envelopeType) {
+        switch (envelopeType) {
+            case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE:
+                return CiphertextMessage.PREKEY_TYPE;
+            case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE:
+                return CiphertextMessage.SENDERKEY_TYPE;
+            case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE:
+                return CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
+            case SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE:
+            default:
+                return CiphertextMessage.WHISPER_TYPE;
+        }
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final SendRetryMessageRequestAction that = (SendRetryMessageRequestAction) o;
+
+        if (!recipientId.equals(that.recipientId)) return false;
+        if (!protocolException.equals(that.protocolException)) return false;
+        return envelope.equals(that.envelope);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = recipientId.hashCode();
+        result = 31 * result + protocolException.hashCode();
+        result = 31 * result + envelope.hashCode();
+        return result;
+    }
+}
index 81aaf0a83048f98645a4bfb987324d92e6b8c45b..0917a214d0821c0232612971dee3b39d90314b5f 100644 (file)
@@ -13,6 +13,7 @@ import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
 import org.asamk.signal.manager.actions.SendGroupInfoAction;
 import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
 import org.asamk.signal.manager.actions.SendReceiptAction;
+import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
 import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
 import org.asamk.signal.manager.actions.SendSyncContactsAction;
 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
@@ -23,12 +24,17 @@ import org.asamk.signal.manager.groups.GroupUtils;
 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
+import org.asamk.signal.manager.storage.recipients.Profile;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
 import org.asamk.signal.manager.storage.stickers.Sticker;
 import org.asamk.signal.manager.storage.stickers.StickerPackId;
+import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
+import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
+import org.signal.libsignal.metadata.ProtocolNoSessionException;
 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
+import org.signal.libsignal.metadata.SelfSendException;
 import org.signal.zkgroup.InvalidInputException;
 import org.signal.zkgroup.profiles.ProfileKey;
 import org.slf4j.Logger;
@@ -59,6 +65,7 @@ public final class IncomingMessageHandler {
     private final ContactHelper contactHelper;
     private final AttachmentHelper attachmentHelper;
     private final SyncHelper syncHelper;
+    private final ProfileProvider profileProvider;
     private final JobExecutor jobExecutor;
 
     public IncomingMessageHandler(
@@ -70,6 +77,7 @@ public final class IncomingMessageHandler {
             final ContactHelper contactHelper,
             final AttachmentHelper attachmentHelper,
             final SyncHelper syncHelper,
+            final ProfileProvider profileProvider,
             final JobExecutor jobExecutor
     ) {
         this.account = account;
@@ -80,6 +88,7 @@ public final class IncomingMessageHandler {
         this.contactHelper = contactHelper;
         this.attachmentHelper = attachmentHelper;
         this.syncHelper = syncHelper;
+        this.profileProvider = profileProvider;
         this.jobExecutor = jobExecutor;
     }
 
@@ -131,11 +140,24 @@ public final class IncomingMessageHandler {
                 actions.add(new RetrieveProfileAction(recipientId));
                 exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
                         e.getSenderDevice());
-            } catch (ProtocolInvalidMessageException e) {
+            } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
                 final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
-                logger.debug("Received invalid message, queuing renew session action.");
-                actions.add(new RenewSessionAction(sender));
+                final var senderProfile = profileProvider.getProfile(sender);
+                final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
+                if (senderProfile != null
+                        && senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
+                        && selfProfile != null
+                        && selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
+                    logger.debug("Received invalid message, requesting message resend.");
+                    actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
+                } else {
+                    logger.debug("Received invalid message, queuing renew session action.");
+                    actions.add(new RenewSessionAction(sender));
+                }
                 exception = e;
+            } catch (SelfSendException e) {
+                logger.debug("Dropping unidentified message from self.");
+                return new Pair<>(List.of(), null);
             } catch (Exception e) {
                 exception = e;
             }
index 22915a95cf99d744380ea3ec862000ea53deeece..5216b030373db5118df2dccf3cf9812f9087c9c2 100644 (file)
@@ -5,5 +5,5 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
 
 public interface ProfileProvider {
 
-    Profile getProfile(RecipientId address);
+    Profile getProfile(RecipientId recipientId);
 }
index 7b37f205822df663368c86093b109051bed41bcd..c0953f1fa27be3deb94f0a95fb3a13cb32bb4670 100644 (file)
@@ -13,6 +13,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
 import org.whispersystems.signalservice.api.crypto.ContentHint;
@@ -156,6 +157,25 @@ public class SendHelper {
         }
     }
 
+    public void sendRetryReceipt(
+            DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
+    ) throws IOException, UntrustedIdentityException {
+        var messageSender = dependencies.getMessageSender();
+        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
+        logger.debug("Sending retry receipt for {} to {}, device: {}",
+                errorMessage.getTimestamp(),
+                recipientId,
+                errorMessage.getDeviceId());
+        try {
+            messageSender.sendRetryReceipt(address,
+                    unidentifiedAccessHelper.getAccessFor(recipientId),
+                    groupId.transform(GroupId::serialize),
+                    errorMessage);
+        } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
+            throw new UntrustedIdentityException(address);
+        }
+    }
+
     public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
         var messageSender = dependencies.getMessageSender();
 
index 9d22d672dcf428b5591c3c5e42c5d81fcbc3ab25..f093ca334515a517ebca3a6ae6ac8950d7f44a28 100644 (file)
@@ -16,6 +16,11 @@ public class RecipientId {
         return id;
     }
 
+    @Override
+    public String toString() {
+        return "RecipientId{" + "id=" + id + '}';
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) return true;
index 15dbd1af0b0047d8703cb92a63b94b4dd4edbdc0..bc9244f8b5822553d40f33076628bcc088769909 100644 (file)
@@ -8,6 +8,7 @@ import org.asamk.signal.manager.groups.GroupUtils;
 import org.asamk.signal.util.DateUtils;
 import org.asamk.signal.util.Util;
 import org.slf4j.helpers.MessageFormatter;
+import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
@@ -113,6 +114,11 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
                     var typingMessage = content.getTypingMessage().get();
                     printTypingMessage(writer.indentedWriter(), typingMessage);
                 }
+                if (content.getDecryptionErrorMessage().isPresent()) {
+                    writer.println("Received a decryption error message (resend request)");
+                    var decryptionErrorMessage = content.getDecryptionErrorMessage().get();
+                    printDecryptionErrorMessage(writer.indentedWriter(), decryptionErrorMessage);
+                }
             }
         } else {
             writer.println("Unknown message received.");
@@ -215,6 +221,15 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         }
     }
 
+    private void printDecryptionErrorMessage(
+            final PlainTextWriter writer, final DecryptionErrorMessage decryptionErrorMessage
+    ) {
+        writer.println("Device id: {}", decryptionErrorMessage.getDeviceId());
+        writer.println("Timestamp: {}", DateUtils.formatTimestamp(decryptionErrorMessage.getTimestamp()));
+        writer.println("Ratchet key: {}",
+                decryptionErrorMessage.getRatchetKey().isPresent() ? "is present" : "not present");
+    }
+
     private void printReceiptMessage(
             final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
     ) {