contactHelper,
attachmentHelper,
syncHelper,
+ this::getRecipientProfile,
jobExecutor);
}
// 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();
--- /dev/null
+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;
+ }
+}
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;
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;
private final ContactHelper contactHelper;
private final AttachmentHelper attachmentHelper;
private final SyncHelper syncHelper;
+ private final ProfileProvider profileProvider;
private final JobExecutor jobExecutor;
public IncomingMessageHandler(
final ContactHelper contactHelper,
final AttachmentHelper attachmentHelper,
final SyncHelper syncHelper,
+ final ProfileProvider profileProvider,
final JobExecutor jobExecutor
) {
this.account = account;
this.contactHelper = contactHelper;
this.attachmentHelper = attachmentHelper;
this.syncHelper = syncHelper;
+ this.profileProvider = profileProvider;
this.jobExecutor = jobExecutor;
}
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;
}
public interface ProfileProvider {
- Profile getProfile(RecipientId address);
+ Profile getProfile(RecipientId 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;
}
}
+ 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();
return id;
}
+ @Override
+ public String toString() {
+ return "RecipientId{" + "id=" + id + '}';
+ }
+
@Override
public boolean equals(final Object o) {
if (this == o) return true;
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;
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.");
}
}
+ 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
) {