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 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1514 Files
.delete(cacheFile
.toPath());
1515 // Try to delete directory if empty
1516 new File(getMessageCachePath()).delete();
1517 } catch (IOException e
) {
1518 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1524 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1525 SignalServiceAddress source
;
1526 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1527 source
= envelope
.getSourceAddress();
1528 } else if (content
!= null) {
1529 source
= content
.getSender();
1533 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1534 if (sourceContact
!= null && sourceContact
.blocked
) {
1538 if (content
!= null && content
.getDataMessage().isPresent()) {
1539 SignalServiceDataMessage message
= content
.getDataMessage().get();
1540 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1541 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1542 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1543 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1551 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1552 List
<HandleAction
> actions
= new ArrayList
<>();
1553 if (content
!= null) {
1554 SignalServiceAddress sender
;
1555 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1556 sender
= envelope
.getSourceAddress();
1558 sender
= content
.getSender();
1560 // Store uuid if we don't have it already
1561 resolveSignalServiceAddress(sender
);
1563 if (content
.getDataMessage().isPresent()) {
1564 SignalServiceDataMessage message
= content
.getDataMessage().get();
1566 if (content
.isNeedsReceipt()) {
1567 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1570 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1572 if (content
.getSyncMessage().isPresent()) {
1573 account
.setMultiDevice(true);
1574 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1575 if (syncMessage
.getSent().isPresent()) {
1576 SentTranscriptMessage message
= syncMessage
.getSent().get();
1577 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1579 if (syncMessage
.getRequest().isPresent()) {
1580 RequestMessage rm
= syncMessage
.getRequest().get();
1581 if (rm
.isContactsRequest()) {
1582 actions
.add(SendSyncContactsAction
.create());
1584 if (rm
.isGroupsRequest()) {
1585 actions
.add(SendSyncGroupsAction
.create());
1587 if (rm
.isBlockedListRequest()) {
1588 actions
.add(SendSyncBlockedListAction
.create());
1590 // TODO Handle rm.isConfigurationRequest();
1592 if (syncMessage
.getGroups().isPresent()) {
1593 File tmpFile
= null;
1595 tmpFile
= IOUtils
.createTempFile();
1596 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1597 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1599 while ((g
= s
.read()) != null) {
1600 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1601 if (syncGroup
== null) {
1602 syncGroup
= new GroupInfo(g
.getId());
1604 if (g
.getName().isPresent()) {
1605 syncGroup
.name
= g
.getName().get();
1607 syncGroup
.addMembers(g
.getMembers()
1609 .map(this::resolveSignalServiceAddress
)
1610 .collect(Collectors
.toSet()));
1611 if (!g
.isActive()) {
1612 syncGroup
.removeMember(account
.getSelfAddress());
1614 // Add ourself to the member set as it's marked as active
1615 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1617 syncGroup
.blocked
= g
.isBlocked();
1618 if (g
.getColor().isPresent()) {
1619 syncGroup
.color
= g
.getColor().get();
1622 if (g
.getAvatar().isPresent()) {
1623 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1625 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1626 syncGroup
.archived
= g
.isArchived();
1627 account
.getGroupStore().updateGroup(syncGroup
);
1630 } catch (Exception e
) {
1631 e
.printStackTrace();
1633 if (tmpFile
!= null) {
1635 Files
.delete(tmpFile
.toPath());
1636 } catch (IOException e
) {
1637 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1642 if (syncMessage
.getBlockedList().isPresent()) {
1643 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1644 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1645 setContactBlocked(resolveSignalServiceAddress(address
), true);
1647 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1649 setGroupBlocked(groupId
, true);
1650 } catch (GroupNotFoundException e
) {
1651 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1655 if (syncMessage
.getContacts().isPresent()) {
1656 File tmpFile
= null;
1658 tmpFile
= IOUtils
.createTempFile();
1659 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1660 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1661 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1662 if (contactsMessage
.isComplete()) {
1663 account
.getContactStore().clear();
1666 while ((c
= s
.read()) != null) {
1667 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1668 account
.setProfileKey(c
.getProfileKey().get());
1670 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1671 ContactInfo contact
= account
.getContactStore().getContact(address
);
1672 if (contact
== null) {
1673 contact
= new ContactInfo(address
);
1675 if (c
.getName().isPresent()) {
1676 contact
.name
= c
.getName().get();
1678 if (c
.getColor().isPresent()) {
1679 contact
.color
= c
.getColor().get();
1681 if (c
.getProfileKey().isPresent()) {
1682 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1684 if (c
.getVerified().isPresent()) {
1685 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1686 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1688 if (c
.getExpirationTimer().isPresent()) {
1689 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1691 contact
.blocked
= c
.isBlocked();
1692 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1693 contact
.archived
= c
.isArchived();
1694 account
.getContactStore().updateContact(contact
);
1696 if (c
.getAvatar().isPresent()) {
1697 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1701 } catch (Exception e
) {
1702 e
.printStackTrace();
1704 if (tmpFile
!= null) {
1706 Files
.delete(tmpFile
.toPath());
1707 } catch (IOException e
) {
1708 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1713 if (syncMessage
.getVerified().isPresent()) {
1714 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1715 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1717 if (syncMessage
.getConfiguration().isPresent()) {
1725 private File
getContactAvatarFile(String number
) {
1726 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1729 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1730 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1731 if (attachment
.isPointer()) {
1732 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1733 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1735 SignalServiceAttachmentStream stream
= attachment
.asStream();
1736 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1740 private File
getGroupAvatarFile(byte[] groupId
) {
1741 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1744 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1745 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1746 if (attachment
.isPointer()) {
1747 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1748 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1750 SignalServiceAttachmentStream stream
= attachment
.asStream();
1751 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1755 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1756 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1759 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1760 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1761 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1762 File outputFile
= getProfileAvatarFile(address
);
1764 File tmpFile
= IOUtils
.createTempFile();
1765 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1766 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1767 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1770 Files
.delete(tmpFile
.toPath());
1771 } catch (IOException e
) {
1772 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1778 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1779 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1782 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1783 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1784 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1787 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1788 if (storePreview
&& pointer
.getPreview().isPresent()) {
1789 File previewFile
= new File(outputFile
+ ".preview");
1790 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1791 byte[] preview
= pointer
.getPreview().get();
1792 output
.write(preview
, 0, preview
.length
);
1793 } catch (FileNotFoundException e
) {
1794 e
.printStackTrace();
1799 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1801 File tmpFile
= IOUtils
.createTempFile();
1802 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1803 IOUtils
.copyStreamToFile(input
, outputFile
);
1806 Files
.delete(tmpFile
.toPath());
1807 } catch (IOException e
) {
1808 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1814 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1815 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1816 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1819 void sendGroups() throws IOException
, UntrustedIdentityException
{
1820 File groupsFile
= IOUtils
.createTempFile();
1823 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1824 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1825 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1826 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1827 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1828 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1829 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1833 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1834 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1835 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1836 .withStream(groupsFileStream
)
1837 .withContentType("application/octet-stream")
1838 .withLength(groupsFile
.length())
1841 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1846 Files
.delete(groupsFile
.toPath());
1847 } catch (IOException e
) {
1848 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1853 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1854 File contactsFile
= IOUtils
.createTempFile();
1857 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1858 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1859 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1860 VerifiedMessage verifiedMessage
= null;
1861 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1862 if (currentIdentity
!= null) {
1863 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1866 ProfileKey profileKey
= null;
1868 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1869 } catch (InvalidInputException ignored
) {
1871 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1872 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1873 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1874 Optional
.of(record.messageExpirationTime
),
1875 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1878 if (account
.getProfileKey() != null) {
1879 // Send our own profile key as well
1880 out
.write(new DeviceContact(account
.getSelfAddress(),
1881 Optional
.absent(), Optional
.absent(),
1882 Optional
.absent(), Optional
.absent(),
1883 Optional
.of(account
.getProfileKey()),
1884 false, Optional
.absent(), Optional
.absent(), false));
1888 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1889 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1890 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1891 .withStream(contactsFileStream
)
1892 .withContentType("application/octet-stream")
1893 .withLength(contactsFile
.length())
1896 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1901 Files
.delete(contactsFile
.toPath());
1902 } catch (IOException e
) {
1903 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1908 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1909 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1910 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1911 if (record.blocked
) {
1912 addresses
.add(record.getAddress());
1915 List
<byte[]> groupIds
= new ArrayList
<>();
1916 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1917 if (record.blocked
) {
1918 groupIds
.add(record.groupId
);
1921 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1924 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1925 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1926 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1929 public List
<ContactInfo
> getContacts() {
1930 return account
.getContactStore().getContacts();
1933 public ContactInfo
getContact(String number
) {
1934 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1937 public GroupInfo
getGroup(byte[] groupId
) {
1938 return account
.getGroupStore().getGroup(groupId
);
1941 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1942 return account
.getSignalProtocolStore().getIdentities();
1945 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1946 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1950 * Trust this the identity with this fingerprint
1952 * @param name username of the identity
1953 * @param fingerprint Fingerprint
1955 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1956 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1957 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1961 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1962 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1966 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1968 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1969 } catch (IOException
| UntrustedIdentityException e
) {
1970 e
.printStackTrace();
1979 * Trust this the identity with this safety number
1981 * @param name username of the identity
1982 * @param safetyNumber Safety number
1984 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1985 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1986 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1990 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1991 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1995 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1997 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1998 } catch (IOException
| UntrustedIdentityException e
) {
1999 e
.printStackTrace();
2008 * Trust all keys of this identity without verification
2010 * @param name username of the identity
2012 public boolean trustIdentityAllKeys(String name
) {
2013 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2014 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2018 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2019 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2020 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2022 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2023 } catch (IOException
| UntrustedIdentityException e
) {
2024 e
.printStackTrace();
2032 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2033 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2036 void saveAccount() {
2040 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2041 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2042 return resolveSignalServiceAddress(canonicalizedNumber
);
2045 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2046 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2048 return resolveSignalServiceAddress(address
);
2051 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2052 if (address
.matches(account
.getSelfAddress())) {
2053 return account
.getSelfAddress();
2056 return account
.getRecipientStore().resolveServiceAddress(address
);
2060 public void close() throws IOException
{
2061 if (messagePipe
!= null) {
2062 messagePipe
.shutdown();
2066 if (unidentifiedMessagePipe
!= null) {
2067 unidentifiedMessagePipe
.shutdown();
2068 unidentifiedMessagePipe
= null;
2074 public interface ReceiveMessageHandler
{
2076 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);