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