From: AsamK Date: Sun, 12 Sep 2021 11:13:45 +0000 (+0200) Subject: Request message resend if incoming message can't be decrypted X-Git-Tag: v0.9.0~3 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/62d8873a9288bcfe79d8eb3a2b7b3b467451db72?ds=sidebyside Request message resend if incoming message can't be decrypted --- diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index b74e8660..05700379 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -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 index 00000000..ecd5597d --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java @@ -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 = 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; + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 81aaf0a8..0917a214 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -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; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java index 22915a95..5216b030 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileProvider.java @@ -5,5 +5,5 @@ import org.asamk.signal.manager.storage.recipients.RecipientId; public interface ProfileProvider { - Profile getProfile(RecipientId address); + Profile getProfile(RecipientId recipientId); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 7b37f205..c0953f1f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -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 + ) 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(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientId.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientId.java index 9d22d672..f093ca33 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientId.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientId.java @@ -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; diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 15dbd1af..bc9244f8 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -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 ) {