2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.signal
.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
.ProfileHelper
;
30 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
31 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
32 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactInfo
;
33 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
34 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
35 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
36 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfile
;
37 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfileEntry
;
38 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
39 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
40 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
41 import org
.asamk
.signal
.manager
.util
.IOUtils
;
42 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
43 import org
.asamk
.signal
.manager
.util
.MessageCacheUtils
;
44 import org
.asamk
.signal
.manager
.util
.Utils
;
45 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
46 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
47 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
52 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
53 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
54 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
55 import org
.signal
.libsignal
.metadata
.SelfSendException
;
56 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
57 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
58 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
59 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
60 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
61 import org
.signal
.zkgroup
.InvalidInputException
;
62 import org
.signal
.zkgroup
.VerificationFailedException
;
63 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
64 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
65 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
66 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
67 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
68 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
69 import org
.slf4j
.Logger
;
70 import org
.slf4j
.LoggerFactory
;
71 import org
.whispersystems
.libsignal
.IdentityKey
;
72 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
73 import org
.whispersystems
.libsignal
.InvalidKeyException
;
74 import org
.whispersystems
.libsignal
.InvalidMessageException
;
75 import org
.whispersystems
.libsignal
.InvalidVersionException
;
76 import org
.whispersystems
.libsignal
.ecc
.Curve
;
77 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
78 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
79 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
80 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
81 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
82 import org
.whispersystems
.libsignal
.util
.Medium
;
83 import org
.whispersystems
.libsignal
.util
.Pair
;
84 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
85 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
86 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
87 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
88 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
89 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
90 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
91 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
92 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
93 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
94 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
95 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
96 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
97 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
98 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
99 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
100 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
101 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
102 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
103 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
104 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
108 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
109 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
111 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
112 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
113 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
115 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
116 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
117 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
122 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
123 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
124 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
125 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
126 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
127 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
128 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
129 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
130 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
131 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
132 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
133 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
134 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
135 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
136 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
137 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
138 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
139 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
140 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
141 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
142 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
143 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
144 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
145 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
146 import org
.whispersystems
.util
.Base64
;
148 import java
.io
.Closeable
;
150 import java
.io
.FileInputStream
;
151 import java
.io
.FileNotFoundException
;
152 import java
.io
.FileOutputStream
;
153 import java
.io
.IOException
;
154 import java
.io
.InputStream
;
155 import java
.io
.OutputStream
;
157 import java
.net
.URISyntaxException
;
158 import java
.net
.URLEncoder
;
159 import java
.nio
.charset
.StandardCharsets
;
160 import java
.nio
.file
.Files
;
161 import java
.nio
.file
.Paths
;
162 import java
.nio
.file
.StandardCopyOption
;
163 import java
.security
.SignatureException
;
164 import java
.util
.ArrayList
;
165 import java
.util
.Arrays
;
166 import java
.util
.Collection
;
167 import java
.util
.Date
;
168 import java
.util
.HashMap
;
169 import java
.util
.HashSet
;
170 import java
.util
.List
;
171 import java
.util
.Locale
;
172 import java
.util
.Map
;
173 import java
.util
.Objects
;
174 import java
.util
.Set
;
175 import java
.util
.UUID
;
176 import java
.util
.concurrent
.ExecutorService
;
177 import java
.util
.concurrent
.TimeUnit
;
178 import java
.util
.concurrent
.TimeoutException
;
179 import java
.util
.stream
.Collectors
;
180 import java
.util
.zip
.ZipEntry
;
181 import java
.util
.zip
.ZipFile
;
183 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
184 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
185 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
187 public class Manager
implements Closeable
{
189 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
191 private final SleepTimer timer
= new UptimeSleepTimer();
192 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
194 private final SignalServiceConfiguration serviceConfiguration
;
195 private final String userAgent
;
196 private final boolean discoverableByPhoneNumber
= true;
197 private final boolean unrestrictedUnidentifiedAccess
= false;
199 private final SignalAccount account
;
200 private final PathConfig pathConfig
;
201 private SignalServiceAccountManager accountManager
;
202 private GroupsV2Api groupsV2Api
;
203 private final GroupsV2Operations groupsV2Operations
;
205 private SignalServiceMessageReceiver messageReceiver
= null;
206 private SignalServiceMessagePipe messagePipe
= null;
207 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
209 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
210 private final ProfileHelper profileHelper
;
211 private final GroupHelper groupHelper
;
214 SignalAccount account
,
215 PathConfig pathConfig
,
216 SignalServiceConfiguration serviceConfiguration
,
219 this.account
= account
;
220 this.pathConfig
= pathConfig
;
221 this.serviceConfiguration
= serviceConfiguration
;
222 this.userAgent
= userAgent
;
223 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
224 serviceConfiguration
)) : null;
225 this.accountManager
= createSignalServiceAccountManager();
226 this.groupsV2Api
= accountManager
.getGroupsV2Api();
228 this.account
.setResolver(this::resolveSignalServiceAddress
);
230 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
231 account
.getProfileStore()::getProfileKey
,
232 this::getRecipientProfile
,
233 this::getSenderCertificate
);
234 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
235 unidentifiedAccessHelper
::getAccessFor
,
236 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
237 this::getOrCreateMessageReceiver
);
238 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
239 this::getRecipientProfile
,
240 account
::getSelfAddress
,
243 this::getGroupAuthForToday
);
246 public String
getUsername() {
247 return account
.getUsername();
250 public SignalServiceAddress
getSelfAddress() {
251 return account
.getSelfAddress();
254 private SignalServiceAccountManager
createSignalServiceAccountManager() {
255 return new SignalServiceAccountManager(serviceConfiguration
,
256 new DynamicCredentialsProvider(account
.getUuid(),
257 account
.getUsername(),
258 account
.getPassword(),
260 account
.getDeviceId()),
266 private IdentityKeyPair
getIdentityKeyPair() {
267 return account
.getSignalProtocolStore().getIdentityKeyPair();
270 public int getDeviceId() {
271 return account
.getDeviceId();
274 private File
getMessageCachePath() {
275 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
278 private File
getMessageCachePath(String sender
) {
279 if (sender
== null || sender
.isEmpty()) {
280 return getMessageCachePath();
283 return new File(getMessageCachePath(), sender
.replace("/", "_"));
286 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
287 File cachePath
= getMessageCachePath(sender
);
288 IOUtils
.createPrivateDirectories(cachePath
);
289 return new File(cachePath
, now
+ "_" + timestamp
);
292 public static Manager
init(
293 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
294 ) throws IOException
{
295 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
297 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
298 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
299 int registrationId
= KeyHelper
.generateRegistrationId(false);
301 ProfileKey profileKey
= KeyUtils
.createProfileKey();
302 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
309 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
312 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
314 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
316 m
.migrateLegacyConfigs();
321 private void migrateLegacyConfigs() {
322 if (account
.getProfileKey() == null && isRegistered()) {
323 // Old config file, creating new profile key
324 account
.setProfileKey(KeyUtils
.createProfileKey());
327 // Store profile keys only in profile store
328 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
329 String profileKeyString
= contact
.profileKey
;
330 if (profileKeyString
== null) {
333 final ProfileKey profileKey
;
335 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
336 } catch (InvalidInputException
| IOException e
) {
339 contact
.profileKey
= null;
340 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
342 // Ensure our profile key is stored in profile store
343 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
346 public void checkAccountState() throws IOException
{
347 if (account
.isRegistered()) {
348 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
352 if (account
.getUuid() == null) {
353 account
.setUuid(accountManager
.getOwnUuid());
356 updateAccountAttributes();
360 public boolean isRegistered() {
361 return account
.isRegistered();
364 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
365 account
.setPassword(KeyUtils
.createPassword());
367 // Resetting UUID, because registering doesn't work otherwise
368 account
.setUuid(null);
369 accountManager
= createSignalServiceAccountManager();
370 this.groupsV2Api
= accountManager
.getGroupsV2Api();
372 if (voiceVerification
) {
373 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
374 Optional
.fromNullable(captcha
),
377 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
380 account
.setRegistered(false);
384 public void updateAccountAttributes() throws IOException
{
385 accountManager
.setAccountAttributes(account
.getSignalingKey(),
386 account
.getSignalProtocolStore().getLocalRegistrationId(),
388 account
.getRegistrationLockPin(),
389 account
.getRegistrationLock(),
390 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
391 unrestrictedUnidentifiedAccess
,
393 discoverableByPhoneNumber
);
396 public void setProfile(String name
, File avatar
) throws IOException
{
397 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
398 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
402 public void unregister() throws IOException
{
403 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
404 // If this is the master device, other users can't send messages to this number anymore.
405 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
406 accountManager
.setGcmId(Optional
.absent());
408 account
.setRegistered(false);
412 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
413 List
<DeviceInfo
> devices
= accountManager
.getDevices();
414 account
.setMultiDevice(devices
.size() > 1);
419 public void removeLinkedDevices(int deviceId
) throws IOException
{
420 accountManager
.removeDevice(deviceId
);
421 List
<DeviceInfo
> devices
= accountManager
.getDevices();
422 account
.setMultiDevice(devices
.size() > 1);
426 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
427 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
429 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
432 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
433 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
434 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
436 accountManager
.addDevice(deviceIdentifier
,
439 Optional
.of(account
.getProfileKey().serialize()),
441 account
.setMultiDevice(true);
445 private List
<PreKeyRecord
> generatePreKeys() {
446 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
448 final int offset
= account
.getPreKeyIdOffset();
449 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
450 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
451 ECKeyPair keyPair
= Curve
.generateKeyPair();
452 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
457 account
.addPreKeys(records
);
463 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
465 ECKeyPair keyPair
= Curve
.generateKeyPair();
466 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
467 keyPair
.getPublicKey().serialize());
468 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
469 System
.currentTimeMillis(),
473 account
.addSignedPreKey(record);
477 } catch (InvalidKeyException e
) {
478 throw new AssertionError(e
);
482 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
483 verificationCode
= verificationCode
.replace("-", "");
484 account
.setSignalingKey(KeyUtils
.createSignalingKey());
485 // TODO make unrestricted unidentified access configurable
486 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
487 account
.getSignalingKey(),
488 account
.getSignalProtocolStore().getLocalRegistrationId(),
492 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
493 unrestrictedUnidentifiedAccess
,
495 discoverableByPhoneNumber
);
497 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
498 // TODO response.isStorageCapable()
499 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
500 account
.setRegistered(true);
501 account
.setUuid(uuid
);
502 account
.setRegistrationLockPin(pin
);
503 account
.getSignalProtocolStore()
504 .saveIdentity(account
.getSelfAddress(),
505 getIdentityKeyPair().getPublicKey(),
506 TrustLevel
.TRUSTED_VERIFIED
);
512 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
513 if (pin
.isPresent()) {
514 account
.setRegistrationLockPin(pin
.get());
515 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
517 account
.setRegistrationLockPin(null);
518 accountManager
.removeRegistrationLockV1();
523 void refreshPreKeys() throws IOException
{
524 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
525 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
526 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
528 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
531 private SignalServiceMessageReceiver
createMessageReceiver() {
532 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
533 serviceConfiguration
).getProfileOperations() : null;
534 return new SignalServiceMessageReceiver(serviceConfiguration
,
536 account
.getUsername(),
537 account
.getPassword(),
538 account
.getDeviceId(),
539 account
.getSignalingKey(),
543 clientZkProfileOperations
);
546 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
547 if (messageReceiver
== null) {
548 messageReceiver
= createMessageReceiver();
550 return messageReceiver
;
553 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
554 if (messagePipe
== null) {
555 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
560 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
561 if (unidentifiedMessagePipe
== null) {
562 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
564 return unidentifiedMessagePipe
;
567 private SignalServiceMessageSender
createMessageSender() {
568 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
569 serviceConfiguration
).getProfileOperations() : null;
570 final ExecutorService executor
= null;
571 return new SignalServiceMessageSender(serviceConfiguration
,
573 account
.getUsername(),
574 account
.getPassword(),
575 account
.getDeviceId(),
576 account
.getSignalProtocolStore(),
578 account
.isMultiDevice(),
579 Optional
.fromNullable(messagePipe
),
580 Optional
.fromNullable(unidentifiedMessagePipe
),
582 clientZkProfileOperations
,
584 ServiceConfig
.MAX_ENVELOPE_SIZE
);
587 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
588 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
591 private SignalProfile
getRecipientProfile(
592 SignalServiceAddress address
594 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
595 if (profileEntry
== null) {
598 long now
= new Date().getTime();
599 // Profiles are cache for 24h before retrieving them again
600 if (!profileEntry
.isRequestPending() && (
601 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
603 ProfileKey profileKey
= profileEntry
.getProfileKey();
604 profileEntry
.setRequestPending(true);
605 SignalProfile profile
;
607 profile
= retrieveRecipientProfile(address
, profileKey
);
608 } catch (IOException e
) {
609 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
610 profileEntry
.setRequestPending(false);
613 profileEntry
.setRequestPending(false);
614 account
.getProfileStore()
615 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
618 return profileEntry
.getProfile();
621 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
622 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
623 if (profileEntry
== null) {
626 if (profileEntry
.getProfileKeyCredential() == null) {
627 ProfileAndCredential profileAndCredential
;
629 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
630 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
631 } catch (IOException e
) {
632 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
636 long now
= new Date().getTime();
637 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
638 final SignalProfile profile
= decryptProfile(address
,
639 profileEntry
.getProfileKey(),
640 profileAndCredential
.getProfile());
641 account
.getProfileStore()
642 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
643 return profileKeyCredential
;
645 return profileEntry
.getProfileKeyCredential();
648 private SignalProfile
retrieveRecipientProfile(
649 SignalServiceAddress address
, ProfileKey profileKey
650 ) throws IOException
{
651 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
653 return decryptProfile(address
, profileKey
, encryptedProfile
);
656 private SignalProfile
decryptProfile(
657 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
659 File avatarFile
= null;
661 avatarFile
= encryptedProfile
.getAvatar() == null
663 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
664 } catch (Throwable e
) {
665 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
668 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
672 name
= encryptedProfile
.getName() == null
674 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
675 } catch (IOException e
) {
678 String unidentifiedAccess
;
680 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
681 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
683 : encryptedProfile
.getUnidentifiedAccess();
684 } catch (IOException e
) {
685 unidentifiedAccess
= null;
687 return new SignalProfile(encryptedProfile
.getIdentityKey(),
691 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
692 encryptedProfile
.getCapabilities());
693 } catch (InvalidCiphertextException e
) {
698 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
699 File file
= getGroupAvatarFile(groupId
);
700 if (!file
.exists()) {
701 return Optional
.absent();
704 return Optional
.of(AttachmentUtils
.createAttachment(file
));
707 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
708 File file
= getContactAvatarFile(number
);
709 if (!file
.exists()) {
710 return Optional
.absent();
713 return Optional
.of(AttachmentUtils
.createAttachment(file
));
716 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
717 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
719 throw new GroupNotFoundException(groupId
);
721 if (!g
.isMember(account
.getSelfAddress())) {
722 throw new NotAGroupMemberException(groupId
, g
.getTitle());
727 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
728 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
730 throw new GroupNotFoundException(groupId
);
732 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
733 throw new NotAGroupMemberException(groupId
, g
.getTitle());
738 public List
<GroupInfo
> getGroups() {
739 return account
.getGroupStore().getGroups();
742 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
743 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
744 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
745 final GroupInfo g
= getGroupForSending(groupId
);
747 GroupUtils
.setGroupContext(messageBuilder
, g
);
748 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
750 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
753 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
754 String messageText
, List
<String
> attachments
, GroupId groupId
755 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
756 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
757 .withBody(messageText
);
758 if (attachments
!= null) {
759 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
762 return sendGroupMessage(messageBuilder
, groupId
);
765 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
766 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
767 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
768 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
770 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
771 targetSentTimestamp
);
772 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
773 .withReaction(reaction
);
775 return sendGroupMessage(messageBuilder
, groupId
);
778 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
780 SignalServiceDataMessage
.Builder messageBuilder
;
782 final GroupInfo g
= getGroupForUpdating(groupId
);
783 if (g
instanceof GroupInfoV1
) {
784 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
785 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
786 .withId(groupId
.serialize())
788 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
789 groupInfoV1
.removeMember(account
.getSelfAddress());
790 account
.getGroupStore().updateGroup(groupInfoV1
);
792 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
793 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
794 groupInfoV2
.setGroup(groupGroupChangePair
.first());
795 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
796 account
.getGroupStore().updateGroup(groupInfoV2
);
799 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
802 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
803 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
804 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
806 SignalServiceDataMessage
.Builder messageBuilder
;
807 if (groupId
== null) {
809 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
811 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
812 gv1
.addMembers(List
.of(account
.getSelfAddress()));
813 updateGroupV1(gv1
, name
, members
, avatarFile
);
814 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
817 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
821 GroupInfo group
= getGroupForUpdating(groupId
);
822 if (group
instanceof GroupInfoV2
) {
823 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
825 Pair
<Long
, List
<SendMessageResult
>> result
= null;
826 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
827 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
828 result
= sendUpdateGroupMessage(groupInfoV2
,
829 groupGroupChangePair
.first(),
830 groupGroupChangePair
.second());
833 if (members
!= null) {
834 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
835 newMembers
.removeAll(group
.getMembers()
837 .map(this::resolveSignalServiceAddress
)
838 .collect(Collectors
.toSet()));
839 if (newMembers
.size() > 0) {
840 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
842 result
= sendUpdateGroupMessage(groupInfoV2
,
843 groupGroupChangePair
.first(),
844 groupGroupChangePair
.second());
847 if (result
== null || name
!= null || avatarFile
!= null) {
848 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
851 result
= sendUpdateGroupMessage(groupInfoV2
,
852 groupGroupChangePair
.first(),
853 groupGroupChangePair
.second());
856 return new Pair
<>(group
.getGroupId(), result
.second());
858 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
859 updateGroupV1(gv1
, name
, members
, avatarFile
);
860 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
865 account
.getGroupStore().updateGroup(g
);
867 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
868 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
869 return new Pair
<>(g
.getGroupId(), result
.second());
872 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
873 GroupInviteLinkUrl inviteLinkUrl
874 ) throws IOException
, GroupLinkNotActiveException
{
875 return sendJoinGroupMessage(inviteLinkUrl
);
878 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
879 GroupInviteLinkUrl inviteLinkUrl
880 ) throws IOException
, GroupLinkNotActiveException
{
881 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
882 inviteLinkUrl
.getPassword());
883 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
884 inviteLinkUrl
.getPassword(),
886 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
887 groupJoinInfo
.getRevision() + 1,
888 groupChange
.toByteArray());
890 if (group
.getGroup() == null) {
891 // Only requested member, can't send update to group members
892 return new Pair
<>(group
.getGroupId(), List
.of());
895 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
897 return new Pair
<>(group
.getGroupId(), result
.second());
900 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
901 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
902 ) throws IOException
{
903 group
.setGroup(newDecryptedGroup
);
904 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
905 groupChange
.toByteArray());
906 account
.getGroupStore().updateGroup(group
);
907 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
910 private void updateGroupV1(
913 final Collection
<SignalServiceAddress
> members
,
914 final String avatarFile
915 ) throws IOException
{
920 if (members
!= null) {
921 final Set
<String
> newE164Members
= new HashSet
<>();
922 for (SignalServiceAddress member
: members
) {
923 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
926 newE164Members
.add(member
.getNumber().get());
929 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
930 if (contacts
.size() != newE164Members
.size()) {
931 // Some of the new members are not registered on Signal
932 for (ContactTokenDetails contact
: contacts
) {
933 newE164Members
.remove(contact
.getNumber());
935 throw new IOException("Failed to add members "
936 + String
.join(", ", newE164Members
)
937 + " to group: Not registered on Signal");
940 g
.addMembers(members
);
943 if (avatarFile
!= null) {
944 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
945 File aFile
= getGroupAvatarFile(g
.getGroupId());
946 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
950 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
951 GroupIdV1 groupId
, SignalServiceAddress recipient
952 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
954 GroupInfo group
= getGroupForSending(groupId
);
955 if (!(group
instanceof GroupInfoV1
)) {
956 throw new RuntimeException("Received an invalid group request for a v2 group!");
958 g
= (GroupInfoV1
) group
;
960 if (!g
.isMember(recipient
)) {
961 throw new NotAGroupMemberException(groupId
, g
.name
);
964 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
966 // Send group message only to the recipient who requested it
967 return sendMessage(messageBuilder
, List
.of(recipient
));
970 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
971 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
972 .withId(g
.getGroupId().serialize())
974 .withMembers(new ArrayList
<>(g
.getMembers()));
976 File aFile
= getGroupAvatarFile(g
.getGroupId());
977 if (aFile
.exists()) {
979 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
980 } catch (IOException e
) {
981 throw new AttachmentInvalidException(aFile
.toString(), e
);
985 return SignalServiceDataMessage
.newBuilder()
986 .asGroupMessage(group
.build())
987 .withExpiration(g
.getMessageExpirationTime());
990 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
991 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
992 .withRevision(g
.getGroup().getRevision())
993 .withSignedGroupChange(signedGroupChange
);
994 return SignalServiceDataMessage
.newBuilder()
995 .asGroupMessage(group
.build())
996 .withExpiration(g
.getMessageExpirationTime());
999 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
1000 GroupIdV1 groupId
, SignalServiceAddress recipient
1001 ) throws IOException
{
1002 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
1003 .withId(groupId
.serialize());
1005 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1006 .asGroupMessage(group
.build());
1008 // Send group info request message to the recipient who sent us a message with this groupId
1009 return sendMessage(messageBuilder
, List
.of(recipient
));
1013 SignalServiceAddress remoteAddress
, long messageId
1014 ) throws IOException
, UntrustedIdentityException
{
1015 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
1017 System
.currentTimeMillis());
1019 createMessageSender().sendReceipt(remoteAddress
,
1020 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1024 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1025 String messageText
, List
<String
> attachments
, List
<String
> recipients
1026 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1027 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1028 .withBody(messageText
);
1029 if (attachments
!= null) {
1030 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
1032 // Upload attachments here, so we only upload once even for multiple recipients
1033 SignalServiceMessageSender messageSender
= createMessageSender();
1034 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1035 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1036 if (attachment
.isStream()) {
1037 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1038 } else if (attachment
.isPointer()) {
1039 attachmentPointers
.add(attachment
.asPointer());
1043 messageBuilder
.withAttachments(attachmentPointers
);
1045 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1048 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1049 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1050 ) throws IOException
, InvalidNumberException
{
1051 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1053 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1054 targetSentTimestamp
);
1055 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1056 .withReaction(reaction
);
1057 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1060 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1061 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1063 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1065 return sendMessage(messageBuilder
, signalServiceAddresses
);
1066 } catch (Exception e
) {
1067 for (SignalServiceAddress address
: signalServiceAddresses
) {
1068 handleEndSession(address
);
1075 public String
getContactName(String number
) throws InvalidNumberException
{
1076 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1077 if (contact
== null) {
1080 return contact
.name
;
1084 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1085 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1086 ContactInfo contact
= account
.getContactStore().getContact(address
);
1087 if (contact
== null) {
1088 contact
= new ContactInfo(address
);
1090 contact
.name
= name
;
1091 account
.getContactStore().updateContact(contact
);
1095 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1096 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1099 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1100 ContactInfo contact
= account
.getContactStore().getContact(address
);
1101 if (contact
== null) {
1102 contact
= new ContactInfo(address
);
1104 contact
.blocked
= blocked
;
1105 account
.getContactStore().updateContact(contact
);
1109 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1110 GroupInfo group
= getGroup(groupId
);
1111 if (group
== null) {
1112 throw new GroupNotFoundException(groupId
);
1115 group
.setBlocked(blocked
);
1116 account
.getGroupStore().updateGroup(group
);
1120 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1121 GroupId groupId
, String name
, List
<String
> members
, String avatar
1122 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1123 return sendUpdateGroupMessage(groupId
,
1125 members
== null ?
null : getSignalServiceAddresses(members
),
1130 * Change the expiration timer for a contact
1132 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1133 ContactInfo contact
= account
.getContactStore().getContact(address
);
1134 contact
.messageExpirationTime
= messageExpirationTimer
;
1135 account
.getContactStore().updateContact(contact
);
1136 sendExpirationTimerUpdate(address
);
1140 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1141 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1142 .asExpirationUpdate();
1143 sendMessage(messageBuilder
, List
.of(address
));
1147 * Change the expiration timer for a contact
1149 public void setExpirationTimer(
1150 String number
, int messageExpirationTimer
1151 ) throws IOException
, InvalidNumberException
{
1152 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1153 setExpirationTimer(address
, messageExpirationTimer
);
1157 * Change the expiration timer for a group
1159 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1160 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1161 if (g
instanceof GroupInfoV1
) {
1162 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1163 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1164 account
.getGroupStore().updateGroup(groupInfoV1
);
1166 throw new RuntimeException("TODO Not implemented!");
1171 * Upload the sticker pack from path.
1173 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1174 * @return if successful, returns the URL to install the sticker pack in the signal app
1176 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1177 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1179 SignalServiceMessageSender messageSender
= createMessageSender();
1181 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1182 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1184 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1185 account
.getStickerStore().updateSticker(sticker
);
1189 return new URI("https",
1192 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1193 Hex
.toStringCondensed(packKey
),
1194 StandardCharsets
.UTF_8
)).toString();
1195 } catch (URISyntaxException e
) {
1196 throw new AssertionError(e
);
1200 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1202 ) throws IOException
, StickerPackInvalidException
{
1204 String rootPath
= null;
1206 if (file
.getName().endsWith(".zip")) {
1207 zip
= new ZipFile(file
);
1208 } else if (file
.getName().equals("manifest.json")) {
1209 rootPath
= file
.getParent();
1211 throw new StickerPackInvalidException("Could not find manifest.json");
1214 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1216 if (pack
.stickers
== null) {
1217 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1220 if (pack
.stickers
.isEmpty()) {
1221 throw new StickerPackInvalidException("Must include stickers.");
1224 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1225 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1226 if (sticker
.file
== null) {
1227 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1230 Pair
<InputStream
, Long
> data
;
1232 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1233 } catch (IOException ignored
) {
1234 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1237 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1238 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1240 Optional
.fromNullable(sticker
.emoji
).or(""),
1242 stickers
.add(stickerInfo
);
1245 StickerInfo cover
= null;
1246 if (pack
.cover
!= null) {
1247 if (pack
.cover
.file
== null) {
1248 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1251 Pair
<InputStream
, Long
> data
;
1253 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1254 } catch (IOException ignored
) {
1255 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1258 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1259 cover
= new StickerInfo(data
.first(),
1261 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1265 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1268 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1269 InputStream inputStream
;
1271 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1273 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1275 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1278 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1279 final String rootPath
, final ZipFile zip
, final String subfile
1280 ) throws IOException
{
1282 final ZipEntry entry
= zip
.getEntry(subfile
);
1283 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1285 final File file
= new File(rootPath
, subfile
);
1286 return new Pair
<>(new FileInputStream(file
), file
.length());
1290 void requestSyncGroups() throws IOException
{
1291 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1292 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1294 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1296 sendSyncMessage(message
);
1297 } catch (UntrustedIdentityException e
) {
1298 e
.printStackTrace();
1302 void requestSyncContacts() throws IOException
{
1303 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1304 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1306 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1308 sendSyncMessage(message
);
1309 } catch (UntrustedIdentityException e
) {
1310 e
.printStackTrace();
1314 void requestSyncBlocked() throws IOException
{
1315 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1316 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1318 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1320 sendSyncMessage(message
);
1321 } catch (UntrustedIdentityException e
) {
1322 e
.printStackTrace();
1326 void requestSyncConfiguration() throws IOException
{
1327 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1328 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1330 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1332 sendSyncMessage(message
);
1333 } catch (UntrustedIdentityException e
) {
1334 e
.printStackTrace();
1338 private byte[] getSenderCertificate() {
1339 // TODO support UUID capable sender certificates
1340 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1343 certificate
= accountManager
.getSenderCertificate();
1344 } catch (IOException e
) {
1345 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1348 // TODO cache for a day
1352 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1353 SignalServiceMessageSender messageSender
= createMessageSender();
1355 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1356 } catch (UntrustedIdentityException e
) {
1357 account
.getSignalProtocolStore()
1358 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1360 TrustLevel
.UNTRUSTED
);
1365 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1366 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1367 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1369 for (String number
: numbers
) {
1370 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1371 if (resolvedAddress
.getUuid().isPresent()) {
1372 signalServiceAddresses
.add(resolvedAddress
);
1374 missingUuids
.add(resolvedAddress
);
1378 Map
<String
, UUID
> registeredUsers
;
1380 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1381 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1383 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1384 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1385 registeredUsers
= new HashMap
<>();
1388 for (SignalServiceAddress address
: missingUuids
) {
1389 final String number
= address
.getNumber().get();
1390 if (registeredUsers
.containsKey(number
)) {
1391 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1392 registeredUsers
.get(number
),
1394 signalServiceAddresses
.add(newAddress
);
1396 signalServiceAddresses
.add(address
);
1400 return signalServiceAddresses
;
1403 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1404 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1405 ) throws IOException
{
1406 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1407 final long timestamp
= System
.currentTimeMillis();
1408 messageBuilder
.withTimestamp(timestamp
);
1409 getOrCreateMessagePipe();
1410 getOrCreateUnidentifiedMessagePipe();
1411 SignalServiceDataMessage message
= null;
1413 message
= messageBuilder
.build();
1414 if (message
.getGroupContext().isPresent()) {
1416 SignalServiceMessageSender messageSender
= createMessageSender();
1417 final boolean isRecipientUpdate
= false;
1418 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1419 unidentifiedAccessHelper
.getAccessFor(recipients
),
1422 for (SendMessageResult r
: result
) {
1423 if (r
.getIdentityFailure() != null) {
1424 account
.getSignalProtocolStore()
1425 .saveIdentity(r
.getAddress(),
1426 r
.getIdentityFailure().getIdentityKey(),
1427 TrustLevel
.UNTRUSTED
);
1430 return new Pair
<>(timestamp
, result
);
1431 } catch (UntrustedIdentityException e
) {
1432 account
.getSignalProtocolStore()
1433 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1435 TrustLevel
.UNTRUSTED
);
1436 return new Pair
<>(timestamp
, List
.of());
1439 // Send to all individually, so sync messages are sent correctly
1440 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1441 for (SignalServiceAddress address
: recipients
) {
1442 ContactInfo contact
= account
.getContactStore().getContact(address
);
1443 if (contact
!= null) {
1444 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1445 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1447 messageBuilder
.withExpiration(0);
1448 messageBuilder
.withProfileKey(null);
1450 message
= messageBuilder
.build();
1451 if (address
.matches(account
.getSelfAddress())) {
1452 results
.add(sendSelfMessage(message
));
1454 results
.add(sendMessage(address
, message
));
1457 return new Pair
<>(timestamp
, results
);
1460 if (message
!= null && message
.isEndSession()) {
1461 for (SignalServiceAddress recipient
: recipients
) {
1462 handleEndSession(recipient
);
1469 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1470 SignalServiceMessageSender messageSender
= createMessageSender();
1472 SignalServiceAddress recipient
= account
.getSelfAddress();
1474 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1475 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1476 message
.getTimestamp(),
1478 message
.getExpiresInSeconds(),
1479 Map
.of(recipient
, unidentifiedAccess
.isPresent()),
1481 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1484 long startTime
= System
.currentTimeMillis();
1485 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1486 return SendMessageResult
.success(recipient
,
1487 unidentifiedAccess
.isPresent(),
1489 System
.currentTimeMillis() - startTime
);
1490 } catch (UntrustedIdentityException e
) {
1491 account
.getSignalProtocolStore()
1492 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1494 TrustLevel
.UNTRUSTED
);
1495 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1499 private SendMessageResult
sendMessage(
1500 SignalServiceAddress address
, SignalServiceDataMessage message
1501 ) throws IOException
{
1502 SignalServiceMessageSender messageSender
= createMessageSender();
1505 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1506 } catch (UntrustedIdentityException e
) {
1507 account
.getSignalProtocolStore()
1508 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1510 TrustLevel
.UNTRUSTED
);
1511 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1515 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1516 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1517 account
.getSignalProtocolStore(),
1518 certificateValidator
);
1520 return cipher
.decrypt(envelope
);
1521 } catch (ProtocolUntrustedIdentityException e
) {
1522 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1523 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1525 account
.getSignalProtocolStore()
1526 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1527 identityException
.getUntrustedIdentity(),
1528 TrustLevel
.UNTRUSTED
);
1529 throw identityException
;
1531 throw new AssertionError(e
);
1535 private void handleEndSession(SignalServiceAddress source
) {
1536 account
.getSignalProtocolStore().deleteAllSessions(source
);
1539 private static int currentTimeDays() {
1540 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1543 private GroupsV2AuthorizationString
getGroupAuthForToday(
1544 final GroupSecretParams groupSecretParams
1545 ) throws IOException
{
1546 final int today
= currentTimeDays();
1547 // Returns credentials for the next 7 days
1548 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1549 // TODO cache credentials until they expire
1550 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1552 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1555 authCredentialResponse
);
1556 } catch (VerificationFailedException e
) {
1557 throw new IOException(e
);
1561 private List
<HandleAction
> handleSignalServiceDataMessage(
1562 SignalServiceDataMessage message
,
1564 SignalServiceAddress source
,
1565 SignalServiceAddress destination
,
1566 boolean ignoreAttachments
1568 List
<HandleAction
> actions
= new ArrayList
<>();
1569 if (message
.getGroupContext().isPresent()) {
1570 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1571 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1572 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1573 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1574 if (group
== null || group
instanceof GroupInfoV1
) {
1575 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1576 switch (groupInfo
.getType()) {
1578 if (groupV1
== null) {
1579 groupV1
= new GroupInfoV1(groupId
);
1582 if (groupInfo
.getAvatar().isPresent()) {
1583 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1584 if (avatar
.isPointer()) {
1586 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1587 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1588 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1595 if (groupInfo
.getName().isPresent()) {
1596 groupV1
.name
= groupInfo
.getName().get();
1599 if (groupInfo
.getMembers().isPresent()) {
1600 groupV1
.addMembers(groupInfo
.getMembers()
1603 .map(this::resolveSignalServiceAddress
)
1604 .collect(Collectors
.toSet()));
1607 account
.getGroupStore().updateGroup(groupV1
);
1611 if (groupV1
== null && !isSync
) {
1612 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1616 if (groupV1
!= null) {
1617 groupV1
.removeMember(source
);
1618 account
.getGroupStore().updateGroup(groupV1
);
1623 if (groupV1
!= null && !isSync
) {
1624 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1629 // Received a group v1 message for a v2 group
1632 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1633 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1634 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1636 getOrMigrateGroup(groupMasterKey
,
1637 groupContext
.getRevision(),
1638 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1642 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1643 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1644 handleEndSession(conversationPartnerAddress
);
1646 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1647 if (message
.getGroupContext().isPresent()) {
1648 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1649 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1650 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1651 if (group
!= null) {
1652 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1653 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1654 account
.getGroupStore().updateGroup(group
);
1657 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1658 // disappearing message timer already stored in the DecryptedGroup
1660 } else if (conversationPartnerAddress
!= null) {
1661 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1662 if (contact
== null) {
1663 contact
= new ContactInfo(conversationPartnerAddress
);
1665 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1666 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1667 account
.getContactStore().updateContact(contact
);
1671 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1672 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1673 if (attachment
.isPointer()) {
1675 retrieveAttachment(attachment
.asPointer());
1676 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1677 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1678 attachment
.asPointer().getRemoteId(),
1684 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1685 final ProfileKey profileKey
;
1687 profileKey
= new ProfileKey(message
.getProfileKey().get());
1688 } catch (InvalidInputException e
) {
1689 throw new AssertionError(e
);
1691 if (source
.matches(account
.getSelfAddress())) {
1692 this.account
.setProfileKey(profileKey
);
1694 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1696 if (message
.getPreviews().isPresent()) {
1697 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1698 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1699 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1700 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1702 retrieveAttachment(attachment
);
1703 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1704 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1705 attachment
.getRemoteId(),
1711 if (message
.getQuote().isPresent()) {
1712 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1714 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1715 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1716 if (attachment
!= null && attachment
.isPointer()) {
1718 retrieveAttachment(attachment
.asPointer());
1719 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1720 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1721 attachment
.asPointer().getRemoteId(),
1727 if (message
.getSticker().isPresent()) {
1728 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1729 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1730 if (sticker
== null) {
1731 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1732 account
.getStickerStore().updateSticker(sticker
);
1738 private GroupInfoV2
getOrMigrateGroup(
1739 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1741 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1743 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1744 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1745 final GroupInfoV2 groupInfoV2
;
1746 if (groupInfo
instanceof GroupInfoV1
) {
1747 // Received a v2 group message for a v1 group, we need to locally migrate the group
1748 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1749 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1750 logger
.info("Locally migrated group {} to group v2, id: {}",
1751 groupInfo
.getGroupId().toBase64(),
1752 groupInfoV2
.getGroupId().toBase64());
1753 } else if (groupInfo
instanceof GroupInfoV2
) {
1754 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1756 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1759 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1760 DecryptedGroup group
= null;
1761 if (signedGroupChange
!= null
1762 && groupInfoV2
.getGroup() != null
1763 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1764 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1766 if (group
== null) {
1767 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1769 if (group
!= null) {
1770 storeProfileKeysFromMembers(group
);
1771 final String avatar
= group
.getAvatar();
1772 if (avatar
!= null && !avatar
.isEmpty()) {
1774 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1775 } catch (IOException e
) {
1776 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1780 groupInfoV2
.setGroup(group
);
1781 account
.getGroupStore().updateGroup(groupInfoV2
);
1787 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1788 for (DecryptedMember member
: group
.getMembersList()) {
1789 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1790 member
.getUuid().toByteArray()), null));
1792 account
.getProfileStore()
1793 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1794 } catch (InvalidInputException ignored
) {
1799 private void retryFailedReceivedMessages(
1800 ReceiveMessageHandler handler
, boolean ignoreAttachments
1802 final File cachePath
= getMessageCachePath();
1803 if (!cachePath
.exists()) {
1806 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1807 if (!dir
.isDirectory()) {
1808 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1812 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1813 if (!fileEntry
.isFile()) {
1816 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1818 // Try to delete directory if empty
1823 private void retryFailedReceivedMessage(
1824 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1826 SignalServiceEnvelope envelope
;
1828 envelope
= MessageCacheUtils
.loadEnvelope(fileEntry
);
1829 if (envelope
== null) {
1832 } catch (IOException e
) {
1833 e
.printStackTrace();
1836 SignalServiceContent content
= null;
1837 if (!envelope
.isReceipt()) {
1839 content
= decryptMessage(envelope
);
1840 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1842 } catch (Exception er
) {
1843 // All other errors are not recoverable, so delete the cached message
1845 Files
.delete(fileEntry
.toPath());
1846 } catch (IOException e
) {
1847 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1851 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1852 for (HandleAction action
: actions
) {
1854 action
.execute(this);
1855 } catch (Throwable e
) {
1856 e
.printStackTrace();
1861 handler
.handleMessage(envelope
, content
, null);
1863 Files
.delete(fileEntry
.toPath());
1864 } catch (IOException e
) {
1865 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1869 public void receiveMessages(
1872 boolean returnOnTimeout
,
1873 boolean ignoreAttachments
,
1874 ReceiveMessageHandler handler
1875 ) throws IOException
{
1876 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1878 Set
<HandleAction
> queuedActions
= null;
1880 getOrCreateMessagePipe();
1882 boolean hasCaughtUpWithOldMessages
= false;
1885 SignalServiceEnvelope envelope
;
1886 SignalServiceContent content
= null;
1887 Exception exception
= null;
1888 final long now
= new Date().getTime();
1890 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1891 // store message on disk, before acknowledging receipt to the server
1893 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1894 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1895 MessageCacheUtils
.storeEnvelope(envelope1
, cacheFile
);
1896 } catch (IOException e
) {
1897 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1900 if (result
.isPresent()) {
1901 envelope
= result
.get();
1903 // Received indicator that server queue is empty
1904 hasCaughtUpWithOldMessages
= true;
1906 if (queuedActions
!= null) {
1907 for (HandleAction action
: queuedActions
) {
1909 action
.execute(this);
1910 } catch (Throwable e
) {
1911 e
.printStackTrace();
1915 queuedActions
.clear();
1916 queuedActions
= null;
1919 // Continue to wait another timeout for new messages
1922 } catch (TimeoutException e
) {
1923 if (returnOnTimeout
) return;
1925 } catch (InvalidVersionException e
) {
1926 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1930 if (envelope
.hasSource()) {
1931 // Store uuid if we don't have it already
1932 SignalServiceAddress source
= envelope
.getSourceAddress();
1933 resolveSignalServiceAddress(source
);
1935 if (!envelope
.isReceipt()) {
1937 content
= decryptMessage(envelope
);
1938 } catch (Exception e
) {
1941 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1942 if (hasCaughtUpWithOldMessages
) {
1943 for (HandleAction action
: actions
) {
1945 action
.execute(this);
1946 } catch (Throwable e
) {
1947 e
.printStackTrace();
1951 if (queuedActions
== null) {
1952 queuedActions
= new HashSet
<>();
1954 queuedActions
.addAll(actions
);
1958 if (!isMessageBlocked(envelope
, content
)) {
1959 handler
.handleMessage(envelope
, content
, exception
);
1961 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1962 File cacheFile
= null;
1964 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1965 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1966 Files
.delete(cacheFile
.toPath());
1967 // Try to delete directory if empty
1968 getMessageCachePath().delete();
1969 } catch (IOException e
) {
1970 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1976 private boolean isMessageBlocked(
1977 SignalServiceEnvelope envelope
, SignalServiceContent content
1979 SignalServiceAddress source
;
1980 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1981 source
= envelope
.getSourceAddress();
1982 } else if (content
!= null) {
1983 source
= content
.getSender();
1987 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1988 if (sourceContact
!= null && sourceContact
.blocked
) {
1992 if (content
!= null && content
.getDataMessage().isPresent()) {
1993 SignalServiceDataMessage message
= content
.getDataMessage().get();
1994 if (message
.getGroupContext().isPresent()) {
1995 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1996 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1997 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
2001 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
2002 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2003 if (group
!= null && group
.isBlocked()) {
2011 private List
<HandleAction
> handleMessage(
2012 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2014 List
<HandleAction
> actions
= new ArrayList
<>();
2015 if (content
!= null) {
2016 final SignalServiceAddress sender
;
2017 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2018 sender
= envelope
.getSourceAddress();
2020 sender
= content
.getSender();
2022 // Store uuid if we don't have it already
2023 resolveSignalServiceAddress(sender
);
2025 if (content
.getDataMessage().isPresent()) {
2026 SignalServiceDataMessage message
= content
.getDataMessage().get();
2028 if (content
.isNeedsReceipt()) {
2029 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2032 actions
.addAll(handleSignalServiceDataMessage(message
,
2035 account
.getSelfAddress(),
2036 ignoreAttachments
));
2038 if (content
.getSyncMessage().isPresent()) {
2039 account
.setMultiDevice(true);
2040 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2041 if (syncMessage
.getSent().isPresent()) {
2042 SentTranscriptMessage message
= syncMessage
.getSent().get();
2043 final SignalServiceAddress destination
= message
.getDestination().orNull();
2044 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2048 ignoreAttachments
));
2050 if (syncMessage
.getRequest().isPresent()) {
2051 RequestMessage rm
= syncMessage
.getRequest().get();
2052 if (rm
.isContactsRequest()) {
2053 actions
.add(SendSyncContactsAction
.create());
2055 if (rm
.isGroupsRequest()) {
2056 actions
.add(SendSyncGroupsAction
.create());
2058 if (rm
.isBlockedListRequest()) {
2059 actions
.add(SendSyncBlockedListAction
.create());
2061 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2063 if (syncMessage
.getGroups().isPresent()) {
2064 File tmpFile
= null;
2066 tmpFile
= IOUtils
.createTempFile();
2067 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2069 .asPointer(), tmpFile
)) {
2070 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2072 while ((g
= s
.read()) != null) {
2073 GroupInfoV1 syncGroup
= account
.getGroupStore()
2074 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2075 if (syncGroup
!= null) {
2076 if (g
.getName().isPresent()) {
2077 syncGroup
.name
= g
.getName().get();
2079 syncGroup
.addMembers(g
.getMembers()
2081 .map(this::resolveSignalServiceAddress
)
2082 .collect(Collectors
.toSet()));
2083 if (!g
.isActive()) {
2084 syncGroup
.removeMember(account
.getSelfAddress());
2086 // Add ourself to the member set as it's marked as active
2087 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
2089 syncGroup
.blocked
= g
.isBlocked();
2090 if (g
.getColor().isPresent()) {
2091 syncGroup
.color
= g
.getColor().get();
2094 if (g
.getAvatar().isPresent()) {
2095 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2097 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2098 syncGroup
.archived
= g
.isArchived();
2099 account
.getGroupStore().updateGroup(syncGroup
);
2103 } catch (Exception e
) {
2104 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2107 e
.printStackTrace();
2109 if (tmpFile
!= null) {
2111 Files
.delete(tmpFile
.toPath());
2112 } catch (IOException e
) {
2113 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2120 if (syncMessage
.getBlockedList().isPresent()) {
2121 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2122 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2123 setContactBlocked(resolveSignalServiceAddress(address
), true);
2125 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2127 .map(GroupId
::unknownVersion
)
2128 .collect(Collectors
.toSet())) {
2130 setGroupBlocked(groupId
, true);
2131 } catch (GroupNotFoundException e
) {
2132 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2133 groupId
.toBase64());
2137 if (syncMessage
.getContacts().isPresent()) {
2138 File tmpFile
= null;
2140 tmpFile
= IOUtils
.createTempFile();
2141 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2142 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2143 .asPointer(), tmpFile
)) {
2144 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2145 if (contactsMessage
.isComplete()) {
2146 account
.getContactStore().clear();
2149 while ((c
= s
.read()) != null) {
2150 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2151 account
.setProfileKey(c
.getProfileKey().get());
2153 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2154 ContactInfo contact
= account
.getContactStore().getContact(address
);
2155 if (contact
== null) {
2156 contact
= new ContactInfo(address
);
2158 if (c
.getName().isPresent()) {
2159 contact
.name
= c
.getName().get();
2161 if (c
.getColor().isPresent()) {
2162 contact
.color
= c
.getColor().get();
2164 if (c
.getProfileKey().isPresent()) {
2165 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2167 if (c
.getVerified().isPresent()) {
2168 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2169 account
.getSignalProtocolStore()
2170 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2171 verifiedMessage
.getIdentityKey(),
2172 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2174 if (c
.getExpirationTimer().isPresent()) {
2175 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2177 contact
.blocked
= c
.isBlocked();
2178 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2179 contact
.archived
= c
.isArchived();
2180 account
.getContactStore().updateContact(contact
);
2182 if (c
.getAvatar().isPresent()) {
2183 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2187 } catch (Exception e
) {
2188 e
.printStackTrace();
2190 if (tmpFile
!= null) {
2192 Files
.delete(tmpFile
.toPath());
2193 } catch (IOException e
) {
2194 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2201 if (syncMessage
.getVerified().isPresent()) {
2202 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2203 account
.getSignalProtocolStore()
2204 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2205 verifiedMessage
.getIdentityKey(),
2206 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2208 if (syncMessage
.getStickerPackOperations().isPresent()) {
2209 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2211 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2212 if (!m
.getPackId().isPresent()) {
2215 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2216 if (sticker
== null) {
2217 if (!m
.getPackKey().isPresent()) {
2220 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2222 sticker
.setInstalled(!m
.getType().isPresent()
2223 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2224 account
.getStickerStore().updateSticker(sticker
);
2227 if (syncMessage
.getConfiguration().isPresent()) {
2235 private File
getContactAvatarFile(String number
) {
2236 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2239 private File
retrieveContactAvatarAttachment(
2240 SignalServiceAttachment attachment
, String number
2241 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2242 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2243 if (attachment
.isPointer()) {
2244 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2245 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2247 SignalServiceAttachmentStream stream
= attachment
.asStream();
2248 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2252 private File
getGroupAvatarFile(GroupId groupId
) {
2253 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2256 private File
retrieveGroupAvatarAttachment(
2257 SignalServiceAttachment attachment
, GroupId groupId
2258 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2259 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2260 if (attachment
.isPointer()) {
2261 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2262 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2264 SignalServiceAttachmentStream stream
= attachment
.asStream();
2265 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2269 private File
retrieveGroupAvatar(
2270 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2271 ) throws IOException
{
2272 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2273 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2274 File outputFile
= getGroupAvatarFile(groupId
);
2275 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2277 File tmpFile
= IOUtils
.createTempFile();
2278 tmpFile
.deleteOnExit();
2279 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2281 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2282 byte[] encryptedData
= IOUtils
.readFully(input
);
2284 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2285 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2286 output
.write(decryptedData
);
2290 Files
.delete(tmpFile
.toPath());
2291 } catch (IOException e
) {
2292 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2300 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2301 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2304 private File
retrieveProfileAvatar(
2305 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2306 ) throws IOException
{
2307 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2308 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2309 File outputFile
= getProfileAvatarFile(address
);
2311 File tmpFile
= IOUtils
.createTempFile();
2312 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2315 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2316 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2317 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2320 Files
.delete(tmpFile
.toPath());
2321 } catch (IOException e
) {
2322 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2330 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2331 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2334 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2335 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2336 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2339 private File
retrieveAttachment(
2340 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2341 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2342 if (storePreview
&& pointer
.getPreview().isPresent()) {
2343 File previewFile
= new File(outputFile
+ ".preview");
2344 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2345 byte[] preview
= pointer
.getPreview().get();
2346 output
.write(preview
, 0, preview
.length
);
2347 } catch (FileNotFoundException e
) {
2348 e
.printStackTrace();
2353 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2355 File tmpFile
= IOUtils
.createTempFile();
2356 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2358 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2359 IOUtils
.copyStreamToFile(input
, outputFile
);
2362 Files
.delete(tmpFile
.toPath());
2363 } catch (IOException e
) {
2364 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2372 private InputStream
retrieveAttachmentAsStream(
2373 SignalServiceAttachmentPointer pointer
, File tmpFile
2374 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2375 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2376 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2379 void sendGroups() throws IOException
, UntrustedIdentityException
{
2380 File groupsFile
= IOUtils
.createTempFile();
2383 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2384 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2385 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2386 if (record instanceof GroupInfoV1
) {
2387 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2388 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2389 Optional
.fromNullable(groupInfo
.name
),
2390 new ArrayList
<>(groupInfo
.getMembers()),
2391 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2392 groupInfo
.isMember(account
.getSelfAddress()),
2393 Optional
.of(groupInfo
.messageExpirationTime
),
2394 Optional
.fromNullable(groupInfo
.color
),
2396 Optional
.fromNullable(groupInfo
.inboxPosition
),
2397 groupInfo
.archived
));
2402 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2403 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2404 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2405 .withStream(groupsFileStream
)
2406 .withContentType("application/octet-stream")
2407 .withLength(groupsFile
.length())
2410 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2415 Files
.delete(groupsFile
.toPath());
2416 } catch (IOException e
) {
2417 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2422 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2423 File contactsFile
= IOUtils
.createTempFile();
2426 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2427 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2428 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2429 VerifiedMessage verifiedMessage
= null;
2430 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2431 if (currentIdentity
!= null) {
2432 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2433 currentIdentity
.getIdentityKey(),
2434 currentIdentity
.getTrustLevel().toVerifiedState(),
2435 currentIdentity
.getDateAdded().getTime());
2438 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2439 out
.write(new DeviceContact(record.getAddress(),
2440 Optional
.fromNullable(record.name
),
2441 createContactAvatarAttachment(record.number
),
2442 Optional
.fromNullable(record.color
),
2443 Optional
.fromNullable(verifiedMessage
),
2444 Optional
.fromNullable(profileKey
),
2446 Optional
.of(record.messageExpirationTime
),
2447 Optional
.fromNullable(record.inboxPosition
),
2451 if (account
.getProfileKey() != null) {
2452 // Send our own profile key as well
2453 out
.write(new DeviceContact(account
.getSelfAddress(),
2458 Optional
.of(account
.getProfileKey()),
2466 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2467 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2468 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2469 .withStream(contactsFileStream
)
2470 .withContentType("application/octet-stream")
2471 .withLength(contactsFile
.length())
2474 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2479 Files
.delete(contactsFile
.toPath());
2480 } catch (IOException e
) {
2481 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2486 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2487 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2488 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2489 if (record.blocked
) {
2490 addresses
.add(record.getAddress());
2493 List
<byte[]> groupIds
= new ArrayList
<>();
2494 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2495 if (record.isBlocked()) {
2496 groupIds
.add(record.getGroupId().serialize());
2499 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2502 private void sendVerifiedMessage(
2503 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2504 ) throws IOException
, UntrustedIdentityException
{
2505 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2507 trustLevel
.toVerifiedState(),
2508 System
.currentTimeMillis());
2509 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2512 public List
<ContactInfo
> getContacts() {
2513 return account
.getContactStore().getContacts();
2516 public ContactInfo
getContact(String number
) {
2517 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2520 public GroupInfo
getGroup(GroupId groupId
) {
2521 return account
.getGroupStore().getGroup(groupId
);
2524 public List
<IdentityInfo
> getIdentities() {
2525 return account
.getSignalProtocolStore().getIdentities();
2528 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2529 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2533 * Trust this the identity with this fingerprint
2535 * @param name username of the identity
2536 * @param fingerprint Fingerprint
2538 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2539 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2540 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2544 for (IdentityInfo id
: ids
) {
2545 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2549 account
.getSignalProtocolStore()
2550 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2552 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2553 } catch (IOException
| UntrustedIdentityException e
) {
2554 e
.printStackTrace();
2563 * Trust this the identity with this safety number
2565 * @param name username of the identity
2566 * @param safetyNumber Safety number
2568 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2569 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2570 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2574 for (IdentityInfo id
: ids
) {
2575 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2579 account
.getSignalProtocolStore()
2580 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2582 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2583 } catch (IOException
| UntrustedIdentityException e
) {
2584 e
.printStackTrace();
2593 * Trust all keys of this identity without verification
2595 * @param name username of the identity
2597 public boolean trustIdentityAllKeys(String name
) {
2598 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2599 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2603 for (IdentityInfo id
: ids
) {
2604 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2605 account
.getSignalProtocolStore()
2606 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2608 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2609 } catch (IOException
| UntrustedIdentityException e
) {
2610 e
.printStackTrace();
2618 public String
computeSafetyNumber(
2619 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2621 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2622 account
.getSelfAddress(),
2623 getIdentityKeyPair().getPublicKey(),
2628 void saveAccount() {
2632 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2633 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2635 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2636 return resolveSignalServiceAddress(canonicalizedNumber
);
2639 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2640 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2642 return resolveSignalServiceAddress(address
);
2645 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2646 if (address
.matches(account
.getSelfAddress())) {
2647 return account
.getSelfAddress();
2650 return account
.getRecipientStore().resolveServiceAddress(address
);
2654 public void close() throws IOException
{
2655 if (messagePipe
!= null) {
2656 messagePipe
.shutdown();
2660 if (unidentifiedMessagePipe
!= null) {
2661 unidentifiedMessagePipe
.shutdown();
2662 unidentifiedMessagePipe
= null;
2668 public interface ReceiveMessageHandler
{
2670 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);