]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
Validate incoming messages before handling them
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / IncomingMessageHandler.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.Manager;
4 import org.asamk.signal.manager.SignalDependencies;
5 import org.asamk.signal.manager.actions.HandleAction;
6 import org.asamk.signal.manager.actions.RefreshPreKeysAction;
7 import org.asamk.signal.manager.actions.RenewSessionAction;
8 import org.asamk.signal.manager.actions.ResendMessageAction;
9 import org.asamk.signal.manager.actions.RetrieveProfileAction;
10 import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
11 import org.asamk.signal.manager.actions.SendGroupInfoAction;
12 import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
13 import org.asamk.signal.manager.actions.SendPniIdentityKeyAction;
14 import org.asamk.signal.manager.actions.SendProfileKeyAction;
15 import org.asamk.signal.manager.actions.SendReceiptAction;
16 import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
17 import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
18 import org.asamk.signal.manager.actions.SendSyncConfigurationAction;
19 import org.asamk.signal.manager.actions.SendSyncContactsAction;
20 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
21 import org.asamk.signal.manager.actions.SendSyncKeysAction;
22 import org.asamk.signal.manager.actions.UpdateAccountAttributesAction;
23 import org.asamk.signal.manager.api.MessageEnvelope;
24 import org.asamk.signal.manager.api.Pair;
25 import org.asamk.signal.manager.api.ReceiveConfig;
26 import org.asamk.signal.manager.api.StickerPackId;
27 import org.asamk.signal.manager.api.TrustLevel;
28 import org.asamk.signal.manager.api.UntrustedIdentityException;
29 import org.asamk.signal.manager.groups.GroupId;
30 import org.asamk.signal.manager.groups.GroupNotFoundException;
31 import org.asamk.signal.manager.groups.GroupUtils;
32 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
33 import org.asamk.signal.manager.storage.SignalAccount;
34 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
35 import org.asamk.signal.manager.storage.recipients.Profile;
36 import org.asamk.signal.manager.storage.recipients.RecipientId;
37 import org.asamk.signal.manager.storage.stickers.StickerPack;
38 import org.asamk.signal.manager.util.KeyUtils;
39 import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
40 import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
41 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
42 import org.signal.libsignal.metadata.ProtocolNoSessionException;
43 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
44 import org.signal.libsignal.metadata.SelfSendException;
45 import org.signal.libsignal.protocol.IdentityKeyPair;
46 import org.signal.libsignal.protocol.InvalidMessageException;
47 import org.signal.libsignal.protocol.groups.GroupSessionBuilder;
48 import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
49 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
50 import org.signal.libsignal.zkgroup.InvalidInputException;
51 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.whispersystems.signalservice.api.InvalidMessageStructureException;
55 import org.whispersystems.signalservice.api.crypto.SignalGroupSessionBuilder;
56 import org.whispersystems.signalservice.api.crypto.SignalServiceCipherResult;
57 import org.whispersystems.signalservice.api.messages.EnvelopeContentValidator;
58 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
59 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
60 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
61 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
62 import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
63 import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
64 import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
65 import org.whispersystems.signalservice.api.messages.SignalServicePniSignatureMessage;
66 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
67 import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
68 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
69 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
70 import org.whispersystems.signalservice.api.push.ACI;
71 import org.whispersystems.signalservice.api.push.PNI;
72 import org.whispersystems.signalservice.api.push.ServiceId;
73 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
74 import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
75 import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
76 import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
77 import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
78 import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
79
80 import java.util.ArrayList;
81 import java.util.List;
82 import java.util.Optional;
83 import java.util.stream.Collectors;
84
85 public final class IncomingMessageHandler {
86
87 private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
88
89 private final SignalAccount account;
90 private final SignalDependencies dependencies;
91 private final Context context;
92
93 public IncomingMessageHandler(final Context context) {
94 this.account = context.getAccount();
95 this.dependencies = context.getDependencies();
96 this.context = context;
97 }
98
99 public Pair<List<HandleAction>, Exception> handleRetryEnvelope(
100 final SignalServiceEnvelope envelope,
101 final ReceiveConfig receiveConfig,
102 final Manager.ReceiveMessageHandler handler
103 ) {
104 final List<HandleAction> actions = new ArrayList<>();
105 if (envelope.isPreKeySignalMessage()) {
106 actions.add(RefreshPreKeysAction.create());
107 }
108
109 SignalServiceContent content = null;
110 if (!envelope.isReceipt()) {
111 account.getIdentityKeyStore().setRetryingDecryption(true);
112 try {
113 final var cipherResult = dependencies.getCipher()
114 .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
115 content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp());
116 if (content == null) {
117 return new Pair<>(List.of(), null);
118 }
119 } catch (ProtocolUntrustedIdentityException e) {
120 final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
121 final var exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
122 .resolveRecipientAddress(recipientId)
123 .toApiRecipientAddress(), e.getSenderDevice());
124 return new Pair<>(List.of(), exception);
125 } catch (Exception e) {
126 return new Pair<>(List.of(), e);
127 } finally {
128 account.getIdentityKeyStore().setRetryingDecryption(false);
129 }
130 }
131 actions.addAll(checkAndHandleMessage(envelope, content, receiveConfig, handler, null));
132 return new Pair<>(actions, null);
133 }
134
135 public Pair<List<HandleAction>, Exception> handleEnvelope(
136 final SignalServiceEnvelope envelope,
137 final ReceiveConfig receiveConfig,
138 final Manager.ReceiveMessageHandler handler
139 ) {
140 final var actions = new ArrayList<HandleAction>();
141 if (envelope.hasSourceUuid()) {
142 // Store uuid if we don't have it already
143 // address/uuid in envelope is sent by server
144 account.getRecipientTrustedResolver().resolveRecipientTrusted(envelope.getSourceAddress());
145 }
146 SignalServiceContent content = null;
147 Exception exception = null;
148 if (!envelope.isReceipt()) {
149 try {
150 final var cipherResult = dependencies.getCipher()
151 .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp());
152 content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp());
153 if (content == null) {
154 return new Pair<>(List.of(), null);
155 }
156 } catch (ProtocolUntrustedIdentityException e) {
157 final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
158 actions.add(new RetrieveProfileAction(recipientId));
159 exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
160 .resolveRecipientAddress(recipientId)
161 .toApiRecipientAddress(), e.getSenderDevice());
162 } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException |
163 ProtocolInvalidMessageException e) {
164 logger.debug("Failed to decrypt incoming message", e);
165 final var sender = account.getRecipientResolver().resolveRecipient(e.getSender());
166 if (context.getContactHelper().isContactBlocked(sender)) {
167 logger.debug("Received invalid message from blocked contact, ignoring.");
168 } else {
169 final var senderProfile = context.getProfileHelper().getRecipientProfile(sender);
170 final var selfProfile = context.getProfileHelper().getSelfProfile();
171 var serviceId = ServiceId.parseOrNull(e.getSender());
172 if (serviceId == null) {
173 // Workaround for libsignal-client issue #492
174 serviceId = account.getRecipientAddressResolver()
175 .resolveRecipientAddress(sender)
176 .serviceId()
177 .orElse(null);
178 }
179 if (serviceId != null) {
180 final var isSelf = sender.equals(account.getSelfRecipientId())
181 && e.getSenderDevice() == account.getDeviceId();
182 final var isSenderSenderKeyCapable = senderProfile != null && senderProfile.getCapabilities()
183 .contains(Profile.Capability.senderKey);
184 final var isSelfSenderKeyCapable = selfProfile != null && selfProfile.getCapabilities()
185 .contains(Profile.Capability.senderKey);
186 if (!isSelf && isSenderSenderKeyCapable && isSelfSenderKeyCapable) {
187 logger.debug("Received invalid message, requesting message resend.");
188 actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope));
189 } else {
190 logger.debug("Received invalid message, queuing renew session action.");
191 actions.add(new RenewSessionAction(sender, serviceId));
192 }
193 } else {
194 logger.debug("Received invalid message from invalid sender: {}", e.getSender());
195 }
196 }
197 exception = e;
198 } catch (SelfSendException e) {
199 logger.debug("Dropping unidentified message from self.");
200 return new Pair<>(List.of(), null);
201 } catch (Exception e) {
202 logger.debug("Failed to handle incoming message", e);
203 exception = e;
204 }
205 }
206
207 actions.addAll(checkAndHandleMessage(envelope, content, receiveConfig, handler, exception));
208 return new Pair<>(actions, exception);
209 }
210
211 private SignalServiceContent validate(
212 SignalServiceProtos.Envelope envelope, SignalServiceCipherResult cipherResult, long serverDeliveredTimestamp
213 ) throws ProtocolInvalidKeyException, ProtocolInvalidMessageException, UnsupportedDataMessageException, InvalidMessageStructureException {
214 final var content = cipherResult.getContent();
215 final var envelopeMetadata = cipherResult.getMetadata();
216 final var validationResult = EnvelopeContentValidator.INSTANCE.validate(envelope, content);
217
218 if (validationResult instanceof EnvelopeContentValidator.Result.Invalid v) {
219 logger.warn("Invalid content! {}", v.getReason(), v.getThrowable());
220 return null;
221 }
222
223 if (validationResult instanceof EnvelopeContentValidator.Result.UnsupportedDataMessage v) {
224 logger.warn("Unsupported DataMessage! Our version: {}, their version: {}",
225 v.getOurVersion(),
226 v.getTheirVersion());
227 return null;
228 }
229
230 final var localAddress = new SignalServiceAddress(envelopeMetadata.getDestinationServiceId(),
231 Optional.ofNullable(account.getNumber()));
232 final var metadata = new SignalServiceMetadata(new SignalServiceAddress(envelopeMetadata.getSourceServiceId(),
233 Optional.ofNullable(envelopeMetadata.getSourceE164())),
234 envelopeMetadata.getSourceDeviceId(),
235 envelope.getTimestamp(),
236 envelope.getServerTimestamp(),
237 serverDeliveredTimestamp,
238 envelopeMetadata.getSealedSender(),
239 envelope.getServerGuid(),
240 Optional.ofNullable(envelopeMetadata.getGroupId()),
241 envelopeMetadata.getDestinationServiceId().toString());
242
243 final var contentProto = SignalServiceContentProto.newBuilder()
244 .setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
245 .setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(metadata))
246 .setContent(content)
247 .build();
248
249 return SignalServiceContent.createFromProto(contentProto);
250 }
251
252 private List<HandleAction> checkAndHandleMessage(
253 final SignalServiceEnvelope envelope,
254 final SignalServiceContent content,
255 final ReceiveConfig receiveConfig,
256 final Manager.ReceiveMessageHandler handler,
257 final Exception exception
258 ) {
259 if (content != null) {
260 // Store uuid if we don't have it already
261 // address/uuid is validated by unidentified sender certificate
262
263 boolean handledPniSignature = false;
264 if (content.getPniSignatureMessage().isPresent()) {
265 final var message = content.getPniSignatureMessage().get();
266 final var senderAddress = getSenderAddress(envelope, content);
267 if (senderAddress != null) {
268 handledPniSignature = handlePniSignatureMessage(message, senderAddress);
269 }
270 }
271 if (!handledPniSignature) {
272 account.getRecipientTrustedResolver().resolveRecipientTrusted(content.getSender());
273 }
274 }
275 if (envelope.isReceipt()) {
276 final var senderDeviceAddress = getSender(envelope, content);
277 final var sender = senderDeviceAddress.serviceId();
278 final var senderDeviceId = senderDeviceAddress.deviceId();
279 account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId);
280 }
281
282 var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content);
283 final var groupContext = getGroupContext(content);
284 if (groupContext != null && groupContext.getGroupV2().isPresent()) {
285 handleGroupV2Context(groupContext.getGroupV2().get());
286 }
287 // Check again in case the user just joined the group
288 notAllowedToSendToGroup = notAllowedToSendToGroup && isNotAllowedToSendToGroup(envelope, content);
289
290 if (isMessageBlocked(envelope, content)) {
291 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
292 return List.of();
293 } else if (notAllowedToSendToGroup) {
294 final var senderAddress = getSenderAddress(envelope, content);
295 logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
296 senderAddress == null ? null : senderAddress.getIdentifier(),
297 envelope.getTimestamp());
298 return List.of();
299 } else {
300 List<HandleAction> actions;
301 if (content != null) {
302 actions = handleMessage(envelope, content, receiveConfig);
303 } else {
304 actions = List.of();
305 }
306 handler.handleMessage(MessageEnvelope.from(envelope,
307 content,
308 account.getRecipientResolver(),
309 account.getRecipientAddressResolver(),
310 context.getAttachmentHelper()::getAttachmentFile,
311 exception), exception);
312 return actions;
313 }
314 }
315
316 public List<HandleAction> handleMessage(
317 SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig
318 ) {
319 var actions = new ArrayList<HandleAction>();
320 final var senderDeviceAddress = getSender(envelope, content);
321 final var sender = senderDeviceAddress.recipientId();
322 final var senderServiceId = senderDeviceAddress.serviceId();
323 final var senderDeviceId = senderDeviceAddress.deviceId();
324 final var destination = getDestination(envelope);
325
326 if (content.getReceiptMessage().isPresent()) {
327 final var message = content.getReceiptMessage().get();
328 if (message.isDeliveryReceipt()) {
329 account.getMessageSendLogStore()
330 .deleteEntriesForRecipient(message.getTimestamps(), senderServiceId, senderDeviceId);
331 }
332 }
333
334 if (content.getSenderKeyDistributionMessage().isPresent()) {
335 final var message = content.getSenderKeyDistributionMessage().get();
336 final var protocolAddress = senderServiceId.toProtocolAddress(senderDeviceId);
337 logger.debug("Received a sender key distribution message for distributionId {} from {}",
338 message.getDistributionId(),
339 protocolAddress);
340 new SignalGroupSessionBuilder(dependencies.getSessionLock(),
341 new GroupSessionBuilder(account.getSenderKeyStore())).process(protocolAddress, message);
342 }
343
344 if (content.getDecryptionErrorMessage().isPresent()) {
345 var message = content.getDecryptionErrorMessage().get();
346 logger.debug("Received a decryption error message from {}.{} (resend request for {})",
347 sender,
348 senderDeviceId,
349 message.getTimestamp());
350 if (message.getDeviceId() == account.getDeviceId()) {
351 handleDecryptionErrorMessage(actions, sender, senderServiceId, senderDeviceId, message);
352 } else {
353 logger.debug("Request is for another one of our devices");
354 }
355 }
356
357 if (content.getDataMessage().isPresent()) {
358 var message = content.getDataMessage().get();
359
360 if (content.isNeedsReceipt()) {
361 actions.add(new SendReceiptAction(sender,
362 SignalServiceReceiptMessage.Type.DELIVERY,
363 message.getTimestamp()));
364 } else {
365 // Message wasn't sent as unidentified sender message
366 final var contact = context.getAccount().getContactStore().getContact(sender);
367 if (account.isPrimaryDevice()
368 && contact != null
369 && !contact.isBlocked()
370 && contact.isProfileSharingEnabled()) {
371 actions.add(UpdateAccountAttributesAction.create());
372 actions.add(new SendProfileKeyAction(sender));
373 }
374 }
375 if (receiveConfig.sendReadReceipts()) {
376 actions.add(new SendReceiptAction(sender,
377 SignalServiceReceiptMessage.Type.READ,
378 message.getTimestamp()));
379 }
380
381 actions.addAll(handleSignalServiceDataMessage(message,
382 false,
383 senderDeviceAddress,
384 destination,
385 receiveConfig.ignoreAttachments()));
386 }
387
388 if (content.getStoryMessage().isPresent()) {
389 final var message = content.getStoryMessage().get();
390 actions.addAll(handleSignalServiceStoryMessage(message, sender, receiveConfig.ignoreAttachments()));
391 }
392
393 if (content.getSyncMessage().isPresent()) {
394 var syncMessage = content.getSyncMessage().get();
395 actions.addAll(handleSyncMessage(envelope,
396 syncMessage,
397 senderDeviceAddress,
398 receiveConfig.ignoreAttachments()));
399 }
400
401 return actions;
402 }
403
404 private boolean handlePniSignatureMessage(
405 final SignalServicePniSignatureMessage message, final SignalServiceAddress senderAddress
406 ) {
407 final var aci = ACI.from(senderAddress.getServiceId());
408 final var aciIdentity = account.getIdentityKeyStore().getIdentityInfo(aci);
409 final var pni = message.getPni();
410 final var pniIdentity = account.getIdentityKeyStore().getIdentityInfo(pni);
411
412 if (aciIdentity == null || pniIdentity == null || aci.equals(pni)) {
413 return false;
414 }
415
416 final var verified = pniIdentity.getIdentityKey()
417 .verifyAlternateIdentity(aciIdentity.getIdentityKey(), message.getSignature());
418
419 if (!verified) {
420 logger.debug("Invalid PNI signature of ACI {} with PNI {}", aci, pni);
421 return false;
422 }
423
424 logger.debug("Verified association of ACI {} with PNI {}", aci, pni);
425 account.getRecipientTrustedResolver()
426 .resolveRecipientTrusted(Optional.of(aci), Optional.of(pni), senderAddress.getNumber());
427 return true;
428 }
429
430 private void handleDecryptionErrorMessage(
431 final List<HandleAction> actions,
432 final RecipientId sender,
433 final ServiceId senderServiceId,
434 final int senderDeviceId,
435 final DecryptionErrorMessage message
436 ) {
437 final var logEntries = account.getMessageSendLogStore()
438 .findMessages(senderServiceId,
439 senderDeviceId,
440 message.getTimestamp(),
441 message.getRatchetKey().isEmpty());
442
443 for (final var logEntry : logEntries) {
444 actions.add(new ResendMessageAction(sender, message.getTimestamp(), logEntry));
445 }
446
447 if (message.getRatchetKey().isPresent()) {
448 if (account.getAciSessionStore()
449 .isCurrentRatchetKey(senderServiceId, senderDeviceId, message.getRatchetKey().get())) {
450 if (logEntries.isEmpty()) {
451 logger.debug("Renewing the session with sender");
452 actions.add(new RenewSessionAction(sender, senderServiceId));
453 } else {
454 logger.trace("Archiving the session with sender, a resend message has already been queued");
455 context.getAccount().getAciSessionStore().archiveSessions(senderServiceId);
456 }
457 }
458 return;
459 }
460
461 var found = false;
462 for (final var logEntry : logEntries) {
463 if (logEntry.groupId().isEmpty()) {
464 continue;
465 }
466 final var group = account.getGroupStore().getGroup(logEntry.groupId().get());
467 if (group == null) {
468 continue;
469 }
470 found = true;
471 logger.trace("Deleting shared sender key with {} ({}): {}",
472 sender,
473 senderDeviceId,
474 group.getDistributionId());
475 account.getSenderKeyStore().deleteSharedWith(senderServiceId, senderDeviceId, group.getDistributionId());
476 }
477 if (!found) {
478 logger.debug("Reset all shared sender keys with this recipient, no related message found in send log");
479 account.getSenderKeyStore().deleteSharedWith(senderServiceId);
480 }
481 }
482
483 private List<HandleAction> handleSyncMessage(
484 final SignalServiceEnvelope envelope,
485 final SignalServiceSyncMessage syncMessage,
486 final DeviceAddress sender,
487 final boolean ignoreAttachments
488 ) {
489 var actions = new ArrayList<HandleAction>();
490 account.setMultiDevice(true);
491 if (syncMessage.getSent().isPresent()) {
492 var message = syncMessage.getSent().get();
493 final var destination = message.getDestination().orElse(null);
494 if (message.getDataMessage().isPresent()) {
495 actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(),
496 true,
497 sender,
498 destination == null
499 ? null
500 : new DeviceAddress(context.getRecipientHelper().resolveRecipient(destination),
501 destination.getServiceId(),
502 0),
503 ignoreAttachments));
504 }
505 if (message.getStoryMessage().isPresent()) {
506 actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
507 sender.recipientId(),
508 ignoreAttachments));
509 }
510 }
511 if (syncMessage.getRequest().isPresent() && account.isPrimaryDevice()) {
512 var rm = syncMessage.getRequest().get();
513 if (rm.isContactsRequest()) {
514 actions.add(SendSyncContactsAction.create());
515 }
516 if (rm.isGroupsRequest()) {
517 actions.add(SendSyncGroupsAction.create());
518 }
519 if (rm.isBlockedListRequest()) {
520 actions.add(SendSyncBlockedListAction.create());
521 }
522 if (rm.isKeysRequest()) {
523 actions.add(SendSyncKeysAction.create());
524 }
525 if (rm.isConfigurationRequest()) {
526 actions.add(SendSyncConfigurationAction.create());
527 }
528 if (rm.isPniIdentityRequest()) {
529 actions.add(SendPniIdentityKeyAction.create());
530 }
531 }
532 if (syncMessage.getGroups().isPresent()) {
533 try {
534 final var groupsMessage = syncMessage.getGroups().get();
535 context.getAttachmentHelper()
536 .retrieveAttachment(groupsMessage, context.getSyncHelper()::handleSyncDeviceGroups);
537 } catch (Exception e) {
538 logger.warn("Failed to handle received sync groups, ignoring: {}", e.getMessage());
539 }
540 }
541 if (syncMessage.getBlockedList().isPresent()) {
542 final var blockedListMessage = syncMessage.getBlockedList().get();
543 for (var address : blockedListMessage.getAddresses()) {
544 context.getContactHelper()
545 .setContactBlocked(context.getRecipientHelper().resolveRecipient(address), true);
546 }
547 for (var groupId : blockedListMessage.getGroupIds()
548 .stream()
549 .map(GroupId::unknownVersion)
550 .collect(Collectors.toSet())) {
551 try {
552 context.getGroupHelper().setGroupBlocked(groupId, true);
553 } catch (GroupNotFoundException e) {
554 logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
555 groupId.toBase64());
556 }
557 }
558 }
559 if (syncMessage.getContacts().isPresent()) {
560 try {
561 final var contactsMessage = syncMessage.getContacts().get();
562 context.getAttachmentHelper()
563 .retrieveAttachment(contactsMessage.getContactsStream(),
564 context.getSyncHelper()::handleSyncDeviceContacts);
565 } catch (Exception e) {
566 logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
567 }
568 }
569 if (syncMessage.getVerified().isPresent()) {
570 final var verifiedMessage = syncMessage.getVerified().get();
571 account.getIdentityKeyStore()
572 .setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
573 verifiedMessage.getIdentityKey(),
574 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
575 }
576 if (syncMessage.getStickerPackOperations().isPresent()) {
577 final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
578 for (var m : stickerPackOperationMessages) {
579 if (m.getPackId().isEmpty()) {
580 continue;
581 }
582 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
583 final var installed = m.getType().isEmpty()
584 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
585
586 var sticker = account.getStickerStore().getStickerPack(stickerPackId);
587 if (m.getPackKey().isPresent()) {
588 if (sticker == null) {
589 sticker = new StickerPack(-1, stickerPackId, m.getPackKey().get(), installed);
590 account.getStickerStore().addStickerPack(sticker);
591 }
592 if (installed) {
593 context.getJobExecutor()
594 .enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
595 }
596 }
597
598 if (sticker != null && sticker.isInstalled() != installed) {
599 account.getStickerStore().updateStickerPackInstalled(sticker.packId(), installed);
600 }
601 }
602 }
603 if (syncMessage.getFetchType().isPresent()) {
604 switch (syncMessage.getFetchType().get()) {
605 case LOCAL_PROFILE -> actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
606 case STORAGE_MANIFEST -> actions.add(RetrieveStorageDataAction.create());
607 }
608 }
609 if (syncMessage.getKeys().isPresent()) {
610 final var keysMessage = syncMessage.getKeys().get();
611 if (keysMessage.getStorageService().isPresent()) {
612 final var storageKey = keysMessage.getStorageService().get();
613 account.setStorageKey(storageKey);
614 actions.add(RetrieveStorageDataAction.create());
615 }
616 }
617 if (syncMessage.getConfiguration().isPresent()) {
618 final var configurationMessage = syncMessage.getConfiguration().get();
619 final var configurationStore = account.getConfigurationStore();
620 if (configurationMessage.getReadReceipts().isPresent()) {
621 configurationStore.setReadReceipts(configurationMessage.getReadReceipts().get());
622 }
623 if (configurationMessage.getLinkPreviews().isPresent()) {
624 configurationStore.setLinkPreviews(configurationMessage.getLinkPreviews().get());
625 }
626 if (configurationMessage.getTypingIndicators().isPresent()) {
627 configurationStore.setTypingIndicators(configurationMessage.getTypingIndicators().get());
628 }
629 if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) {
630 configurationStore.setUnidentifiedDeliveryIndicators(configurationMessage.getUnidentifiedDeliveryIndicators()
631 .get());
632 }
633 }
634 if (syncMessage.getPniIdentity().isPresent()) {
635 final var pniIdentity = syncMessage.getPniIdentity().get();
636 account.setPniIdentityKeyPair(KeyUtils.getIdentityKeyPair(pniIdentity.getPublicKey().toByteArray(),
637 pniIdentity.getPrivateKey().toByteArray()));
638 actions.add(RefreshPreKeysAction.create());
639 }
640 if (syncMessage.getPniChangeNumber().isPresent()) {
641 final var pniChangeNumber = syncMessage.getPniChangeNumber().get();
642 logger.debug("Received PNI change number sync message, applying.");
643 if (pniChangeNumber.hasIdentityKeyPair()
644 && pniChangeNumber.hasRegistrationId()
645 && pniChangeNumber.hasSignedPreKey()
646 && !envelope.getUpdatedPni().isEmpty()) {
647 logger.debug("New PNI: {}", envelope.getUpdatedPni());
648 try {
649 final var updatedPni = PNI.parseOrThrow(envelope.getUpdatedPni());
650 context.getAccountHelper()
651 .setPni(updatedPni,
652 new IdentityKeyPair(pniChangeNumber.getIdentityKeyPair().toByteArray()),
653 new SignedPreKeyRecord(pniChangeNumber.getSignedPreKey().toByteArray()),
654 pniChangeNumber.getRegistrationId());
655 } catch (Exception e) {
656 logger.warn("Failed to handle change number message", e);
657 }
658 }
659 }
660 return actions;
661 }
662
663 private SignalServiceGroupContext getGroupContext(SignalServiceContent content) {
664 if (content == null) {
665 return null;
666 }
667
668 if (content.getDataMessage().isPresent()) {
669 var message = content.getDataMessage().get();
670 if (message.getGroupContext().isPresent()) {
671 return message.getGroupContext().get();
672 }
673 }
674
675 if (content.getStoryMessage().isPresent()) {
676 var message = content.getStoryMessage().get();
677 if (message.getGroupContext().isPresent()) {
678 try {
679 return SignalServiceGroupContext.create(null, message.getGroupContext().get());
680 } catch (InvalidMessageException e) {
681 throw new AssertionError(e);
682 }
683 }
684 }
685
686 return null;
687 }
688
689 private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
690 SignalServiceAddress source = getSenderAddress(envelope, content);
691 if (source == null) {
692 return false;
693 }
694 final var recipientId = context.getRecipientHelper().resolveRecipient(source);
695 if (context.getContactHelper().isContactBlocked(recipientId)) {
696 return true;
697 }
698
699 final var groupContext = getGroupContext(content);
700 if (groupContext != null) {
701 var groupId = GroupUtils.getGroupId(groupContext);
702 return context.getGroupHelper().isGroupBlocked(groupId);
703 }
704
705 return false;
706 }
707
708 private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
709 SignalServiceAddress source = getSenderAddress(envelope, content);
710 if (source == null) {
711 return false;
712 }
713
714 final var groupContext = getGroupContext(content);
715 if (groupContext == null) {
716 return false;
717 }
718
719 if (groupContext.getGroupV1().isPresent()) {
720 var groupInfo = groupContext.getGroupV1().get();
721 if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
722 return false;
723 }
724 }
725
726 var groupId = GroupUtils.getGroupId(groupContext);
727 var group = context.getGroupHelper().getGroup(groupId);
728 if (group == null) {
729 return false;
730 }
731
732 final var message = content.getDataMessage().orElse(null);
733
734 final var recipientId = context.getRecipientHelper().resolveRecipient(source);
735 if (!group.isMember(recipientId) && !(
736 group.isPendingMember(recipientId) && message != null && message.isGroupV2Update()
737 )) {
738 return true;
739 }
740
741 if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
742 return message == null
743 || message.getBody().isPresent()
744 || message.getAttachments().isPresent()
745 || message.getQuote().isPresent()
746 || message.getPreviews().isPresent()
747 || message.getMentions().isPresent()
748 || message.getSticker().isPresent();
749 }
750 return false;
751 }
752
753 private List<HandleAction> handleSignalServiceDataMessage(
754 SignalServiceDataMessage message,
755 boolean isSync,
756 DeviceAddress source,
757 DeviceAddress destination,
758 boolean ignoreAttachments
759 ) {
760 var actions = new ArrayList<HandleAction>();
761 if (message.getGroupContext().isPresent()) {
762 final var groupContext = message.getGroupContext().get();
763 if (groupContext.getGroupV1().isPresent()) {
764 var groupInfo = groupContext.getGroupV1().get();
765 var groupId = GroupId.v1(groupInfo.getGroupId());
766 var group = context.getGroupHelper().getGroup(groupId);
767 if (group == null || group instanceof GroupInfoV1) {
768 var groupV1 = (GroupInfoV1) group;
769 switch (groupInfo.getType()) {
770 case UPDATE -> {
771 if (groupV1 == null) {
772 groupV1 = new GroupInfoV1(groupId);
773 }
774
775 if (groupInfo.getAvatar().isPresent()) {
776 var avatar = groupInfo.getAvatar().get();
777 context.getGroupHelper().downloadGroupAvatar(groupV1.getGroupId(), avatar);
778 }
779
780 if (groupInfo.getName().isPresent()) {
781 groupV1.name = groupInfo.getName().get();
782 }
783
784 if (groupInfo.getMembers().isPresent()) {
785 groupV1.addMembers(groupInfo.getMembers()
786 .get()
787 .stream()
788 .map(context.getRecipientHelper()::resolveRecipient)
789 .collect(Collectors.toSet()));
790 }
791
792 account.getGroupStore().updateGroup(groupV1);
793 }
794 case DELIVER -> {
795 if (groupV1 == null && !isSync) {
796 actions.add(new SendGroupInfoRequestAction(source.recipientId(), groupId));
797 }
798 }
799 case QUIT -> {
800 if (groupV1 != null) {
801 groupV1.removeMember(source.recipientId());
802 account.getGroupStore().updateGroup(groupV1);
803 }
804 }
805 case REQUEST_INFO -> {
806 if (groupV1 != null && !isSync) {
807 actions.add(new SendGroupInfoAction(source.recipientId(), groupV1.getGroupId()));
808 }
809 }
810 }
811 } else {
812 // Received a group v1 message for a v2 group
813 }
814 }
815 if (groupContext.getGroupV2().isPresent()) {
816 handleGroupV2Context(groupContext.getGroupV2().get());
817 }
818 }
819
820 final var conversationPartnerAddress = isSync ? destination : source;
821 if (conversationPartnerAddress != null && message.isEndSession()) {
822 account.getAciSessionStore().deleteAllSessions(conversationPartnerAddress.serviceId());
823 }
824 if (message.isExpirationUpdate() || message.getBody().isPresent()) {
825 if (message.getGroupContext().isPresent()) {
826 final var groupContext = message.getGroupContext().get();
827 if (groupContext.getGroupV1().isPresent()) {
828 var groupInfo = groupContext.getGroupV1().get();
829 var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
830 if (group != null) {
831 if (group.messageExpirationTime != message.getExpiresInSeconds()) {
832 group.messageExpirationTime = message.getExpiresInSeconds();
833 account.getGroupStore().updateGroup(group);
834 }
835 }
836 } else if (groupContext.getGroupV2().isPresent()) {
837 // disappearing message timer already stored in the DecryptedGroup
838 }
839 } else if (conversationPartnerAddress != null) {
840 context.getContactHelper()
841 .setExpirationTimer(conversationPartnerAddress.recipientId(), message.getExpiresInSeconds());
842 }
843 }
844 if (!ignoreAttachments) {
845 if (message.getAttachments().isPresent()) {
846 for (var attachment : message.getAttachments().get()) {
847 context.getAttachmentHelper().downloadAttachment(attachment);
848 }
849 }
850 if (message.getSharedContacts().isPresent()) {
851 for (var contact : message.getSharedContacts().get()) {
852 if (contact.getAvatar().isPresent()) {
853 context.getAttachmentHelper().downloadAttachment(contact.getAvatar().get().getAttachment());
854 }
855 }
856 }
857 if (message.getPreviews().isPresent()) {
858 final var previews = message.getPreviews().get();
859 for (var preview : previews) {
860 if (preview.getImage().isPresent()) {
861 context.getAttachmentHelper().downloadAttachment(preview.getImage().get());
862 }
863 }
864 }
865 if (message.getQuote().isPresent()) {
866 final var quote = message.getQuote().get();
867
868 for (var quotedAttachment : quote.getAttachments()) {
869 final var thumbnail = quotedAttachment.getThumbnail();
870 if (thumbnail != null) {
871 context.getAttachmentHelper().downloadAttachment(thumbnail);
872 }
873 }
874 }
875 }
876 if (message.getGiftBadge().isPresent()) {
877 handleIncomingGiftBadge(message.getGiftBadge().get());
878 }
879 if (message.getProfileKey().isPresent()) {
880 handleIncomingProfileKey(message.getProfileKey().get(), source.recipientId());
881 }
882 if (message.getSticker().isPresent()) {
883 final var messageSticker = message.getSticker().get();
884 final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
885 var sticker = account.getStickerStore().getStickerPack(stickerPackId);
886 if (sticker == null) {
887 sticker = new StickerPack(stickerPackId, messageSticker.getPackKey());
888 account.getStickerStore().addStickerPack(sticker);
889 }
890 context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
891 }
892 return actions;
893 }
894
895 private void handleIncomingGiftBadge(final SignalServiceDataMessage.GiftBadge giftBadge) {
896 // TODO
897 }
898
899 private List<HandleAction> handleSignalServiceStoryMessage(
900 SignalServiceStoryMessage message, RecipientId source, boolean ignoreAttachments
901 ) {
902 var actions = new ArrayList<HandleAction>();
903 if (message.getGroupContext().isPresent()) {
904 handleGroupV2Context(message.getGroupContext().get());
905 }
906
907 if (!ignoreAttachments) {
908 if (message.getFileAttachment().isPresent()) {
909 context.getAttachmentHelper().downloadAttachment(message.getFileAttachment().get());
910 }
911 if (message.getTextAttachment().isPresent()) {
912 final var textAttachment = message.getTextAttachment().get();
913 if (textAttachment.getPreview().isPresent()) {
914 final var preview = textAttachment.getPreview().get();
915 if (preview.getImage().isPresent()) {
916 context.getAttachmentHelper().downloadAttachment(preview.getImage().get());
917 }
918 }
919 }
920 }
921
922 if (message.getProfileKey().isPresent()) {
923 handleIncomingProfileKey(message.getProfileKey().get(), source);
924 }
925
926 return actions;
927 }
928
929 private void handleGroupV2Context(final SignalServiceGroupV2 groupContext) {
930 final var groupMasterKey = groupContext.getMasterKey();
931
932 context.getGroupHelper()
933 .getOrMigrateGroup(groupMasterKey,
934 groupContext.getRevision(),
935 groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
936 }
937
938 private void handleIncomingProfileKey(final byte[] profileKeyBytes, final RecipientId source) {
939 if (profileKeyBytes.length != 32) {
940 logger.debug("Received invalid profile key of length {}", profileKeyBytes.length);
941 return;
942 }
943 final ProfileKey profileKey;
944 try {
945 profileKey = new ProfileKey(profileKeyBytes);
946 } catch (InvalidInputException e) {
947 throw new AssertionError(e);
948 }
949 if (account.getSelfRecipientId().equals(source)) {
950 this.account.setProfileKey(profileKey);
951 }
952 this.account.getProfileStore().storeProfileKey(source, profileKey);
953 }
954
955 private SignalServiceAddress getSenderAddress(SignalServiceEnvelope envelope, SignalServiceContent content) {
956 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
957 return envelope.getSourceAddress();
958 } else if (content != null) {
959 return content.getSender();
960 } else {
961 return null;
962 }
963 }
964
965 private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
966 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
967 return new DeviceAddress(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
968 envelope.getSourceAddress().getServiceId(),
969 envelope.getSourceDevice());
970 } else {
971 return new DeviceAddress(context.getRecipientHelper().resolveRecipient(content.getSender()),
972 content.getSender().getServiceId(),
973 content.getSenderDevice());
974 }
975 }
976
977 private DeviceAddress getDestination(SignalServiceEnvelope envelope) {
978 if (!envelope.hasDestinationUuid()) {
979 return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
980 }
981 final var addressOptional = SignalServiceAddress.fromRaw(envelope.getDestinationUuid(), null);
982 if (addressOptional.isEmpty()) {
983 return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
984 }
985 final var address = addressOptional.get();
986 return new DeviceAddress(context.getRecipientHelper().resolveRecipient(address), address.getServiceId(), 0);
987 }
988
989 private record DeviceAddress(RecipientId recipientId, ServiceId serviceId, int deviceId) {}
990 }