]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/Manager.java
Implement sticker pack retrieval
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
1 /*
2 Copyright (C) 2015-2021 AsamK and contributors
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 package org.asamk.signal.manager;
18
19 import org.asamk.signal.manager.api.Device;
20 import org.asamk.signal.manager.api.TypingAction;
21 import org.asamk.signal.manager.config.ServiceConfig;
22 import org.asamk.signal.manager.config.ServiceEnvironment;
23 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
24 import org.asamk.signal.manager.groups.GroupId;
25 import org.asamk.signal.manager.groups.GroupIdV1;
26 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
27 import org.asamk.signal.manager.groups.GroupLinkState;
28 import org.asamk.signal.manager.groups.GroupNotFoundException;
29 import org.asamk.signal.manager.groups.GroupPermission;
30 import org.asamk.signal.manager.groups.GroupUtils;
31 import org.asamk.signal.manager.groups.LastGroupAdminException;
32 import org.asamk.signal.manager.groups.NotAGroupMemberException;
33 import org.asamk.signal.manager.helper.GroupV2Helper;
34 import org.asamk.signal.manager.helper.PinHelper;
35 import org.asamk.signal.manager.helper.ProfileHelper;
36 import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
37 import org.asamk.signal.manager.jobs.Context;
38 import org.asamk.signal.manager.jobs.Job;
39 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
40 import org.asamk.signal.manager.storage.SignalAccount;
41 import org.asamk.signal.manager.storage.groups.GroupInfo;
42 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
43 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
44 import org.asamk.signal.manager.storage.identities.IdentityInfo;
45 import org.asamk.signal.manager.storage.messageCache.CachedMessage;
46 import org.asamk.signal.manager.storage.recipients.Contact;
47 import org.asamk.signal.manager.storage.recipients.Profile;
48 import org.asamk.signal.manager.storage.recipients.RecipientId;
49 import org.asamk.signal.manager.storage.stickers.Sticker;
50 import org.asamk.signal.manager.storage.stickers.StickerPackId;
51 import org.asamk.signal.manager.util.AttachmentUtils;
52 import org.asamk.signal.manager.util.IOUtils;
53 import org.asamk.signal.manager.util.KeyUtils;
54 import org.asamk.signal.manager.util.ProfileUtils;
55 import org.asamk.signal.manager.util.StickerUtils;
56 import org.asamk.signal.manager.util.Utils;
57 import org.signal.libsignal.metadata.InvalidMetadataMessageException;
58 import org.signal.libsignal.metadata.InvalidMetadataVersionException;
59 import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
60 import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
61 import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
62 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
63 import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
64 import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
65 import org.signal.libsignal.metadata.ProtocolNoSessionException;
66 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
67 import org.signal.libsignal.metadata.SelfSendException;
68 import org.signal.libsignal.metadata.certificate.CertificateValidator;
69 import org.signal.storageservice.protos.groups.GroupChange;
70 import org.signal.storageservice.protos.groups.local.DecryptedGroup;
71 import org.signal.zkgroup.InvalidInputException;
72 import org.signal.zkgroup.VerificationFailedException;
73 import org.signal.zkgroup.groups.GroupMasterKey;
74 import org.signal.zkgroup.groups.GroupSecretParams;
75 import org.signal.zkgroup.profiles.ClientZkProfileOperations;
76 import org.signal.zkgroup.profiles.ProfileKey;
77 import org.signal.zkgroup.profiles.ProfileKeyCredential;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80 import org.whispersystems.libsignal.IdentityKey;
81 import org.whispersystems.libsignal.IdentityKeyPair;
82 import org.whispersystems.libsignal.InvalidKeyException;
83 import org.whispersystems.libsignal.InvalidMessageException;
84 import org.whispersystems.libsignal.ecc.ECPublicKey;
85 import org.whispersystems.libsignal.state.PreKeyRecord;
86 import org.whispersystems.libsignal.state.SignedPreKeyRecord;
87 import org.whispersystems.libsignal.util.Pair;
88 import org.whispersystems.libsignal.util.guava.Optional;
89 import org.whispersystems.signalservice.api.SignalServiceAccountManager;
90 import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
91 import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
92 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
93 import org.whispersystems.signalservice.api.SignalSessionLock;
94 import org.whispersystems.signalservice.api.crypto.ContentHint;
95 import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
96 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
97 import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
98 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
99 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
100 import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
101 import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
102 import org.whispersystems.signalservice.api.messages.SendMessageResult;
103 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
104 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
105 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
106 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
107 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
108 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
109 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
110 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
111 import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
112 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
113 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
114 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
115 import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
116 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
117 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream;
118 import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream;
119 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
120 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
121 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
122 import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
123 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
124 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
125 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
126 import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
127 import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
128 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
129 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
130 import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
131 import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
132 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
133 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
134 import org.whispersystems.signalservice.api.util.InvalidNumberException;
135 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
136 import org.whispersystems.signalservice.api.util.SleepTimer;
137 import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
138 import org.whispersystems.signalservice.api.util.UuidUtil;
139 import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
140 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
141 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
142 import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
143 import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
144 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
145 import org.whispersystems.signalservice.internal.util.Hex;
146 import org.whispersystems.signalservice.internal.util.Util;
147
148 import java.io.Closeable;
149 import java.io.File;
150 import java.io.FileInputStream;
151 import java.io.FileOutputStream;
152 import java.io.IOException;
153 import java.io.InputStream;
154 import java.io.OutputStream;
155 import java.net.URI;
156 import java.net.URISyntaxException;
157 import java.net.URLEncoder;
158 import java.nio.charset.StandardCharsets;
159 import java.nio.file.Files;
160 import java.security.SignatureException;
161 import java.util.ArrayList;
162 import java.util.Arrays;
163 import java.util.Base64;
164 import java.util.Collection;
165 import java.util.Date;
166 import java.util.HashSet;
167 import java.util.List;
168 import java.util.Map;
169 import java.util.Set;
170 import java.util.UUID;
171 import java.util.concurrent.ExecutorService;
172 import java.util.concurrent.Executors;
173 import java.util.concurrent.TimeUnit;
174 import java.util.concurrent.TimeoutException;
175 import java.util.concurrent.locks.ReentrantLock;
176 import java.util.function.Function;
177 import java.util.stream.Collectors;
178
179 import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
180
181 public class Manager implements Closeable {
182
183 private final static Logger logger = LoggerFactory.getLogger(Manager.class);
184
185 private final CertificateValidator certificateValidator;
186
187 private final ServiceEnvironmentConfig serviceEnvironmentConfig;
188 private final String userAgent;
189
190 private SignalAccount account;
191 private final SignalServiceAccountManager accountManager;
192 private final GroupsV2Api groupsV2Api;
193 private final GroupsV2Operations groupsV2Operations;
194 private final SignalServiceMessageReceiver messageReceiver;
195 private final ClientZkProfileOperations clientZkProfileOperations;
196
197 private final ExecutorService executor = Executors.newCachedThreadPool();
198
199 private SignalServiceMessagePipe messagePipe = null;
200 private SignalServiceMessagePipe unidentifiedMessagePipe = null;
201
202 private final UnidentifiedAccessHelper unidentifiedAccessHelper;
203 private final ProfileHelper profileHelper;
204 private final GroupV2Helper groupV2Helper;
205 private final PinHelper pinHelper;
206 private final AvatarStore avatarStore;
207 private final AttachmentStore attachmentStore;
208 private final StickerPackStore stickerPackStore;
209 private final SignalSessionLock sessionLock = new SignalSessionLock() {
210 private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
211
212 @Override
213 public Lock acquire() {
214 LEGACY_LOCK.lock();
215 return LEGACY_LOCK::unlock;
216 }
217 };
218
219 Manager(
220 SignalAccount account,
221 PathConfig pathConfig,
222 ServiceEnvironmentConfig serviceEnvironmentConfig,
223 String userAgent
224 ) {
225 this.account = account;
226 this.serviceEnvironmentConfig = serviceEnvironmentConfig;
227 this.certificateValidator = new CertificateValidator(serviceEnvironmentConfig.getUnidentifiedSenderTrustRoot());
228 this.userAgent = userAgent;
229 this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
230 serviceEnvironmentConfig.getSignalServiceConfiguration())) : null;
231 final SleepTimer timer = new UptimeSleepTimer();
232 this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
233 new DynamicCredentialsProvider(account.getUuid(),
234 account.getUsername(),
235 account.getPassword(),
236 account.getDeviceId()),
237 userAgent,
238 groupsV2Operations,
239 ServiceConfig.AUTOMATIC_NETWORK_RETRY,
240 timer);
241 this.groupsV2Api = accountManager.getGroupsV2Api();
242 final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
243 serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(),
244 serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
245 serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
246 10);
247
248 this.pinHelper = new PinHelper(keyBackupService);
249 this.clientZkProfileOperations = capabilities.isGv2()
250 ? ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())
251 .getProfileOperations()
252 : null;
253 this.messageReceiver = new SignalServiceMessageReceiver(serviceEnvironmentConfig.getSignalServiceConfiguration(),
254 account.getUuid(),
255 account.getUsername(),
256 account.getPassword(),
257 account.getDeviceId(),
258 userAgent,
259 null,
260 timer,
261 clientZkProfileOperations,
262 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
263
264 this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
265 account.getProfileStore()::getProfileKey,
266 this::getRecipientProfile,
267 this::getSenderCertificate);
268 this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey,
269 unidentifiedAccessHelper::getAccessFor,
270 unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
271 () -> messageReceiver,
272 this::resolveSignalServiceAddress);
273 this.groupV2Helper = new GroupV2Helper(this::getRecipientProfileKeyCredential,
274 this::getRecipientProfile,
275 account::getSelfRecipientId,
276 groupsV2Operations,
277 groupsV2Api,
278 this::getGroupAuthForToday,
279 this::resolveSignalServiceAddress);
280 this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
281 this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
282 this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
283 }
284
285 public String getUsername() {
286 return account.getUsername();
287 }
288
289 public SignalServiceAddress getSelfAddress() {
290 return account.getSelfAddress();
291 }
292
293 public RecipientId getSelfRecipientId() {
294 return account.getSelfRecipientId();
295 }
296
297 private IdentityKeyPair getIdentityKeyPair() {
298 return account.getIdentityKeyPair();
299 }
300
301 public int getDeviceId() {
302 return account.getDeviceId();
303 }
304
305 public static Manager init(
306 String username, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
307 ) throws IOException, NotRegisteredException {
308 var pathConfig = PathConfig.createDefault(settingsPath);
309
310 if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
311 throw new NotRegisteredException();
312 }
313
314 var account = SignalAccount.load(pathConfig.getDataPath(), username, true);
315
316 if (!account.isRegistered()) {
317 throw new NotRegisteredException();
318 }
319
320 final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
321
322 return new Manager(account, pathConfig, serviceEnvironmentConfig, userAgent);
323 }
324
325 public static List<String> getAllLocalUsernames(File settingsPath) {
326 var pathConfig = PathConfig.createDefault(settingsPath);
327 final var dataPath = pathConfig.getDataPath();
328 final var files = dataPath.listFiles();
329
330 if (files == null) {
331 return List.of();
332 }
333
334 return Arrays.stream(files)
335 .filter(File::isFile)
336 .map(File::getName)
337 .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
338 .collect(Collectors.toList());
339 }
340
341 public void checkAccountState() throws IOException {
342 if (account.getLastReceiveTimestamp() == 0) {
343 logger.warn("The Signal protocol expects that incoming messages are regularly received.");
344 } else {
345 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
346 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
347 if (days > 7) {
348 logger.warn(
349 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
350 days);
351 }
352 }
353 if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
354 refreshPreKeys();
355 }
356 if (account.getUuid() == null) {
357 account.setUuid(accountManager.getOwnUuid());
358 }
359 updateAccountAttributes();
360 }
361
362 /**
363 * This is used for checking a set of phone numbers for registration on Signal
364 *
365 * @param numbers The set of phone number in question
366 * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
367 * @throws IOException if its unable to get the contacts to check if they're registered
368 */
369 public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
370 // Note "contactDetails" has no optionals. It only gives us info on users who are registered
371 var contactDetails = getRegisteredUsers(numbers);
372
373 var registeredUsers = contactDetails.keySet();
374
375 return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
376 }
377
378 public void updateAccountAttributes() throws IOException {
379 accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
380 null,
381 account.getLocalRegistrationId(),
382 true,
383 // set legacy pin only if no KBS master key is set
384 account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null,
385 account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
386 account.getSelfUnidentifiedAccessKey(),
387 account.isUnrestrictedUnidentifiedAccess(),
388 capabilities,
389 account.isDiscoverableByPhoneNumber());
390 }
391
392 /**
393 * @param givenName if null, the previous givenName will be kept
394 * @param familyName if null, the previous familyName will be kept
395 * @param about if null, the previous about text will be kept
396 * @param aboutEmoji if null, the previous about emoji will be kept
397 * @param avatar if avatar is null the image from the local avatar store is used (if present),
398 */
399 public void setProfile(
400 String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
401 ) throws IOException {
402 var profile = getRecipientProfile(account.getSelfRecipientId());
403 var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
404 if (givenName != null) {
405 builder.withGivenName(givenName);
406 }
407 if (familyName != null) {
408 builder.withFamilyName(familyName);
409 }
410 if (about != null) {
411 builder.withAbout(about);
412 }
413 if (aboutEmoji != null) {
414 builder.withAboutEmoji(aboutEmoji);
415 }
416 var newProfile = builder.build();
417
418 try (final var streamDetails = avatar == null
419 ? avatarStore.retrieveProfileAvatar(getSelfAddress())
420 : avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) {
421 accountManager.setVersionedProfile(account.getUuid(),
422 account.getProfileKey(),
423 newProfile.getInternalServiceName(),
424 newProfile.getAbout() == null ? "" : newProfile.getAbout(),
425 newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
426 Optional.absent(),
427 streamDetails);
428 }
429
430 if (avatar != null) {
431 if (avatar.isPresent()) {
432 avatarStore.storeProfileAvatar(getSelfAddress(),
433 outputStream -> IOUtils.copyFileToStream(avatar.get(), outputStream));
434 } else {
435 avatarStore.deleteProfileAvatar(getSelfAddress());
436 }
437 }
438 account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
439
440 try {
441 sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE));
442 } catch (UntrustedIdentityException ignored) {
443 }
444 }
445
446 public void unregister() throws IOException {
447 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
448 // If this is the master device, other users can't send messages to this number anymore.
449 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
450 accountManager.setGcmId(Optional.absent());
451
452 account.setRegistered(false);
453 }
454
455 public void deleteAccount() throws IOException {
456 accountManager.deleteAccount();
457
458 account.setRegistered(false);
459 }
460
461 public List<Device> getLinkedDevices() throws IOException {
462 var devices = accountManager.getDevices();
463 account.setMultiDevice(devices.size() > 1);
464 var identityKey = account.getIdentityKeyPair().getPrivateKey();
465 return devices.stream().map(d -> {
466 String deviceName = d.getName();
467 if (deviceName != null) {
468 try {
469 deviceName = DeviceNameUtil.decryptDeviceName(deviceName, identityKey);
470 } catch (IOException e) {
471 logger.debug("Failed to decrypt device name, maybe plain text?", e);
472 }
473 }
474 return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
475 }).collect(Collectors.toList());
476 }
477
478 public void removeLinkedDevices(int deviceId) throws IOException {
479 accountManager.removeDevice(deviceId);
480 var devices = accountManager.getDevices();
481 account.setMultiDevice(devices.size() > 1);
482 }
483
484 public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
485 var info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
486
487 addDevice(info.deviceIdentifier, info.deviceKey);
488 }
489
490 private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
491 var identityKeyPair = getIdentityKeyPair();
492 var verificationCode = accountManager.getNewDeviceVerificationCode();
493
494 accountManager.addDevice(deviceIdentifier,
495 deviceKey,
496 identityKeyPair,
497 Optional.of(account.getProfileKey().serialize()),
498 verificationCode);
499 account.setMultiDevice(true);
500 }
501
502 public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
503 if (!account.isMasterDevice()) {
504 throw new RuntimeException("Only master device can set a PIN");
505 }
506 if (pin.isPresent()) {
507 final var masterKey = account.getPinMasterKey() != null
508 ? account.getPinMasterKey()
509 : KeyUtils.createMasterKey();
510
511 pinHelper.setRegistrationLockPin(pin.get(), masterKey);
512
513 account.setRegistrationLockPin(pin.get(), masterKey);
514 } else {
515 // Remove legacy registration lock
516 accountManager.removeRegistrationLockV1();
517
518 // Remove KBS Pin
519 pinHelper.removeRegistrationLockPin();
520
521 account.setRegistrationLockPin(null, null);
522 }
523 }
524
525 void refreshPreKeys() throws IOException {
526 var oneTimePreKeys = generatePreKeys();
527 final var identityKeyPair = getIdentityKeyPair();
528 var signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
529
530 accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
531 }
532
533 private List<PreKeyRecord> generatePreKeys() {
534 final var offset = account.getPreKeyIdOffset();
535
536 var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
537 account.addPreKeys(records);
538
539 return records;
540 }
541
542 private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
543 final var signedPreKeyId = account.getNextSignedPreKeyId();
544
545 var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
546 account.addSignedPreKey(record);
547
548 return record;
549 }
550
551 private SignalServiceMessagePipe getOrCreateMessagePipe() {
552 if (messagePipe == null) {
553 messagePipe = messageReceiver.createMessagePipe();
554 }
555 return messagePipe;
556 }
557
558 private SignalServiceMessagePipe getOrCreateUnidentifiedMessagePipe() {
559 if (unidentifiedMessagePipe == null) {
560 unidentifiedMessagePipe = messageReceiver.createUnidentifiedMessagePipe();
561 }
562 return unidentifiedMessagePipe;
563 }
564
565 private SignalServiceMessageSender createMessageSender() {
566 return new SignalServiceMessageSender(serviceEnvironmentConfig.getSignalServiceConfiguration(),
567 account.getUuid(),
568 account.getUsername(),
569 account.getPassword(),
570 account.getDeviceId(),
571 account.getSignalProtocolStore(),
572 sessionLock,
573 userAgent,
574 account.isMultiDevice(),
575 Optional.fromNullable(messagePipe),
576 Optional.fromNullable(unidentifiedMessagePipe),
577 Optional.absent(),
578 clientZkProfileOperations,
579 executor,
580 ServiceConfig.MAX_ENVELOPE_SIZE,
581 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
582 }
583
584 public Profile getRecipientProfile(
585 RecipientId recipientId
586 ) {
587 return getRecipientProfile(recipientId, false);
588 }
589
590 private final Set<RecipientId> pendingProfileRequest = new HashSet<>();
591
592 Profile getRecipientProfile(
593 RecipientId recipientId, boolean force
594 ) {
595 var profile = account.getProfileStore().getProfile(recipientId);
596
597 var now = System.currentTimeMillis();
598 // Profiles are cached for 24h before retrieving them again, unless forced
599 if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
600 return profile;
601 }
602
603 synchronized (pendingProfileRequest) {
604 if (pendingProfileRequest.contains(recipientId)) {
605 return profile;
606 }
607 pendingProfileRequest.add(recipientId);
608 }
609 final SignalServiceProfile encryptedProfile;
610 try {
611 encryptedProfile = retrieveEncryptedProfile(recipientId);
612 } finally {
613 synchronized (pendingProfileRequest) {
614 pendingProfileRequest.remove(recipientId);
615 }
616 }
617 if (encryptedProfile == null) {
618 return null;
619 }
620
621 var profileKey = account.getProfileStore().getProfileKey(recipientId);
622 if (profileKey == null) {
623 profile = new Profile(System.currentTimeMillis(),
624 null,
625 null,
626 null,
627 null,
628 ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null),
629 ProfileUtils.getCapabilities(encryptedProfile));
630 } else {
631 profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile);
632 }
633 account.getProfileStore().storeProfile(recipientId, profile);
634
635 return profile;
636 }
637
638 private SignalServiceProfile retrieveEncryptedProfile(RecipientId recipientId) {
639 try {
640 return retrieveProfileAndCredential(recipientId, SignalServiceProfile.RequestType.PROFILE).getProfile();
641 } catch (IOException e) {
642 logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
643 return null;
644 }
645 }
646
647 private ProfileAndCredential retrieveProfileAndCredential(
648 final RecipientId recipientId, final SignalServiceProfile.RequestType requestType
649 ) throws IOException {
650 final var profileAndCredential = profileHelper.retrieveProfileSync(recipientId, requestType);
651 final var profile = profileAndCredential.getProfile();
652
653 try {
654 var newIdentity = account.getIdentityKeyStore()
655 .saveIdentity(recipientId,
656 new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
657 new Date());
658
659 if (newIdentity) {
660 account.getSessionStore().archiveSessions(recipientId);
661 }
662 } catch (InvalidKeyException ignored) {
663 logger.warn("Got invalid identity key in profile for {}",
664 resolveSignalServiceAddress(recipientId).getIdentifier());
665 }
666 return profileAndCredential;
667 }
668
669 private ProfileKeyCredential getRecipientProfileKeyCredential(RecipientId recipientId) {
670 var profileKeyCredential = account.getProfileStore().getProfileKeyCredential(recipientId);
671 if (profileKeyCredential != null) {
672 return profileKeyCredential;
673 }
674
675 ProfileAndCredential profileAndCredential;
676 try {
677 profileAndCredential = retrieveProfileAndCredential(recipientId,
678 SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
679 } catch (IOException e) {
680 logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
681 return null;
682 }
683
684 profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
685 account.getProfileStore().storeProfileKeyCredential(recipientId, profileKeyCredential);
686
687 var profileKey = account.getProfileStore().getProfileKey(recipientId);
688 if (profileKey != null) {
689 final var profile = decryptProfileAndDownloadAvatar(recipientId,
690 profileKey,
691 profileAndCredential.getProfile());
692 account.getProfileStore().storeProfile(recipientId, profile);
693 }
694
695 return profileKeyCredential;
696 }
697
698 private Profile decryptProfileAndDownloadAvatar(
699 final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
700 ) {
701 if (encryptedProfile.getAvatar() != null) {
702 downloadProfileAvatar(resolveSignalServiceAddress(recipientId), encryptedProfile.getAvatar(), profileKey);
703 }
704
705 return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
706 }
707
708 private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(GroupId groupId) throws IOException {
709 final var streamDetails = avatarStore.retrieveGroupAvatar(groupId);
710 if (streamDetails == null) {
711 return Optional.absent();
712 }
713
714 return Optional.of(AttachmentUtils.createAttachment(streamDetails, Optional.absent()));
715 }
716
717 private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(SignalServiceAddress address) throws IOException {
718 final var streamDetails = avatarStore.retrieveContactAvatar(address);
719 if (streamDetails == null) {
720 return Optional.absent();
721 }
722
723 return Optional.of(AttachmentUtils.createAttachment(streamDetails, Optional.absent()));
724 }
725
726 private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
727 var g = getGroup(groupId);
728 if (g == null) {
729 throw new GroupNotFoundException(groupId);
730 }
731 if (!g.isMember(account.getSelfRecipientId())) {
732 throw new NotAGroupMemberException(groupId, g.getTitle());
733 }
734 return g;
735 }
736
737 private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
738 var g = getGroup(groupId);
739 if (g == null) {
740 throw new GroupNotFoundException(groupId);
741 }
742 if (!g.isMember(account.getSelfRecipientId()) && !g.isPendingMember(account.getSelfRecipientId())) {
743 throw new NotAGroupMemberException(groupId, g.getTitle());
744 }
745 return g;
746 }
747
748 public List<GroupInfo> getGroups() {
749 return account.getGroupStore().getGroups();
750 }
751
752 public Pair<Long, List<SendMessageResult>> sendGroupMessage(
753 String messageText, List<String> attachments, GroupId groupId
754 ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
755 final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
756 if (attachments != null) {
757 messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
758 }
759
760 return sendGroupMessage(messageBuilder, groupId);
761 }
762
763 public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction(
764 String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId
765 ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException {
766 var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
767 var reaction = new SignalServiceDataMessage.Reaction(emoji,
768 remove,
769 resolveSignalServiceAddress(targetAuthorRecipientId),
770 targetSentTimestamp);
771 final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
772
773 return sendGroupMessage(messageBuilder, groupId);
774 }
775
776 public Pair<Long, List<SendMessageResult>> sendGroupMessage(
777 SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
778 ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
779 final var g = getGroupForSending(groupId);
780
781 GroupUtils.setGroupContext(messageBuilder, g);
782 messageBuilder.withExpiration(g.getMessageExpirationTime());
783
784 return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
785 }
786
787 public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
788 GroupId groupId, Set<String> groupAdmins
789 ) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
790 var group = getGroupForUpdating(groupId);
791 if (group instanceof GroupInfoV1) {
792 return quitGroupV1((GroupInfoV1) group);
793 }
794
795 final var newAdmins = getSignalServiceAddresses(groupAdmins);
796 try {
797 return quitGroupV2((GroupInfoV2) group, newAdmins);
798 } catch (ConflictException e) {
799 // Detected conflicting update, refreshing group and trying again
800 group = getGroup(groupId, true);
801 return quitGroupV2((GroupInfoV2) group, newAdmins);
802 }
803 }
804
805 private Pair<Long, List<SendMessageResult>> quitGroupV1(final GroupInfoV1 groupInfoV1) throws IOException {
806 var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
807 .withId(groupInfoV1.getGroupId().serialize())
808 .build();
809
810 var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
811 groupInfoV1.removeMember(account.getSelfRecipientId());
812 account.getGroupStore().updateGroup(groupInfoV1);
813 return sendMessage(messageBuilder, groupInfoV1.getMembersWithout(account.getSelfRecipientId()));
814 }
815
816 private Pair<Long, List<SendMessageResult>> quitGroupV2(
817 final GroupInfoV2 groupInfoV2, final Set<RecipientId> newAdmins
818 ) throws LastGroupAdminException, IOException {
819 final var currentAdmins = groupInfoV2.getAdminMembers();
820 newAdmins.removeAll(currentAdmins);
821 newAdmins.retainAll(groupInfoV2.getMembers());
822 if (currentAdmins.contains(getSelfRecipientId())
823 && currentAdmins.size() == 1
824 && groupInfoV2.getMembers().size() > 1
825 && newAdmins.size() == 0) {
826 // Last admin can't leave the group, unless she's also the last member
827 throw new LastGroupAdminException(groupInfoV2.getGroupId(), groupInfoV2.getTitle());
828 }
829 final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
830 groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
831 var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
832 account.getGroupStore().updateGroup(groupInfoV2);
833 return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId()));
834 }
835
836 public void deleteGroup(GroupId groupId) throws IOException {
837 account.getGroupStore().deleteGroup(groupId);
838 avatarStore.deleteGroupAvatar(groupId);
839 }
840
841 public Pair<GroupId, List<SendMessageResult>> createGroup(
842 String name, List<String> members, File avatarFile
843 ) throws IOException, AttachmentInvalidException, InvalidNumberException {
844 return createGroup(name, members == null ? null : getSignalServiceAddresses(members), avatarFile);
845 }
846
847 private Pair<GroupId, List<SendMessageResult>> createGroup(
848 String name, Set<RecipientId> members, File avatarFile
849 ) throws IOException, AttachmentInvalidException {
850 final var selfRecipientId = account.getSelfRecipientId();
851 if (members != null && members.contains(selfRecipientId)) {
852 members = new HashSet<>(members);
853 members.remove(selfRecipientId);
854 }
855
856 var gv2Pair = groupV2Helper.createGroup(name == null ? "" : name,
857 members == null ? Set.of() : members,
858 avatarFile);
859
860 SignalServiceDataMessage.Builder messageBuilder;
861 if (gv2Pair == null) {
862 // Failed to create v2 group, creating v1 group instead
863 var gv1 = new GroupInfoV1(GroupIdV1.createRandom());
864 gv1.addMembers(List.of(selfRecipientId));
865 final var result = updateGroupV1(gv1, name, members, avatarFile);
866 return new Pair<>(gv1.getGroupId(), result.second());
867 }
868
869 final var gv2 = gv2Pair.first();
870 final var decryptedGroup = gv2Pair.second();
871
872 gv2.setGroup(decryptedGroup, this::resolveRecipient);
873 if (avatarFile != null) {
874 avatarStore.storeGroupAvatar(gv2.getGroupId(),
875 outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
876 }
877 messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
878 account.getGroupStore().updateGroup(gv2);
879
880 final var result = sendMessage(messageBuilder, gv2.getMembersIncludingPendingWithout(selfRecipientId));
881 return new Pair<>(gv2.getGroupId(), result.second());
882 }
883
884 public Pair<Long, List<SendMessageResult>> updateGroup(
885 GroupId groupId,
886 String name,
887 String description,
888 List<String> members,
889 List<String> removeMembers,
890 List<String> admins,
891 List<String> removeAdmins,
892 boolean resetGroupLink,
893 GroupLinkState groupLinkState,
894 GroupPermission addMemberPermission,
895 GroupPermission editDetailsPermission,
896 File avatarFile,
897 Integer expirationTimer
898 ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
899 return updateGroup(groupId,
900 name,
901 description,
902 members == null ? null : getSignalServiceAddresses(members),
903 removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
904 admins == null ? null : getSignalServiceAddresses(admins),
905 removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
906 resetGroupLink,
907 groupLinkState,
908 addMemberPermission,
909 editDetailsPermission,
910 avatarFile,
911 expirationTimer);
912 }
913
914 private Pair<Long, List<SendMessageResult>> updateGroup(
915 final GroupId groupId,
916 final String name,
917 final String description,
918 final Set<RecipientId> members,
919 final Set<RecipientId> removeMembers,
920 final Set<RecipientId> admins,
921 final Set<RecipientId> removeAdmins,
922 final boolean resetGroupLink,
923 final GroupLinkState groupLinkState,
924 final GroupPermission addMemberPermission,
925 final GroupPermission editDetailsPermission,
926 final File avatarFile,
927 final Integer expirationTimer
928 ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
929 var group = getGroupForUpdating(groupId);
930
931 if (group instanceof GroupInfoV2) {
932 try {
933 return updateGroupV2((GroupInfoV2) group,
934 name,
935 description,
936 members,
937 removeMembers,
938 admins,
939 removeAdmins,
940 resetGroupLink,
941 groupLinkState,
942 addMemberPermission,
943 editDetailsPermission,
944 avatarFile,
945 expirationTimer);
946 } catch (ConflictException e) {
947 // Detected conflicting update, refreshing group and trying again
948 group = getGroup(groupId, true);
949 return updateGroupV2((GroupInfoV2) group,
950 name,
951 description,
952 members,
953 removeMembers,
954 admins,
955 removeAdmins,
956 resetGroupLink,
957 groupLinkState,
958 addMemberPermission,
959 editDetailsPermission,
960 avatarFile,
961 expirationTimer);
962 }
963 }
964
965 final var gv1 = (GroupInfoV1) group;
966 final var result = updateGroupV1(gv1, name, members, avatarFile);
967 if (expirationTimer != null) {
968 setExpirationTimer(gv1, expirationTimer);
969 }
970 return result;
971 }
972
973 private Pair<Long, List<SendMessageResult>> updateGroupV1(
974 final GroupInfoV1 gv1, final String name, final Set<RecipientId> members, final File avatarFile
975 ) throws IOException, AttachmentInvalidException {
976 updateGroupV1Details(gv1, name, members, avatarFile);
977 var messageBuilder = getGroupUpdateMessageBuilder(gv1);
978
979 account.getGroupStore().updateGroup(gv1);
980
981 return sendMessage(messageBuilder, gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
982 }
983
984 private void updateGroupV1Details(
985 final GroupInfoV1 g, final String name, final Collection<RecipientId> members, final File avatarFile
986 ) throws IOException {
987 if (name != null) {
988 g.name = name;
989 }
990
991 if (members != null) {
992 final var newMemberAddresses = members.stream()
993 .filter(member -> !g.isMember(member))
994 .map(this::resolveSignalServiceAddress)
995 .collect(Collectors.toList());
996 final var newE164Members = new HashSet<String>();
997 for (var member : newMemberAddresses) {
998 if (!member.getNumber().isPresent()) {
999 continue;
1000 }
1001 newE164Members.add(member.getNumber().get());
1002 }
1003
1004 final var registeredUsers = getRegisteredUsers(newE164Members);
1005 if (registeredUsers.size() != newE164Members.size()) {
1006 // Some of the new members are not registered on Signal
1007 newE164Members.removeAll(registeredUsers.keySet());
1008 throw new IOException("Failed to add members "
1009 + String.join(", ", newE164Members)
1010 + " to group: Not registered on Signal");
1011 }
1012
1013 g.addMembers(members);
1014 }
1015
1016 if (avatarFile != null) {
1017 avatarStore.storeGroupAvatar(g.getGroupId(),
1018 outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
1019 }
1020 }
1021
1022 private Pair<Long, List<SendMessageResult>> updateGroupV2(
1023 final GroupInfoV2 group,
1024 final String name,
1025 final String description,
1026 final Set<RecipientId> members,
1027 final Set<RecipientId> removeMembers,
1028 final Set<RecipientId> admins,
1029 final Set<RecipientId> removeAdmins,
1030 final boolean resetGroupLink,
1031 final GroupLinkState groupLinkState,
1032 final GroupPermission addMemberPermission,
1033 final GroupPermission editDetailsPermission,
1034 final File avatarFile,
1035 Integer expirationTimer
1036 ) throws IOException {
1037 Pair<Long, List<SendMessageResult>> result = null;
1038 if (group.isPendingMember(account.getSelfRecipientId())) {
1039 var groupGroupChangePair = groupV2Helper.acceptInvite(group);
1040 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1041 }
1042
1043 if (members != null) {
1044 final var newMembers = new HashSet<>(members);
1045 newMembers.removeAll(group.getMembers());
1046 if (newMembers.size() > 0) {
1047 var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers);
1048 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1049 }
1050 }
1051
1052 if (removeMembers != null) {
1053 var existingRemoveMembers = new HashSet<>(removeMembers);
1054 existingRemoveMembers.retainAll(group.getMembers());
1055 existingRemoveMembers.remove(getSelfRecipientId());// self can be removed with sendQuitGroupMessage
1056 if (existingRemoveMembers.size() > 0) {
1057 var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers);
1058 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1059 }
1060
1061 var pendingRemoveMembers = new HashSet<>(removeMembers);
1062 pendingRemoveMembers.retainAll(group.getPendingMembers());
1063 if (pendingRemoveMembers.size() > 0) {
1064 var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers);
1065 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1066 }
1067 }
1068
1069 if (admins != null) {
1070 final var newAdmins = new HashSet<>(admins);
1071 newAdmins.retainAll(group.getMembers());
1072 newAdmins.removeAll(group.getAdminMembers());
1073 if (newAdmins.size() > 0) {
1074 for (var admin : newAdmins) {
1075 var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
1076 result = sendUpdateGroupV2Message(group,
1077 groupGroupChangePair.first(),
1078 groupGroupChangePair.second());
1079 }
1080 }
1081 }
1082
1083 if (removeAdmins != null) {
1084 final var existingRemoveAdmins = new HashSet<>(removeAdmins);
1085 existingRemoveAdmins.retainAll(group.getAdminMembers());
1086 if (existingRemoveAdmins.size() > 0) {
1087 for (var admin : existingRemoveAdmins) {
1088 var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
1089 result = sendUpdateGroupV2Message(group,
1090 groupGroupChangePair.first(),
1091 groupGroupChangePair.second());
1092 }
1093 }
1094 }
1095
1096 if (resetGroupLink) {
1097 var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
1098 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1099 }
1100
1101 if (groupLinkState != null) {
1102 var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState);
1103 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1104 }
1105
1106 if (addMemberPermission != null) {
1107 var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission);
1108 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1109 }
1110
1111 if (editDetailsPermission != null) {
1112 var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission);
1113 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1114 }
1115
1116 if (expirationTimer != null) {
1117 var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer);
1118 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1119 }
1120
1121 if (name != null || description != null || avatarFile != null) {
1122 var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
1123 if (avatarFile != null) {
1124 avatarStore.storeGroupAvatar(group.getGroupId(),
1125 outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
1126 }
1127 result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
1128 }
1129
1130 return result;
1131 }
1132
1133 public Pair<GroupId, List<SendMessageResult>> joinGroup(
1134 GroupInviteLinkUrl inviteLinkUrl
1135 ) throws IOException, GroupLinkNotActiveException {
1136 final var groupJoinInfo = groupV2Helper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
1137 inviteLinkUrl.getPassword());
1138 final var groupChange = groupV2Helper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
1139 inviteLinkUrl.getPassword(),
1140 groupJoinInfo);
1141 final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
1142 groupJoinInfo.getRevision() + 1,
1143 groupChange.toByteArray());
1144
1145 if (group.getGroup() == null) {
1146 // Only requested member, can't send update to group members
1147 return new Pair<>(group.getGroupId(), List.of());
1148 }
1149
1150 final var result = sendUpdateGroupV2Message(group, group.getGroup(), groupChange);
1151
1152 return new Pair<>(group.getGroupId(), result.second());
1153 }
1154
1155 private Pair<Long, List<SendMessageResult>> sendUpdateGroupV2Message(
1156 GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
1157 ) throws IOException {
1158 final var selfRecipientId = account.getSelfRecipientId();
1159 final var members = group.getMembersIncludingPendingWithout(selfRecipientId);
1160 group.setGroup(newDecryptedGroup, this::resolveRecipient);
1161 members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
1162
1163 final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
1164 account.getGroupStore().updateGroup(group);
1165 return sendMessage(messageBuilder, members);
1166 }
1167
1168 private static int currentTimeDays() {
1169 return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
1170 }
1171
1172 private GroupsV2AuthorizationString getGroupAuthForToday(
1173 final GroupSecretParams groupSecretParams
1174 ) throws IOException {
1175 final var today = currentTimeDays();
1176 // Returns credentials for the next 7 days
1177 final var credentials = groupsV2Api.getCredentials(today);
1178 // TODO cache credentials until they expire
1179 var authCredentialResponse = credentials.get(today);
1180 try {
1181 return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
1182 today,
1183 groupSecretParams,
1184 authCredentialResponse);
1185 } catch (VerificationFailedException e) {
1186 throw new IOException(e);
1187 }
1188 }
1189
1190 Pair<Long, List<SendMessageResult>> sendGroupInfoMessage(
1191 GroupIdV1 groupId, SignalServiceAddress recipient
1192 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
1193 GroupInfoV1 g;
1194 var group = getGroupForSending(groupId);
1195 if (!(group instanceof GroupInfoV1)) {
1196 throw new RuntimeException("Received an invalid group request for a v2 group!");
1197 }
1198 g = (GroupInfoV1) group;
1199
1200 final var recipientId = resolveRecipient(recipient);
1201 if (!g.isMember(recipientId)) {
1202 throw new NotAGroupMemberException(groupId, g.name);
1203 }
1204
1205 var messageBuilder = getGroupUpdateMessageBuilder(g);
1206
1207 // Send group message only to the recipient who requested it
1208 return sendMessage(messageBuilder, Set.of(recipientId));
1209 }
1210
1211 private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
1212 var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE)
1213 .withId(g.getGroupId().serialize())
1214 .withName(g.name)
1215 .withMembers(g.getMembers()
1216 .stream()
1217 .map(this::resolveSignalServiceAddress)
1218 .collect(Collectors.toList()));
1219
1220 try {
1221 final var attachment = createGroupAvatarAttachment(g.getGroupId());
1222 if (attachment.isPresent()) {
1223 group.withAvatar(attachment.get());
1224 }
1225 } catch (IOException e) {
1226 throw new AttachmentInvalidException(g.getGroupId().toBase64(), e);
1227 }
1228
1229 return SignalServiceDataMessage.newBuilder()
1230 .asGroupMessage(group.build())
1231 .withExpiration(g.getMessageExpirationTime());
1232 }
1233
1234 private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
1235 var group = SignalServiceGroupV2.newBuilder(g.getMasterKey())
1236 .withRevision(g.getGroup().getRevision())
1237 .withSignedGroupChange(signedGroupChange);
1238 return SignalServiceDataMessage.newBuilder()
1239 .asGroupMessage(group.build())
1240 .withExpiration(g.getMessageExpirationTime());
1241 }
1242
1243 Pair<Long, List<SendMessageResult>> sendGroupInfoRequest(
1244 GroupIdV1 groupId, SignalServiceAddress recipient
1245 ) throws IOException {
1246 var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize());
1247
1248 var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group.build());
1249
1250 // Send group info request message to the recipient who sent us a message with this groupId
1251 return sendMessage(messageBuilder, Set.of(resolveRecipient(recipient)));
1252 }
1253
1254 void sendReceipt(
1255 SignalServiceAddress remoteAddress, long messageId
1256 ) throws IOException, UntrustedIdentityException {
1257 var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
1258 List.of(messageId),
1259 System.currentTimeMillis());
1260
1261 createMessageSender().sendReceipt(remoteAddress,
1262 unidentifiedAccessHelper.getAccessFor(resolveRecipient(remoteAddress)),
1263 receiptMessage);
1264 }
1265
1266 public Pair<Long, List<SendMessageResult>> sendMessage(
1267 String messageText, List<String> attachments, List<String> recipients
1268 ) throws IOException, AttachmentInvalidException, InvalidNumberException {
1269 final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
1270 if (attachments != null) {
1271 var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
1272
1273 // Upload attachments here, so we only upload once even for multiple recipients
1274 var messageSender = createMessageSender();
1275 var attachmentPointers = new ArrayList<SignalServiceAttachment>(attachmentStreams.size());
1276 for (var attachment : attachmentStreams) {
1277 if (attachment.isStream()) {
1278 attachmentPointers.add(messageSender.uploadAttachment(attachment.asStream()));
1279 } else if (attachment.isPointer()) {
1280 attachmentPointers.add(attachment.asPointer());
1281 }
1282 }
1283
1284 messageBuilder.withAttachments(attachmentPointers);
1285 }
1286 return sendMessage(messageBuilder, getSignalServiceAddresses(recipients));
1287 }
1288
1289 public Pair<Long, SendMessageResult> sendSelfMessage(
1290 String messageText, List<String> attachments
1291 ) throws IOException, AttachmentInvalidException {
1292 final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
1293 if (attachments != null) {
1294 messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
1295 }
1296 return sendSelfMessage(messageBuilder);
1297 }
1298
1299 public Pair<Long, List<SendMessageResult>> sendRemoteDeleteMessage(
1300 long targetSentTimestamp, List<String> recipients
1301 ) throws IOException, InvalidNumberException {
1302 var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
1303 final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete);
1304 return sendMessage(messageBuilder, getSignalServiceAddresses(recipients));
1305 }
1306
1307 public Pair<Long, List<SendMessageResult>> sendGroupRemoteDeleteMessage(
1308 long targetSentTimestamp, GroupId groupId
1309 ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
1310 var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
1311 final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete);
1312 return sendGroupMessage(messageBuilder, groupId);
1313 }
1314
1315 public Pair<Long, List<SendMessageResult>> sendMessageReaction(
1316 String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List<String> recipients
1317 ) throws IOException, InvalidNumberException {
1318 var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
1319 var reaction = new SignalServiceDataMessage.Reaction(emoji,
1320 remove,
1321 resolveSignalServiceAddress(targetAuthorRecipientId),
1322 targetSentTimestamp);
1323 final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
1324 return sendMessage(messageBuilder, getSignalServiceAddresses(recipients));
1325 }
1326
1327 public Pair<Long, List<SendMessageResult>> sendEndSessionMessage(List<String> recipients) throws IOException, InvalidNumberException {
1328 var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage();
1329
1330 final var signalServiceAddresses = getSignalServiceAddresses(recipients);
1331 try {
1332 return sendMessage(messageBuilder, signalServiceAddresses);
1333 } catch (Exception e) {
1334 for (var address : signalServiceAddresses) {
1335 handleEndSession(address);
1336 }
1337 throw e;
1338 }
1339 }
1340
1341 void renewSession(RecipientId recipientId) throws IOException {
1342 account.getSessionStore().archiveSessions(recipientId);
1343 if (!recipientId.equals(getSelfRecipientId())) {
1344 sendNullMessage(recipientId);
1345 }
1346 }
1347
1348 public String getContactName(String number) throws InvalidNumberException {
1349 var contact = account.getContactStore().getContact(canonicalizeAndResolveRecipient(number));
1350 return contact == null || contact.getName() == null ? "" : contact.getName();
1351 }
1352
1353 public void setContactName(String number, String name) throws InvalidNumberException, NotMasterDeviceException {
1354 if (!account.isMasterDevice()) {
1355 throw new NotMasterDeviceException();
1356 }
1357 final var recipientId = canonicalizeAndResolveRecipient(number);
1358 var contact = account.getContactStore().getContact(recipientId);
1359 final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
1360 account.getContactStore().storeContact(recipientId, builder.withName(name).build());
1361 }
1362
1363 public void setContactBlocked(
1364 String number, boolean blocked
1365 ) throws InvalidNumberException, NotMasterDeviceException {
1366 if (!account.isMasterDevice()) {
1367 throw new NotMasterDeviceException();
1368 }
1369 setContactBlocked(canonicalizeAndResolveRecipient(number), blocked);
1370 }
1371
1372 private void setContactBlocked(RecipientId recipientId, boolean blocked) {
1373 var contact = account.getContactStore().getContact(recipientId);
1374 final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
1375 account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build());
1376 }
1377
1378 public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException {
1379 var group = getGroup(groupId);
1380 if (group == null) {
1381 throw new GroupNotFoundException(groupId);
1382 }
1383
1384 group.setBlocked(blocked);
1385 account.getGroupStore().updateGroup(group);
1386 }
1387
1388 private void setExpirationTimer(RecipientId recipientId, int messageExpirationTimer) {
1389 var contact = account.getContactStore().getContact(recipientId);
1390 if (contact != null && contact.getMessageExpirationTime() == messageExpirationTimer) {
1391 return;
1392 }
1393 final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
1394 account.getContactStore()
1395 .storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build());
1396 }
1397
1398 private void sendExpirationTimerUpdate(RecipientId recipientId) throws IOException {
1399 final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
1400 sendMessage(messageBuilder, Set.of(recipientId));
1401 }
1402
1403 /**
1404 * Change the expiration timer for a contact
1405 */
1406 public void setExpirationTimer(
1407 String number, int messageExpirationTimer
1408 ) throws IOException, InvalidNumberException {
1409 var recipientId = canonicalizeAndResolveRecipient(number);
1410 setExpirationTimer(recipientId, messageExpirationTimer);
1411 sendExpirationTimerUpdate(recipientId);
1412 }
1413
1414 /**
1415 * Change the expiration timer for a group
1416 */
1417 private void setExpirationTimer(
1418 GroupInfoV1 groupInfoV1, int messageExpirationTimer
1419 ) throws NotAGroupMemberException, GroupNotFoundException, IOException {
1420 groupInfoV1.messageExpirationTime = messageExpirationTimer;
1421 account.getGroupStore().updateGroup(groupInfoV1);
1422 sendExpirationTimerUpdate(groupInfoV1.getGroupId());
1423 }
1424
1425 private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
1426 final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
1427 sendGroupMessage(messageBuilder, groupId);
1428 }
1429
1430 /**
1431 * Upload the sticker pack from path.
1432 *
1433 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1434 * @return if successful, returns the URL to install the sticker pack in the signal app
1435 */
1436 public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
1437 var manifest = StickerUtils.getSignalServiceStickerManifestUpload(path);
1438
1439 var messageSender = createMessageSender();
1440
1441 var packKey = KeyUtils.createStickerUploadKey();
1442 var packIdString = messageSender.uploadStickerManifest(manifest, packKey);
1443 var packId = StickerPackId.deserialize(Hex.fromStringCondensed(packIdString));
1444
1445 var sticker = new Sticker(packId, packKey);
1446 account.getStickerStore().updateSticker(sticker);
1447
1448 try {
1449 return new URI("https",
1450 "signal.art",
1451 "/addstickers/",
1452 "pack_id="
1453 + URLEncoder.encode(Hex.toStringCondensed(packId.serialize()), StandardCharsets.UTF_8)
1454 + "&pack_key="
1455 + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)).toString();
1456 } catch (URISyntaxException e) {
1457 throw new AssertionError(e);
1458 }
1459 }
1460
1461 public void requestAllSyncData() throws IOException {
1462 requestSyncGroups();
1463 requestSyncContacts();
1464 requestSyncBlocked();
1465 requestSyncConfiguration();
1466 requestSyncKeys();
1467 }
1468
1469 private void requestSyncGroups() throws IOException {
1470 var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
1471 .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS)
1472 .build();
1473 var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
1474 try {
1475 sendSyncMessage(message);
1476 } catch (UntrustedIdentityException e) {
1477 throw new AssertionError(e);
1478 }
1479 }
1480
1481 private void requestSyncContacts() throws IOException {
1482 var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
1483 .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS)
1484 .build();
1485 var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
1486 try {
1487 sendSyncMessage(message);
1488 } catch (UntrustedIdentityException e) {
1489 throw new AssertionError(e);
1490 }
1491 }
1492
1493 private void requestSyncBlocked() throws IOException {
1494 var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
1495 .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED)
1496 .build();
1497 var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
1498 try {
1499 sendSyncMessage(message);
1500 } catch (UntrustedIdentityException e) {
1501 throw new AssertionError(e);
1502 }
1503 }
1504
1505 private void requestSyncConfiguration() throws IOException {
1506 var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
1507 .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION)
1508 .build();
1509 var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
1510 try {
1511 sendSyncMessage(message);
1512 } catch (UntrustedIdentityException e) {
1513 throw new AssertionError(e);
1514 }
1515 }
1516
1517 private void requestSyncKeys() throws IOException {
1518 var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
1519 .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS)
1520 .build();
1521 var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
1522 try {
1523 sendSyncMessage(message);
1524 } catch (UntrustedIdentityException e) {
1525 throw new AssertionError(e);
1526 }
1527 }
1528
1529 private byte[] getSenderCertificate() {
1530 byte[] certificate;
1531 try {
1532 if (account.isPhoneNumberShared()) {
1533 certificate = accountManager.getSenderCertificate();
1534 } else {
1535 certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1536 }
1537 } catch (IOException e) {
1538 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
1539 return null;
1540 }
1541 // TODO cache for a day
1542 return certificate;
1543 }
1544
1545 private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException {
1546 var messageSender = createMessageSender();
1547 messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
1548 }
1549
1550 private Set<RecipientId> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
1551 final var signalServiceAddresses = new HashSet<SignalServiceAddress>(numbers.size());
1552 final var addressesMissingUuid = new HashSet<SignalServiceAddress>();
1553
1554 for (var number : numbers) {
1555 final var resolvedAddress = resolveSignalServiceAddress(canonicalizeAndResolveRecipient(number));
1556 if (resolvedAddress.getUuid().isPresent()) {
1557 signalServiceAddresses.add(resolvedAddress);
1558 } else {
1559 addressesMissingUuid.add(resolvedAddress);
1560 }
1561 }
1562
1563 final var numbersMissingUuid = addressesMissingUuid.stream()
1564 .map(a -> a.getNumber().get())
1565 .collect(Collectors.toSet());
1566 Map<String, UUID> registeredUsers;
1567 try {
1568 registeredUsers = getRegisteredUsers(numbersMissingUuid);
1569 } catch (IOException e) {
1570 logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage());
1571 registeredUsers = Map.of();
1572 }
1573
1574 for (var address : addressesMissingUuid) {
1575 final var number = address.getNumber().get();
1576 if (registeredUsers.containsKey(number)) {
1577 final var newAddress = resolveSignalServiceAddress(resolveRecipientTrusted(new SignalServiceAddress(
1578 registeredUsers.get(number),
1579 number)));
1580 signalServiceAddresses.add(newAddress);
1581 } else {
1582 signalServiceAddresses.add(address);
1583 }
1584 }
1585
1586 return signalServiceAddresses.stream().map(this::resolveRecipient).collect(Collectors.toSet());
1587 }
1588
1589 private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException {
1590 final var address = resolveSignalServiceAddress(recipientId);
1591 if (!address.getNumber().isPresent()) {
1592 return recipientId;
1593 }
1594 final var number = address.getNumber().get();
1595 final var uuidMap = getRegisteredUsers(Set.of(number));
1596 return resolveRecipientTrusted(new SignalServiceAddress(uuidMap.getOrDefault(number, null), number));
1597 }
1598
1599 private Map<String, UUID> getRegisteredUsers(final Set<String> numbers) throws IOException {
1600 try {
1601 return accountManager.getRegisteredUsers(ServiceConfig.getIasKeyStore(),
1602 numbers,
1603 serviceEnvironmentConfig.getCdsMrenclave());
1604 } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) {
1605 throw new IOException(e);
1606 }
1607 }
1608
1609 public void sendTypingMessage(
1610 TypingAction action, Set<String> recipients
1611 ) throws IOException, UntrustedIdentityException, InvalidNumberException {
1612 sendTypingMessageInternal(action, getSignalServiceAddresses(recipients));
1613 }
1614
1615 private void sendTypingMessageInternal(
1616 TypingAction action, Set<RecipientId> recipientIds
1617 ) throws IOException, UntrustedIdentityException {
1618 final var timestamp = System.currentTimeMillis();
1619 var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent());
1620 var messageSender = createMessageSender();
1621 for (var recipientId : recipientIds) {
1622 final var address = resolveSignalServiceAddress(recipientId);
1623 messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
1624 }
1625 }
1626
1627 public void sendGroupTypingMessage(
1628 TypingAction action, GroupId groupId
1629 ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
1630 final var timestamp = System.currentTimeMillis();
1631 final var g = getGroupForSending(groupId);
1632 final var message = new SignalServiceTypingMessage(action.toSignalService(),
1633 timestamp,
1634 Optional.of(groupId.serialize()));
1635 final var messageSender = createMessageSender();
1636 final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId()));
1637 final var addresses = recipientIdList.stream()
1638 .map(this::resolveSignalServiceAddress)
1639 .collect(Collectors.toList());
1640 messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
1641 }
1642
1643 private Pair<Long, List<SendMessageResult>> sendMessage(
1644 SignalServiceDataMessage.Builder messageBuilder, Set<RecipientId> recipientIds
1645 ) throws IOException {
1646 final var timestamp = System.currentTimeMillis();
1647 messageBuilder.withTimestamp(timestamp);
1648 getOrCreateMessagePipe();
1649 getOrCreateUnidentifiedMessagePipe();
1650 SignalServiceDataMessage message = null;
1651 try {
1652 message = messageBuilder.build();
1653 if (message.getGroupContext().isPresent()) {
1654 try {
1655 var messageSender = createMessageSender();
1656 final var isRecipientUpdate = false;
1657 final var recipientIdList = new ArrayList<>(recipientIds);
1658 final var addresses = recipientIdList.stream()
1659 .map(this::resolveSignalServiceAddress)
1660 .collect(Collectors.toList());
1661 var result = messageSender.sendDataMessage(addresses,
1662 unidentifiedAccessHelper.getAccessFor(recipientIdList),
1663 isRecipientUpdate,
1664 ContentHint.DEFAULT,
1665 message);
1666
1667 for (var r : result) {
1668 if (r.getIdentityFailure() != null) {
1669 final var recipientId = resolveRecipient(r.getAddress());
1670 final var newIdentity = account.getIdentityKeyStore()
1671 .saveIdentity(recipientId, r.getIdentityFailure().getIdentityKey(), new Date());
1672 if (newIdentity) {
1673 account.getSessionStore().archiveSessions(recipientId);
1674 }
1675 }
1676 }
1677
1678 return new Pair<>(timestamp, result);
1679 } catch (UntrustedIdentityException e) {
1680 return new Pair<>(timestamp, List.of());
1681 }
1682 } else {
1683 // Send to all individually, so sync messages are sent correctly
1684 messageBuilder.withProfileKey(account.getProfileKey().serialize());
1685 var results = new ArrayList<SendMessageResult>(recipientIds.size());
1686 for (var recipientId : recipientIds) {
1687 final var contact = account.getContactStore().getContact(recipientId);
1688 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
1689 messageBuilder.withExpiration(expirationTime);
1690 message = messageBuilder.build();
1691 results.add(sendMessage(recipientId, message));
1692 }
1693 return new Pair<>(timestamp, results);
1694 }
1695 } finally {
1696 if (message != null && message.isEndSession()) {
1697 for (var recipient : recipientIds) {
1698 handleEndSession(recipient);
1699 }
1700 }
1701 }
1702 }
1703
1704 private Pair<Long, SendMessageResult> sendSelfMessage(
1705 SignalServiceDataMessage.Builder messageBuilder
1706 ) throws IOException {
1707 final var timestamp = System.currentTimeMillis();
1708 messageBuilder.withTimestamp(timestamp);
1709 getOrCreateMessagePipe();
1710 getOrCreateUnidentifiedMessagePipe();
1711 final var recipientId = account.getSelfRecipientId();
1712
1713 final var contact = account.getContactStore().getContact(recipientId);
1714 final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
1715 messageBuilder.withExpiration(expirationTime);
1716
1717 var message = messageBuilder.build();
1718 final var result = sendSelfMessage(message);
1719 return new Pair<>(timestamp, result);
1720 }
1721
1722 private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
1723 var messageSender = createMessageSender();
1724
1725 var recipientId = account.getSelfRecipientId();
1726
1727 final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipientId);
1728 var recipient = resolveSignalServiceAddress(recipientId);
1729 var transcript = new SentTranscriptMessage(Optional.of(recipient),
1730 message.getTimestamp(),
1731 message,
1732 message.getExpiresInSeconds(),
1733 Map.of(recipient, unidentifiedAccess.isPresent()),
1734 false);
1735 var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
1736
1737 try {
1738 var startTime = System.currentTimeMillis();
1739 messageSender.sendSyncMessage(syncMessage, unidentifiedAccess);
1740 return SendMessageResult.success(recipient,
1741 unidentifiedAccess.isPresent(),
1742 false,
1743 System.currentTimeMillis() - startTime);
1744 } catch (UntrustedIdentityException e) {
1745 return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
1746 }
1747 }
1748
1749 private SendMessageResult sendMessage(
1750 RecipientId recipientId, SignalServiceDataMessage message
1751 ) throws IOException {
1752 var messageSender = createMessageSender();
1753
1754 final var address = resolveSignalServiceAddress(recipientId);
1755 try {
1756 try {
1757 return messageSender.sendDataMessage(address,
1758 unidentifiedAccessHelper.getAccessFor(recipientId),
1759 ContentHint.DEFAULT,
1760 message);
1761 } catch (UnregisteredUserException e) {
1762 final var newRecipientId = refreshRegisteredUser(recipientId);
1763 return messageSender.sendDataMessage(resolveSignalServiceAddress(newRecipientId),
1764 unidentifiedAccessHelper.getAccessFor(newRecipientId),
1765 ContentHint.DEFAULT,
1766 message);
1767 }
1768 } catch (UntrustedIdentityException e) {
1769 return SendMessageResult.identityFailure(address, e.getIdentityKey());
1770 }
1771 }
1772
1773 private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
1774 var messageSender = createMessageSender();
1775
1776 final var address = resolveSignalServiceAddress(recipientId);
1777 try {
1778 try {
1779 return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
1780 } catch (UnregisteredUserException e) {
1781 final var newRecipientId = refreshRegisteredUser(recipientId);
1782 final var newAddress = resolveSignalServiceAddress(newRecipientId);
1783 return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
1784 }
1785 } catch (UntrustedIdentityException e) {
1786 return SendMessageResult.identityFailure(address, e.getIdentityKey());
1787 }
1788 }
1789
1790 private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException {
1791 var cipher = new SignalServiceCipher(account.getSelfAddress(),
1792 account.getSignalProtocolStore(),
1793 sessionLock,
1794 certificateValidator);
1795 return cipher.decrypt(envelope);
1796 }
1797
1798 private void handleEndSession(RecipientId recipientId) {
1799 account.getSessionStore().deleteAllSessions(recipientId);
1800 }
1801
1802 private List<HandleAction> handleSignalServiceDataMessage(
1803 SignalServiceDataMessage message,
1804 boolean isSync,
1805 SignalServiceAddress source,
1806 SignalServiceAddress destination,
1807 boolean ignoreAttachments
1808 ) {
1809 var actions = new ArrayList<HandleAction>();
1810 if (message.getGroupContext().isPresent()) {
1811 if (message.getGroupContext().get().getGroupV1().isPresent()) {
1812 var groupInfo = message.getGroupContext().get().getGroupV1().get();
1813 var groupId = GroupId.v1(groupInfo.getGroupId());
1814 var group = getGroup(groupId);
1815 if (group == null || group instanceof GroupInfoV1) {
1816 var groupV1 = (GroupInfoV1) group;
1817 switch (groupInfo.getType()) {
1818 case UPDATE: {
1819 if (groupV1 == null) {
1820 groupV1 = new GroupInfoV1(groupId);
1821 }
1822
1823 if (groupInfo.getAvatar().isPresent()) {
1824 var avatar = groupInfo.getAvatar().get();
1825 downloadGroupAvatar(avatar, groupV1.getGroupId());
1826 }
1827
1828 if (groupInfo.getName().isPresent()) {
1829 groupV1.name = groupInfo.getName().get();
1830 }
1831
1832 if (groupInfo.getMembers().isPresent()) {
1833 groupV1.addMembers(groupInfo.getMembers()
1834 .get()
1835 .stream()
1836 .map(this::resolveRecipient)
1837 .collect(Collectors.toSet()));
1838 }
1839
1840 account.getGroupStore().updateGroup(groupV1);
1841 break;
1842 }
1843 case DELIVER:
1844 if (groupV1 == null && !isSync) {
1845 actions.add(new SendGroupInfoRequestAction(source, groupId));
1846 }
1847 break;
1848 case QUIT: {
1849 if (groupV1 != null) {
1850 groupV1.removeMember(resolveRecipient(source));
1851 account.getGroupStore().updateGroup(groupV1);
1852 }
1853 break;
1854 }
1855 case REQUEST_INFO:
1856 if (groupV1 != null && !isSync) {
1857 actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
1858 }
1859 break;
1860 }
1861 } else {
1862 // Received a group v1 message for a v2 group
1863 }
1864 }
1865 if (message.getGroupContext().get().getGroupV2().isPresent()) {
1866 final var groupContext = message.getGroupContext().get().getGroupV2().get();
1867 final var groupMasterKey = groupContext.getMasterKey();
1868
1869 getOrMigrateGroup(groupMasterKey,
1870 groupContext.getRevision(),
1871 groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
1872 }
1873 }
1874
1875 final var conversationPartnerAddress = isSync ? destination : source;
1876 if (conversationPartnerAddress != null && message.isEndSession()) {
1877 handleEndSession(resolveRecipient(conversationPartnerAddress));
1878 }
1879 if (message.isExpirationUpdate() || message.getBody().isPresent()) {
1880 if (message.getGroupContext().isPresent()) {
1881 if (message.getGroupContext().get().getGroupV1().isPresent()) {
1882 var groupInfo = message.getGroupContext().get().getGroupV1().get();
1883 var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
1884 if (group != null) {
1885 if (group.messageExpirationTime != message.getExpiresInSeconds()) {
1886 group.messageExpirationTime = message.getExpiresInSeconds();
1887 account.getGroupStore().updateGroup(group);
1888 }
1889 }
1890 } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
1891 // disappearing message timer already stored in the DecryptedGroup
1892 }
1893 } else if (conversationPartnerAddress != null) {
1894 setExpirationTimer(resolveRecipient(conversationPartnerAddress), message.getExpiresInSeconds());
1895 }
1896 }
1897 if (!ignoreAttachments) {
1898 if (message.getAttachments().isPresent()) {
1899 for (var attachment : message.getAttachments().get()) {
1900 downloadAttachment(attachment);
1901 }
1902 }
1903 if (message.getSharedContacts().isPresent()) {
1904 for (var contact : message.getSharedContacts().get()) {
1905 if (contact.getAvatar().isPresent()) {
1906 downloadAttachment(contact.getAvatar().get().getAttachment());
1907 }
1908 }
1909 }
1910 }
1911 if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
1912 final ProfileKey profileKey;
1913 try {
1914 profileKey = new ProfileKey(message.getProfileKey().get());
1915 } catch (InvalidInputException e) {
1916 throw new AssertionError(e);
1917 }
1918 if (source.matches(account.getSelfAddress())) {
1919 this.account.setProfileKey(profileKey);
1920 }
1921 this.account.getProfileStore().storeProfileKey(resolveRecipient(source), profileKey);
1922 }
1923 if (message.getPreviews().isPresent()) {
1924 final var previews = message.getPreviews().get();
1925 for (var preview : previews) {
1926 if (preview.getImage().isPresent()) {
1927 downloadAttachment(preview.getImage().get());
1928 }
1929 }
1930 }
1931 if (message.getQuote().isPresent()) {
1932 final var quote = message.getQuote().get();
1933
1934 for (var quotedAttachment : quote.getAttachments()) {
1935 final var thumbnail = quotedAttachment.getThumbnail();
1936 if (thumbnail != null) {
1937 downloadAttachment(thumbnail);
1938 }
1939 }
1940 }
1941 if (message.getSticker().isPresent()) {
1942 final var messageSticker = message.getSticker().get();
1943 final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
1944 var sticker = account.getStickerStore().getSticker(stickerPackId);
1945 if (sticker == null) {
1946 sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
1947 account.getStickerStore().updateSticker(sticker);
1948 }
1949 enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
1950 }
1951 return actions;
1952 }
1953
1954 private GroupInfoV2 getOrMigrateGroup(
1955 final GroupMasterKey groupMasterKey, final int revision, final byte[] signedGroupChange
1956 ) {
1957 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
1958
1959 var groupId = GroupUtils.getGroupIdV2(groupSecretParams);
1960 var groupInfo = getGroup(groupId);
1961 final GroupInfoV2 groupInfoV2;
1962 if (groupInfo instanceof GroupInfoV1) {
1963 // Received a v2 group message for a v1 group, we need to locally migrate the group
1964 account.getGroupStore().deleteGroupV1(((GroupInfoV1) groupInfo).getGroupId());
1965 groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
1966 logger.info("Locally migrated group {} to group v2, id: {}",
1967 groupInfo.getGroupId().toBase64(),
1968 groupInfoV2.getGroupId().toBase64());
1969 } else if (groupInfo instanceof GroupInfoV2) {
1970 groupInfoV2 = (GroupInfoV2) groupInfo;
1971 } else {
1972 groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
1973 }
1974
1975 if (groupInfoV2.getGroup() == null || groupInfoV2.getGroup().getRevision() < revision) {
1976 DecryptedGroup group = null;
1977 if (signedGroupChange != null
1978 && groupInfoV2.getGroup() != null
1979 && groupInfoV2.getGroup().getRevision() + 1 == revision) {
1980 group = groupV2Helper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
1981 signedGroupChange,
1982 groupMasterKey);
1983 }
1984 if (group == null) {
1985 group = groupV2Helper.getDecryptedGroup(groupSecretParams);
1986 }
1987 if (group != null) {
1988 storeProfileKeysFromMembers(group);
1989 final var avatar = group.getAvatar();
1990 if (avatar != null && !avatar.isEmpty()) {
1991 downloadGroupAvatar(groupId, groupSecretParams, avatar);
1992 }
1993 }
1994 groupInfoV2.setGroup(group, this::resolveRecipient);
1995 account.getGroupStore().updateGroup(groupInfoV2);
1996 }
1997
1998 return groupInfoV2;
1999 }
2000
2001 private void storeProfileKeysFromMembers(final DecryptedGroup group) {
2002 for (var member : group.getMembersList()) {
2003 final var uuid = UuidUtil.parseOrThrow(member.getUuid().toByteArray());
2004 final var recipientId = account.getRecipientStore().resolveRecipient(uuid);
2005 try {
2006 account.getProfileStore()
2007 .storeProfileKey(recipientId, new ProfileKey(member.getProfileKey().toByteArray()));
2008 } catch (InvalidInputException ignored) {
2009 }
2010 }
2011 }
2012
2013 private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
2014 Set<HandleAction> queuedActions = new HashSet<>();
2015 for (var cachedMessage : account.getMessageCache().getCachedMessages()) {
2016 var actions = retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
2017 if (actions != null) {
2018 queuedActions.addAll(actions);
2019 }
2020 }
2021 for (var action : queuedActions) {
2022 try {
2023 action.execute(this);
2024 } catch (Throwable e) {
2025 logger.warn("Message action failed.", e);
2026 }
2027 }
2028 }
2029
2030 private List<HandleAction> retryFailedReceivedMessage(
2031 final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
2032 ) {
2033 var envelope = cachedMessage.loadEnvelope();
2034 if (envelope == null) {
2035 return null;
2036 }
2037 SignalServiceContent content = null;
2038 List<HandleAction> actions = null;
2039 if (!envelope.isReceipt()) {
2040 try {
2041 content = decryptMessage(envelope);
2042 } catch (ProtocolUntrustedIdentityException e) {
2043 if (!envelope.hasSource()) {
2044 final var identifier = e.getSender();
2045 final var recipientId = resolveRecipient(identifier);
2046 try {
2047 account.getMessageCache().replaceSender(cachedMessage, recipientId);
2048 } catch (IOException ioException) {
2049 logger.warn("Failed to move cached message to recipient folder: {}", ioException.getMessage());
2050 }
2051 }
2052 return null;
2053 } catch (Exception er) {
2054 // All other errors are not recoverable, so delete the cached message
2055 cachedMessage.delete();
2056 return null;
2057 }
2058 actions = handleMessage(envelope, content, ignoreAttachments);
2059 }
2060 handler.handleMessage(envelope, content, null);
2061 cachedMessage.delete();
2062 return actions;
2063 }
2064
2065 public void receiveMessages(
2066 long timeout,
2067 TimeUnit unit,
2068 boolean returnOnTimeout,
2069 boolean ignoreAttachments,
2070 ReceiveMessageHandler handler
2071 ) throws IOException {
2072 retryFailedReceivedMessages(handler, ignoreAttachments);
2073
2074 Set<HandleAction> queuedActions = null;
2075
2076 final var messagePipe = getOrCreateMessagePipe();
2077
2078 var hasCaughtUpWithOldMessages = false;
2079
2080 while (true) {
2081 SignalServiceEnvelope envelope;
2082 SignalServiceContent content = null;
2083 Exception exception = null;
2084 final CachedMessage[] cachedMessage = {null};
2085 account.setLastReceiveTimestamp(System.currentTimeMillis());
2086 logger.debug("Checking for new message from server");
2087 try {
2088 var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
2089 final var recipientId = envelope1.hasSource()
2090 ? resolveRecipient(envelope1.getSourceIdentifier())
2091 : null;
2092 // store message on disk, before acknowledging receipt to the server
2093 cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
2094 });
2095 logger.debug("New message received from server");
2096 if (result.isPresent()) {
2097 envelope = result.get();
2098 } else {
2099 // Received indicator that server queue is empty
2100 hasCaughtUpWithOldMessages = true;
2101
2102 if (queuedActions != null) {
2103 for (var action : queuedActions) {
2104 try {
2105 action.execute(this);
2106 } catch (Throwable e) {
2107 logger.warn("Message action failed.", e);
2108 }
2109 }
2110 queuedActions.clear();
2111 queuedActions = null;
2112 }
2113
2114 // Continue to wait another timeout for new messages
2115 continue;
2116 }
2117 } catch (TimeoutException e) {
2118 if (returnOnTimeout) return;
2119 continue;
2120 }
2121
2122 if (envelope.hasSource()) {
2123 // Store uuid if we don't have it already
2124 // address/uuid in envelope is sent by server
2125 resolveRecipientTrusted(envelope.getSourceAddress());
2126 }
2127 final var notAGroupMember = isNotAGroupMember(envelope, content);
2128 if (!envelope.isReceipt()) {
2129 try {
2130 content = decryptMessage(envelope);
2131 } catch (Exception e) {
2132 exception = e;
2133 }
2134 if (!envelope.hasSource() && content != null) {
2135 // Store uuid if we don't have it already
2136 // address/uuid is validated by unidentified sender certificate
2137 resolveRecipientTrusted(content.getSender());
2138 }
2139 var actions = handleMessage(envelope, content, ignoreAttachments);
2140 if (exception instanceof ProtocolInvalidMessageException) {
2141 final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender());
2142 logger.debug("Received invalid message, queuing renew session action.");
2143 actions.add(new RenewSessionAction(sender));
2144 }
2145 if (hasCaughtUpWithOldMessages) {
2146 for (var action : actions) {
2147 try {
2148 action.execute(this);
2149 } catch (Throwable e) {
2150 logger.warn("Message action failed.", e);
2151 }
2152 }
2153 } else {
2154 if (queuedActions == null) {
2155 queuedActions = new HashSet<>();
2156 }
2157 queuedActions.addAll(actions);
2158 }
2159 }
2160 if (isMessageBlocked(envelope, content)) {
2161 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
2162 } else if (notAGroupMember) {
2163 logger.info("Ignoring a message from a non group member: {}", envelope.getTimestamp());
2164 } else {
2165 handler.handleMessage(envelope, content, exception);
2166 }
2167 if (cachedMessage[0] != null) {
2168 if (exception instanceof ProtocolUntrustedIdentityException) {
2169 final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender();
2170 final var recipientId = resolveRecipient(identifier);
2171 queuedActions.add(new RetrieveProfileAction(recipientId));
2172 if (!envelope.hasSource()) {
2173 try {
2174 cachedMessage[0] = account.getMessageCache().replaceSender(cachedMessage[0], recipientId);
2175 } catch (IOException ioException) {
2176 logger.warn("Failed to move cached message to recipient folder: {}",
2177 ioException.getMessage());
2178 }
2179 }
2180 } else {
2181 cachedMessage[0].delete();
2182 }
2183 }
2184 }
2185 }
2186
2187 private boolean isMessageBlocked(
2188 SignalServiceEnvelope envelope, SignalServiceContent content
2189 ) {
2190 SignalServiceAddress source;
2191 if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
2192 source = envelope.getSourceAddress();
2193 } else if (content != null) {
2194 source = content.getSender();
2195 } else {
2196 return false;
2197 }
2198 final var recipientId = resolveRecipient(source);
2199 if (isContactBlocked(recipientId)) {
2200 return true;
2201 }
2202
2203 if (content != null && content.getDataMessage().isPresent()) {
2204 var message = content.getDataMessage().get();
2205 if (message.getGroupContext().isPresent()) {
2206 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
2207 var group = getGroup(groupId);
2208 if (group != null && group.isBlocked()) {
2209 return true;
2210 }
2211 }
2212 }
2213 return false;
2214 }
2215
2216 public boolean isContactBlocked(final String identifier) throws InvalidNumberException {
2217 final var recipientId = canonicalizeAndResolveRecipient(identifier);
2218 return isContactBlocked(recipientId);
2219 }
2220
2221 private boolean isContactBlocked(final RecipientId recipientId) {
2222 var sourceContact = account.getContactStore().getContact(recipientId);
2223 return sourceContact != null && sourceContact.isBlocked();
2224 }
2225
2226 private boolean isNotAGroupMember(
2227 SignalServiceEnvelope envelope, SignalServiceContent content
2228 ) {
2229 SignalServiceAddress source;
2230 if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
2231 source = envelope.getSourceAddress();
2232 } else if (content != null) {
2233 source = content.getSender();
2234 } else {
2235 return false;
2236 }
2237
2238 if (content != null && content.getDataMessage().isPresent()) {
2239 var message = content.getDataMessage().get();
2240 if (message.getGroupContext().isPresent()) {
2241 if (message.getGroupContext().get().getGroupV1().isPresent()) {
2242 var groupInfo = message.getGroupContext().get().getGroupV1().get();
2243 if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
2244 return false;
2245 }
2246 }
2247 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
2248 var group = getGroup(groupId);
2249 if (group != null && !group.isMember(resolveRecipient(source))) {
2250 return true;
2251 }
2252 }
2253 }
2254 return false;
2255 }
2256
2257 private List<HandleAction> handleMessage(
2258 SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
2259 ) {
2260 var actions = new ArrayList<HandleAction>();
2261 if (content != null) {
2262 final SignalServiceAddress sender;
2263 if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
2264 sender = envelope.getSourceAddress();
2265 } else {
2266 sender = content.getSender();
2267 }
2268
2269 if (content.getDataMessage().isPresent()) {
2270 var message = content.getDataMessage().get();
2271
2272 if (content.isNeedsReceipt()) {
2273 actions.add(new SendReceiptAction(sender, message.getTimestamp()));
2274 }
2275
2276 actions.addAll(handleSignalServiceDataMessage(message,
2277 false,
2278 sender,
2279 account.getSelfAddress(),
2280 ignoreAttachments));
2281 }
2282 if (content.getSyncMessage().isPresent()) {
2283 account.setMultiDevice(true);
2284 var syncMessage = content.getSyncMessage().get();
2285 if (syncMessage.getSent().isPresent()) {
2286 var message = syncMessage.getSent().get();
2287 final var destination = message.getDestination().orNull();
2288 actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
2289 true,
2290 sender,
2291 destination,
2292 ignoreAttachments));
2293 }
2294 if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
2295 var rm = syncMessage.getRequest().get();
2296 if (rm.isContactsRequest()) {
2297 actions.add(SendSyncContactsAction.create());
2298 }
2299 if (rm.isGroupsRequest()) {
2300 actions.add(SendSyncGroupsAction.create());
2301 }
2302 if (rm.isBlockedListRequest()) {
2303 actions.add(SendSyncBlockedListAction.create());
2304 }
2305 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2306 }
2307 if (syncMessage.getGroups().isPresent()) {
2308 File tmpFile = null;
2309 try {
2310 tmpFile = IOUtils.createTempFile();
2311 final var groupsMessage = syncMessage.getGroups().get();
2312 try (var attachmentAsStream = retrieveAttachmentAsStream(groupsMessage.asPointer(), tmpFile)) {
2313 var s = new DeviceGroupsInputStream(attachmentAsStream);
2314 DeviceGroup g;
2315 while (true) {
2316 try {
2317 g = s.read();
2318 } catch (IOException e) {
2319 logger.warn("Sync groups contained invalid group, ignoring: {}", e.getMessage());
2320 continue;
2321 }
2322 if (g == null) {
2323 break;
2324 }
2325 var syncGroup = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(g.getId()));
2326 if (syncGroup != null) {
2327 if (g.getName().isPresent()) {
2328 syncGroup.name = g.getName().get();
2329 }
2330 syncGroup.addMembers(g.getMembers()
2331 .stream()
2332 .map(this::resolveRecipient)
2333 .collect(Collectors.toSet()));
2334 if (!g.isActive()) {
2335 syncGroup.removeMember(account.getSelfRecipientId());
2336 } else {
2337 // Add ourself to the member set as it's marked as active
2338 syncGroup.addMembers(List.of(account.getSelfRecipientId()));
2339 }
2340 syncGroup.blocked = g.isBlocked();
2341 if (g.getColor().isPresent()) {
2342 syncGroup.color = g.getColor().get();
2343 }
2344
2345 if (g.getAvatar().isPresent()) {
2346 downloadGroupAvatar(g.getAvatar().get(), syncGroup.getGroupId());
2347 }
2348 syncGroup.archived = g.isArchived();
2349 account.getGroupStore().updateGroup(syncGroup);
2350 }
2351 }
2352 }
2353 } catch (Exception e) {
2354 logger.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2355 tmpFile,
2356 e.getMessage());
2357 } finally {
2358 if (tmpFile != null) {
2359 try {
2360 Files.delete(tmpFile.toPath());
2361 } catch (IOException e) {
2362 logger.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2363 tmpFile,
2364 e.getMessage());
2365 }
2366 }
2367 }
2368 }
2369 if (syncMessage.getBlockedList().isPresent()) {
2370 final var blockedListMessage = syncMessage.getBlockedList().get();
2371 for (var address : blockedListMessage.getAddresses()) {
2372 setContactBlocked(resolveRecipient(address), true);
2373 }
2374 for (var groupId : blockedListMessage.getGroupIds()
2375 .stream()
2376 .map(GroupId::unknownVersion)
2377 .collect(Collectors.toSet())) {
2378 try {
2379 setGroupBlocked(groupId, true);
2380 } catch (GroupNotFoundException e) {
2381 logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2382 groupId.toBase64());
2383 }
2384 }
2385 }
2386 if (syncMessage.getContacts().isPresent()) {
2387 File tmpFile = null;
2388 try {
2389 tmpFile = IOUtils.createTempFile();
2390 final var contactsMessage = syncMessage.getContacts().get();
2391 try (var attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream()
2392 .asPointer(), tmpFile)) {
2393 var s = new DeviceContactsInputStream(attachmentAsStream);
2394 DeviceContact c;
2395 while (true) {
2396 try {
2397 c = s.read();
2398 } catch (IOException e) {
2399 logger.warn("Sync contacts contained invalid contact, ignoring: {}",
2400 e.getMessage());
2401 continue;
2402 }
2403 if (c == null) {
2404 break;
2405 }
2406 if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) {
2407 account.setProfileKey(c.getProfileKey().get());
2408 }
2409 final var recipientId = resolveRecipientTrusted(c.getAddress());
2410 var contact = account.getContactStore().getContact(recipientId);
2411 final var builder = contact == null
2412 ? Contact.newBuilder()
2413 : Contact.newBuilder(contact);
2414 if (c.getName().isPresent()) {
2415 builder.withName(c.getName().get());
2416 }
2417 if (c.getColor().isPresent()) {
2418 builder.withColor(c.getColor().get());
2419 }
2420 if (c.getProfileKey().isPresent()) {
2421 account.getProfileStore().storeProfileKey(recipientId, c.getProfileKey().get());
2422 }
2423 if (c.getVerified().isPresent()) {
2424 final var verifiedMessage = c.getVerified().get();
2425 account.getIdentityKeyStore()
2426 .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
2427 verifiedMessage.getIdentityKey(),
2428 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
2429 }
2430 if (c.getExpirationTimer().isPresent()) {
2431 builder.withMessageExpirationTime(c.getExpirationTimer().get());
2432 }
2433 builder.withBlocked(c.isBlocked());
2434 builder.withArchived(c.isArchived());
2435 account.getContactStore().storeContact(recipientId, builder.build());
2436
2437 if (c.getAvatar().isPresent()) {
2438 downloadContactAvatar(c.getAvatar().get(), c.getAddress());
2439 }
2440 }
2441 }
2442 } catch (Exception e) {
2443 logger.warn("Failed to handle received sync contacts “{}”, ignoring: {}",
2444 tmpFile,
2445 e.getMessage());
2446 } finally {
2447 if (tmpFile != null) {
2448 try {
2449 Files.delete(tmpFile.toPath());
2450 } catch (IOException e) {
2451 logger.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2452 tmpFile,
2453 e.getMessage());
2454 }
2455 }
2456 }
2457 }
2458 if (syncMessage.getVerified().isPresent()) {
2459 final var verifiedMessage = syncMessage.getVerified().get();
2460 account.getIdentityKeyStore()
2461 .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
2462 verifiedMessage.getIdentityKey(),
2463 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
2464 }
2465 if (syncMessage.getStickerPackOperations().isPresent()) {
2466 final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
2467 for (var m : stickerPackOperationMessages) {
2468 if (!m.getPackId().isPresent()) {
2469 continue;
2470 }
2471 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
2472 final var installed = !m.getType().isPresent()
2473 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
2474
2475 var sticker = account.getStickerStore().getSticker(stickerPackId);
2476 if (m.getPackKey().isPresent()) {
2477 if (sticker == null) {
2478 sticker = new Sticker(stickerPackId, m.getPackKey().get());
2479 }
2480 if (installed) {
2481 enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
2482 }
2483 }
2484
2485 if (sticker != null) {
2486 sticker.setInstalled(installed);
2487 account.getStickerStore().updateSticker(sticker);
2488 }
2489 }
2490 }
2491 if (syncMessage.getFetchType().isPresent()) {
2492 switch (syncMessage.getFetchType().get()) {
2493 case LOCAL_PROFILE:
2494 getRecipientProfile(account.getSelfRecipientId(), true);
2495 case STORAGE_MANIFEST:
2496 // TODO
2497 }
2498 }
2499 if (syncMessage.getKeys().isPresent()) {
2500 final var keysMessage = syncMessage.getKeys().get();
2501 if (keysMessage.getStorageService().isPresent()) {
2502 final var storageKey = keysMessage.getStorageService().get();
2503 account.setStorageKey(storageKey);
2504 }
2505 }
2506 if (syncMessage.getConfiguration().isPresent()) {
2507 // TODO
2508 }
2509 }
2510 }
2511 return actions;
2512 }
2513
2514 private void downloadContactAvatar(SignalServiceAttachment avatar, SignalServiceAddress address) {
2515 try {
2516 avatarStore.storeContactAvatar(address, outputStream -> retrieveAttachment(avatar, outputStream));
2517 } catch (IOException e) {
2518 logger.warn("Failed to download avatar for contact {}, ignoring: {}", address, e.getMessage());
2519 }
2520 }
2521
2522 private void downloadGroupAvatar(SignalServiceAttachment avatar, GroupId groupId) {
2523 try {
2524 avatarStore.storeGroupAvatar(groupId, outputStream -> retrieveAttachment(avatar, outputStream));
2525 } catch (IOException e) {
2526 logger.warn("Failed to download avatar for group {}, ignoring: {}", groupId.toBase64(), e.getMessage());
2527 }
2528 }
2529
2530 private void downloadGroupAvatar(GroupId groupId, GroupSecretParams groupSecretParams, String cdnKey) {
2531 try {
2532 avatarStore.storeGroupAvatar(groupId,
2533 outputStream -> retrieveGroupV2Avatar(groupSecretParams, cdnKey, outputStream));
2534 } catch (IOException e) {
2535 logger.warn("Failed to download avatar for group {}, ignoring: {}", groupId.toBase64(), e.getMessage());
2536 }
2537 }
2538
2539 private void downloadProfileAvatar(
2540 SignalServiceAddress address, String avatarPath, ProfileKey profileKey
2541 ) {
2542 try {
2543 avatarStore.storeProfileAvatar(address,
2544 outputStream -> retrieveProfileAvatar(avatarPath, profileKey, outputStream));
2545 } catch (Throwable e) {
2546 logger.warn("Failed to download profile avatar, ignoring: {}", e.getMessage());
2547 }
2548 }
2549
2550 public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
2551 return attachmentStore.getAttachmentFile(attachmentId);
2552 }
2553
2554 private void downloadAttachment(final SignalServiceAttachment attachment) {
2555 if (!attachment.isPointer()) {
2556 logger.warn("Invalid state, can't store an attachment stream.");
2557 }
2558
2559 var pointer = attachment.asPointer();
2560 if (pointer.getPreview().isPresent()) {
2561 final var preview = pointer.getPreview().get();
2562 try {
2563 attachmentStore.storeAttachmentPreview(pointer.getRemoteId(),
2564 outputStream -> outputStream.write(preview, 0, preview.length));
2565 } catch (IOException e) {
2566 logger.warn("Failed to download attachment preview, ignoring: {}", e.getMessage());
2567 }
2568 }
2569
2570 try {
2571 attachmentStore.storeAttachment(pointer.getRemoteId(),
2572 outputStream -> retrieveAttachmentPointer(pointer, outputStream));
2573 } catch (IOException e) {
2574 logger.warn("Failed to download attachment ({}), ignoring: {}", pointer.getRemoteId(), e.getMessage());
2575 }
2576 }
2577
2578 private void retrieveGroupV2Avatar(
2579 GroupSecretParams groupSecretParams, String cdnKey, OutputStream outputStream
2580 ) throws IOException {
2581 var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
2582
2583 var tmpFile = IOUtils.createTempFile();
2584 try (InputStream input = messageReceiver.retrieveGroupsV2ProfileAvatar(cdnKey,
2585 tmpFile,
2586 ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
2587 var encryptedData = IOUtils.readFully(input);
2588
2589 var decryptedData = groupOperations.decryptAvatar(encryptedData);
2590 outputStream.write(decryptedData);
2591 } finally {
2592 try {
2593 Files.delete(tmpFile.toPath());
2594 } catch (IOException e) {
2595 logger.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2596 tmpFile,
2597 e.getMessage());
2598 }
2599 }
2600 }
2601
2602 private void retrieveProfileAvatar(
2603 String avatarPath, ProfileKey profileKey, OutputStream outputStream
2604 ) throws IOException {
2605 var tmpFile = IOUtils.createTempFile();
2606 try (var input = messageReceiver.retrieveProfileAvatar(avatarPath,
2607 tmpFile,
2608 profileKey,
2609 ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
2610 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2611 IOUtils.copyStream(input, outputStream, (int) ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
2612 } finally {
2613 try {
2614 Files.delete(tmpFile.toPath());
2615 } catch (IOException e) {
2616 logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2617 tmpFile,
2618 e.getMessage());
2619 }
2620 }
2621 }
2622
2623 private void retrieveAttachment(
2624 final SignalServiceAttachment attachment, final OutputStream outputStream
2625 ) throws IOException {
2626 if (attachment.isPointer()) {
2627 var pointer = attachment.asPointer();
2628 retrieveAttachmentPointer(pointer, outputStream);
2629 } else {
2630 var stream = attachment.asStream();
2631 IOUtils.copyStream(stream.getInputStream(), outputStream);
2632 }
2633 }
2634
2635 private void retrieveAttachmentPointer(
2636 SignalServiceAttachmentPointer pointer, OutputStream outputStream
2637 ) throws IOException {
2638 var tmpFile = IOUtils.createTempFile();
2639 try (var input = retrieveAttachmentAsStream(pointer, tmpFile)) {
2640 IOUtils.copyStream(input, outputStream);
2641 } catch (MissingConfigurationException | InvalidMessageException e) {
2642 throw new IOException(e);
2643 } finally {
2644 try {
2645 Files.delete(tmpFile.toPath());
2646 } catch (IOException e) {
2647 logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2648 tmpFile,
2649 e.getMessage());
2650 }
2651 }
2652 }
2653
2654 private InputStream retrieveAttachmentAsStream(
2655 SignalServiceAttachmentPointer pointer, File tmpFile
2656 ) throws IOException, InvalidMessageException, MissingConfigurationException {
2657 return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
2658 }
2659
2660 void sendGroups() throws IOException, UntrustedIdentityException {
2661 var groupsFile = IOUtils.createTempFile();
2662
2663 try {
2664 try (OutputStream fos = new FileOutputStream(groupsFile)) {
2665 var out = new DeviceGroupsOutputStream(fos);
2666 for (var record : getGroups()) {
2667 if (record instanceof GroupInfoV1) {
2668 var groupInfo = (GroupInfoV1) record;
2669 out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
2670 Optional.fromNullable(groupInfo.name),
2671 groupInfo.getMembers()
2672 .stream()
2673 .map(this::resolveSignalServiceAddress)
2674 .collect(Collectors.toList()),
2675 createGroupAvatarAttachment(groupInfo.getGroupId()),
2676 groupInfo.isMember(account.getSelfRecipientId()),
2677 Optional.of(groupInfo.messageExpirationTime),
2678 Optional.fromNullable(groupInfo.color),
2679 groupInfo.blocked,
2680 Optional.absent(),
2681 groupInfo.archived));
2682 }
2683 }
2684 }
2685
2686 if (groupsFile.exists() && groupsFile.length() > 0) {
2687 try (var groupsFileStream = new FileInputStream(groupsFile)) {
2688 var attachmentStream = SignalServiceAttachment.newStreamBuilder()
2689 .withStream(groupsFileStream)
2690 .withContentType("application/octet-stream")
2691 .withLength(groupsFile.length())
2692 .build();
2693
2694 sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream));
2695 }
2696 }
2697 } finally {
2698 try {
2699 Files.delete(groupsFile.toPath());
2700 } catch (IOException e) {
2701 logger.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile, e.getMessage());
2702 }
2703 }
2704 }
2705
2706 public void sendContacts() throws IOException, UntrustedIdentityException {
2707 var contactsFile = IOUtils.createTempFile();
2708
2709 try {
2710 try (OutputStream fos = new FileOutputStream(contactsFile)) {
2711 var out = new DeviceContactsOutputStream(fos);
2712 for (var contactPair : account.getContactStore().getContacts()) {
2713 final var recipientId = contactPair.first();
2714 final var contact = contactPair.second();
2715 final var address = resolveSignalServiceAddress(recipientId);
2716
2717 var currentIdentity = account.getIdentityKeyStore().getIdentity(recipientId);
2718 VerifiedMessage verifiedMessage = null;
2719 if (currentIdentity != null) {
2720 verifiedMessage = new VerifiedMessage(address,
2721 currentIdentity.getIdentityKey(),
2722 currentIdentity.getTrustLevel().toVerifiedState(),
2723 currentIdentity.getDateAdded().getTime());
2724 }
2725
2726 var profileKey = account.getProfileStore().getProfileKey(recipientId);
2727 out.write(new DeviceContact(address,
2728 Optional.fromNullable(contact.getName()),
2729 createContactAvatarAttachment(address),
2730 Optional.fromNullable(contact.getColor()),
2731 Optional.fromNullable(verifiedMessage),
2732 Optional.fromNullable(profileKey),
2733 contact.isBlocked(),
2734 Optional.of(contact.getMessageExpirationTime()),
2735 Optional.absent(),
2736 contact.isArchived()));
2737 }
2738
2739 if (account.getProfileKey() != null) {
2740 // Send our own profile key as well
2741 out.write(new DeviceContact(account.getSelfAddress(),
2742 Optional.absent(),
2743 Optional.absent(),
2744 Optional.absent(),
2745 Optional.absent(),
2746 Optional.of(account.getProfileKey()),
2747 false,
2748 Optional.absent(),
2749 Optional.absent(),
2750 false));
2751 }
2752 }
2753
2754 if (contactsFile.exists() && contactsFile.length() > 0) {
2755 try (var contactsFileStream = new FileInputStream(contactsFile)) {
2756 var attachmentStream = SignalServiceAttachment.newStreamBuilder()
2757 .withStream(contactsFileStream)
2758 .withContentType("application/octet-stream")
2759 .withLength(contactsFile.length())
2760 .build();
2761
2762 sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, true)));
2763 }
2764 }
2765 } finally {
2766 try {
2767 Files.delete(contactsFile.toPath());
2768 } catch (IOException e) {
2769 logger.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile, e.getMessage());
2770 }
2771 }
2772 }
2773
2774 void sendBlockedList() throws IOException, UntrustedIdentityException {
2775 var addresses = new ArrayList<SignalServiceAddress>();
2776 for (var record : account.getContactStore().getContacts()) {
2777 if (record.second().isBlocked()) {
2778 addresses.add(resolveSignalServiceAddress(record.first()));
2779 }
2780 }
2781 var groupIds = new ArrayList<byte[]>();
2782 for (var record : getGroups()) {
2783 if (record.isBlocked()) {
2784 groupIds.add(record.getGroupId().serialize());
2785 }
2786 }
2787 sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds)));
2788 }
2789
2790 private void sendVerifiedMessage(
2791 SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel
2792 ) throws IOException, UntrustedIdentityException {
2793 var verifiedMessage = new VerifiedMessage(destination,
2794 identityKey,
2795 trustLevel.toVerifiedState(),
2796 System.currentTimeMillis());
2797 sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage));
2798 }
2799
2800 public List<Pair<RecipientId, Contact>> getContacts() {
2801 return account.getContactStore().getContacts();
2802 }
2803
2804 public String getContactOrProfileName(String number) throws InvalidNumberException {
2805 final var recipientId = canonicalizeAndResolveRecipient(number);
2806 final var recipient = account.getRecipientStore().getRecipient(recipientId);
2807 if (recipient == null) {
2808 return null;
2809 }
2810
2811 if (recipient.getContact() != null && !Util.isEmpty(recipient.getContact().getName())) {
2812 return recipient.getContact().getName();
2813 }
2814
2815 if (recipient.getProfile() != null && recipient.getProfile() != null) {
2816 return recipient.getProfile().getDisplayName();
2817 }
2818
2819 return null;
2820 }
2821
2822 public GroupInfo getGroup(GroupId groupId) {
2823 return getGroup(groupId, false);
2824 }
2825
2826 public GroupInfo getGroup(GroupId groupId, boolean forceUpdate) {
2827 final var group = account.getGroupStore().getGroup(groupId);
2828 if (group instanceof GroupInfoV2 && (forceUpdate || ((GroupInfoV2) group).getGroup() == null)) {
2829 final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(((GroupInfoV2) group).getMasterKey());
2830 ((GroupInfoV2) group).setGroup(groupV2Helper.getDecryptedGroup(groupSecretParams), this::resolveRecipient);
2831 account.getGroupStore().updateGroup(group);
2832 }
2833 return group;
2834 }
2835
2836 public List<IdentityInfo> getIdentities() {
2837 return account.getIdentityKeyStore().getIdentities();
2838 }
2839
2840 public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
2841 final var identity = account.getIdentityKeyStore().getIdentity(canonicalizeAndResolveRecipient(number));
2842 return identity == null ? List.of() : List.of(identity);
2843 }
2844
2845 /**
2846 * Trust this the identity with this fingerprint
2847 *
2848 * @param name username of the identity
2849 * @param fingerprint Fingerprint
2850 */
2851 public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
2852 var recipientId = canonicalizeAndResolveRecipient(name);
2853 return trustIdentity(recipientId,
2854 identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
2855 TrustLevel.TRUSTED_VERIFIED);
2856 }
2857
2858 /**
2859 * Trust this the identity with this safety number
2860 *
2861 * @param name username of the identity
2862 * @param safetyNumber Safety number
2863 */
2864 public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
2865 var recipientId = canonicalizeAndResolveRecipient(name);
2866 var address = account.getRecipientStore().resolveServiceAddress(recipientId);
2867 return trustIdentity(recipientId,
2868 identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
2869 TrustLevel.TRUSTED_VERIFIED);
2870 }
2871
2872 /**
2873 * Trust all keys of this identity without verification
2874 *
2875 * @param name username of the identity
2876 */
2877 public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
2878 var recipientId = canonicalizeAndResolveRecipient(name);
2879 return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
2880 }
2881
2882 private boolean trustIdentity(
2883 RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
2884 ) {
2885 var identity = account.getIdentityKeyStore().getIdentity(recipientId);
2886 if (identity == null) {
2887 return false;
2888 }
2889
2890 if (!verifier.apply(identity.getIdentityKey())) {
2891 return false;
2892 }
2893
2894 account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
2895 try {
2896 var address = account.getRecipientStore().resolveServiceAddress(recipientId);
2897 sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
2898 } catch (IOException | UntrustedIdentityException e) {
2899 logger.warn("Failed to send verification sync message: {}", e.getMessage());
2900 }
2901
2902 return true;
2903 }
2904
2905 public String computeSafetyNumber(
2906 SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
2907 ) {
2908 return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
2909 account.getSelfAddress(),
2910 getIdentityKeyPair().getPublicKey(),
2911 theirAddress,
2912 theirIdentityKey);
2913 }
2914
2915 @Deprecated
2916 public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
2917 var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
2918
2919 return resolveSignalServiceAddress(address);
2920 }
2921
2922 @Deprecated
2923 public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
2924 if (address.matches(account.getSelfAddress())) {
2925 return account.getSelfAddress();
2926 }
2927
2928 return account.getRecipientStore().resolveServiceAddress(address);
2929 }
2930
2931 public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
2932 return account.getRecipientStore().resolveServiceAddress(recipientId);
2933 }
2934
2935 public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
2936 var canonicalizedNumber = UuidUtil.isUuid(identifier)
2937 ? identifier
2938 : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
2939
2940 return resolveRecipient(canonicalizedNumber);
2941 }
2942
2943 private RecipientId resolveRecipient(final String identifier) {
2944 var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
2945
2946 return resolveRecipient(address);
2947 }
2948
2949 public RecipientId resolveRecipient(SignalServiceAddress address) {
2950 return account.getRecipientStore().resolveRecipient(address);
2951 }
2952
2953 private RecipientId resolveRecipientTrusted(SignalServiceAddress address) {
2954 return account.getRecipientStore().resolveRecipientTrusted(address);
2955 }
2956
2957 private void enqueueJob(Job job) {
2958 var context = new Context(account, accountManager, messageReceiver, stickerPackStore);
2959 job.run(context);
2960 }
2961
2962 @Override
2963 public void close() throws IOException {
2964 close(true);
2965 }
2966
2967 void close(boolean closeAccount) throws IOException {
2968 executor.shutdown();
2969
2970 if (messagePipe != null) {
2971 messagePipe.shutdown();
2972 messagePipe = null;
2973 }
2974
2975 if (unidentifiedMessagePipe != null) {
2976 unidentifiedMessagePipe.shutdown();
2977 unidentifiedMessagePipe = null;
2978 }
2979
2980 if (closeAccount && account != null) {
2981 account.close();
2982 }
2983 account = null;
2984 }
2985
2986 public interface ReceiveMessageHandler {
2987
2988 void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e);
2989 }
2990 }