mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
Request message resend if incoming message can't be decrypted
This commit is contained in:
parent
fbafa75fe2
commit
62d8873a92
7 changed files with 158 additions and 6 deletions
|
@ -239,6 +239,7 @@ public class Manager implements Closeable {
|
||||||
contactHelper,
|
contactHelper,
|
||||||
attachmentHelper,
|
attachmentHelper,
|
||||||
syncHelper,
|
syncHelper,
|
||||||
|
this::getRecipientProfile,
|
||||||
jobExecutor);
|
jobExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,11 +877,11 @@ public class Manager implements Closeable {
|
||||||
// store message on disk, before acknowledging receipt to the server
|
// store message on disk, before acknowledging receipt to the server
|
||||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||||
});
|
});
|
||||||
logger.debug("New message received from server");
|
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
envelope = result.get();
|
envelope = result.get();
|
||||||
|
logger.debug("New message received from server");
|
||||||
} else {
|
} else {
|
||||||
// Received indicator that server queue is empty
|
logger.debug("Received indicator that server queue is empty");
|
||||||
handleQueuedActions(queuedActions);
|
handleQueuedActions(queuedActions);
|
||||||
queuedActions.clear();
|
queuedActions.clear();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
|
||||||
import org.asamk.signal.manager.actions.SendGroupInfoAction;
|
import org.asamk.signal.manager.actions.SendGroupInfoAction;
|
||||||
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
|
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
|
||||||
import org.asamk.signal.manager.actions.SendReceiptAction;
|
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.SendSyncBlockedListAction;
|
||||||
import org.asamk.signal.manager.actions.SendSyncContactsAction;
|
import org.asamk.signal.manager.actions.SendSyncContactsAction;
|
||||||
import org.asamk.signal.manager.actions.SendSyncGroupsAction;
|
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.jobs.RetrieveStickerPackJob;
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
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.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.asamk.signal.manager.storage.stickers.Sticker;
|
import org.asamk.signal.manager.storage.stickers.Sticker;
|
||||||
import org.asamk.signal.manager.storage.stickers.StickerPackId;
|
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.ProtocolInvalidMessageException;
|
||||||
|
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||||
|
import org.signal.libsignal.metadata.SelfSendException;
|
||||||
import org.signal.zkgroup.InvalidInputException;
|
import org.signal.zkgroup.InvalidInputException;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -59,6 +65,7 @@ public final class IncomingMessageHandler {
|
||||||
private final ContactHelper contactHelper;
|
private final ContactHelper contactHelper;
|
||||||
private final AttachmentHelper attachmentHelper;
|
private final AttachmentHelper attachmentHelper;
|
||||||
private final SyncHelper syncHelper;
|
private final SyncHelper syncHelper;
|
||||||
|
private final ProfileProvider profileProvider;
|
||||||
private final JobExecutor jobExecutor;
|
private final JobExecutor jobExecutor;
|
||||||
|
|
||||||
public IncomingMessageHandler(
|
public IncomingMessageHandler(
|
||||||
|
@ -70,6 +77,7 @@ public final class IncomingMessageHandler {
|
||||||
final ContactHelper contactHelper,
|
final ContactHelper contactHelper,
|
||||||
final AttachmentHelper attachmentHelper,
|
final AttachmentHelper attachmentHelper,
|
||||||
final SyncHelper syncHelper,
|
final SyncHelper syncHelper,
|
||||||
|
final ProfileProvider profileProvider,
|
||||||
final JobExecutor jobExecutor
|
final JobExecutor jobExecutor
|
||||||
) {
|
) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
|
@ -80,6 +88,7 @@ public final class IncomingMessageHandler {
|
||||||
this.contactHelper = contactHelper;
|
this.contactHelper = contactHelper;
|
||||||
this.attachmentHelper = attachmentHelper;
|
this.attachmentHelper = attachmentHelper;
|
||||||
this.syncHelper = syncHelper;
|
this.syncHelper = syncHelper;
|
||||||
|
this.profileProvider = profileProvider;
|
||||||
this.jobExecutor = jobExecutor;
|
this.jobExecutor = jobExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,11 +140,24 @@ public final class IncomingMessageHandler {
|
||||||
actions.add(new RetrieveProfileAction(recipientId));
|
actions.add(new RetrieveProfileAction(recipientId));
|
||||||
exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
|
exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
|
||||||
e.getSenderDevice());
|
e.getSenderDevice());
|
||||||
} catch (ProtocolInvalidMessageException e) {
|
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
|
||||||
final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
|
final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
|
||||||
logger.debug("Received invalid message, queuing renew session action.");
|
final var senderProfile = profileProvider.getProfile(sender);
|
||||||
actions.add(new RenewSessionAction(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;
|
exception = e;
|
||||||
|
} catch (SelfSendException e) {
|
||||||
|
logger.debug("Dropping unidentified message from self.");
|
||||||
|
return new Pair<>(List.of(), null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
public interface ProfileProvider {
|
public interface ProfileProvider {
|
||||||
|
|
||||||
Profile getProfile(RecipientId address);
|
Profile getProfile(RecipientId recipientId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
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 {
|
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
|
||||||
var messageSender = dependencies.getMessageSender();
|
var messageSender = dependencies.getMessageSender();
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class RecipientId {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecipientId{" + "id=" + id + '}';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.asamk.signal.manager.groups.GroupUtils;
|
||||||
import org.asamk.signal.util.DateUtils;
|
import org.asamk.signal.util.DateUtils;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
import org.slf4j.helpers.MessageFormatter;
|
import org.slf4j.helpers.MessageFormatter;
|
||||||
|
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
@ -113,6 +114,11 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
var typingMessage = content.getTypingMessage().get();
|
var typingMessage = content.getTypingMessage().get();
|
||||||
printTypingMessage(writer.indentedWriter(), typingMessage);
|
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 {
|
} else {
|
||||||
writer.println("Unknown message received.");
|
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(
|
private void printReceiptMessage(
|
||||||
final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
|
final PlainTextWriter writer, final SignalServiceReceiptMessage receiptMessage
|
||||||
) {
|
) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue