]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
Update libsignal-service-java
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / IncomingMessageHandler.java
index 1d4678fa1d936016d6d14a82962fc0134c71268c..dd9cb38fa5e9dd431b3216ffc699d87814c8fb85 100644 (file)
@@ -1,16 +1,13 @@
 package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.SignalDependencies;
 import org.asamk.signal.manager.actions.HandleAction;
 import org.asamk.signal.manager.actions.RefreshPreKeysAction;
 import org.asamk.signal.manager.actions.RenewSessionAction;
 import org.asamk.signal.manager.actions.ResendMessageAction;
 import org.asamk.signal.manager.actions.RetrieveProfileAction;
-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.SendPniIdentityKeyAction;
 import org.asamk.signal.manager.actions.SendProfileKeyAction;
 import org.asamk.signal.manager.actions.SendReceiptAction;
 import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
@@ -19,53 +16,68 @@ import org.asamk.signal.manager.actions.SendSyncConfigurationAction;
 import org.asamk.signal.manager.actions.SendSyncContactsAction;
 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
 import org.asamk.signal.manager.actions.SendSyncKeysAction;
+import org.asamk.signal.manager.actions.SyncStorageDataAction;
 import org.asamk.signal.manager.actions.UpdateAccountAttributesAction;
+import org.asamk.signal.manager.api.GroupId;
+import org.asamk.signal.manager.api.GroupNotFoundException;
 import org.asamk.signal.manager.api.MessageEnvelope;
 import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.api.ReceiveConfig;
 import org.asamk.signal.manager.api.StickerPackId;
 import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.api.UntrustedIdentityException;
-import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.internal.SignalDependencies;
 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.RecipientAddress;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.stickers.Sticker;
-import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.storage.stickers.StickerPack;
 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.libsignal.protocol.SignalProtocolAddress;
+import org.signal.libsignal.protocol.InvalidMessageException;
+import org.signal.libsignal.protocol.UsePqRatchet;
+import org.signal.libsignal.protocol.groups.GroupSessionBuilder;
 import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
 import org.signal.libsignal.zkgroup.InvalidInputException;
 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.InvalidMessageStructureException;
