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