]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
Request message resend if incoming message can't be decrypted
[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.JobExecutor;
4 import org.asamk.signal.manager.Manager;
5 import org.asamk.signal.manager.SignalDependencies;
6 import org.asamk.signal.manager.TrustLevel;
7 import org.asamk.signal.manager.UntrustedIdentityException;
8 import org.asamk.signal.manager.actions.HandleAction;
9 import org.asamk.signal.manager.actions.RefreshPreKeysAction;
10 import org.asamk.signal.manager.actions.RenewSessionAction;
11 import org.asamk.signal.manager.actions.RetrieveProfileAction;
12 import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
13 import org.asamk.signal.manager.actions.SendGroupInfoAction;
14 import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
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.SendSyncContactsAction;
19 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
20 import org.asamk.signal.manager.actions.SendSyncKeysAction;
21 import org.asamk.signal.manager.groups.GroupId;
22 import org.asamk.signal.manager.groups.GroupNotFoundException;
23 import org.asamk.signal.manager.groups.GroupUtils;
24 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
25 import org.asamk.signal.manager.storage.SignalAccount;
26 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
27 import org.asamk.signal.manager.storage.recipients.Profile;
28 import org.asamk.signal.manager.storage.recipients.RecipientId;
29 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
30 import org.asamk.signal.manager.storage.stickers.Sticker;
31 import org.asamk.signal.manager.storage.stickers.StickerPackId;
32 import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
33 import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
34 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
35 import org.signal.libsignal.metadata.ProtocolNoSessionException;
36 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
37 import org.signal.libsignal.metadata.SelfSendException;
38 import org.signal.zkgroup.InvalidInputException;
39 import org.signal.zkgroup.profiles.ProfileKey;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.whispersystems.libsignal.SignalProtocolAddress;
43 import org.whispersystems.libsignal.util.Pair;
44 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
45 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
46 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
47 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
48 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
49 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
50 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
51
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.stream.Collectors;
55
56 public final class IncomingMessageHandler {
57
58 private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
59
60 private final SignalAccount account;
61 private final SignalDependencies dependencies;
62 private final RecipientResolver recipientResolver;
63 private final SignalServiceAddressResolver addressResolver;
64 private final GroupHelper groupHelper;
65 private final ContactHelper contactHelper;
66 private final AttachmentHelper attachmentHelper;
67 private final SyncHelper syncHelper;
68 private final ProfileProvider profileProvider;
69 private final JobExecutor jobExecutor;
70
71 public IncomingMessageHandler(
72 final SignalAccount account,
73 final SignalDependencies dependencies,
74 final RecipientResolver recipientResolver,
75 final SignalServiceAddressResolver addressResolver,
76 final GroupHelper groupHelper,
77 final ContactHelper contactHelper,
78 final AttachmentHelper attachmentHelper,
79 final SyncHelper syncHelper,
80 final ProfileProvider profileProvider,
81 final JobExecutor jobExecutor
82 ) {
83 this.account = account;
84 this.dependencies = dependencies;
85 this.recipientResolver = recipientResolver;
86 this.addressResolver = addressResolver;
87 this.groupHelper = groupHelper;
88 this.contactHelper = contactHelper;
89 this.attachmentHelper = attachmentHelper;
90 this.syncHelper = syncHelper;
91 this.profileProvider = profileProvider;
92 this.jobExecutor = jobExecutor;
93 }
94
95 public Pair<List<HandleAction>, Exception> handleRetryEnvelope(
96 final SignalServiceEnvelope envelope,
97 final boolean ignoreAttachments,
98 final Manager.ReceiveMessageHandler handler
99 ) {
100 final List<HandleAction> actions = new ArrayList<>();
101 if (envelope.isPreKeySignalMessage()) {
102 actions.add(RefreshPreKeysAction.create());
103 }
104
105 SignalServiceContent content = null;
106 if (!envelope.isReceipt()) {
107 try {
108 content = dependencies.getCipher().decrypt(envelope);
109 } catch (ProtocolUntrustedIdentityException e) {
110 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
111 final var exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(
112 recipientId), e.getSenderDevice());
113 return new Pair<>(List.of(), exception);
114 } catch (Exception e) {
115 return new Pair<>(List.of(), e);
116 }
117 }
118 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, null));
119 return new Pair<>(actions, null);
120 }
121
122 public Pair<List<HandleAction>, Exception> handleEnvelope(
123 final SignalServiceEnvelope envelope,
124 final boolean ignoreAttachments,
125 final Manager.ReceiveMessageHandler handler
126 ) {
127 final var actions = new ArrayList<HandleAction>();
128 if (envelope.hasSourceUuid()) {
129 // Store uuid if we don't have it already
130 // address/uuid in envelope is sent by server
131 account.getRecipientStore().resolveRecipientTrusted(envelope.getSourceAddress());
132 }
133 SignalServiceContent content = null;
134 Exception exception = null;
135 if (!envelope.isReceipt()) {
136 try {
137 content = dependencies.getCipher().decrypt(envelope);
138 } catch (ProtocolUntrustedIdentityException e) {
139 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
140 actions.add(new RetrieveProfileAction(recipientId));
141 exception = new UntrustedIdentityException(addressResolver.resolveSignalServiceAddress(recipientId),
142 e.getSenderDevice());
143 } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
144 final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
145 final var senderProfile = profileProvider.getProfile(sender);
146 final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
147 if (senderProfile != null
148 && senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
149 && selfProfile != null
150 && selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
151 logger.debug("Received invalid message, requesting message resend.");
152 actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
153 } else {
154 logger.debug("Received invalid message, queuing renew session action.");
155 actions.add(new RenewSessionAction(sender));
156 }
157 exception = e;
158 } catch (SelfSendException e) {
159 logger.debug("Dropping unidentified message from self.");
160 return new Pair<>(List.of(), null);
161 } catch (Exception e) {
162 exception = e;
163 }
164 }
165
166 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, exception));
167 return new Pair<>(actions, exception);
168 }
169
170 private List<HandleAction> checkAndHandleMessage(
171 final SignalServiceEnvelope envelope,
172 final SignalServiceContent content,
173 final boolean ignoreAttachments,
174 final Manager.ReceiveMessageHandler handler,
175 final Exception exception
176 ) {
177 if (!envelope.hasSourceUuid() && content != null) {
178 // Store uuid if we don't have it already
179 // address/uuid is validated by unidentified sender certificate
180 account.getRecipientStore().resolveRecipientTrusted(content.getSender());
181 }
182 if (isMessageBlocked(envelope, content)) {
183 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
184 return List.of();
185 } else if (isNotAllowedToSendToGroup(envelope, content)) {
186 logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
187 (envelope.hasSourceUuid() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
188 envelope.getTimestamp());
189 return List.of();
190 } else {
191 List<HandleAction> actions;
192 if (content != null) {
193 actions = handleMessage(envelope, content, ignoreAttachments);
194 } else {
195 actions = List.of();
196 }
197 handler.handleMessage(envelope, content, exception);
198 return actions;
199 }
200 }
201
202 public List<HandleAction> handleMessage(
203 SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
204 ) {
205 var actions = new ArrayList<HandleAction>();
206 final RecipientId sender;
207 final int senderDeviceId;
208 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
209 sender = recipientResolver.resolveRecipient(envelope.getSourceAddress());
210 senderDeviceId = envelope.getSourceDevice();
211 } else {
212 sender = recipientResolver.resolveRecipient(content.getSender());
213 senderDeviceId = content.getSenderDevice();
214 }
215
216 if (content.getSenderKeyDistributionMessage().isPresent()) {
217 final var message = content.getSenderKeyDistributionMessage().get();
218 final var protocolAddress = new SignalProtocolAddress(addressResolver.resolveSignalServiceAddress(sender)
219 .getIdentifier(), senderDeviceId);
220 dependencies.getMessageSender().processSenderKeyDistributionMessage(protocolAddress, message);
221 }
222
223 if (content.getDataMessage().isPresent()) {
224 var message = content.getDataMessage().get();
225
226 if (content.isNeedsReceipt()) {
227 actions.add(new SendReceiptAction(sender, message.getTimestamp()));
228 }
229
230 actions.addAll(handleSignalServiceDataMessage(message,
231 false,
232 sender,
233 account.getSelfRecipientId(),
234 ignoreAttachments));
235 }
236
237 if (content.getSyncMessage().isPresent()) {
238 var syncMessage = content.getSyncMessage().get();
239 actions.addAll(handleSyncMessage(syncMessage, sender, ignoreAttachments));
240 }
241
242 return actions;
243 }
244
245 private List<HandleAction> handleSyncMessage(
246 final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
247 ) {
248 var actions = new ArrayList<HandleAction>();
249 account.setMultiDevice(true);
250 if (syncMessage.getSent().isPresent()) {
251 var message = syncMessage.getSent().get();
252 final var destination = message.getDestination().orNull();
253 actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
254 true,
255 sender,
256 destination == null ? null : recipientResolver.resolveRecipient(destination),
257 ignoreAttachments));
258 }
259 if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
260 var rm = syncMessage.getRequest().get();
261 if (rm.isContactsRequest()) {
262 actions.add(SendSyncContactsAction.create());
263 }
264 if (rm.isGroupsRequest()) {
265 actions.add(SendSyncGroupsAction.create());
266 }
267 if (rm.isBlockedListRequest()) {
268 actions.add(SendSyncBlockedListAction.create());
269 }
270 if (rm.isKeysRequest()) {
271 actions.add(SendSyncKeysAction.create());
272 }
273 // TODO Handle rm.isConfigurationRequest();
274 }
275 if (syncMessage.getGroups().isPresent()) {
276 logger.warn("Received a group v1 sync message, that can't be handled anymore, ignoring.");
277 }
278 if (syncMessage.getBlockedList().isPresent()) {
279 final var blockedListMessage = syncMessage.getBlockedList().get();
280 for (var address : blockedListMessage.getAddresses()) {
281 contactHelper.setContactBlocked(recipientResolver.resolveRecipient(address), true);
282 }
283 for (var groupId : blockedListMessage.getGroupIds()
284 .stream()
285 .map(GroupId::unknownVersion)
286 .collect(Collectors.toSet())) {
287 try {
288 groupHelper.setGroupBlocked(groupId, true);
289 } catch (GroupNotFoundException e) {
290 logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
291 groupId.toBase64());
292 }
293 }
294 }
295 if (syncMessage.getContacts().isPresent()) {
296 try {
297 final var contactsMessage = syncMessage.getContacts().get();
298 attachmentHelper.retrieveAttachment(contactsMessage.getContactsStream(),
299 syncHelper::handleSyncDeviceContacts);
300 } catch (Exception e) {
301 logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
302 }
303 }
304 if (syncMessage.getVerified().isPresent()) {
305 final var verifiedMessage = syncMessage.getVerified().get();
306 account.getIdentityKeyStore()
307 .setIdentityTrustLevel(account.getRecipientStore()
308 .resolveRecipientTrusted(verifiedMessage.getDestination()),
309 verifiedMessage.getIdentityKey(),
310 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
311 }
312 if (syncMessage.getStickerPackOperations().isPresent()) {
313 final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
314 for (var m : stickerPackOperationMessages) {
315 if (!m.getPackId().isPresent()) {
316 continue;
317 }
318 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
319 final var installed = !m.getType().isPresent()
320 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
321
322 var sticker = account.getStickerStore().getSticker(stickerPackId);
323 if (m.getPackKey().isPresent()) {
324 if (sticker == null) {
325 sticker = new Sticker(stickerPackId, m.getPackKey().get());
326 }
327 if (installed) {
328 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
329 }
330 }
331
332 if (sticker != null) {
333 sticker.setInstalled(installed);
334 account.getStickerStore().updateSticker(sticker);
335 }
336 }
337 }
338 if (syncMessage.getFetchType().isPresent()) {
339 switch (syncMessage.getFetchType().get()) {
340 case LOCAL_PROFILE:
341 actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
342 case STORAGE_MANIFEST:
343 actions.add(RetrieveStorageDataAction.create());
344 }
345 }
346 if (syncMessage.getKeys().isPresent()) {
347 final var keysMessage = syncMessage.getKeys().get();
348 if (keysMessage.getStorageService().isPresent()) {
349 final var storageKey = keysMessage.getStorageService().get();
350 account.setStorageKey(storageKey);
351 actions.add(RetrieveStorageDataAction.create());
352 }
353 }
354 if (syncMessage.getConfiguration().isPresent()) {
355 // TODO
356 }
357 return actions;
358 }
359
360 private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
361 SignalServiceAddress source;
362 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
363 source = envelope.getSourceAddress();
364 } else if (content != null) {
365 source = content.getSender();
366 } else {
367 return false;
368 }
369 final var recipientId = recipientResolver.resolveRecipient(source);
370 if (contactHelper.isContactBlocked(recipientId)) {
371 return true;
372 }
373
374 if (content != null && content.getDataMessage().isPresent()) {
375 var message = content.getDataMessage().get();
376 if (message.getGroupContext().isPresent()) {
377 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
378 return groupHelper.isGroupBlocked(groupId);
379 }
380 }
381
382 return false;
383 }
384
385 private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
386 SignalServiceAddress source;
387 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
388 source = envelope.getSourceAddress();
389 } else if (content != null) {
390 source = content.getSender();
391 } else {
392 return false;
393 }
394
395 if (content == null || !content.getDataMessage().isPresent()) {
396 return false;
397 }
398
399 var message = content.getDataMessage().get();
400 if (!message.getGroupContext().isPresent()) {
401 return false;
402 }
403
404 if (message.getGroupContext().get().getGroupV1().isPresent()) {
405 var groupInfo = message.getGroupContext().get().getGroupV1().get();
406 if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
407 return false;
408 }
409 }
410
411 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
412 var group = groupHelper.getGroup(groupId);
413 if (group == null) {
414 return false;
415 }
416
417 final var recipientId = recipientResolver.resolveRecipient(source);
418 if (!group.isMember(recipientId)) {
419 return true;
420 }
421
422 if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
423 return message.getBody().isPresent()
424 || message.getAttachments().isPresent()
425 || message.getQuote()
426 .isPresent()
427 || message.getPreviews().isPresent()
428 || message.getMentions().isPresent()
429 || message.getSticker().isPresent();
430 }
431 return false;
432 }
433
434 private List<HandleAction> handleSignalServiceDataMessage(
435 SignalServiceDataMessage message,
436 boolean isSync,
437 RecipientId source,
438 RecipientId destination,
439 boolean ignoreAttachments
440 ) {
441 var actions = new ArrayList<HandleAction>();
442 if (message.getGroupContext().isPresent()) {
443 if (message.getGroupContext().get().getGroupV1().isPresent()) {
444 var groupInfo = message.getGroupContext().get().getGroupV1().get();
445 var groupId = GroupId.v1(groupInfo.getGroupId());
446 var group = groupHelper.getGroup(groupId);
447 if (group == null || group instanceof GroupInfoV1) {
448 var groupV1 = (GroupInfoV1) group;
449 switch (groupInfo.getType()) {
450 case UPDATE: {
451 if (groupV1 == null) {
452 groupV1 = new GroupInfoV1(groupId);
453 }
454
455 if (groupInfo.getAvatar().isPresent()) {
456 var avatar = groupInfo.getAvatar().get();
457 groupHelper.downloadGroupAvatar(groupV1.getGroupId(), avatar);
458 }
459
460 if (groupInfo.getName().isPresent()) {
461 groupV1.name = groupInfo.getName().get();
462 }
463
464 if (groupInfo.getMembers().isPresent()) {
465 groupV1.addMembers(groupInfo.getMembers()
466 .get()
467 .stream()
468 .map(recipientResolver::resolveRecipient)
469 .collect(Collectors.toSet()));
470 }
471
472 account.getGroupStore().updateGroup(groupV1);
473 break;
474 }
475 case DELIVER:
476 if (groupV1 == null && !isSync) {
477 actions.add(new SendGroupInfoRequestAction(source, groupId));
478 }
479 break;
480 case QUIT: {
481 if (groupV1 != null) {
482 groupV1.removeMember(source);
483 account.getGroupStore().updateGroup(groupV1);
484 }
485 break;
486 }
487 case REQUEST_INFO:
488 if (groupV1 != null && !isSync) {
489 actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
490 }
491 break;
492 }
493 } else {
494 // Received a group v1 message for a v2 group
495 }
496 }
497 if (message.getGroupContext().get().getGroupV2().isPresent()) {
498 final var groupContext = message.getGroupContext().get().getGroupV2().get();
499 final var groupMasterKey = groupContext.getMasterKey();
500
501 groupHelper.getOrMigrateGroup(groupMasterKey,
502 groupContext.getRevision(),
503 groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
504 }
505 }
506
507 final var conversationPartnerAddress = isSync ? destination : source;
508 if (conversationPartnerAddress != null && message.isEndSession()) {
509 account.getSessionStore().deleteAllSessions(conversationPartnerAddress);
510 }
511 if (message.isExpirationUpdate() || message.getBody().isPresent()) {
512 if (message.getGroupContext().isPresent()) {
513 if (message.getGroupContext().get().getGroupV1().isPresent()) {
514 var groupInfo = message.getGroupContext().get().getGroupV1().get();
515 var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
516 if (group != null) {
517 if (group.messageExpirationTime != message.getExpiresInSeconds()) {
518 group.messageExpirationTime = message.getExpiresInSeconds();
519 account.getGroupStore().updateGroup(group);
520 }
521 }
522 } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
523 // disappearing message timer already stored in the DecryptedGroup
524 }
525 } else if (conversationPartnerAddress != null) {
526 contactHelper.setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
527 }
528 }
529 if (!ignoreAttachments) {
530 if (message.getAttachments().isPresent()) {
531 for (var attachment : message.getAttachments().get()) {
532 attachmentHelper.downloadAttachment(attachment);
533 }
534 }
535 if (message.getSharedContacts().isPresent()) {
536 for (var contact : message.getSharedContacts().get()) {
537 if (contact.getAvatar().isPresent()) {
538 attachmentHelper.downloadAttachment(contact.getAvatar().get().getAttachment());
539 }
540 }
541 }
542 if (message.getPreviews().isPresent()) {
543 final var previews = message.getPreviews().get();
544 for (var preview : previews) {
545 if (preview.getImage().isPresent()) {
546 attachmentHelper.downloadAttachment(preview.getImage().get());
547 }
548 }
549 }
550 if (message.getQuote().isPresent()) {
551 final var quote = message.getQuote().get();
552
553 for (var quotedAttachment : quote.getAttachments()) {
554 final var thumbnail = quotedAttachment.getThumbnail();
555 if (thumbnail != null) {
556 attachmentHelper.downloadAttachment(thumbnail);
557 }
558 }
559 }
560 }
561 if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
562 final ProfileKey profileKey;
563 try {
564 profileKey = new ProfileKey(message.getProfileKey().get());
565 } catch (InvalidInputException e) {
566 throw new AssertionError(e);
567 }
568 if (account.getSelfRecipientId().equals(source)) {
569 this.account.setProfileKey(profileKey);
570 }
571 this.account.getProfileStore().storeProfileKey(source, profileKey);
572 }
573 if (message.getSticker().isPresent()) {
574 final var messageSticker = message.getSticker().get();
575 final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
576 var sticker = account.getStickerStore().getSticker(stickerPackId);
577 if (sticker == null) {
578 sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
579 account.getStickerStore().updateSticker(sticker);
580 }
581 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
582 }
583 return actions;
584 }
585 }