+import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder;
+import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult;
+import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
+import org.whispersystems.signalservice.api.messages.SignalServicePniSignatureMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
+import org.whispersystems.signalservice.api.push.ServiceId;
+import org.whispersystems.signalservice.api.push.ServiceId.ACI;
+import org.whispersystems.signalservice.api.push.ServiceIdType;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.internal.push.Envelope;
+import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 public final class IncomingMessageHandler {
 
-    private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
+    private static final Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
 
     private final SignalAccount account;
     private final SignalDependencies dependencies;
@@ -90,12 +102,20 @@ public final class IncomingMessageHandler {
         SignalServiceContent content = null;
         if (!envelope.isReceipt()) {
             account.getIdentityKeyStore().setRetryingDecryption(true);
+            final var destination = getDestination(envelope).serviceId();
             try {
-                content = dependencies.getCipher().decrypt(envelope);
+                final var cipherResult = dependencies.getCipher(destination == null
+                                || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
+                        .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO);
+                content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp());
+                if (content == null) {
+                    return new Pair<>(List.of(), null);
+                }
             } catch (ProtocolUntrustedIdentityException e) {
                 final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
                 final var exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
-                        .resolveRecipientAddress(recipientId), e.getSenderDevice());
+                        .resolveRecipientAddress(recipientId)
+                        .toApiRecipientAddress(), e.getSenderDevice());
                 return new Pair<>(List.of(), exception);
             } catch (Exception e) {
                 return new Pair<>(List.of(), e);
@@ -113,40 +133,50 @@ public final class IncomingMessageHandler {
             final Manager.ReceiveMessageHandler handler
     ) {
         final var actions = new ArrayList<HandleAction>();
-        if (envelope.hasSourceUuid()) {
-            // Store uuid if we don't have it already
-            // address/uuid in envelope is sent by server
-            account.getRecipientTrustedResolver().resolveRecipientTrusted(envelope.getSourceAddress());
-        }
         SignalServiceContent content = null;
         Exception exception = null;
+        envelope.getSourceServiceId().map(ServiceId::parseOrNull)
+                // Store uuid if we don't have it already
+                // uuid in envelope is sent by server
+                .ifPresent(serviceId -> account.getRecipientResolver().resolveRecipient(serviceId));
         if (!envelope.isReceipt()) {
+            final var destination = getDestination(envelope).serviceId();
             try {
-                content = dependencies.getCipher().decrypt(envelope);
+                final var cipherResult = dependencies.getCipher(destination == null
+                                || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI)
+                        .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO);
+                content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp());
+                if (content == null) {
+                    return new Pair<>(List.of(), null);
+                }
             } catch (ProtocolUntrustedIdentityException e) {
                 final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
                 actions.add(new RetrieveProfileAction(recipientId));
                 exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
-                        .resolveRecipientAddress(recipientId), e.getSenderDevice());
+                        .resolveRecipientAddress(recipientId)
+                        .toApiRecipientAddress(), e.getSenderDevice());
             } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException |
                      ProtocolInvalidMessageException e) {
                 logger.debug("Failed to decrypt incoming message", e);
+                if (e instanceof ProtocolInvalidKeyIdException) {
+                    actions.add(RefreshPreKeysAction.create());
+                }
                 final var sender = account.getRecipientResolver().resolveRecipient(e.getSender());
                 if (context.getContactHelper().isContactBlocked(sender)) {
                     logger.debug("Received invalid message from blocked contact, ignoring.");
                 } else {
-                    final var senderProfile = context.getProfileHelper().getRecipientProfile(sender);
-                    final var selfProfile = context.getProfileHelper().getSelfProfile();
-                    if ((!sender.equals(account.getSelfRecipientId()) || e.getSenderDevice() != account.getDeviceId())
-                            && 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 {
+                    var serviceId = ServiceId.parseOrNull(e.getSender());
+                    if (serviceId != null) {
+                        final var isSelf = sender.equals(account.getSelfRecipientId())
+                                && e.getSenderDevice() == account.getDeviceId();
                         logger.debug("Received invalid message, queuing renew session action.");
-                        actions.add(new RenewSessionAction(sender));
+                        actions.add(new RenewSessionAction(sender, serviceId, destination));
+                        if (!isSelf) {
+                            logger.debug("Received invalid message, requesting message resend.");
+                            actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
+                        }
+                    } else {
+                        logger.debug("Received invalid message from invalid sender: {}", e.getSender());
                     }
                 }
                 exception = e;
@@ -163,6 +193,34 @@ public final class IncomingMessageHandler {
         return new Pair<>(actions, exception);
     }
 
+    private SignalServiceContent validate(
+            Envelope envelope,
+            SignalServiceCipherResult cipherResult,
+            long serverDeliveredTimestamp
+    ) throws ProtocolInvalidKeyException, ProtocolInvalidMessageException, UnsupportedDataMessageException, InvalidMessageStructureException {
+        final var content = cipherResult.getContent();
+        final var envelopeMetadata = cipherResult.getMetadata();
+        final var validationResult = EnvelopeContentValidator.INSTANCE.validate(envelope, content, account.getAci());
+
+        if (validationResult instanceof EnvelopeContentValidator.Result.Invalid v) {
+            logger.warn("Invalid content! {}", v.getReason(), v.getThrowable());
+            return null;
+        }
+
+        if (validationResult instanceof EnvelopeContentValidator.Result.UnsupportedDataMessage v) {
+            logger.warn("Unsupported DataMessage! Our version: {}, their version: {}",
+                    v.getOurVersion(),
+                    v.getTheirVersion());
+            return null;
+        }
+
+        return SignalServiceContent.Companion.createFrom(account.getNumber(),
+                envelope,
+                envelopeMetadata,
+                content,
+                serverDeliveredTimestamp);
+    }
+
     private List<HandleAction> checkAndHandleMessage(
             final SignalServiceEnvelope envelope,
             final SignalServiceContent content,
@@ -170,24 +228,44 @@ public final class IncomingMessageHandler {
             final Manager.ReceiveMessageHandler handler,
             final Exception exception
     ) {
-        if (!envelope.hasSourceUuid() && content != null) {
+        if (content != null) {
             // Store uuid if we don't have it already
             // address/uuid is validated by unidentified sender certificate
-            account.getRecipientTrustedResolver().resolveRecipientTrusted(content.getSender());
+
+            boolean handledPniSignature = false;
+            if (content.getPniSignatureMessage().isPresent()) {
+                final var message = content.getPniSignatureMessage().get();
+                final var senderAddress = getSenderAddress(envelope, content);
+                if (senderAddress != null) {
+                    handledPniSignature = handlePniSignatureMessage(message, senderAddress);
+                }
+            }
+            if (!handledPniSignature) {
+                account.getRecipientTrustedResolver().resolveRecipientTrusted(content.getSender());
+            }
         }
         if (envelope.isReceipt()) {
-            final var senderPair = getSender(envelope, content);
-            final var sender = senderPair.first();
-            final var senderDeviceId = senderPair.second();
+            final var senderDeviceAddress = getSender(envelope, content);
+            final var sender = senderDeviceAddress.serviceId();
+            final var senderDeviceId = senderDeviceAddress.deviceId();
             account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId);
         }
 
+        var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content);
+        final var groupContext = getGroupContext(content);
+        if (groupContext != null && groupContext.getGroupV2().isPresent()) {
+            handleGroupV2Context(groupContext.getGroupV2().get());
+        }
+        // Check again in case the user just joined the group
+        notAllowedToSendToGroup = notAllowedToSendToGroup && isNotAllowedToSendToGroup(envelope, content);
+
         if (isMessageBlocked(envelope, content)) {
             logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
             return List.of();
-        } else if (isNotAllowedToSendToGroup(envelope, content)) {
+        } else if (notAllowedToSendToGroup) {
+            final var senderAddress = getSenderAddress(envelope, content);
             logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
-                    (envelope.hasSourceUuid() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
+                    senderAddress == null ? null : senderAddress.getIdentifier(),
                     envelope.getTimestamp());
             return List.of();
         } else {
@@ -208,30 +286,39 @@ public final class IncomingMessageHandler {
     }
 
     public List<HandleAction> handleMessage(
-            SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig
+            SignalServiceEnvelope envelope,
+            SignalServiceContent content,
+            ReceiveConfig receiveConfig
     ) {
         var actions = new ArrayList<HandleAction>();
-        final var senderPair = getSender(envelope, content);
-        final var sender = senderPair.first();
-        final var senderDeviceId = senderPair.second();
+        final var senderDeviceAddress = getSender(envelope, content);
+        final var sender = senderDeviceAddress.recipientId();
+        final var senderServiceId = senderDeviceAddress.serviceId();
+        final var senderDeviceId = senderDeviceAddress.deviceId();
+        final var destination = getDestination(envelope);
+
+        if (account.getPni().equals(destination.serviceId)) {
+            account.getRecipientStore().markNeedsPniSignature(destination.recipientId, true);
+        } else if (account.getAci().equals(destination.serviceId)) {
+            account.getRecipientStore().markNeedsPniSignature(destination.recipientId, false);
+        }
 
         if (content.getReceiptMessage().isPresent()) {
             final var message = content.getReceiptMessage().get();
             if (message.isDeliveryReceipt()) {
                 account.getMessageSendLogStore()
-                        .deleteEntriesForRecipient(message.getTimestamps(), sender, senderDeviceId);
+                        .deleteEntriesForRecipient(message.getTimestamps(), senderServiceId, senderDeviceId);
             }
         }
 
         if (content.getSenderKeyDistributionMessage().isPresent()) {
             final var message = content.getSenderKeyDistributionMessage().get();
-            final var protocolAddress = new SignalProtocolAddress(context.getRecipientHelper()
-                    .resolveSignalServiceAddress(sender)
-                    .getIdentifier(), senderDeviceId);
+            final var protocolAddress = senderServiceId.toProtocolAddress(senderDeviceId);
             logger.debug("Received a sender key distribution message for distributionId {} from {}",
                     message.getDistributionId(),
                     protocolAddress);
-            dependencies.getMessageSender().processSenderKeyDistributionMessage(protocolAddress, message);
+            new SignalGroupSessionBuilder(dependencies.getSessionLock(),
+                    new GroupSessionBuilder(account.getSenderKeyStore())).process(protocolAddress, message);
         }
 
         if (content.getDecryptionErrorMessage().isPresent()) {
@@ -241,14 +328,21 @@ public final class IncomingMessageHandler {
                     senderDeviceId,
                     message.getTimestamp());
             if (message.getDeviceId() == account.getDeviceId()) {
-                handleDecryptionErrorMessage(actions, sender, senderDeviceId, message);
+                handleDecryptionErrorMessage(actions,
+                        sender,
+                        senderServiceId,
+                        senderDeviceId,
+                        message,
+                        destination.serviceId());
             } else {
                 logger.debug("Request is for another one of our devices");
             }
         }
 
-        if (content.getDataMessage().isPresent()) {
-            var message = content.getDataMessage().get();
+        if (content.getDataMessage().isPresent() || content.getEditMessage().isPresent()) {
+            var message = content.getDataMessage().isPresent()
+                    ? content.getDataMessage().get()
+                    : content.getEditMessage().get().getDataMessage();
 
             if (content.isNeedsReceipt()) {
                 actions.add(new SendReceiptAction(sender,
@@ -273,8 +367,8 @@ public final class IncomingMessageHandler {
 
             actions.addAll(handleSignalServiceDataMessage(message,
                     false,
-                    sender,
-                    account.getSelfRecipientId(),
+                    senderDeviceAddress,
+                    destination,
                     receiveConfig.ignoreAttachments()));
         }
 
@@ -285,33 +379,71 @@ public final class IncomingMessageHandler {
 
         if (content.getSyncMessage().isPresent()) {
             var syncMessage = content.getSyncMessage().get();
-            actions.addAll(handleSyncMessage(syncMessage, sender, receiveConfig.ignoreAttachments()));
+            actions.addAll(handleSyncMessage(envelope,
+                    syncMessage,
+                    senderDeviceAddress,
+                    receiveConfig.ignoreAttachments()));
         }
 
         return actions;
     }
 
+    private boolean handlePniSignatureMessage(
+            final SignalServicePniSignatureMessage message,
+            final SignalServiceAddress senderAddress
+    ) {
+        final var aci = senderAddress.getServiceId();
+        final var aciIdentity = account.getIdentityKeyStore().getIdentityInfo(aci);
+        final var pni = message.getPni();
+        final var pniIdentity = account.getIdentityKeyStore().getIdentityInfo(pni);
+
+        if (aciIdentity == null || pniIdentity == null || aci.equals(pni)) {
+            return false;
+        }
+
+        final var verified = pniIdentity.getIdentityKey()
+                .verifyAlternateIdentity(aciIdentity.getIdentityKey(), message.getSignature());
+
+        if (!verified) {
+            logger.debug("Invalid PNI signature of ACI {} with PNI {}", aci, pni);
+            return false;
+        }
+
+        logger.debug("Verified association of ACI {} with PNI {}", aci, pni);
+        account.getRecipientTrustedResolver()
+                .resolveRecipientTrusted(Optional.of(ACI.from(aci.getRawUuid())),
+                        Optional.of(pni),
+                        senderAddress.getNumber());
+        return true;
+    }
+
     private void handleDecryptionErrorMessage(
             final List<HandleAction> actions,
             final RecipientId sender,
+            final ServiceId senderServiceId,
             final int senderDeviceId,
-            final DecryptionErrorMessage message
+            final DecryptionErrorMessage message,
+            final ServiceId destination
     ) {
         final var logEntries = account.getMessageSendLogStore()
-                .findMessages(sender, senderDeviceId, message.getTimestamp(), message.getRatchetKey().isEmpty());
+                .findMessages(senderServiceId,
+                        senderDeviceId,
+                        message.getTimestamp(),
+                        message.getRatchetKey().isEmpty());
 
         for (final var logEntry : logEntries) {
             actions.add(new ResendMessageAction(sender, message.getTimestamp(), logEntry));
         }
 
         if (message.getRatchetKey().isPresent()) {
-            if (account.getSessionStore().isCurrentRatchetKey(sender, senderDeviceId, message.getRatchetKey().get())) {
+            final var sessionStore = account.getAccountData(destination).getSessionStore();
+            if (sessionStore.isCurrentRatchetKey(senderServiceId, senderDeviceId, message.getRatchetKey().get())) {
                 if (logEntries.isEmpty()) {
                     logger.debug("Renewing the session with sender");
-                    actions.add(new RenewSessionAction(sender));
+                    actions.add(new RenewSessionAction(sender, senderServiceId, destination));
                 } else {
                     logger.trace("Archiving the session with sender, a resend message has already been queued");
-                    context.getAccount().getSessionStore().archiveSessions(sender);
+                    sessionStore.archiveSessions(senderServiceId);
                 }
             }
             return;
@@ -331,16 +463,19 @@ public final class IncomingMessageHandler {
                     sender,
                     senderDeviceId,
                     group.getDistributionId());
-            account.getSenderKeyStore().deleteSharedWith(sender, senderDeviceId, group.getDistributionId());
+            account.getSenderKeyStore().deleteSharedWith(senderServiceId, senderDeviceId, group.getDistributionId());
         }
         if (!found) {
             logger.debug("Reset all shared sender keys with this recipient, no related message found in send log");
-            account.getSenderKeyStore().deleteSharedWith(sender);
+            account.getSenderKeyStore().deleteSharedWith(senderServiceId);
         }
     }
 
     private List<HandleAction> handleSyncMessage(
-            final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
+            final SignalServiceEnvelope envelope,
+            final SignalServiceSyncMessage syncMessage,
+            final DeviceAddress sender,
+            final boolean ignoreAttachments
     ) {
         var actions = new ArrayList<HandleAction>();
         account.setMultiDevice(true);
@@ -351,12 +486,16 @@ public final class IncomingMessageHandler {
                 actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(),
                         true,
                         sender,
-                        destination == null ? null : context.getRecipientHelper().resolveRecipient(destination),
+                        destination == null
+                                ? null
+                                : new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
+                                        destination.getServiceId(),
+                                        0),
                         ignoreAttachments));
             }
             if (message.getStoryMessage().isPresent()) {
                 actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
-                        sender,
+                        sender.recipientId(),
                         ignoreAttachments));
             }
         }
@@ -377,9 +516,7 @@ public final class IncomingMessageHandler {
             if (rm.isConfigurationRequest()) {
                 actions.add(SendSyncConfigurationAction.create());
             }
-            if (rm.isPniIdentityRequest()) {
-                actions.add(SendPniIdentityKeyAction.create());
-            }
+            actions.add(SyncStorageDataAction.create());
         }
         if (syncMessage.getGroups().isPresent()) {
             try {
@@ -392,12 +529,12 @@ public final class IncomingMessageHandler {
         }
         if (syncMessage.getBlockedList().isPresent()) {
             final var blockedListMessage = syncMessage.getBlockedList().get();
-            for (var address : blockedListMessage.getAddresses()) {
-                context.getContactHelper()
-                        .setContactBlocked(context.getRecipientHelper().resolveRecipient(address), true);
+            for (var individual : blockedListMessage.individuals) {
+                final var address = new RecipientAddress(individual.getAci(), individual.getE164());
+                final var recipientId = account.getRecipientResolver().resolveRecipient(address);
+                context.getContactHelper().setContactBlocked(recipientId, true);
             }
-            for (var groupId : blockedListMessage.getGroupIds()
-                    .stream()
+            for (var groupId : blockedListMessage.groupIds.stream()
                     .map(GroupId::unknownVersion)
                     .collect(Collectors.toSet())) {
                 try {
@@ -421,8 +558,7 @@ public final class IncomingMessageHandler {
         if (syncMessage.getVerified().isPresent()) {
             final var verifiedMessage = syncMessage.getVerified().get();
             account.getIdentityKeyStore()
-                    .setIdentityTrustLevel(account.getRecipientTrustedResolver()
-                                    .resolveRecipientTrusted(verifiedMessage.getDestination()),
+                    .setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
                             verifiedMessage.getIdentityKey(),
                             TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
         }
@@ -433,40 +569,43 @@ public final class IncomingMessageHandler {
                     continue;
                 }
                 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
+                final var stickerPackKey = m.getPackKey().orElse(null);
                 final var installed = m.getType().isEmpty()
                         || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
 
-                var sticker = account.getStickerStore().getStickerPack(stickerPackId);
-                if (m.getPackKey().isPresent()) {
-                    if (sticker == null) {
-                        sticker = new Sticker(stickerPackId, m.getPackKey().get());
-                    }
-                    if (installed) {
-                        context.getJobExecutor()
-                                .enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
-                    }
-                }
+                final var sticker = context.getStickerHelper()
+                        .addOrUpdateStickerPack(stickerPackId, stickerPackKey, installed);
 
-                if (sticker != null) {
-                    sticker.setInstalled(installed);
-                    account.getStickerStore().updateSticker(sticker);
+                if (sticker != null && installed) {
+                    context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, sticker.packKey()));
                 }
             }
         }
         if (syncMessage.getFetchType().isPresent()) {
             switch (syncMessage.getFetchType().get()) {
-                case LOCAL_PROFILE:
-                    actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
-                case STORAGE_MANIFEST:
-                    actions.add(RetrieveStorageDataAction.create());
+                case LOCAL_PROFILE -> actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
+                case STORAGE_MANIFEST -> actions.add(SyncStorageDataAction.create());
             }
         }
         if (syncMessage.getKeys().isPresent()) {
             final var keysMessage = syncMessage.getKeys().get();
-            if (keysMessage.getStorageService().isPresent()) {
-                final var storageKey = keysMessage.getStorageService().get();
+            if (keysMessage.getAccountEntropyPool() != null) {
+                final var aep = keysMessage.getAccountEntropyPool();
+                account.setAccountEntropyPool(aep);
+                actions.add(SyncStorageDataAction.create());
+            } else if (keysMessage.getMaster() != null) {
+                final var masterKey = keysMessage.getMaster();
+                account.setMasterKey(masterKey);
+                actions.add(SyncStorageDataAction.create());
+            } else if (keysMessage.getStorageService() != null) {
+                final var storageKey = keysMessage.getStorageService();
                 account.setStorageKey(storageKey);
-                actions.add(RetrieveStorageDataAction.create());
+                actions.add(SyncStorageDataAction.create());
+            }
+            if (keysMessage.getMediaRootBackupKey() != null) {
+                final var mrb = keysMessage.getMediaRootBackupKey();
+                account.setMediaRootBackupKey(mrb);
+                actions.add(SyncStorageDataAction.create());
             }
         }
         if (syncMessage.getConfiguration().isPresent()) {
@@ -486,82 +625,101 @@ public final class IncomingMessageHandler {
                         .get());
             }
         }
-        if (syncMessage.getPniIdentity().isPresent()) {
-            final var pniIdentity = syncMessage.getPniIdentity().get();
-            account.setPniIdentityKeyPair(KeyUtils.getIdentityKeyPair(pniIdentity.getPublicKey().toByteArray(),
-                    pniIdentity.getPrivateKey().toByteArray()));
-            actions.add(RefreshPreKeysAction.create());
+        if (syncMessage.getPniChangeNumber().isPresent()) {
+            final var pniChangeNumber = syncMessage.getPniChangeNumber().get();
+            logger.debug("Received PNI change number sync message, applying.");
+            final var updatedPniString = envelope.getUpdatedPni();
+            if (updatedPniString != null && !updatedPniString.isEmpty()) {
+                final var updatedPni = ServiceId.PNI.parseOrThrow(updatedPniString);
+                context.getAccountHelper().handlePniChangeNumberMessage(pniChangeNumber, updatedPni);
+            }
         }
         return actions;
     }
 
+    private SignalServiceGroupContext getGroupContext(SignalServiceContent content) {
+        if (content == null) {
+            return null;
+        }
+
+        if (content.getDataMessage().isPresent()) {
+            var message = content.getDataMessage().get();
+            if (message.getGroupContext().isPresent()) {
+                return message.getGroupContext().get();
+            }
+        }
+
+        if (content.getStoryMessage().isPresent()) {
+            var message = content.getStoryMessage().get();
+            if (message.getGroupContext().isPresent()) {
+                try {
+                    return SignalServiceGroupContext.create(null, message.getGroupContext().get());
+                } catch (InvalidMessageException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        }
+
+        return null;
+    }
+
     private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
-        SignalServiceAddress source;
-        if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
-            source = envelope.getSourceAddress();
-        } else if (content != null) {
-            source = content.getSender();
-        } else {
+        SignalServiceAddress source = getSenderAddress(envelope, content);
+        if (source == null) {
             return false;
         }
-        final var recipientId = context.getRecipientHelper().resolveRecipient(source);
+        final var recipientId = account.getRecipientResolver().resolveRecipient(source);
         if (context.getContactHelper().isContactBlocked(recipientId)) {
             return true;
         }
 
-        if (content != null && content.getDataMessage().isPresent()) {
-            var message = content.getDataMessage().get();
-            if (message.getGroupContext().isPresent()) {
-                var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
-                return context.getGroupHelper().isGroupBlocked(groupId);
-            }
+        final var groupContext = getGroupContext(content);
+        if (groupContext != null) {
+            var groupId = GroupUtils.getGroupId(groupContext);
+            return context.getGroupHelper().isGroupBlocked(groupId);
         }
 
         return false;
     }
 
     private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
-        SignalServiceAddress source;
-        if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
-            source = envelope.getSourceAddress();
-        } else if (content != null) {
-            source = content.getSender();
-        } else {
-            return false;
-        }
-
-        if (content == null || content.getDataMessage().isEmpty()) {
+        SignalServiceAddress source = getSenderAddress(envelope, content);
+        if (source == null) {
             return false;
         }
 
-        var message = content.getDataMessage().get();
-        if (message.getGroupContext().isEmpty()) {
+        final var groupContext = getGroupContext(content);
+        if (groupContext == null) {
             return false;
         }
 
-        if (message.getGroupContext().get().getGroupV1().isPresent()) {
-            var groupInfo = message.getGroupContext().get().getGroupV1().get();
+        if (groupContext.getGroupV1().isPresent()) {
+            var groupInfo = groupContext.getGroupV1().get();
             if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
                 return false;
             }
         }
 
-        var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
+        var groupId = GroupUtils.getGroupId(groupContext);
         var group = context.getGroupHelper().getGroup(groupId);
         if (group == null) {
             return false;
         }
 
-        final var recipientId = context.getRecipientHelper().resolveRecipient(source);
-        if (!group.isMember(recipientId) && !(group.isPendingMember(recipientId) && message.isGroupV2Update())) {
+        final var message = content.getDataMessage().orElse(null);
+
+        final var recipientId = account.getRecipientResolver().resolveRecipient(source);
+        if (!group.isMember(recipientId) && !(
+                group.isPendingMember(recipientId) && message != null && message.isGroupV2Update()
+        )) {
             return true;
         }
 
         if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
-            return message.getBody().isPresent()
+            return message == null
+                    || message.getBody().isPresent()
                     || message.getAttachments().isPresent()
-                    || message.getQuote()
-                    .isPresent()
+                    || message.getQuote().isPresent()
                     || message.getPreviews().isPresent()
                     || message.getMentions().isPresent()
                     || message.getSticker().isPresent();
@@ -572,8 +730,8 @@ public final class IncomingMessageHandler {
     private List<HandleAction> handleSignalServiceDataMessage(
             SignalServiceDataMessage message,
             boolean isSync,
-            RecipientId source,
-            RecipientId destination,
+            DeviceAddress source,
+            DeviceAddress destination,
             boolean ignoreAttachments
     ) {
         var actions = new ArrayList<HandleAction>();
@@ -586,7 +744,7 @@ public final class IncomingMessageHandler {
                 if (group == null || group instanceof GroupInfoV1) {
                     var groupV1 = (GroupInfoV1) group;
                     switch (groupInfo.getType()) {
-                        case UPDATE: {
+                        case UPDATE -> {
                             if (groupV1 == null) {
                                 groupV1 = new GroupInfoV1(groupId);
                             }
@@ -601,33 +759,32 @@ public final class IncomingMessageHandler {
                             }
 
                             if (groupInfo.getMembers().isPresent()) {
+                                final var recipientResolver = account.getRecipientResolver();
                                 groupV1.addMembers(groupInfo.getMembers()
                                         .get()
                                         .stream()
-                                        .map(context.getRecipientHelper()::resolveRecipient)
+                                        .map(recipientResolver::resolveRecipient)
                                         .collect(Collectors.toSet()));
                             }
 
                             account.getGroupStore().updateGroup(groupV1);
-                            break;
                         }
-                        case DELIVER:
+                        case DELIVER -> {
                             if (groupV1 == null && !isSync) {
-                                actions.add(new SendGroupInfoRequestAction(source, groupId));
+                                actions.add(new SendGroupInfoRequestAction(source.recipientId(), groupId));
                             }
-                            break;
-                        case QUIT: {
+                        }
+                        case QUIT -> {
                             if (groupV1 != null) {
-                                groupV1.removeMember(source);
+                                groupV1.removeMember(source.recipientId());
                                 account.getGroupStore().updateGroup(groupV1);
                             }
-                            break;
                         }
-                        case REQUEST_INFO:
+                        case REQUEST_INFO -> {
                             if (groupV1 != null && !isSync) {
-                                actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
+                                actions.add(new SendGroupInfoAction(source.recipientId(), groupV1.getGroupId()));
                             }
-                            break;
+                        }
                     }
                 } else {
                     // Received a group v1 message for a v2 group
@@ -638,9 +795,12 @@ public final class IncomingMessageHandler {
             }
         }
 
+        final var selfAddress = isSync ? source : destination;
         final var conversationPartnerAddress = isSync ? destination : source;
         if (conversationPartnerAddress != null && message.isEndSession()) {
-            account.getSessionStore().deleteAllSessions(conversationPartnerAddress);
+            account.getAccountData(selfAddress.serviceId())
+                    .getSessionStore()
+                    .deleteAllSessions(conversationPartnerAddress.serviceId());
         }
         if (message.isExpirationUpdate() || message.getBody().isPresent()) {
             if (message.getGroupContext().isPresent()) {
@@ -659,7 +819,9 @@ public final class IncomingMessageHandler {
                 }
             } else if (conversationPartnerAddress != null) {
                 context.getContactHelper()
-                        .setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
+                        .setExpirationTimer(conversationPartnerAddress.recipientId(),
+                                message.getExpiresInSeconds(),
+                                message.getExpireTimerVersion());
             }
         }
         if (!ignoreAttachments) {
@@ -686,32 +848,43 @@ public final class IncomingMessageHandler {
             if (message.getQuote().isPresent()) {
                 final var quote = message.getQuote().get();
 
-                for (var quotedAttachment : quote.getAttachments()) {
-                    final var thumbnail = quotedAttachment.getThumbnail();
-                    if (thumbnail != null) {
-                        context.getAttachmentHelper().downloadAttachment(thumbnail);
+                if (quote.getAttachments() != null) {
+                    for (var quotedAttachment : quote.getAttachments()) {
+                        final var thumbnail = quotedAttachment.getThumbnail();
+                        if (thumbnail != null) {
+                            context.getAttachmentHelper().downloadAttachment(thumbnail);
+                        }
                     }
                 }
             }
         }
+        if (message.getGiftBadge().isPresent()) {
+            handleIncomingGiftBadge(message.getGiftBadge().get());
+        }
         if (message.getProfileKey().isPresent()) {
-            handleIncomingProfileKey(message.getProfileKey().get(), source);
+            handleIncomingProfileKey(message.getProfileKey().get(), source.recipientId());
         }
         if (message.getSticker().isPresent()) {
             final var messageSticker = message.getSticker().get();
             final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
             var sticker = account.getStickerStore().getStickerPack(stickerPackId);
             if (sticker == null) {
-                sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
-                account.getStickerStore().updateSticker(sticker);
+                sticker = new StickerPack(stickerPackId, messageSticker.getPackKey());
+                account.getStickerStore().addStickerPack(sticker);
             }
             context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
         }
         return actions;
     }
 
+    private void handleIncomingGiftBadge(final SignalServiceDataMessage.GiftBadge giftBadge) {
+        // TODO
+    }
+
     private List<HandleAction> handleSignalServiceStoryMessage(
-            SignalServiceStoryMessage message, RecipientId source, boolean ignoreAttachments
+            SignalServiceStoryMessage message,
+            RecipientId source,
+            boolean ignoreAttachments
     ) {
         var actions = new ArrayList<HandleAction>();
         if (message.getGroupContext().isPresent()) {
@@ -766,13 +939,39 @@ public final class IncomingMessageHandler {
         this.account.getProfileStore().storeProfileKey(source, profileKey);
     }
 
-    private Pair<RecipientId, Integer> getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
-        if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
-            return new Pair<>(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
+    private SignalServiceAddress getSenderAddress(SignalServiceEnvelope envelope, SignalServiceContent content) {
+        final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
+        if (!envelope.isUnidentifiedSender() && serviceId != null) {
+            return new SignalServiceAddress(serviceId);
+        } else if (content != null) {
+            return content.getSender();
+        } else {
+            return null;
+        }
+    }
+
+    private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
+        final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
+        if (!envelope.isUnidentifiedSender() && serviceId != null) {
+            return new DeviceAddress(account.getRecipientResolver().resolveRecipient(serviceId),
+                    serviceId,
                     envelope.getSourceDevice());
         } else {
-            return new Pair<>(context.getRecipientHelper().resolveRecipient(content.getSender()),
+            return new DeviceAddress(account.getRecipientResolver().resolveRecipient(content.getSender()),
+                    content.getSender().getServiceId(),
                     content.getSenderDevice());
         }
     }
+
+    private DeviceAddress getDestination(SignalServiceEnvelope envelope) {
+        final var destination = envelope.getDestinationServiceId();
+        if (destination == null || destination.isUnknown()) {
+            return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
+        }
+        return new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
+                destination,
+                account.getDeviceId());
+    }
+
+    private record DeviceAddress(RecipientId recipientId, ServiceId serviceId, int deviceId) {}
 }