2 Copyright (C) 2015-2020 AsamK and contributors
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.
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.
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/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.signal
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
25 import org
.asamk
.signal
.storage
.profiles
.SignalProfile
;
26 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
27 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
28 import org
.asamk
.signal
.util
.IOUtils
;
29 import org
.asamk
.signal
.util
.Util
;
30 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
31 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
40 import org
.signal
.libsignal
.metadata
.SelfSendException
;
41 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
42 import org
.signal
.zkgroup
.InvalidInputException
;
43 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
44 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
45 import org
.whispersystems
.libsignal
.IdentityKey
;
46 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
47 import org
.whispersystems
.libsignal
.InvalidKeyException
;
48 import org
.whispersystems
.libsignal
.InvalidMessageException
;
49 import org
.whispersystems
.libsignal
.InvalidVersionException
;
50 import org
.whispersystems
.libsignal
.ecc
.Curve
;
51 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
52 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
53 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
54 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
55 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
56 import org
.whispersystems
.libsignal
.util
.Medium
;
57 import org
.whispersystems
.libsignal
.util
.Pair
;
58 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
69 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
70 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
96 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
97 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
98 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
100 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
101 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
103 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
104 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
105 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
106 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
108 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
112 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
113 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
114 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.Closeable
;
118 import java
.io
.FileInputStream
;
119 import java
.io
.FileNotFoundException
;
120 import java
.io
.FileOutputStream
;
121 import java
.io
.IOException
;
122 import java
.io
.InputStream
;
123 import java
.io
.OutputStream
;
125 import java
.net
.URISyntaxException
;
126 import java
.net
.URLEncoder
;
127 import java
.nio
.file
.Files
;
128 import java
.nio
.file
.Paths
;
129 import java
.nio
.file
.StandardCopyOption
;
130 import java
.util
.ArrayList
;
131 import java
.util
.Arrays
;
132 import java
.util
.Collection
;
133 import java
.util
.Collections
;
134 import java
.util
.Date
;
135 import java
.util
.HashSet
;
136 import java
.util
.LinkedList
;
137 import java
.util
.List
;
138 import java
.util
.Locale
;
139 import java
.util
.Objects
;
140 import java
.util
.Set
;
141 import java
.util
.UUID
;
142 import java
.util
.concurrent
.ExecutionException
;
143 import java
.util
.concurrent
.ExecutorService
;
144 import java
.util
.concurrent
.TimeUnit
;
145 import java
.util
.concurrent
.TimeoutException
;
146 import java
.util
.stream
.Collectors
;
147 import java
.util
.zip
.ZipEntry
;
148 import java
.util
.zip
.ZipFile
;
150 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
152 public class Manager
implements Closeable
{
154 private final SleepTimer timer
= new UptimeSleepTimer();
155 private final SignalServiceConfiguration serviceConfiguration
;
156 private final String userAgent
;
158 private final SignalAccount account
;
159 private final PathConfig pathConfig
;
160 private SignalServiceAccountManager accountManager
;
161 private SignalServiceMessagePipe messagePipe
= null;
162 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
163 private boolean discoverableByPhoneNumber
= true;
165 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
166 this.account
= account
;
167 this.pathConfig
= pathConfig
;
168 this.serviceConfiguration
= serviceConfiguration
;
169 this.userAgent
= userAgent
;
170 this.accountManager
= createSignalServiceAccountManager();
172 this.account
.setResolver(this::resolveSignalServiceAddress
);
175 public String
getUsername() {
176 return account
.getUsername();
179 public SignalServiceAddress
getSelfAddress() {
180 return account
.getSelfAddress();
183 private SignalServiceAccountManager
createSignalServiceAccountManager() {
184 GroupsV2Operations groupsV2Operations
;
186 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceConfiguration
));
187 } catch (Throwable ignored
) {
188 groupsV2Operations
= null;
190 return new SignalServiceAccountManager(serviceConfiguration
,
191 new DynamicCredentialsProvider(account
.getUuid(), account
.getUsername(), account
.getPassword(), null, account
.getDeviceId()),
197 private IdentityKeyPair
getIdentityKeyPair() {
198 return account
.getSignalProtocolStore().getIdentityKeyPair();
201 public int getDeviceId() {
202 return account
.getDeviceId();
205 private String
getMessageCachePath() {
206 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
209 private String
getMessageCachePath(String sender
) {
210 if (sender
== null || sender
.isEmpty()) {
211 return getMessageCachePath();
214 return getMessageCachePath() + "/" + sender
.replace("/", "_");
217 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
218 String cachePath
= getMessageCachePath(sender
);
219 IOUtils
.createPrivateDirectories(cachePath
);
220 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
223 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
224 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
226 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
227 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
228 int registrationId
= KeyHelper
.generateRegistrationId(false);
230 ProfileKey profileKey
= KeyUtils
.createProfileKey();
231 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
234 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
237 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
239 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
241 m
.migrateLegacyConfigs();
246 private void migrateLegacyConfigs() {
247 // Copy group avatars that were previously stored in the attachments folder
248 // to the new avatar folder
249 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
250 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
251 File avatarFile
= getGroupAvatarFile(g
.groupId
);
252 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
253 if (!avatarFile
.exists() && attachmentFile
.exists()) {
255 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
256 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
257 } catch (Exception e
) {
262 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
265 if (account
.getProfileKey() == null) {
266 // Old config file, creating new profile key
267 account
.setProfileKey(KeyUtils
.createProfileKey());
272 public void checkAccountState() throws IOException
{
273 if (account
.isRegistered()) {
274 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
278 if (account
.getUuid() == null) {
279 account
.setUuid(accountManager
.getOwnUuid());
285 public boolean isRegistered() {
286 return account
.isRegistered();
289 public void register(boolean voiceVerification
) throws IOException
{
290 account
.setPassword(KeyUtils
.createPassword());
292 // Resetting UUID, because registering doesn't work otherwise
293 account
.setUuid(null);
294 accountManager
= createSignalServiceAccountManager();
296 if (voiceVerification
) {
297 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
299 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
302 account
.setRegistered(false);
306 public void updateAccountAttributes() throws IOException
{
307 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
310 public void setProfile(String name
, File avatar
) throws IOException
{
311 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
312 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
316 public void unregister() throws IOException
{
317 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
318 // If this is the master device, other users can't send messages to this number anymore.
319 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
320 accountManager
.setGcmId(Optional
.absent());
322 account
.setRegistered(false);
326 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
327 List
<DeviceInfo
> devices
= accountManager
.getDevices();
328 account
.setMultiDevice(devices
.size() > 1);
333 public void removeLinkedDevices(int deviceId
) throws IOException
{
334 accountManager
.removeDevice(deviceId
);
335 List
<DeviceInfo
> devices
= accountManager
.getDevices();
336 account
.setMultiDevice(devices
.size() > 1);
340 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
341 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
343 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
346 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
347 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
348 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
350 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
351 account
.setMultiDevice(true);
355 private List
<PreKeyRecord
> generatePreKeys() {
356 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
358 final int offset
= account
.getPreKeyIdOffset();
359 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
360 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
361 ECKeyPair keyPair
= Curve
.generateKeyPair();
362 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
367 account
.addPreKeys(records
);
373 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
375 ECKeyPair keyPair
= Curve
.generateKeyPair();
376 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
377 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
379 account
.addSignedPreKey(record);
383 } catch (InvalidKeyException e
) {
384 throw new AssertionError(e
);
388 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
389 verificationCode
= verificationCode
.replace("-", "");
390 account
.setSignalingKey(KeyUtils
.createSignalingKey());
391 // TODO make unrestricted unidentified access configurable
392 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
394 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
395 // TODO response.isStorageCapable()
396 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
397 account
.setRegistered(true);
398 account
.setUuid(uuid
);
399 account
.setRegistrationLockPin(pin
);
400 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
406 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
407 if (pin
.isPresent()) {
408 account
.setRegistrationLockPin(pin
.get());
409 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
411 account
.setRegistrationLockPin(null);
412 accountManager
.removeRegistrationLockV1();
417 void refreshPreKeys() throws IOException
{
418 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
419 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
420 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
422 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
425 private SignalServiceMessageReceiver
getMessageReceiver() {
426 // TODO implement ZkGroup support
427 final ClientZkProfileOperations clientZkProfileOperations
= null;
428 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
431 private SignalServiceMessageSender
getMessageSender() {
432 // TODO implement ZkGroup support
433 final ClientZkProfileOperations clientZkProfileOperations
= null;
434 final boolean attachmentsV3
= false;
435 final ExecutorService executor
= null;
436 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
437 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
);
440 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
441 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
446 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
447 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
451 SignalServiceMessageReceiver receiver
= getMessageReceiver();
453 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
454 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
455 throw new IOException("Failed to retrieve profile", e
);
459 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
460 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
461 long now
= new Date().getTime();
462 // Profiles are cache for 24h before retrieving them again
463 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
464 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
465 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
468 return profileEntry
.getProfile();
471 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
472 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
474 File avatarFile
= null;
476 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
477 } catch (Throwable e
) {
478 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
481 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
483 return new SignalProfile(
484 encryptedProfile
.getIdentityKey(),
485 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
487 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
488 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
489 encryptedProfile
.getCapabilities());
490 } catch (InvalidCiphertextException e
) {
495 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
496 File file
= getGroupAvatarFile(groupId
);
497 if (!file
.exists()) {
498 return Optional
.absent();
501 return Optional
.of(Utils
.createAttachment(file
));
504 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
505 File file
= getContactAvatarFile(number
);
506 if (!file
.exists()) {
507 return Optional
.absent();
510 return Optional
.of(Utils
.createAttachment(file
));
513 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
514 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
516 throw new GroupNotFoundException(groupId
);
518 if (!g
.isMember(account
.getSelfAddress())) {
519 throw new NotAGroupMemberException(groupId
, g
.name
);
524 public List
<GroupInfo
> getGroups() {
525 return account
.getGroupStore().getGroups();
528 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
530 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
531 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
532 if (attachments
!= null) {
533 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
535 if (groupId
!= null) {
536 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
539 messageBuilder
.asGroupMessage(group
);
542 final GroupInfo g
= getGroupForSending(groupId
);
544 messageBuilder
.withExpiration(g
.messageExpirationTime
);
546 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
549 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
550 long targetSentTimestamp
, byte[] groupId
)
551 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
552 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
553 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
554 .withReaction(reaction
);
555 if (groupId
!= null) {
556 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
559 messageBuilder
.asGroupMessage(group
);
561 final GroupInfo g
= getGroupForSending(groupId
);
562 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
565 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
566 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
570 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
571 .asGroupMessage(group
);
573 final GroupInfo g
= getGroupForSending(groupId
);
574 g
.removeMember(account
.getSelfAddress());
575 account
.getGroupStore().updateGroup(g
);
577 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
580 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
582 if (groupId
== null) {
584 g
= new GroupInfo(KeyUtils
.createGroupId());
585 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
587 g
= getGroupForSending(groupId
);
594 if (members
!= null) {
595 final Set
<String
> newE164Members
= new HashSet
<>();
596 for (SignalServiceAddress member
: members
) {
597 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
600 newE164Members
.add(member
.getNumber().get());
603 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
604 if (contacts
.size() != newE164Members
.size()) {
605 // Some of the new members are not registered on Signal
606 for (ContactTokenDetails contact
: contacts
) {
607 newE164Members
.remove(contact
.getNumber());
609 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
612 g
.addMembers(members
);
615 if (avatarFile
!= null) {
616 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
617 File aFile
= getGroupAvatarFile(g
.groupId
);
618 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
621 account
.getGroupStore().updateGroup(g
);
623 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
625 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
629 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
630 if (groupId
== null) {
633 GroupInfo g
= getGroupForSending(groupId
);
635 if (!g
.isMember(recipient
)) {
639 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
641 // Send group message only to the recipient who requested it
642 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
645 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
646 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
649 .withMembers(new ArrayList
<>(g
.getMembers()));
651 File aFile
= getGroupAvatarFile(g
.groupId
);
652 if (aFile
.exists()) {
654 group
.withAvatar(Utils
.createAttachment(aFile
));
655 } catch (IOException e
) {
656 throw new AttachmentInvalidException(aFile
.toString(), e
);
660 return SignalServiceDataMessage
.newBuilder()
661 .asGroupMessage(group
.build())
662 .withExpiration(g
.messageExpirationTime
);
665 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
666 if (groupId
== null) {
670 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
673 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
674 .asGroupMessage(group
.build());
676 // Send group info request message to the recipient who sent us a message with this groupId
677 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
680 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
681 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
682 Collections
.singletonList(messageId
),
683 System
.currentTimeMillis());
685 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
688 public long sendMessage(String messageText
, List
<String
> attachments
,
689 List
<String
> recipients
)
690 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
691 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
692 if (attachments
!= null) {
693 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
695 // Upload attachments here, so we only upload once even for multiple recipients
696 SignalServiceMessageSender messageSender
= getMessageSender();
697 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
698 for (SignalServiceAttachment attachment
: attachmentStreams
) {
699 if (attachment
.isStream()) {
700 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
701 } else if (attachment
.isPointer()) {
702 attachmentPointers
.add(attachment
.asPointer());
706 messageBuilder
.withAttachments(attachmentPointers
);
708 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
711 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
712 long targetSentTimestamp
, List
<String
> recipients
)
713 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
714 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
715 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
716 .withReaction(reaction
);
717 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
720 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
721 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
722 .asEndSessionMessage();
724 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
726 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
727 } catch (Exception e
) {
728 for (SignalServiceAddress address
: signalServiceAddresses
) {
729 handleEndSession(address
);
736 public String
getContactName(String number
) throws InvalidNumberException
{
737 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
738 if (contact
== null) {
745 public void setContactName(String number
, String name
) throws InvalidNumberException
{
746 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
747 ContactInfo contact
= account
.getContactStore().getContact(address
);
748 if (contact
== null) {
749 contact
= new ContactInfo(address
);
752 account
.getContactStore().updateContact(contact
);
756 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
757 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
760 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
761 ContactInfo contact
= account
.getContactStore().getContact(address
);
762 if (contact
== null) {
763 contact
= new ContactInfo(address
);
765 contact
.blocked
= blocked
;
766 account
.getContactStore().updateContact(contact
);
770 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
771 GroupInfo group
= getGroup(groupId
);
773 throw new GroupNotFoundException(groupId
);
776 group
.blocked
= blocked
;
777 account
.getGroupStore().updateGroup(group
);
781 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
782 if (groupId
.length
== 0) {
785 if (name
.isEmpty()) {
788 if (members
.isEmpty()) {
791 if (avatar
.isEmpty()) {
794 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
798 * Change the expiration timer for a contact
800 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
801 ContactInfo contact
= account
.getContactStore().getContact(address
);
802 contact
.messageExpirationTime
= messageExpirationTimer
;
803 account
.getContactStore().updateContact(contact
);
804 sendExpirationTimerUpdate(address
);
808 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
809 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
810 .asExpirationUpdate();
811 sendMessage(messageBuilder
, Collections
.singleton(address
));
815 * Change the expiration timer for a contact
817 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
818 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
819 setExpirationTimer(address
, messageExpirationTimer
);
823 * Change the expiration timer for a group
825 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
826 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
827 g
.messageExpirationTime
= messageExpirationTimer
;
828 account
.getGroupStore().updateGroup(g
);
832 * Upload the sticker pack from path.
834 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
835 * @return if successful, returns the URL to install the sticker pack in the signal app
837 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
838 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
840 SignalServiceMessageSender messageSender
= getMessageSender();
842 byte[] packKey
= KeyUtils
.createStickerUploadKey();
843 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
846 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
848 } catch (URISyntaxException e
) {
849 throw new AssertionError(e
);
853 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
855 String rootPath
= null;
857 final File file
= new File(path
);
858 if (file
.getName().endsWith(".zip")) {
859 zip
= new ZipFile(file
);
860 } else if (file
.getName().equals("manifest.json")) {
861 rootPath
= file
.getParent();
863 throw new StickerPackInvalidException("Could not find manifest.json");
866 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
868 if (pack
.stickers
== null) {
869 throw new StickerPackInvalidException("Must set a 'stickers' field.");
872 if (pack
.stickers
.isEmpty()) {
873 throw new StickerPackInvalidException("Must include stickers.");
876 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
877 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
878 if (sticker
.file
== null) {
879 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
882 Pair
<InputStream
, Long
> data
;
884 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
885 } catch (IOException ignored
) {
886 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
889 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
890 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
891 stickers
.add(stickerInfo
);
894 StickerInfo cover
= null;
895 if (pack
.cover
!= null) {
896 if (pack
.cover
.file
== null) {
897 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
900 Pair
<InputStream
, Long
> data
;
902 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
903 } catch (IOException ignored
) {
904 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
907 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
908 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
911 return new SignalServiceStickerManifestUpload(
918 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
919 InputStream inputStream
;
921 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
923 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
925 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
928 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
930 final ZipEntry entry
= zip
.getEntry(subfile
);
931 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
933 final File file
= new File(rootPath
, subfile
);
934 return new Pair
<>(new FileInputStream(file
), file
.length());
938 void requestSyncGroups() throws IOException
{
939 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
940 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
942 sendSyncMessage(message
);
943 } catch (UntrustedIdentityException e
) {
948 void requestSyncContacts() throws IOException
{
949 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
950 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
952 sendSyncMessage(message
);
953 } catch (UntrustedIdentityException e
) {
958 void requestSyncBlocked() throws IOException
{
959 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
960 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
962 sendSyncMessage(message
);
963 } catch (UntrustedIdentityException e
) {
968 void requestSyncConfiguration() throws IOException
{
969 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
970 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
972 sendSyncMessage(message
);
973 } catch (UntrustedIdentityException e
) {
978 private byte[] getSenderCertificate() {
979 // TODO support UUID capable sender certificates
980 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
983 certificate
= accountManager
.getSenderCertificate();
984 } catch (IOException e
) {
985 System
.err
.println("Failed to get sender certificate: " + e
);
988 // TODO cache for a day
992 private byte[] getSelfUnidentifiedAccessKey() {
993 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
996 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
997 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
998 if (contact
== null || contact
.profileKey
== null) {
1001 ProfileKey theirProfileKey
;
1003 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1004 } catch (InvalidInputException
| IOException e
) {
1005 throw new AssertionError(e
);
1007 SignalProfile targetProfile
;
1009 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1010 } catch (IOException e
) {
1011 System
.err
.println("Failed to get recipient profile: " + e
);
1015 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1019 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1020 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1023 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1026 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1027 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1028 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1030 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1031 return Optional
.absent();
1035 return Optional
.of(new UnidentifiedAccessPair(
1036 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1037 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1039 } catch (InvalidCertificateException e
) {
1040 return Optional
.absent();
1044 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1045 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1046 for (SignalServiceAddress recipient
: recipients
) {
1047 result
.add(getAccessFor(recipient
));
1052 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1053 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1054 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1055 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1057 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1058 return Optional
.absent();
1062 return Optional
.of(new UnidentifiedAccessPair(
1063 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1064 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1066 } catch (InvalidCertificateException e
) {
1067 return Optional
.absent();
1071 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1072 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1074 if (unidentifiedAccess
.isPresent()) {
1075 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1078 return Optional
.absent();
1081 private void sendSyncMessage(SignalServiceSyncMessage message
)
1082 throws IOException
, UntrustedIdentityException
{
1083 SignalServiceMessageSender messageSender
= getMessageSender();
1085 messageSender
.sendMessage(message
, getAccessForSync());
1086 } catch (UntrustedIdentityException e
) {
1087 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1093 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1095 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1096 throws EncapsulatedExceptions
, IOException
{
1097 final long timestamp
= System
.currentTimeMillis();
1098 messageBuilder
.withTimestamp(timestamp
);
1099 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1101 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1102 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1103 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1105 for (SendMessageResult result
: results
) {
1106 if (result
.isUnregisteredFailure()) {
1107 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1108 } else if (result
.isNetworkFailure()) {
1109 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1110 } else if (result
.getIdentityFailure() != null) {
1111 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1114 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1115 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1120 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1121 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1123 for (String number
: numbers
) {
1124 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1126 return signalServiceAddresses
;
1129 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1130 throws IOException
{
1131 if (messagePipe
== null) {
1132 messagePipe
= getMessageReceiver().createMessagePipe();
1134 if (unidentifiedMessagePipe
== null) {
1135 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1137 SignalServiceDataMessage message
= null;
1139 message
= messageBuilder
.build();
1140 if (message
.getGroupContext().isPresent()) {
1142 SignalServiceMessageSender messageSender
= getMessageSender();
1143 final boolean isRecipientUpdate
= false;
1144 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1145 for (SendMessageResult r
: result
) {
1146 if (r
.getIdentityFailure() != null) {
1147 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1151 } catch (UntrustedIdentityException e
) {
1152 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1153 return Collections
.emptyList();
1156 // Send to all individually, so sync messages are sent correctly
1157 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1158 for (SignalServiceAddress address
: recipients
) {
1159 ContactInfo contact
= account
.getContactStore().getContact(address
);
1160 if (contact
!= null) {
1161 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1162 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1164 messageBuilder
.withExpiration(0);
1165 messageBuilder
.withProfileKey(null);
1167 message
= messageBuilder
.build();
1168 if (address
.matches(account
.getSelfAddress())) {
1169 results
.add(sendSelfMessage(message
));
1171 results
.add(sendMessage(address
, message
));
1177 if (message
!= null && message
.isEndSession()) {
1178 for (SignalServiceAddress recipient
: recipients
) {
1179 handleEndSession(recipient
);
1186 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1187 SignalServiceMessageSender messageSender
= getMessageSender();
1189 SignalServiceAddress recipient
= account
.getSelfAddress();
1191 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1192 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1193 message
.getTimestamp(),
1195 message
.getExpiresInSeconds(),
1196 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1198 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1201 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1202 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1203 } catch (UntrustedIdentityException e
) {
1204 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1205 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1209 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1210 SignalServiceMessageSender messageSender
= getMessageSender();
1213 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1214 } catch (UntrustedIdentityException e
) {
1215 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1216 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1220 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1221 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1223 return cipher
.decrypt(envelope
);
1224 } catch (ProtocolUntrustedIdentityException e
) {
1225 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1226 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1227 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1228 throw identityException
;
1230 throw new AssertionError(e
);
1234 private void handleEndSession(SignalServiceAddress source
) {
1235 account
.getSignalProtocolStore().deleteAllSessions(source
);
1238 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1239 List
<HandleAction
> actions
= new ArrayList
<>();
1240 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1241 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1242 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1243 switch (groupInfo
.getType()) {
1245 if (group
== null) {
1246 group
= new GroupInfo(groupInfo
.getGroupId());
1249 if (groupInfo
.getAvatar().isPresent()) {
1250 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1251 if (avatar
.isPointer()) {
1253 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1254 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1255 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1260 if (groupInfo
.getName().isPresent()) {
1261 group
.name
= groupInfo
.getName().get();
1264 if (groupInfo
.getMembers().isPresent()) {
1265 group
.addMembers(groupInfo
.getMembers().get()
1267 .map(this::resolveSignalServiceAddress
)
1268 .collect(Collectors
.toSet()));
1271 account
.getGroupStore().updateGroup(group
);
1274 if (group
== null && !isSync
) {
1275 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1279 if (group
!= null) {
1280 group
.removeMember(source
);
1281 account
.getGroupStore().updateGroup(group
);
1285 if (group
!= null && !isSync
) {
1286 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1291 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1292 if (message
.isEndSession()) {
1293 handleEndSession(conversationPartnerAddress
);
1295 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1296 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1297 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1298 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1299 if (group
== null) {
1300 group
= new GroupInfo(groupInfo
.getGroupId());
1302 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1303 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1304 account
.getGroupStore().updateGroup(group
);
1307 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1308 if (contact
== null) {
1309 contact
= new ContactInfo(conversationPartnerAddress
);
1311 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1312 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1313 account
.getContactStore().updateContact(contact
);
1317 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1318 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1319 if (attachment
.isPointer()) {
1321 retrieveAttachment(attachment
.asPointer());
1322 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1323 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1328 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1329 if (source
.matches(account
.getSelfAddress())) {
1331 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1332 } catch (InvalidInputException ignored
) {
1334 ContactInfo contact
= account
.getContactStore().getContact(source
);
1335 if (contact
!= null) {
1336 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1337 account
.getContactStore().updateContact(contact
);
1340 ContactInfo contact
= account
.getContactStore().getContact(source
);
1341 if (contact
== null) {
1342 contact
= new ContactInfo(source
);
1344 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1345 account
.getContactStore().updateContact(contact
);
1348 if (message
.getPreviews().isPresent()) {
1349 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1350 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1351 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1352 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1354 retrieveAttachment(attachment
);
1355 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1356 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1364 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1365 final File cachePath
= new File(getMessageCachePath());
1366 if (!cachePath
.exists()) {
1369 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1370 if (!dir
.isDirectory()) {
1371 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1375 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1376 if (!fileEntry
.isFile()) {
1379 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1381 // Try to delete directory if empty
1386 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1387 SignalServiceEnvelope envelope
;
1389 envelope
= Utils
.loadEnvelope(fileEntry
);
1390 if (envelope
== null) {
1393 } catch (IOException e
) {
1394 e
.printStackTrace();
1397 SignalServiceContent content
= null;
1398 if (!envelope
.isReceipt()) {
1400 content
= decryptMessage(envelope
);
1401 } catch (Exception e
) {
1404 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1405 for (HandleAction action
: actions
) {
1407 action
.execute(this);
1408 } catch (Throwable e
) {
1409 e
.printStackTrace();
1414 handler
.handleMessage(envelope
, content
, null);
1416 Files
.delete(fileEntry
.toPath());
1417 } catch (IOException e
) {
1418 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1422 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1423 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1424 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1426 Set
<HandleAction
> queuedActions
= null;
1428 if (messagePipe
== null) {
1429 messagePipe
= messageReceiver
.createMessagePipe();
1432 boolean hasCaughtUpWithOldMessages
= false;
1435 SignalServiceEnvelope envelope
;
1436 SignalServiceContent content
= null;
1437 Exception exception
= null;
1438 final long now
= new Date().getTime();
1440 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1441 // store message on disk, before acknowledging receipt to the server
1443 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1444 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1445 Utils
.storeEnvelope(envelope1
, cacheFile
);
1446 } catch (IOException e
) {
1447 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1450 if (result
.isPresent()) {
1451 envelope
= result
.get();
1453 // Received indicator that server queue is empty
1454 hasCaughtUpWithOldMessages
= true;
1456 if (queuedActions
!= null) {
1457 for (HandleAction action
: queuedActions
) {
1459 action
.execute(this);
1460 } catch (Throwable e
) {
1461 e
.printStackTrace();
1464 queuedActions
.clear();
1465 queuedActions
= null;
1468 // Continue to wait another timeout for new messages
1471 } catch (TimeoutException e
) {
1472 if (returnOnTimeout
)
1475 } catch (InvalidVersionException e
) {
1476 System
.err
.println("Ignoring error: " + e
.getMessage());
1479 if (envelope
.hasSource()) {
1480 // Store uuid if we don't have it already
1481 SignalServiceAddress source
= envelope
.getSourceAddress();
1482 resolveSignalServiceAddress(source
);
1484 if (!envelope
.isReceipt()) {
1486 content
= decryptMessage(envelope
);
1487 } catch (Exception e
) {
1490 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1491 if (hasCaughtUpWithOldMessages
) {
1492 for (HandleAction action
: actions
) {
1494 action
.execute(this);
1495 } catch (Throwable e
) {
1496 e
.printStackTrace();
1500 if (queuedActions
== null) {
1501 queuedActions
= new HashSet
<>();
1503 queuedActions
.addAll(actions
);
1507 if (!isMessageBlocked(envelope
, content
)) {
1508 handler
.handleMessage(envelope
, content
, exception
);
1510 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1511 File cacheFile
= null;
1513 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1514 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1515 Files
.delete(cacheFile
.toPath());
1516 // Try to delete directory if empty
1517 new File(getMessageCachePath()).delete();
1518 } catch (IOException e
) {
1519 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1525 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1526 SignalServiceAddress source
;
1527 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1528 source
= envelope
.getSourceAddress();
1529 } else if (content
!= null) {
1530 source
= content
.getSender();
1534 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1535 if (sourceContact
!= null && sourceContact
.blocked
) {
1539 if (content
!= null && content
.getDataMessage().isPresent()) {
1540 SignalServiceDataMessage message
= content
.getDataMessage().get();
1541 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1542 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1543 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1544 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1552 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1553 List
<HandleAction
> actions
= new ArrayList
<>();
1554 if (content
!= null) {
1555 SignalServiceAddress sender
;
1556 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1557 sender
= envelope
.getSourceAddress();
1559 sender
= content
.getSender();
1561 // Store uuid if we don't have it already
1562 resolveSignalServiceAddress(sender
);
1564 if (content
.getDataMessage().isPresent()) {
1565 SignalServiceDataMessage message
= content
.getDataMessage().get();
1567 if (content
.isNeedsReceipt()) {
1568 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1571 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1573 if (content
.getSyncMessage().isPresent()) {
1574 account
.setMultiDevice(true);
1575 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1576 if (syncMessage
.getSent().isPresent()) {
1577 SentTranscriptMessage message
= syncMessage
.getSent().get();
1578 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1580 if (syncMessage
.getRequest().isPresent()) {
1581 RequestMessage rm
= syncMessage
.getRequest().get();
1582 if (rm
.isContactsRequest()) {
1583 actions
.add(SendSyncContactsAction
.create());
1585 if (rm
.isGroupsRequest()) {
1586 actions
.add(SendSyncGroupsAction
.create());
1588 if (rm
.isBlockedListRequest()) {
1589 actions
.add(SendSyncBlockedListAction
.create());
1591 // TODO Handle rm.isConfigurationRequest();
1593 if (syncMessage
.getGroups().isPresent()) {
1594 File tmpFile
= null;
1596 tmpFile
= IOUtils
.createTempFile();
1597 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1598 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1600 while ((g
= s
.read()) != null) {
1601 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1602 if (syncGroup
== null) {
1603 syncGroup
= new GroupInfo(g
.getId());
1605 if (g
.getName().isPresent()) {
1606 syncGroup
.name
= g
.getName().get();
1608 syncGroup
.addMembers(g
.getMembers()
1610 .map(this::resolveSignalServiceAddress
)
1611 .collect(Collectors
.toSet()));
1612 if (!g
.isActive()) {
1613 syncGroup
.removeMember(account
.getSelfAddress());
1615 // Add ourself to the member set as it's marked as active
1616 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1618 syncGroup
.blocked
= g
.isBlocked();
1619 if (g
.getColor().isPresent()) {
1620 syncGroup
.color
= g
.getColor().get();
1623 if (g
.getAvatar().isPresent()) {
1624 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1626 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1627 syncGroup
.archived
= g
.isArchived();
1628 account
.getGroupStore().updateGroup(syncGroup
);
1631 } catch (Exception e
) {
1632 e
.printStackTrace();
1634 if (tmpFile
!= null) {
1636 Files
.delete(tmpFile
.toPath());
1637 } catch (IOException e
) {
1638 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1643 if (syncMessage
.getBlockedList().isPresent()) {
1644 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1645 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1646 setContactBlocked(resolveSignalServiceAddress(address
), true);
1648 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1650 setGroupBlocked(groupId
, true);
1651 } catch (GroupNotFoundException e
) {
1652 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1656 if (syncMessage
.getContacts().isPresent()) {
1657 File tmpFile
= null;
1659 tmpFile
= IOUtils
.createTempFile();
1660 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1661 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1662 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1663 if (contactsMessage
.isComplete()) {
1664 account
.getContactStore().clear();
1667 while ((c
= s
.read()) != null) {
1668 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1669 account
.setProfileKey(c
.getProfileKey().get());
1671 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1672 ContactInfo contact
= account
.getContactStore().getContact(address
);
1673 if (contact
== null) {
1674 contact
= new ContactInfo(address
);
1676 if (c
.getName().isPresent()) {
1677 contact
.name
= c
.getName().get();
1679 if (c
.getColor().isPresent()) {
1680 contact
.color
= c
.getColor().get();
1682 if (c
.getProfileKey().isPresent()) {
1683 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1685 if (c
.getVerified().isPresent()) {
1686 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1687 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1689 if (c
.getExpirationTimer().isPresent()) {
1690 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1692 contact
.blocked
= c
.isBlocked();
1693 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1694 contact
.archived
= c
.isArchived();
1695 account
.getContactStore().updateContact(contact
);
1697 if (c
.getAvatar().isPresent()) {
1698 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1702 } catch (Exception e
) {
1703 e
.printStackTrace();
1705 if (tmpFile
!= null) {
1707 Files
.delete(tmpFile
.toPath());
1708 } catch (IOException e
) {
1709 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1714 if (syncMessage
.getVerified().isPresent()) {
1715 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1716 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1718 if (syncMessage
.getConfiguration().isPresent()) {
1726 private File
getContactAvatarFile(String number
) {
1727 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1730 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1731 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1732 if (attachment
.isPointer()) {
1733 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1734 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1736 SignalServiceAttachmentStream stream
= attachment
.asStream();
1737 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1741 private File
getGroupAvatarFile(byte[] groupId
) {
1742 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1745 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1746 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1747 if (attachment
.isPointer()) {
1748 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1749 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1751 SignalServiceAttachmentStream stream
= attachment
.asStream();
1752 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1756 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1757 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1760 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1761 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1762 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1763 File outputFile
= getProfileAvatarFile(address
);
1765 File tmpFile
= IOUtils
.createTempFile();
1766 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1767 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1768 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1771 Files
.delete(tmpFile
.toPath());
1772 } catch (IOException e
) {
1773 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1779 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1780 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1783 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1784 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1785 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1788 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1789 if (storePreview
&& pointer
.getPreview().isPresent()) {
1790 File previewFile
= new File(outputFile
+ ".preview");
1791 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1792 byte[] preview
= pointer
.getPreview().get();
1793 output
.write(preview
, 0, preview
.length
);
1794 } catch (FileNotFoundException e
) {
1795 e
.printStackTrace();
1800 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1802 File tmpFile
= IOUtils
.createTempFile();
1803 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1804 IOUtils
.copyStreamToFile(input
, outputFile
);
1807 Files
.delete(tmpFile
.toPath());
1808 } catch (IOException e
) {
1809 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1815 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1816 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1817 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1820 void sendGroups() throws IOException
, UntrustedIdentityException
{
1821 File groupsFile
= IOUtils
.createTempFile();
1824 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1825 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1826 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1827 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1828 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1829 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1830 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1834 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1835 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1836 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1837 .withStream(groupsFileStream
)
1838 .withContentType("application/octet-stream")
1839 .withLength(groupsFile
.length())
1842 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1847 Files
.delete(groupsFile
.toPath());
1848 } catch (IOException e
) {
1849 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1854 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1855 File contactsFile
= IOUtils
.createTempFile();
1858 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1859 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1860 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1861 VerifiedMessage verifiedMessage
= null;
1862 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1863 if (currentIdentity
!= null) {
1864 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1867 ProfileKey profileKey
= null;
1869 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1870 } catch (InvalidInputException ignored
) {
1872 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1873 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1874 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1875 Optional
.of(record.messageExpirationTime
),
1876 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1879 if (account
.getProfileKey() != null) {
1880 // Send our own profile key as well
1881 out
.write(new DeviceContact(account
.getSelfAddress(),
1882 Optional
.absent(), Optional
.absent(),
1883 Optional
.absent(), Optional
.absent(),
1884 Optional
.of(account
.getProfileKey()),
1885 false, Optional
.absent(), Optional
.absent(), false));
1889 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1890 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1891 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1892 .withStream(contactsFileStream
)
1893 .withContentType("application/octet-stream")
1894 .withLength(contactsFile
.length())
1897 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1902 Files
.delete(contactsFile
.toPath());
1903 } catch (IOException e
) {
1904 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1909 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1910 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1911 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1912 if (record.blocked
) {
1913 addresses
.add(record.getAddress());
1916 List
<byte[]> groupIds
= new ArrayList
<>();
1917 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1918 if (record.blocked
) {
1919 groupIds
.add(record.groupId
);
1922 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1925 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1926 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1927 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1930 public List
<ContactInfo
> getContacts() {
1931 return account
.getContactStore().getContacts();
1934 public ContactInfo
getContact(String number
) {
1935 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1938 public GroupInfo
getGroup(byte[] groupId
) {
1939 return account
.getGroupStore().getGroup(groupId
);
1942 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1943 return account
.getSignalProtocolStore().getIdentities();
1946 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1947 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1951 * Trust this the identity with this fingerprint
1953 * @param name username of the identity
1954 * @param fingerprint Fingerprint
1956 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1957 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1958 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1962 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1963 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1967 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1969 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 } catch (IOException
| UntrustedIdentityException e
) {
1971 e
.printStackTrace();
1980 * Trust this the identity with this safety number
1982 * @param name username of the identity
1983 * @param safetyNumber Safety number
1985 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1986 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1987 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1991 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1992 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1996 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1998 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1999 } catch (IOException
| UntrustedIdentityException e
) {
2000 e
.printStackTrace();
2009 * Trust all keys of this identity without verification
2011 * @param name username of the identity
2013 public boolean trustIdentityAllKeys(String name
) {
2014 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2015 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2019 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2020 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2021 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2023 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2024 } catch (IOException
| UntrustedIdentityException e
) {
2025 e
.printStackTrace();
2033 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2034 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2037 void saveAccount() {
2041 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2042 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2043 return resolveSignalServiceAddress(canonicalizedNumber
);
2046 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2047 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2049 return resolveSignalServiceAddress(address
);
2052 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2053 if (address
.matches(account
.getSelfAddress())) {
2054 return account
.getSelfAddress();
2057 return account
.getRecipientStore().resolveServiceAddress(address
);
2061 public void close() throws IOException
{
2062 if (messagePipe
!= null) {
2063 messagePipe
.shutdown();
2067 if (unidentifiedMessagePipe
!= null) {
2068 unidentifiedMessagePipe
.shutdown();
2069 unidentifiedMessagePipe
= null;
2075 public interface ReceiveMessageHandler
{
2077 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);