Request message resend if incoming message can't be decrypted

This commit is contained in:
AsamK 2021-09-12 13:13:45 +02:00
parent fbafa75fe2
commit 62d8873a92
7 changed files with 158 additions and 6 deletions

View file

@ -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();

View file

@ -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;
}
}

View 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.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;
} }

View file

@ -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);
} }

View file

@ -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();

View file

@ -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;

View file

@ -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
) { ) {