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 private 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 logger
.info("Ignoring a message from blocked user/group: {}", envelope
.getTimestamp());
1805 } else if (isNotAGroupMember(envelope
, content
)) {
1806 logger
.info("Ignoring a message from a non group member: {}", envelope
.getTimestamp());
1808 handler
.handleMessage(envelope
, content
, exception
);
1810 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1811 if (cachedMessage
[0] != null) {
1812 cachedMessage
[0].delete();
1818 private boolean isMessageBlocked(
1819 SignalServiceEnvelope envelope
, SignalServiceContent content
1821 SignalServiceAddress source
;
1822 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1823 source
= envelope
.getSourceAddress();
1824 } else if (content
!= null) {
1825 source
= content
.getSender();
1829 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1830 if (sourceContact
!= null && sourceContact
.blocked
) {
1834 if (content
!= null && content
.getDataMessage().isPresent()) {
1835 SignalServiceDataMessage message
= content
.getDataMessage().get();
1836 if (message
.getGroupContext().isPresent()) {
1837 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1838 GroupInfo group
= getGroup(groupId
);
1839 if (group
!= null && group
.isBlocked()) {
1847 private boolean isNotAGroupMember(
1848 SignalServiceEnvelope envelope
, SignalServiceContent content
1850 SignalServiceAddress source
;
1851 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1852 source
= envelope
.getSourceAddress();
1853 } else if (content
!= null) {
1854 source
= content
.getSender();
1859 if (content
!= null && content
.getDataMessage().isPresent()) {
1860 SignalServiceDataMessage message
= content
.getDataMessage().get();
1861 if (message
.getGroupContext().isPresent()) {
1862 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1863 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1864 if (groupInfo
.getType() == SignalServiceGroup
.Type
.QUIT
) {
1868 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1869 GroupInfo group
= getGroup(groupId
);
1870 if (group
!= null && !group
.isMember(source
)) {
1878 private List
<HandleAction
> handleMessage(
1879 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1881 List
<HandleAction
> actions
= new ArrayList
<>();
1882 if (content
!= null) {
1883 final SignalServiceAddress sender
;
1884 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1885 sender
= envelope
.getSourceAddress();
1887 sender
= content
.getSender();
1889 // Store uuid if we don't have it already
1890 resolveSignalServiceAddress(sender
);
1892 if (content
.getDataMessage().isPresent()) {
1893 SignalServiceDataMessage message
= content
.getDataMessage().get();
1895 if (content
.isNeedsReceipt()) {
1896 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1899 actions
.addAll(handleSignalServiceDataMessage(message
,
1902 account
.getSelfAddress(),
1903 ignoreAttachments
));
1905 if (content
.getSyncMessage().isPresent()) {
1906 account
.setMultiDevice(true);
1907 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1908 if (syncMessage
.getSent().isPresent()) {
1909 SentTranscriptMessage message
= syncMessage
.getSent().get();
1910 final SignalServiceAddress destination
= message
.getDestination().orNull();
1911 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1915 ignoreAttachments
));
1917 if (syncMessage
.getRequest().isPresent()) {
1918 RequestMessage rm
= syncMessage
.getRequest().get();
1919 if (rm
.isContactsRequest()) {
1920 actions
.add(SendSyncContactsAction
.create());
1922 if (rm
.isGroupsRequest()) {
1923 actions
.add(SendSyncGroupsAction
.create());
1925 if (rm
.isBlockedListRequest()) {
1926 actions
.add(SendSyncBlockedListAction
.create());
1928 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1930 if (syncMessage
.getGroups().isPresent()) {
1931 File tmpFile
= null;
1933 tmpFile
= IOUtils
.createTempFile();
1934 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1936 .asPointer(), tmpFile
)) {
1937 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1939 while ((g
= s
.read()) != null) {
1940 GroupInfoV1 syncGroup
= account
.getGroupStore()
1941 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
1942 if (syncGroup
!= null) {
1943 if (g
.getName().isPresent()) {
1944 syncGroup
.name
= g
.getName().get();
1946 syncGroup
.addMembers(g
.getMembers()
1948 .map(this::resolveSignalServiceAddress
)
1949 .collect(Collectors
.toSet()));
1950 if (!g
.isActive()) {
1951 syncGroup
.removeMember(account
.getSelfAddress());
1953 // Add ourself to the member set as it's marked as active
1954 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
1956 syncGroup
.blocked
= g
.isBlocked();
1957 if (g
.getColor().isPresent()) {
1958 syncGroup
.color
= g
.getColor().get();
1961 if (g
.getAvatar().isPresent()) {
1962 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
1964 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1965 syncGroup
.archived
= g
.isArchived();
1966 account
.getGroupStore().updateGroup(syncGroup
);
1970 } catch (Exception e
) {
1971 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
1974 e
.printStackTrace();
1976 if (tmpFile
!= null) {
1978 Files
.delete(tmpFile
.toPath());
1979 } catch (IOException e
) {
1980 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
1987 if (syncMessage
.getBlockedList().isPresent()) {
1988 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1989 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1990 setContactBlocked(resolveSignalServiceAddress(address
), true);
1992 for (GroupId groupId
: blockedListMessage
.getGroupIds()
1994 .map(GroupId
::unknownVersion
)
1995 .collect(Collectors
.toSet())) {
1997 setGroupBlocked(groupId
, true);
1998 } catch (GroupNotFoundException e
) {
1999 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2000 groupId
.toBase64());
2004 if (syncMessage
.getContacts().isPresent()) {
2005 File tmpFile
= null;
2007 tmpFile
= IOUtils
.createTempFile();
2008 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2009 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2010 .asPointer(), tmpFile
)) {
2011 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2012 if (contactsMessage
.isComplete()) {
2013 account
.getContactStore().clear();
2016 while ((c
= s
.read()) != null) {
2017 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2018 account
.setProfileKey(c
.getProfileKey().get());
2020 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2021 ContactInfo contact
= account
.getContactStore().getContact(address
);
2022 if (contact
== null) {
2023 contact
= new ContactInfo(address
);
2025 if (c
.getName().isPresent()) {
2026 contact
.name
= c
.getName().get();
2028 if (c
.getColor().isPresent()) {
2029 contact
.color
= c
.getColor().get();
2031 if (c
.getProfileKey().isPresent()) {
2032 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2034 if (c
.getVerified().isPresent()) {
2035 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2036 account
.getSignalProtocolStore()
2037 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2038 verifiedMessage
.getIdentityKey(),
2039 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2041 if (c
.getExpirationTimer().isPresent()) {
2042 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2044 contact
.blocked
= c
.isBlocked();
2045 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2046 contact
.archived
= c
.isArchived();
2047 account
.getContactStore().updateContact(contact
);
2049 if (c
.getAvatar().isPresent()) {
2050 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2054 } catch (Exception e
) {
2055 e
.printStackTrace();
2057 if (tmpFile
!= null) {
2059 Files
.delete(tmpFile
.toPath());
2060 } catch (IOException e
) {
2061 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2068 if (syncMessage
.getVerified().isPresent()) {
2069 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2070 account
.getSignalProtocolStore()
2071 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2072 verifiedMessage
.getIdentityKey(),
2073 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2075 if (syncMessage
.getStickerPackOperations().isPresent()) {
2076 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2078 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2079 if (!m
.getPackId().isPresent()) {
2082 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2083 if (sticker
== null) {
2084 if (!m
.getPackKey().isPresent()) {
2087 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2089 sticker
.setInstalled(!m
.getType().isPresent()
2090 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2091 account
.getStickerStore().updateSticker(sticker
);
2094 if (syncMessage
.getConfiguration().isPresent()) {
2102 private File
getContactAvatarFile(String number
) {
2103 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2106 private File
retrieveContactAvatarAttachment(
2107 SignalServiceAttachment attachment
, String number
2108 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2109 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2110 if (attachment
.isPointer()) {
2111 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2112 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2114 SignalServiceAttachmentStream stream
= attachment
.asStream();
2115 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2119 private File
getGroupAvatarFile(GroupId groupId
) {
2120 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2123 private File
retrieveGroupAvatarAttachment(
2124 SignalServiceAttachment attachment
, GroupId groupId
2125 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2126 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2127 if (attachment
.isPointer()) {
2128 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2129 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2131 SignalServiceAttachmentStream stream
= attachment
.asStream();
2132 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2136 private File
retrieveGroupAvatar(
2137 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2138 ) throws IOException
{
2139 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2140 File outputFile
= getGroupAvatarFile(groupId
);
2141 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2143 File tmpFile
= IOUtils
.createTempFile();
2144 try (InputStream input
= messageReceiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2146 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2147 byte[] encryptedData
= IOUtils
.readFully(input
);
2149 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2150 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2151 output
.write(decryptedData
);
2155 Files
.delete(tmpFile
.toPath());
2156 } catch (IOException e
) {
2157 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2165 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2166 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2169 private File
retrieveProfileAvatar(
2170 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2171 ) throws IOException
{
2172 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2173 File outputFile
= getProfileAvatarFile(address
);
2175 File tmpFile
= IOUtils
.createTempFile();
2176 try (InputStream input
= messageReceiver
.retrieveProfileAvatar(avatarPath
,
2179 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2180 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2181 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2184 Files
.delete(tmpFile
.toPath());
2185 } catch (IOException e
) {
2186 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2194 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2195 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2198 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2199 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2200 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2203 private File
retrieveAttachment(
2204 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2205 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2206 if (storePreview
&& pointer
.getPreview().isPresent()) {
2207 File previewFile
= new File(outputFile
+ ".preview");
2208 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2209 byte[] preview
= pointer
.getPreview().get();
2210 output
.write(preview
, 0, preview
.length
);
2211 } catch (FileNotFoundException e
) {
2212 e
.printStackTrace();
2217 File tmpFile
= IOUtils
.createTempFile();
2218 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2220 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2221 IOUtils
.copyStreamToFile(input
, outputFile
);
2224 Files
.delete(tmpFile
.toPath());
2225 } catch (IOException e
) {
2226 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2234 private InputStream
retrieveAttachmentAsStream(
2235 SignalServiceAttachmentPointer pointer
, File tmpFile
2236 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2237 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2240 void sendGroups() throws IOException
, UntrustedIdentityException
{
2241 File groupsFile
= IOUtils
.createTempFile();
2244 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2245 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2246 for (GroupInfo
record : getGroups()) {
2247 if (record instanceof GroupInfoV1
) {
2248 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2249 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2250 Optional
.fromNullable(groupInfo
.name
),
2251 new ArrayList
<>(groupInfo
.getMembers()),
2252 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2253 groupInfo
.isMember(account
.getSelfAddress()),
2254 Optional
.of(groupInfo
.messageExpirationTime
),
2255 Optional
.fromNullable(groupInfo
.color
),
2257 Optional
.fromNullable(groupInfo
.inboxPosition
),
2258 groupInfo
.archived
));
2263 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2264 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2265 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2266 .withStream(groupsFileStream
)
2267 .withContentType("application/octet-stream")
2268 .withLength(groupsFile
.length())
2271 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2276 Files
.delete(groupsFile
.toPath());
2277 } catch (IOException e
) {
2278 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2283 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2284 File contactsFile
= IOUtils
.createTempFile();
2287 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2288 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2289 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2290 VerifiedMessage verifiedMessage
= null;
2291 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2292 if (currentIdentity
!= null) {
2293 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2294 currentIdentity
.getIdentityKey(),
2295 currentIdentity
.getTrustLevel().toVerifiedState(),
2296 currentIdentity
.getDateAdded().getTime());
2299 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2300 out
.write(new DeviceContact(record.getAddress(),
2301 Optional
.fromNullable(record.name
),
2302 createContactAvatarAttachment(record.number
),
2303 Optional
.fromNullable(record.color
),
2304 Optional
.fromNullable(verifiedMessage
),
2305 Optional
.fromNullable(profileKey
),
2307 Optional
.of(record.messageExpirationTime
),
2308 Optional
.fromNullable(record.inboxPosition
),
2312 if (account
.getProfileKey() != null) {
2313 // Send our own profile key as well
2314 out
.write(new DeviceContact(account
.getSelfAddress(),
2319 Optional
.of(account
.getProfileKey()),
2327 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2328 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2329 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2330 .withStream(contactsFileStream
)
2331 .withContentType("application/octet-stream")
2332 .withLength(contactsFile
.length())
2335 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2340 Files
.delete(contactsFile
.toPath());
2341 } catch (IOException e
) {
2342 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2347 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2348 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2349 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2350 if (record.blocked
) {
2351 addresses
.add(record.getAddress());
2354 List
<byte[]> groupIds
= new ArrayList
<>();
2355 for (GroupInfo
record : getGroups()) {
2356 if (record.isBlocked()) {
2357 groupIds
.add(record.getGroupId().serialize());
2360 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2363 private void sendVerifiedMessage(
2364 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2365 ) throws IOException
, UntrustedIdentityException
{
2366 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2368 trustLevel
.toVerifiedState(),
2369 System
.currentTimeMillis());
2370 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2373 public List
<ContactInfo
> getContacts() {
2374 return account
.getContactStore().getContacts();
2377 public ContactInfo
getContact(String number
) {
2378 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2381 public GroupInfo
getGroup(GroupId groupId
) {
2382 final GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2383 if (group
instanceof GroupInfoV2
&& ((GroupInfoV2
) group
).getGroup() == null) {
2384 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(((GroupInfoV2
) group
).getMasterKey());
2385 ((GroupInfoV2
) group
).setGroup(groupHelper
.getDecryptedGroup(groupSecretParams
));
2386 account
.getGroupStore().updateGroup(group
);
2391 public List
<IdentityInfo
> getIdentities() {
2392 return account
.getSignalProtocolStore().getIdentities();
2395 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2396 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2400 * Trust this the identity with this fingerprint
2402 * @param name username of the identity
2403 * @param fingerprint Fingerprint
2405 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2406 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2407 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2411 for (IdentityInfo id
: ids
) {
2412 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2416 account
.getSignalProtocolStore()
2417 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2419 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2420 } catch (IOException
| UntrustedIdentityException e
) {
2421 e
.printStackTrace();
2430 * Trust this the identity with this safety number
2432 * @param name username of the identity
2433 * @param safetyNumber Safety number
2435 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2436 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2437 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2441 for (IdentityInfo id
: ids
) {
2442 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2446 account
.getSignalProtocolStore()
2447 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2449 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2450 } catch (IOException
| UntrustedIdentityException e
) {
2451 e
.printStackTrace();
2460 * Trust all keys of this identity without verification
2462 * @param name username of the identity
2464 public boolean trustIdentityAllKeys(String name
) {
2465 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2466 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2470 for (IdentityInfo id
: ids
) {
2471 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2472 account
.getSignalProtocolStore()
2473 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2475 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2476 } catch (IOException
| UntrustedIdentityException e
) {
2477 e
.printStackTrace();
2485 public String
computeSafetyNumber(
2486 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2488 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2489 account
.getSelfAddress(),
2490 getIdentityKeyPair().getPublicKey(),
2495 void saveAccount() {
2499 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2500 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2502 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2503 return resolveSignalServiceAddress(canonicalizedNumber
);
2506 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2507 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2509 return resolveSignalServiceAddress(address
);
2512 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2513 if (address
.matches(account
.getSelfAddress())) {
2514 return account
.getSelfAddress();
2517 return account
.getRecipientStore().resolveServiceAddress(address
);
2521 public void close() throws IOException
{
2525 void close(boolean closeAccount
) throws IOException
{
2526 if (messagePipe
!= null) {
2527 messagePipe
.shutdown();
2531 if (unidentifiedMessagePipe
!= null) {
2532 unidentifiedMessagePipe
.shutdown();
2533 unidentifiedMessagePipe
= null;
2536 if (closeAccount
&& account
!= null) {
2542 public interface ReceiveMessageHandler
{
2544 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);