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