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

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

View file

@ -5,5 +5,5 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
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.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();

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

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