2 Copyright (C) 2015-2021 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
.manager
.groups
.GroupId
;
22 import org
.asamk
.signal
.manager
.groups
.GroupIdV1
;
23 import org
.asamk
.signal
.manager
.groups
.GroupIdV2
;
24 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
25 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
26 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
27 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
28 import org
.asamk
.signal
.manager
.helper
.GroupHelper
;
29 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
30 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
31 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
32 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
33 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactInfo
;
34 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
35 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
36 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
37 import org
.asamk
.signal
.manager
.storage
.messageCache
.CachedMessage
;
38 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfile
;
39 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfileEntry
;
40 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
41 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
42 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
43 import org
.asamk
.signal
.manager
.util
.IOUtils
;
44 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
45 import org
.asamk
.signal
.manager
.util
.Utils
;
46 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
47 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
52 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
53 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
54 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
55 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
56 import org
.signal
.libsignal
.metadata
.SelfSendException
;
57 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
58 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
59 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
60 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
61 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
62 import org
.signal
.zkgroup
.InvalidInputException
;
63 import org
.signal
.zkgroup
.VerificationFailedException
;
64 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
65 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
66 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
67 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
68 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
69 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
70 import org
.slf4j
.Logger
;
71 import org
.slf4j
.LoggerFactory
;
72 import org
.whispersystems
.libsignal
.IdentityKey
;
73 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
74 import org
.whispersystems
.libsignal
.InvalidKeyException
;
75 import org
.whispersystems
.libsignal
.InvalidMessageException
;
76 import org
.whispersystems
.libsignal
.InvalidVersionException
;
77 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
78 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
79 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
80 import org
.whispersystems
.libsignal
.util
.Pair
;
81 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
82 import org
.whispersystems
.signalservice
.api
.KeyBackupService
;
83 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
84 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
85 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
86 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
87 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
88 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
89 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
90 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
91 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
92 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
93 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
94 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
95 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
96 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
97 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
98 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
99 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
100 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
101 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
102 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
103 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
104 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
108 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
110 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
113 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
115 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
116 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
117 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
122 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
123 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
124 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
125 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
126 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
127 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
128 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
129 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
130 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
131 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
132 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
133 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
134 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
135 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
136 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
137 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
138 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
139 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
140 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
141 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
142 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
143 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
144 import org
.whispersystems
.util
.Base64
;
146 import java
.io
.Closeable
;
148 import java
.io
.FileInputStream
;
149 import java
.io
.FileNotFoundException
;
150 import java
.io
.FileOutputStream
;
151 import java
.io
.IOException
;
152 import java
.io
.InputStream
;
153 import java
.io
.OutputStream
;
155 import java
.net
.URISyntaxException
;
156 import java
.net
.URLEncoder
;
157 import java
.nio
.charset
.StandardCharsets
;
158 import java
.nio
.file
.Files
;
159 import java
.nio
.file
.Paths
;
160 import java
.nio
.file
.StandardCopyOption
;
161 import java
.security
.SignatureException
;
162 import java
.util
.ArrayList
;
163 import java
.util
.Arrays
;
164 import java
.util
.Collection
;
165 import java
.util
.Date
;
166 import java
.util
.HashMap
;
167 import java
.util
.HashSet
;
168 import java
.util
.List
;
169 import java
.util
.Map
;
170 import java
.util
.Set
;
171 import java
.util
.UUID
;
172 import java
.util
.concurrent
.ExecutorService
;
173 import java
.util
.concurrent
.TimeUnit
;
174 import java
.util
.concurrent
.TimeoutException
;
175 import java
.util
.stream
.Collectors
;
176 import java
.util
.zip
.ZipEntry
;
177 import java
.util
.zip
.ZipFile
;
179 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
180 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
181 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
183 public class Manager
implements Closeable
{
185 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
187 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
189 private final SignalServiceConfiguration serviceConfiguration
;
190 private final String userAgent
;
192 private SignalAccount account
;
193 private final PathConfig pathConfig
;
194 private final SignalServiceAccountManager accountManager
;
195 private final GroupsV2Api groupsV2Api
;
196 private final GroupsV2Operations groupsV2Operations
;
197 private final SignalServiceMessageReceiver messageReceiver
;
198 private final ClientZkProfileOperations clientZkProfileOperations
;
200 private SignalServiceMessagePipe messagePipe
= null;
201 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
203 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
204 private final ProfileHelper profileHelper
;
205 private final GroupHelper groupHelper
;
206 private final PinHelper pinHelper
;
209 SignalAccount account
,
210 PathConfig pathConfig
,
211 SignalServiceConfiguration serviceConfiguration
,
214 this.account
= account
;
215 this.pathConfig
= pathConfig
;
216 this.serviceConfiguration
= serviceConfiguration
;
217 this.userAgent
= userAgent
;
218 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
219 serviceConfiguration
)) : null;
220 final SleepTimer timer
= new UptimeSleepTimer();
221 this.accountManager
= new SignalServiceAccountManager(serviceConfiguration
,
222 new DynamicCredentialsProvider(account
.getUuid(),
223 account
.getUsername(),
224 account
.getPassword(),
225 account
.getSignalingKey(),
226 account
.getDeviceId()),
230 this.groupsV2Api
= accountManager
.getGroupsV2Api();
231 final KeyBackupService keyBackupService
= ServiceConfig
.createKeyBackupService(accountManager
);
232 this.pinHelper
= new PinHelper(keyBackupService
);
233 this.clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(serviceConfiguration
)
234 .getProfileOperations() : null;
235 this.messageReceiver
= new SignalServiceMessageReceiver(serviceConfiguration
,
237 account
.getUsername(),
238 account
.getPassword(),
239 account
.getDeviceId(),
240 account
.getSignalingKey(),
244 clientZkProfileOperations
);
246 this.account
.setResolver(this::resolveSignalServiceAddress
);
248 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
249 account
.getProfileStore()::getProfileKey
,
250 this::getRecipientProfile
,
251 this::getSenderCertificate
);
252 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
253 unidentifiedAccessHelper
::getAccessFor
,
254 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
255 () -> messageReceiver
);
256 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
257 this::getRecipientProfile
,
258 account
::getSelfAddress
,
261 this::getGroupAuthForToday
);
264 public String
getUsername() {
265 return account
.getUsername();
268 public SignalServiceAddress
getSelfAddress() {
269 return account
.getSelfAddress();
272 private IdentityKeyPair
getIdentityKeyPair() {
273 return account
.getSignalProtocolStore().getIdentityKeyPair();
276 public int getDeviceId() {
277 return account
.getDeviceId();
280 public static Manager
init(
281 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
282 ) throws IOException
, NotRegisteredException
{
283 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
285 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
286 throw new NotRegisteredException();
289 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
291 if (!account
.isRegistered()) {
292 throw new NotRegisteredException();
295 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
298 public void checkAccountState() throws IOException
{
299 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
303 if (account
.getUuid() == null) {
304 account
.setUuid(accountManager
.getOwnUuid());
307 updateAccountAttributes();
311 * This is used for checking a set of phone numbers for registration on Signal
313 * @param numbers The set of phone number in question
314 * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
315 * @throws IOException if its unable to get the contacts to check if they're registered
317 public Map
<String
, Boolean
> areUsersRegistered(Set
<String
> numbers
) throws IOException
{
318 // Note "contactDetails" has no optionals. It only gives us info on users who are registered
319 List
<ContactTokenDetails
> contactDetails
= this.accountManager
.getContacts(numbers
);
321 Set
<String
> registeredUsers
= contactDetails
.stream()
322 .map(ContactTokenDetails
::getNumber
)
323 .collect(Collectors
.toSet());
325 return numbers
.stream().collect(Collectors
.toMap(x
-> x
, registeredUsers
::contains
));
328 public void updateAccountAttributes() throws IOException
{
329 accountManager
.setAccountAttributes(account
.getSignalingKey(),
330 account
.getSignalProtocolStore().getLocalRegistrationId(),
332 // set legacy pin only if no KBS master key is set
333 account
.getPinMasterKey() == null ? account
.getRegistrationLockPin() : null,
334 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
335 account
.getSelfUnidentifiedAccessKey(),
336 account
.isUnrestrictedUnidentifiedAccess(),
338 account
.isDiscoverableByPhoneNumber());
341 public void setProfile(String name
, File avatar
) throws IOException
{
342 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
343 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
347 public void unregister() throws IOException
{
348 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
349 // If this is the master device, other users can't send messages to this number anymore.
350 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
351 accountManager
.setGcmId(Optional
.absent());
353 account
.setRegistered(false);
357 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
358 List
<DeviceInfo
> devices
= accountManager
.getDevices();
359 account
.setMultiDevice(devices
.size() > 1);
364 public void removeLinkedDevices(int deviceId
) throws IOException
{
365 accountManager
.removeDevice(deviceId
);
366 List
<DeviceInfo
> devices
= accountManager
.getDevices();
367 account
.setMultiDevice(devices
.size() > 1);
371 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
372 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
374 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
377 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
378 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
379 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
381 accountManager
.addDevice(deviceIdentifier
,
384 Optional
.of(account
.getProfileKey().serialize()),
386 account
.setMultiDevice(true);
390 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
, UnauthenticatedResponseException
{
391 if (pin
.isPresent()) {
392 final MasterKey masterKey
= account
.getPinMasterKey() != null
393 ? account
.getPinMasterKey()
394 : KeyUtils
.createMasterKey();
396 pinHelper
.setRegistrationLockPin(pin
.get(), masterKey
);
398 account
.setRegistrationLockPin(pin
.get());
399 account
.setPinMasterKey(masterKey
);
401 // Remove legacy registration lock
402 accountManager
.removeRegistrationLockV1();
405 pinHelper
.removeRegistrationLockPin();
407 account
.setRegistrationLockPin(null);
408 account
.setPinMasterKey(null);
413 void refreshPreKeys() throws IOException
{
414 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
415 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
416 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
418 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
421 private List
<PreKeyRecord
> generatePreKeys() {
422 final int offset
= account
.getPreKeyIdOffset();
424 List
<PreKeyRecord
> records
= KeyUtils
.generatePreKeyRecords(offset
, ServiceConfig
.PREKEY_BATCH_SIZE
);
425 account
.addPreKeys(records
);
431 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
432 final int signedPreKeyId
= account
.getNextSignedPreKeyId();
434 SignedPreKeyRecord
record = KeyUtils
.generateSignedPreKeyRecord(identityKeyPair
, signedPreKeyId
);
435 account
.addSignedPreKey(record);
441 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
442 if (messagePipe
== null) {
443 messagePipe
= messageReceiver
.createMessagePipe();
448 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
449 if (unidentifiedMessagePipe
== null) {
450 unidentifiedMessagePipe
= messageReceiver
.createUnidentifiedMessagePipe();
452 return unidentifiedMessagePipe
;
455 private SignalServiceMessageSender
createMessageSender() {
456 final ExecutorService executor
= null;
457 return new SignalServiceMessageSender(serviceConfiguration
,
459 account
.getUsername(),
460 account
.getPassword(),
461 account
.getDeviceId(),
462 account
.getSignalProtocolStore(),
464 account
.isMultiDevice(),
465 Optional
.fromNullable(messagePipe
),
466 Optional
.fromNullable(unidentifiedMessagePipe
),
468 clientZkProfileOperations
,
470 ServiceConfig
.MAX_ENVELOPE_SIZE
);
473 private SignalProfile
getRecipientProfile(
474 SignalServiceAddress address
476 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
477 if (profileEntry
== null) {
480 long now
= new Date().getTime();
481 // Profiles are cache for 24h before retrieving them again
482 if (!profileEntry
.isRequestPending() && (
483 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
485 ProfileKey profileKey
= profileEntry
.getProfileKey();
486 profileEntry
.setRequestPending(true);
487 SignalProfile profile
;
489 profile
= retrieveRecipientProfile(address
, profileKey
);
490 } catch (IOException e
) {
491 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
492 profileEntry
.setRequestPending(false);
495 profileEntry
.setRequestPending(false);
496 account
.getProfileStore()
497 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
500 return profileEntry
.getProfile();
503 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
504 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
505 if (profileEntry
== null) {
508 if (profileEntry
.getProfileKeyCredential() == null) {
509 ProfileAndCredential profileAndCredential
;
511 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
512 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
513 } catch (IOException e
) {
514 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
518 long now
= new Date().getTime();
519 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
520 final SignalProfile profile
= decryptProfile(address
,
521 profileEntry
.getProfileKey(),
522 profileAndCredential
.getProfile());
523 account
.getProfileStore()
524 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
525 return profileKeyCredential
;
527 return profileEntry
.getProfileKeyCredential();
530 private SignalProfile
retrieveRecipientProfile(
531 SignalServiceAddress address
, ProfileKey profileKey
532 ) throws IOException
{
533 final SignalServiceProfile encryptedProfile
= profileHelper
.retrieveProfileSync(address
,
534 SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
536 return decryptProfile(address
, profileKey
, encryptedProfile
);
539 private SignalProfile
decryptProfile(
540 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
542 File avatarFile
= null;
544 avatarFile
= encryptedProfile
.getAvatar() == null
546 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
547 } catch (Throwable e
) {
548 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
551 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
555 name
= encryptedProfile
.getName() == null
557 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
558 } catch (IOException e
) {
561 String unidentifiedAccess
;
563 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
564 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
566 : encryptedProfile
.getUnidentifiedAccess();
567 } catch (IOException e
) {
568 unidentifiedAccess
= null;
570 return new SignalProfile(encryptedProfile
.getIdentityKey(),
574 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
575 encryptedProfile
.getCapabilities());
576 } catch (InvalidCiphertextException e
) {
581 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
582 File file
= getGroupAvatarFile(groupId
);
583 if (!file
.exists()) {
584 return Optional
.absent();
587 return Optional
.of(AttachmentUtils
.createAttachment(file
));
590 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
591 File file
= getContactAvatarFile(number
);
592 if (!file
.exists()) {
593 return Optional
.absent();
596 return Optional
.of(AttachmentUtils
.createAttachment(file
));
599 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
600 GroupInfo g
= getGroup(groupId
);
602 throw new GroupNotFoundException(groupId
);
604 if (!g
.isMember(account
.getSelfAddress())) {
605 throw new NotAGroupMemberException(groupId
, g
.getTitle());
610 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
611 GroupInfo g
= getGroup(groupId
);
613 throw new GroupNotFoundException(groupId
);
615 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
616 throw new NotAGroupMemberException(groupId
, g
.getTitle());
621 public List
<GroupInfo
> getGroups() {
622 return account
.getGroupStore().getGroups();
625 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
626 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
627 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
628 final GroupInfo g
= getGroupForSending(groupId
);
630 GroupUtils
.setGroupContext(messageBuilder
, g
);
631 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
633 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
636 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
637 String messageText
, List
<String
> attachments
, GroupId groupId
638 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
639 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
640 .withBody(messageText
);
641 if (attachments
!= null) {
642 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
645 return sendGroupMessage(messageBuilder
, groupId
);
648 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
649 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
650 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
651 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
653 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
654 targetSentTimestamp
);
655 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
656 .withReaction(reaction
);
658 return sendGroupMessage(messageBuilder
, groupId
);
661 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
663 SignalServiceDataMessage
.Builder messageBuilder
;
665 final GroupInfo g
= getGroupForUpdating(groupId
);
666 if (g
instanceof GroupInfoV1
) {
667 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
668 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
669 .withId(groupId
.serialize())
671 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
672 groupInfoV1
.removeMember(account
.getSelfAddress());
673 account
.getGroupStore().updateGroup(groupInfoV1
);
675 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
676 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
677 groupInfoV2
.setGroup(groupGroupChangePair
.first());
678 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
679 account
.getGroupStore().updateGroup(groupInfoV2
);
682 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
685 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
686 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
687 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
689 SignalServiceDataMessage
.Builder messageBuilder
;
690 if (groupId
== null) {
692 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
694 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
695 gv1
.addMembers(List
.of(account
.getSelfAddress()));
696 updateGroupV1(gv1
, name
, members
, avatarFile
);
697 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
700 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
704 GroupInfo group
= getGroupForUpdating(groupId
);
705 if (group
instanceof GroupInfoV2
) {
706 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
708 Pair
<Long
, List
<SendMessageResult
>> result
= null;
709 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
710 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
711 result
= sendUpdateGroupMessage(groupInfoV2
,
712 groupGroupChangePair
.first(),
713 groupGroupChangePair
.second());
716 if (members
!= null) {
717 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
718 newMembers
.removeAll(group
.getMembers()
720 .map(this::resolveSignalServiceAddress
)
721 .collect(Collectors
.toSet()));
722 if (newMembers
.size() > 0) {
723 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
725 result
= sendUpdateGroupMessage(groupInfoV2
,
726 groupGroupChangePair
.first(),
727 groupGroupChangePair
.second());
730 if (result
== null || name
!= null || avatarFile
!= null) {
731 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
734 result
= sendUpdateGroupMessage(groupInfoV2
,
735 groupGroupChangePair
.first(),
736 groupGroupChangePair
.second());
739 return new Pair
<>(group
.getGroupId(), result
.second());
741 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
742 updateGroupV1(gv1
, name
, members
, avatarFile
);
743 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
748 account
.getGroupStore().updateGroup(g
);
750 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
751 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
752 return new Pair
<>(g
.getGroupId(), result
.second());
755 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
756 GroupInviteLinkUrl inviteLinkUrl
757 ) throws IOException
, GroupLinkNotActiveException
{
758 return sendJoinGroupMessage(inviteLinkUrl
);
761 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
762 GroupInviteLinkUrl inviteLinkUrl
763 ) throws IOException
, GroupLinkNotActiveException
{
764 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
765 inviteLinkUrl
.getPassword());
766 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
767 inviteLinkUrl
.getPassword(),
769 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
770 groupJoinInfo
.getRevision() + 1,
771 groupChange
.toByteArray());
773 if (group
.getGroup() == null) {
774 // Only requested member, can't send update to group members
775 return new Pair
<>(group
.getGroupId(), List
.of());
778 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
780 return new Pair
<>(group
.getGroupId(), result
.second());
783 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
784 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
785 ) throws IOException
{
786 group
.setGroup(newDecryptedGroup
);
787 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
788 groupChange
.toByteArray());
789 account
.getGroupStore().updateGroup(group
);
790 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
793 private void updateGroupV1(
796 final Collection
<SignalServiceAddress
> members
,
797 final String avatarFile
798 ) throws IOException
{
803 if (members
!= null) {
804 final Set
<String
> newE164Members
= new HashSet
<>();
805 for (SignalServiceAddress member
: members
) {
806 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
809 newE164Members
.add(member
.getNumber().get());
812 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
813 if (contacts
.size() != newE164Members
.size()) {
814 // Some of the new members are not registered on Signal
815 for (ContactTokenDetails contact
: contacts
) {
816 newE164Members
.remove(contact
.getNumber());
818 throw new IOException("Failed to add members "
819 + String
.join(", ", newE164Members
)
820 + " to group: Not registered on Signal");
823 g
.addMembers(members
);
826 if (avatarFile
!= null) {
827 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
828 File aFile
= getGroupAvatarFile(g
.getGroupId());
829 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
833 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
834 GroupIdV1 groupId
, SignalServiceAddress recipient
835 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
837 GroupInfo group
= getGroupForSending(groupId
);
838 if (!(group
instanceof GroupInfoV1
)) {
839 throw new RuntimeException("Received an invalid group request for a v2 group!");
841 g
= (GroupInfoV1
) group
;
843 if (!g
.isMember(recipient
)) {
844 throw new NotAGroupMemberException(groupId
, g
.name
);
847 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
849 // Send group message only to the recipient who requested it
850 return sendMessage(messageBuilder
, List
.of(recipient
));
853 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
854 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
855 .withId(g
.getGroupId().serialize())
857 .withMembers(new ArrayList
<>(g
.getMembers()));
859 File aFile
= getGroupAvatarFile(g
.getGroupId());
860 if (aFile
.exists()) {
862 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
863 } catch (IOException e
) {
864 throw new AttachmentInvalidException(aFile
.toString(), e
);
868 return SignalServiceDataMessage
.newBuilder()
869 .asGroupMessage(group
.build())
870 .withExpiration(g
.getMessageExpirationTime());
873 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
874 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
875 .withRevision(g
.getGroup().getRevision())
876 .withSignedGroupChange(signedGroupChange
);
877 return SignalServiceDataMessage
.newBuilder()
878 .asGroupMessage(group
.build())
879 .withExpiration(g
.getMessageExpirationTime());
882 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
883 GroupIdV1 groupId
, SignalServiceAddress recipient
884 ) throws IOException
{
885 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
886 .withId(groupId
.serialize());
888 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
889 .asGroupMessage(group
.build());
891 // Send group info request message to the recipient who sent us a message with this groupId
892 return sendMessage(messageBuilder
, List
.of(recipient
));
896 SignalServiceAddress remoteAddress
, long messageId
897 ) throws IOException
, UntrustedIdentityException
{
898 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
900 System
.currentTimeMillis());
902 createMessageSender().sendReceipt(remoteAddress
,
903 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
907 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
908 String messageText
, List
<String
> attachments
, List
<String
> recipients
909 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
910 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
911 .withBody(messageText
);
912 if (attachments
!= null) {
913 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
915 // Upload attachments here, so we only upload once even for multiple recipients
916 SignalServiceMessageSender messageSender
= createMessageSender();
917 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
918 for (SignalServiceAttachment attachment
: attachmentStreams
) {
919 if (attachment
.isStream()) {
920 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
921 } else if (attachment
.isPointer()) {
922 attachmentPointers
.add(attachment
.asPointer());
926 messageBuilder
.withAttachments(attachmentPointers
);
928 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
931 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
932 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
933 ) throws IOException
, InvalidNumberException
{
934 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
936 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
937 targetSentTimestamp
);
938 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
939 .withReaction(reaction
);
940 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
943 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
944 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
946 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
948 return sendMessage(messageBuilder
, signalServiceAddresses
);
949 } catch (Exception e
) {
950 for (SignalServiceAddress address
: signalServiceAddresses
) {
951 handleEndSession(address
);
958 public String
getContactName(String number
) throws InvalidNumberException
{
959 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
960 if (contact
== null) {
967 public void setContactName(String number
, String name
) throws InvalidNumberException
{
968 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
969 ContactInfo contact
= account
.getContactStore().getContact(address
);
970 if (contact
== null) {
971 contact
= new ContactInfo(address
);
974 account
.getContactStore().updateContact(contact
);
978 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
979 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
982 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
983 ContactInfo contact
= account
.getContactStore().getContact(address
);
984 if (contact
== null) {
985 contact
= new ContactInfo(address
);
987 contact
.blocked
= blocked
;
988 account
.getContactStore().updateContact(contact
);
992 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
993 GroupInfo group
= getGroup(groupId
);
995 throw new GroupNotFoundException(groupId
);
998 group
.setBlocked(blocked
);
999 account
.getGroupStore().updateGroup(group
);
1003 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1004 GroupId groupId
, String name
, List
<String
> members
, String avatar
1005 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1006 return sendUpdateGroupMessage(groupId
,
1008 members
== null ?
null : getSignalServiceAddresses(members
),
1013 * Change the expiration timer for a contact
1015 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1016 ContactInfo contact
= account
.getContactStore().getContact(address
);
1017 contact
.messageExpirationTime
= messageExpirationTimer
;
1018 account
.getContactStore().updateContact(contact
);
1019 sendExpirationTimerUpdate(address
);
1023 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1024 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1025 .asExpirationUpdate();
1026 sendMessage(messageBuilder
, List
.of(address
));
1030 * Change the expiration timer for a contact
1032 public void setExpirationTimer(
1033 String number
, int messageExpirationTimer
1034 ) throws IOException
, InvalidNumberException
{
1035 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1036 setExpirationTimer(address
, messageExpirationTimer
);
1040 * Change the expiration timer for a group
1042 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1043 GroupInfo g
= getGroup(groupId
);
1044 if (g
instanceof GroupInfoV1
) {
1045 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1046 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1047 account
.getGroupStore().updateGroup(groupInfoV1
);
1049 throw new RuntimeException("TODO Not implemented!");
1054 * Upload the sticker pack from path.
1056 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1057 * @return if successful, returns the URL to install the sticker pack in the signal app
1059 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1060 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1062 SignalServiceMessageSender messageSender
= createMessageSender();
1064 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1065 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1067 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1068 account
.getStickerStore().updateSticker(sticker
);
1072 return new URI("https",
1075 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1076 Hex
.toStringCondensed(packKey
),
1077 StandardCharsets
.UTF_8
)).toString();
1078 } catch (URISyntaxException e
) {
1079 throw new AssertionError(e
);
1083 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1085 ) throws IOException
, StickerPackInvalidException
{
1087 String rootPath
= null;
1089 if (file
.getName().endsWith(".zip")) {
1090 zip
= new ZipFile(file
);
1091 } else if (file
.getName().equals("manifest.json")) {
1092 rootPath
= file
.getParent();
1094 throw new StickerPackInvalidException("Could not find manifest.json");
1097 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1099 if (pack
.stickers
== null) {
1100 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1103 if (pack
.stickers
.isEmpty()) {
1104 throw new StickerPackInvalidException("Must include stickers.");
1107 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1108 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1109 if (sticker
.file
== null) {
1110 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1113 Pair
<InputStream
, Long
> data
;
1115 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1116 } catch (IOException ignored
) {
1117 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1120 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1121 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1123 Optional
.fromNullable(sticker
.emoji
).or(""),
1125 stickers
.add(stickerInfo
);
1128 StickerInfo cover
= null;
1129 if (pack
.cover
!= null) {
1130 if (pack
.cover
.file
== null) {
1131 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1134 Pair
<InputStream
, Long
> data
;
1136 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1137 } catch (IOException ignored
) {
1138 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1141 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1142 cover
= new StickerInfo(data
.first(),
1144 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1148 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1151 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1152 InputStream inputStream
;
1154 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1156 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1158 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1161 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1162 final String rootPath
, final ZipFile zip
, final String subfile
1163 ) throws IOException
{
1165 final ZipEntry entry
= zip
.getEntry(subfile
);
1166 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1168 final File file
= new File(rootPath
, subfile
);
1169 return new Pair
<>(new FileInputStream(file
), file
.length());
1173 void requestSyncGroups() throws IOException
{
1174 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1175 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1177 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1179 sendSyncMessage(message
);
1180 } catch (UntrustedIdentityException e
) {
1181 e
.printStackTrace();
1185 void requestSyncContacts() throws IOException
{
1186 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1187 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1189 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1191 sendSyncMessage(message
);
1192 } catch (UntrustedIdentityException e
) {
1193 e
.printStackTrace();
1197 void requestSyncBlocked() throws IOException
{
1198 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1199 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1201 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1203 sendSyncMessage(message
);
1204 } catch (UntrustedIdentityException e
) {
1205 e
.printStackTrace();
1209 void requestSyncConfiguration() throws IOException
{
1210 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1211 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1213 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1215 sendSyncMessage(message
);
1216 } catch (UntrustedIdentityException e
) {
1217 e
.printStackTrace();
1221 private byte[] getSenderCertificate() {
1222 // TODO support UUID capable sender certificates
1223 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1226 certificate
= accountManager
.getSenderCertificate();
1227 } catch (IOException e
) {
1228 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1231 // TODO cache for a day
1235 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1236 SignalServiceMessageSender messageSender
= createMessageSender();
1238 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1239 } catch (UntrustedIdentityException e
) {
1240 account
.getSignalProtocolStore()
1241 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1243 TrustLevel
.UNTRUSTED
);
1248 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1249 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1250 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1252 for (String number
: numbers
) {
1253 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1254 if (resolvedAddress
.getUuid().isPresent()) {
1255 signalServiceAddresses
.add(resolvedAddress
);
1257 missingUuids
.add(resolvedAddress
);
1261 Map
<String
, UUID
> registeredUsers
;
1263 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1264 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1266 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1267 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1268 registeredUsers
= new HashMap
<>();
1271 for (SignalServiceAddress address
: missingUuids
) {
1272 final String number
= address
.getNumber().get();
1273 if (registeredUsers
.containsKey(number
)) {
1274 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1275 registeredUsers
.get(number
),
1277 signalServiceAddresses
.add(newAddress
);
1279 signalServiceAddresses
.add(address
);
1283 return signalServiceAddresses
;
1286 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1287 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1288 ) throws IOException
{
1289 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1290 final long timestamp
= System
.currentTimeMillis();
1291 messageBuilder
.withTimestamp(timestamp
);
1292 getOrCreateMessagePipe();
1293 getOrCreateUnidentifiedMessagePipe();
1294 SignalServiceDataMessage message
= null;
1296 message
= messageBuilder
.build();
1297 if (message
.getGroupContext().isPresent()) {
1299 SignalServiceMessageSender messageSender
= createMessageSender();
1300 final boolean isRecipientUpdate
= false;
1301 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1302 unidentifiedAccessHelper
.getAccessFor(recipients
),
1305 for (SendMessageResult r
: result
) {
1306 if (r
.getIdentityFailure() != null) {
1307 account
.getSignalProtocolStore()
1308 .saveIdentity(r
.getAddress(),
1309 r
.getIdentityFailure().getIdentityKey(),
1310 TrustLevel
.UNTRUSTED
);
1313 return new Pair
<>(timestamp
, result
);
1314 } catch (UntrustedIdentityException e
) {
1315 account
.getSignalProtocolStore()
1316 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1318 TrustLevel
.UNTRUSTED
);
1319 return new Pair
<>(timestamp
, List
.of());
1322 // Send to all individually, so sync messages are sent correctly
1323 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1324 for (SignalServiceAddress address
: recipients
) {
1325 ContactInfo contact
= account
.getContactStore().getContact(address
);
1326 if (contact
!= null) {
1327 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1328 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1330 messageBuilder
.withExpiration(0);
1331 messageBuilder
.withProfileKey(null);
1333 message
= messageBuilder
.build();
1334 if (address
.matches(account
.getSelfAddress())) {
1335 results
.add(sendSelfMessage(message
));
1337 results
.add(sendMessage(address
, message
));
1340 return new Pair
<>(timestamp
, results
);
1343 if (message
!= null && message
.isEndSession()) {
1344 for (SignalServiceAddress recipient
: recipients
) {
1345 handleEndSession(recipient
);
1352 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1353 SignalServiceMessageSender messageSender
= createMessageSender();
1355 SignalServiceAddress recipient
= account
.getSelfAddress();
1357 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1358 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1359 message
.getTimestamp(),
1361 message
.getExpiresInSeconds(),
1362 Map
.of(recipient
, unidentifiedAccess
.isPresent()),
1364 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1367 long startTime
= System
.currentTimeMillis();
1368 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1369 return SendMessageResult
.success(recipient
,
1370 unidentifiedAccess
.isPresent(),
1372 System
.currentTimeMillis() - startTime
);
1373 } catch (UntrustedIdentityException e
) {
1374 account
.getSignalProtocolStore()
1375 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1377 TrustLevel
.UNTRUSTED
);
1378 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1382 private SendMessageResult
sendMessage(
1383 SignalServiceAddress address
, SignalServiceDataMessage message
1384 ) throws IOException
{
1385 SignalServiceMessageSender messageSender
= createMessageSender();
1388 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1389 } catch (UntrustedIdentityException e
) {
1390 account
.getSignalProtocolStore()
1391 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1393 TrustLevel
.UNTRUSTED
);
1394 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1398 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1399 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1400 account
.getSignalProtocolStore(),
1401 certificateValidator
);
1403 return cipher
.decrypt(envelope
);
1404 } catch (ProtocolUntrustedIdentityException e
) {
1405 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1406 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1408 account
.getSignalProtocolStore()
1409 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1410 identityException
.getUntrustedIdentity(),
1411 TrustLevel
.UNTRUSTED
);
1412 throw identityException
;
1414 throw new AssertionError(e
);
1418 private void handleEndSession(SignalServiceAddress source
) {
1419 account
.getSignalProtocolStore().deleteAllSessions(source
);
1422 private static int currentTimeDays() {
1423 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1426 private GroupsV2AuthorizationString
getGroupAuthForToday(
1427 final GroupSecretParams groupSecretParams
1428 ) throws IOException
{
1429 final int today
= currentTimeDays();
1430 // Returns credentials for the next 7 days
1431 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1432 // TODO cache credentials until they expire
1433 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1435 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1438 authCredentialResponse
);
1439 } catch (VerificationFailedException e
) {
1440 throw new IOException(e
);
1444 private List
<HandleAction
> handleSignalServiceDataMessage(
1445 SignalServiceDataMessage message
,
1447 SignalServiceAddress source
,
1448 SignalServiceAddress destination
,
1449 boolean ignoreAttachments
1451 List
<HandleAction
> actions
= new ArrayList
<>();
1452 if (message
.getGroupContext().isPresent()) {
1453 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1454 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1455 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1456 GroupInfo group
= getGroup(groupId
);
1457 if (group
== null || group
instanceof GroupInfoV1
) {
1458 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1459 switch (groupInfo
.getType()) {
1461 if (groupV1
== null) {
1462 groupV1
= new GroupInfoV1(groupId
);
1465 if (groupInfo
.getAvatar().isPresent()) {
1466 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1467 if (avatar
.isPointer()) {
1469 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1470 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1471 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1478 if (groupInfo
.getName().isPresent()) {
1479 groupV1
.name
= groupInfo
.getName().get();
1482 if (groupInfo
.getMembers().isPresent()) {
1483 groupV1
.addMembers(groupInfo
.getMembers()
1486 .map(this::resolveSignalServiceAddress
)
1487 .collect(Collectors
.toSet()));
1490 account
.getGroupStore().updateGroup(groupV1
);
1494 if (groupV1
== null && !isSync
) {
1495 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1499 if (groupV1
!= null) {
1500 groupV1
.removeMember(source
);
1501 account
.getGroupStore().updateGroup(groupV1
);
1506 if (groupV1
!= null && !isSync
) {
1507 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1512 // Received a group v1 message for a v2 group
1515 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1516 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1517 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1519 getOrMigrateGroup(groupMasterKey
,
1520 groupContext
.getRevision(),
1521 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1525 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1526 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1527 handleEndSession(conversationPartnerAddress
);
1529 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1530 if (message
.getGroupContext().isPresent()) {
1531 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1532 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1533 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1534 if (group
!= null) {
1535 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1536 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1537 account
.getGroupStore().updateGroup(group
);
1540 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1541 // disappearing message timer already stored in the DecryptedGroup
1543 } else if (conversationPartnerAddress
!= null) {
1544 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1545 if (contact
== null) {
1546 contact
= new ContactInfo(conversationPartnerAddress
);
1548 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1549 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1550 account
.getContactStore().updateContact(contact
);
1554 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1555 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1556 if (attachment
.isPointer()) {
1558 retrieveAttachment(attachment
.asPointer());
1559 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1560 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1561 attachment
.asPointer().getRemoteId(),
1567 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1568 final ProfileKey profileKey
;
1570 profileKey
= new ProfileKey(message
.getProfileKey().get());
1571 } catch (InvalidInputException e
) {
1572 throw new AssertionError(e
);
1574 if (source
.matches(account
.getSelfAddress())) {
1575 this.account
.setProfileKey(profileKey
);
1577 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1579 if (message
.getPreviews().isPresent()) {
1580 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1581 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1582 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1583 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1585 retrieveAttachment(attachment
);
1586 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1587 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1588 attachment
.getRemoteId(),
1594 if (message
.getQuote().isPresent()) {
1595 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1597 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1598 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1599 if (attachment
!= null && attachment
.isPointer()) {
1601 retrieveAttachment(attachment
.asPointer());
1602 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1603 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1604 attachment
.asPointer().getRemoteId(),
1610 if (message
.getSticker().isPresent()) {
1611 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1612 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1613 if (sticker
== null) {
1614 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1615 account
.getStickerStore().updateSticker(sticker
);
1621 private GroupInfoV2
getOrMigrateGroup(
1622 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1624 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1626 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1627 GroupInfo groupInfo
= getGroup(groupId
);
1628 final GroupInfoV2 groupInfoV2
;
1629 if (groupInfo
instanceof GroupInfoV1
) {
1630 // Received a v2 group message for a v1 group, we need to locally migrate the group
1631 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1632 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1633 logger
.info("Locally migrated group {} to group v2, id: {}",
1634 groupInfo
.getGroupId().toBase64(),
1635 groupInfoV2
.getGroupId().toBase64());
1636 } else if (groupInfo
instanceof GroupInfoV2
) {
1637 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1639 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1642 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1643 DecryptedGroup group
= null;
1644 if (signedGroupChange
!= null
1645 && groupInfoV2
.getGroup() != null
1646 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1647 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1649 if (group
== null) {
1650 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1652 if (group
!= null) {
1653 storeProfileKeysFromMembers(group
);
1654 final String avatar
= group
.getAvatar();
1655 if (avatar
!= null && !avatar
.isEmpty()) {
1657 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1658 } catch (IOException e
) {
1659 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1663 groupInfoV2
.setGroup(group
);
1664 account
.getGroupStore().updateGroup(groupInfoV2
);
1670 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1671 for (DecryptedMember member
: group
.getMembersList()) {
1672 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1673 member
.getUuid().toByteArray()), null));
1675 account
.getProfileStore()
1676 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1677 } catch (InvalidInputException ignored
) {
1682 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1683 for (CachedMessage cachedMessage
: account
.getMessageCache().getCachedMessages()) {
1684 retryFailedReceivedMessage(handler
, ignoreAttachments
, cachedMessage
);
1688 private void retryFailedReceivedMessage(
1689 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final CachedMessage cachedMessage
1691 SignalServiceEnvelope envelope
= cachedMessage
.loadEnvelope();
1692 if (envelope
== null) {
1695 SignalServiceContent content
= null;
1696 if (!envelope
.isReceipt()) {
1698 content
= decryptMessage(envelope
);
1699 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1701 } catch (Exception er
) {
1702 // All other errors are not recoverable, so delete the cached message
1703 cachedMessage
.delete();
1706 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1707 for (HandleAction action
: actions
) {
1709 action
.execute(this);
1710 } catch (Throwable e
) {
1711 e
.printStackTrace();
1716 handler
.handleMessage(envelope
, content
, null);
1717 cachedMessage
.delete();
1720 public void receiveMessages(
1723 boolean returnOnTimeout
,
1724 boolean ignoreAttachments
,
1725 ReceiveMessageHandler handler
1726 ) throws IOException
{
1727 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1729 Set
<HandleAction
> queuedActions
= null;
1731 final SignalServiceMessagePipe messagePipe
= getOrCreateMessagePipe();
1733 boolean hasCaughtUpWithOldMessages
= false;
1736 SignalServiceEnvelope envelope
;
1737 SignalServiceContent content
= null;
1738 Exception exception
= null;
1739 final CachedMessage
[] cachedMessage
= {null};
1741 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1742 // store message on disk, before acknowledging receipt to the server
1743 cachedMessage
[0] = account
.getMessageCache().cacheMessage(envelope1
);
1745 if (result
.isPresent()) {
1746 envelope
= result
.get();
1748 // Received indicator that server queue is empty
1749 hasCaughtUpWithOldMessages
= true;
1751 if (queuedActions
!= null) {
1752 for (HandleAction action
: queuedActions
) {
1754 action
.execute(this);
1755 } catch (Throwable e
) {
1756 e
.printStackTrace();
1760 queuedActions
.clear();
1761 queuedActions
= null;
1764 // Continue to wait another timeout for new messages
1767 } catch (TimeoutException e
) {
1768 if (returnOnTimeout
) return;
1770 } catch (InvalidVersionException e
) {
1771 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1775 if (envelope
.hasSource()) {
1776 // Store uuid if we don't have it already
1777 SignalServiceAddress source
= envelope
.getSourceAddress();
1778 resolveSignalServiceAddress(source
);
1780 if (!envelope
.isReceipt()) {
1782 content
= decryptMessage(envelope
);
1783 } catch (Exception e
) {
1786 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1787 if (hasCaughtUpWithOldMessages
) {
1788 for (HandleAction action
: actions
) {
1790 action
.execute(this);
1791 } catch (Throwable e
) {
1792 e
.printStackTrace();
1796 if (queuedActions
== null) {
1797 queuedActions
= new HashSet
<>();
1799 queuedActions
.addAll(actions
);
1803 if (!isMessageBlocked(envelope
, content
)) {
1804 handler
.handleMessage(envelope
, content
, exception
);
1806 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1807 if (cachedMessage
[0] != null) {
1808 cachedMessage
[0].delete();
1814 private boolean isMessageBlocked(
1815 SignalServiceEnvelope envelope
, SignalServiceContent content
1817 SignalServiceAddress source
;
1818 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1819 source
= envelope
.getSourceAddress();
1820 } else if (content
!= null) {
1821 source
= content
.getSender();
1825 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1826 if (sourceContact
!= null && sourceContact
.blocked
) {
1830 if (content
!= null && content
.getDataMessage().isPresent()) {
1831 SignalServiceDataMessage message
= content
.getDataMessage().get();
1832 if (message
.getGroupContext().isPresent()) {
1833 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1834 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1835 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1839 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1840 GroupInfo group
= getGroup(groupId
);
1841 if (group
!= null && group
.isBlocked()) {
1849 private List
<HandleAction
> handleMessage(
1850 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1852 List
<HandleAction
> actions
= new ArrayList
<>();
1853 if (content
!= null) {
1854 final SignalServiceAddress sender
;
1855 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1856 sender
= envelope
.getSourceAddress();
1858 sender
= content
.getSender();
1860 // Store uuid if we don't have it already
1861 resolveSignalServiceAddress(sender
);
1863 if (content
.getDataMessage().isPresent()) {
1864 SignalServiceDataMessage message
= content
.getDataMessage().get();
1866 if (content
.isNeedsReceipt()) {
1867 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1870 actions
.addAll(handleSignalServiceDataMessage(message
,
1873 account
.getSelfAddress(),
1874 ignoreAttachments
));
1876 if (content
.getSyncMessage().isPresent()) {
1877 account
.setMultiDevice(true);
1878 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1879 if (syncMessage
.getSent().isPresent()) {
1880 SentTranscriptMessage message
= syncMessage
.getSent().get();
1881 final SignalServiceAddress destination
= message
.getDestination().orNull();
1882 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1886 ignoreAttachments
));
1888 if (syncMessage
.getRequest().isPresent()) {
1889 RequestMessage rm
= syncMessage
.getRequest().get();
1890 if (rm
.isContactsRequest()) {
1891 actions
.add(SendSyncContactsAction
.create());
1893 if (rm
.isGroupsRequest()) {
1894 actions
.add(SendSyncGroupsAction
.create());
1896 if (rm
.isBlockedListRequest()) {
1897 actions
.add(SendSyncBlockedListAction
.create());
1899 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1901 if (syncMessage
.getGroups().isPresent()) {
1902 File tmpFile
= null;
1904 tmpFile
= IOUtils
.createTempFile();
1905 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1907 .asPointer(), tmpFile
)) {
1908 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1910 while ((g
= s
.read()) != null) {
1911 GroupInfoV1 syncGroup
= account
.getGroupStore()
1912 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
1913 if (syncGroup
!= null) {
1914 if (g
.getName().isPresent()) {
1915 syncGroup
.name
= g
.getName().get();
1917 syncGroup
.addMembers(g
.getMembers()
1919 .map(this::resolveSignalServiceAddress
)
1920 .collect(Collectors
.toSet()));
1921 if (!g
.isActive()) {
1922 syncGroup
.removeMember(account
.getSelfAddress());
1924 // Add ourself to the member set as it's marked as active
1925 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
1927 syncGroup
.blocked
= g
.isBlocked();
1928 if (g
.getColor().isPresent()) {
1929 syncGroup
.color
= g
.getColor().get();
1932 if (g
.getAvatar().isPresent()) {
1933 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
1935 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1936 syncGroup
.archived
= g
.isArchived();
1937 account
.getGroupStore().updateGroup(syncGroup
);
1941 } catch (Exception e
) {
1942 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
1945 e
.printStackTrace();
1947 if (tmpFile
!= null) {
1949 Files
.delete(tmpFile
.toPath());
1950 } catch (IOException e
) {
1951 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
1958 if (syncMessage
.getBlockedList().isPresent()) {
1959 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1960 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1961 setContactBlocked(resolveSignalServiceAddress(address
), true);
1963 for (GroupId groupId
: blockedListMessage
.getGroupIds()
1965 .map(GroupId
::unknownVersion
)
1966 .collect(Collectors
.toSet())) {
1968 setGroupBlocked(groupId
, true);
1969 } catch (GroupNotFoundException e
) {
1970 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
1971 groupId
.toBase64());
1975 if (syncMessage
.getContacts().isPresent()) {
1976 File tmpFile
= null;
1978 tmpFile
= IOUtils
.createTempFile();
1979 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1980 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1981 .asPointer(), tmpFile
)) {
1982 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1983 if (contactsMessage
.isComplete()) {
1984 account
.getContactStore().clear();
1987 while ((c
= s
.read()) != null) {
1988 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1989 account
.setProfileKey(c
.getProfileKey().get());
1991 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1992 ContactInfo contact
= account
.getContactStore().getContact(address
);
1993 if (contact
== null) {
1994 contact
= new ContactInfo(address
);
1996 if (c
.getName().isPresent()) {
1997 contact
.name
= c
.getName().get();
1999 if (c
.getColor().isPresent()) {
2000 contact
.color
= c
.getColor().get();
2002 if (c
.getProfileKey().isPresent()) {
2003 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2005 if (c
.getVerified().isPresent()) {
2006 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2007 account
.getSignalProtocolStore()
2008 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2009 verifiedMessage
.getIdentityKey(),
2010 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2012 if (c
.getExpirationTimer().isPresent()) {
2013 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2015 contact
.blocked
= c
.isBlocked();
2016 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2017 contact
.archived
= c
.isArchived();
2018 account
.getContactStore().updateContact(contact
);
2020 if (c
.getAvatar().isPresent()) {
2021 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2025 } catch (Exception e
) {
2026 e
.printStackTrace();
2028 if (tmpFile
!= null) {
2030 Files
.delete(tmpFile
.toPath());
2031 } catch (IOException e
) {
2032 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2039 if (syncMessage
.getVerified().isPresent()) {
2040 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2041 account
.getSignalProtocolStore()
2042 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2043 verifiedMessage
.getIdentityKey(),
2044 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2046 if (syncMessage
.getStickerPackOperations().isPresent()) {
2047 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2049 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2050 if (!m
.getPackId().isPresent()) {
2053 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2054 if (sticker
== null) {
2055 if (!m
.getPackKey().isPresent()) {
2058 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2060 sticker
.setInstalled(!m
.getType().isPresent()
2061 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2062 account
.getStickerStore().updateSticker(sticker
);
2065 if (syncMessage
.getConfiguration().isPresent()) {
2073 private File
getContactAvatarFile(String number
) {
2074 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2077 private File
retrieveContactAvatarAttachment(
2078 SignalServiceAttachment attachment
, String number
2079 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2080 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2081 if (attachment
.isPointer()) {
2082 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2083 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2085 SignalServiceAttachmentStream stream
= attachment
.asStream();
2086 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2090 private File
getGroupAvatarFile(GroupId groupId
) {
2091 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2094 private File
retrieveGroupAvatarAttachment(
2095 SignalServiceAttachment attachment
, GroupId groupId
2096 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2097 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2098 if (attachment
.isPointer()) {
2099 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2100 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2102 SignalServiceAttachmentStream stream
= attachment
.asStream();
2103 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2107 private File
retrieveGroupAvatar(
2108 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2109 ) throws IOException
{
2110 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2111 File outputFile
= getGroupAvatarFile(groupId
);
2112 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2114 File tmpFile
= IOUtils
.createTempFile();
2115 tmpFile
.deleteOnExit();
2116 try (InputStream input
= messageReceiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2118 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2119 byte[] encryptedData
= IOUtils
.readFully(input
);
2121 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2122 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2123 output
.write(decryptedData
);
2127 Files
.delete(tmpFile
.toPath());
2128 } catch (IOException e
) {
2129 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2137 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2138 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2141 private File
retrieveProfileAvatar(
2142 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2143 ) throws IOException
{
2144 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2145 File outputFile
= getProfileAvatarFile(address
);
2147 File tmpFile
= IOUtils
.createTempFile();
2148 try (InputStream input
= messageReceiver
.retrieveProfileAvatar(avatarPath
,
2151 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2152 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2153 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2156 Files
.delete(tmpFile
.toPath());
2157 } catch (IOException e
) {
2158 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2166 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2167 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2170 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2171 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2172 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2175 private File
retrieveAttachment(
2176 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2177 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2178 if (storePreview
&& pointer
.getPreview().isPresent()) {
2179 File previewFile
= new File(outputFile
+ ".preview");
2180 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2181 byte[] preview
= pointer
.getPreview().get();
2182 output
.write(preview
, 0, preview
.length
);
2183 } catch (FileNotFoundException e
) {
2184 e
.printStackTrace();
2189 File tmpFile
= IOUtils
.createTempFile();
2190 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2192 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2193 IOUtils
.copyStreamToFile(input
, outputFile
);
2196 Files
.delete(tmpFile
.toPath());
2197 } catch (IOException e
) {
2198 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2206 private InputStream
retrieveAttachmentAsStream(
2207 SignalServiceAttachmentPointer pointer
, File tmpFile
2208 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2209 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2212 void sendGroups() throws IOException
, UntrustedIdentityException
{
2213 File groupsFile
= IOUtils
.createTempFile();
2216 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2217 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2218 for (GroupInfo
record : getGroups()) {
2219 if (record instanceof GroupInfoV1
) {
2220 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2221 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2222 Optional
.fromNullable(groupInfo
.name
),
2223 new ArrayList
<>(groupInfo
.getMembers()),
2224 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2225 groupInfo
.isMember(account
.getSelfAddress()),
2226 Optional
.of(groupInfo
.messageExpirationTime
),
2227 Optional
.fromNullable(groupInfo
.color
),
2229 Optional
.fromNullable(groupInfo
.inboxPosition
),
2230 groupInfo
.archived
));
2235 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2236 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2237 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2238 .withStream(groupsFileStream
)
2239 .withContentType("application/octet-stream")
2240 .withLength(groupsFile
.length())
2243 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2248 Files
.delete(groupsFile
.toPath());
2249 } catch (IOException e
) {
2250 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2255 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2256 File contactsFile
= IOUtils
.createTempFile();
2259 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2260 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2261 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2262 VerifiedMessage verifiedMessage
= null;
2263 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2264 if (currentIdentity
!= null) {
2265 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2266 currentIdentity
.getIdentityKey(),
2267 currentIdentity
.getTrustLevel().toVerifiedState(),
2268 currentIdentity
.getDateAdded().getTime());
2271 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2272 out
.write(new DeviceContact(record.getAddress(),
2273 Optional
.fromNullable(record.name
),
2274 createContactAvatarAttachment(record.number
),
2275 Optional
.fromNullable(record.color
),
2276 Optional
.fromNullable(verifiedMessage
),
2277 Optional
.fromNullable(profileKey
),
2279 Optional
.of(record.messageExpirationTime
),
2280 Optional
.fromNullable(record.inboxPosition
),
2284 if (account
.getProfileKey() != null) {
2285 // Send our own profile key as well
2286 out
.write(new DeviceContact(account
.getSelfAddress(),
2291 Optional
.of(account
.getProfileKey()),
2299 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2300 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2301 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2302 .withStream(contactsFileStream
)
2303 .withContentType("application/octet-stream")
2304 .withLength(contactsFile
.length())
2307 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2312 Files
.delete(contactsFile
.toPath());
2313 } catch (IOException e
) {
2314 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2319 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2320 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2321 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2322 if (record.blocked
) {
2323 addresses
.add(record.getAddress());
2326 List
<byte[]> groupIds
= new ArrayList
<>();
2327 for (GroupInfo
record : getGroups()) {
2328 if (record.isBlocked()) {
2329 groupIds
.add(record.getGroupId().serialize());
2332 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2335 private void sendVerifiedMessage(
2336 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2337 ) throws IOException
, UntrustedIdentityException
{
2338 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2340 trustLevel
.toVerifiedState(),
2341 System
.currentTimeMillis());
2342 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2345 public List
<ContactInfo
> getContacts() {
2346 return account
.getContactStore().getContacts();
2349 public ContactInfo
getContact(String number
) {
2350 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2353 public GroupInfo
getGroup(GroupId groupId
) {
2354 final GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2355 if (group
instanceof GroupInfoV2
&& ((GroupInfoV2
) group
).getGroup() == null) {
2356 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(((GroupInfoV2
) group
).getMasterKey());
2357 ((GroupInfoV2
) group
).setGroup(groupHelper
.getDecryptedGroup(groupSecretParams
));
2358 account
.getGroupStore().updateGroup(group
);
2363 public List
<IdentityInfo
> getIdentities() {
2364 return account
.getSignalProtocolStore().getIdentities();
2367 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2368 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2372 * Trust this the identity with this fingerprint
2374 * @param name username of the identity
2375 * @param fingerprint Fingerprint
2377 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2378 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2379 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2383 for (IdentityInfo id
: ids
) {
2384 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2388 account
.getSignalProtocolStore()
2389 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2391 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2392 } catch (IOException
| UntrustedIdentityException e
) {
2393 e
.printStackTrace();
2402 * Trust this the identity with this safety number
2404 * @param name username of the identity
2405 * @param safetyNumber Safety number
2407 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2408 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2409 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2413 for (IdentityInfo id
: ids
) {
2414 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2418 account
.getSignalProtocolStore()
2419 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2421 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2422 } catch (IOException
| UntrustedIdentityException e
) {
2423 e
.printStackTrace();
2432 * Trust all keys of this identity without verification
2434 * @param name username of the identity
2436 public boolean trustIdentityAllKeys(String name
) {
2437 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2438 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2442 for (IdentityInfo id
: ids
) {
2443 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2444 account
.getSignalProtocolStore()
2445 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2447 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2448 } catch (IOException
| UntrustedIdentityException e
) {
2449 e
.printStackTrace();
2457 public String
computeSafetyNumber(
2458 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2460 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2461 account
.getSelfAddress(),
2462 getIdentityKeyPair().getPublicKey(),
2467 void saveAccount() {
2471 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2472 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2474 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2475 return resolveSignalServiceAddress(canonicalizedNumber
);
2478 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2479 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2481 return resolveSignalServiceAddress(address
);
2484 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2485 if (address
.matches(account
.getSelfAddress())) {
2486 return account
.getSelfAddress();
2489 return account
.getRecipientStore().resolveServiceAddress(address
);
2493 public void close() throws IOException
{
2497 void close(boolean closeAccount
) throws IOException
{
2498 if (messagePipe
!= null) {
2499 messagePipe
.shutdown();
2503 if (unidentifiedMessagePipe
!= null) {
2504 unidentifiedMessagePipe
.shutdown();
2505 unidentifiedMessagePipe
= null;
2508 if (closeAccount
&& account
!= null) {
2514 public interface ReceiveMessageHandler
{
2516 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);