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
.Collections
;
168 import java
.util
.Date
;
169 import java
.util
.HashMap
;
170 import java
.util
.HashSet
;
171 import java
.util
.List
;
172 import java
.util
.Locale
;
173 import java
.util
.Map
;
174 import java
.util
.Objects
;
175 import java
.util
.Set
;
176 import java
.util
.UUID
;
177 import java
.util
.concurrent
.ExecutorService
;
178 import java
.util
.concurrent
.TimeUnit
;
179 import java
.util
.concurrent
.TimeoutException
;
180 import java
.util
.stream
.Collectors
;
181 import java
.util
.zip
.ZipEntry
;
182 import java
.util
.zip
.ZipFile
;
184 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
185 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
186 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
188 public class Manager
implements Closeable
{
190 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
192 private final SleepTimer timer
= new UptimeSleepTimer();
193 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
195 private final SignalServiceConfiguration serviceConfiguration
;
196 private final String userAgent
;
197 private final boolean discoverableByPhoneNumber
= true;
198 private final boolean unrestrictedUnidentifiedAccess
= false;
200 private final SignalAccount account
;
201 private final PathConfig pathConfig
;
202 private SignalServiceAccountManager accountManager
;
203 private GroupsV2Api groupsV2Api
;
204 private final GroupsV2Operations groupsV2Operations
;
206 private SignalServiceMessageReceiver messageReceiver
= null;
207 private SignalServiceMessagePipe messagePipe
= null;
208 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
210 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
211 private final ProfileHelper profileHelper
;
212 private final GroupHelper groupHelper
;
215 SignalAccount account
,
216 PathConfig pathConfig
,
217 SignalServiceConfiguration serviceConfiguration
,
220 this.account
= account
;
221 this.pathConfig
= pathConfig
;
222 this.serviceConfiguration
= serviceConfiguration
;
223 this.userAgent
= userAgent
;
224 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
225 serviceConfiguration
)) : null;
226 this.accountManager
= createSignalServiceAccountManager();
227 this.groupsV2Api
= accountManager
.getGroupsV2Api();
229 this.account
.setResolver(this::resolveSignalServiceAddress
);
231 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
232 account
.getProfileStore()::getProfileKey
,
233 this::getRecipientProfile
,
234 this::getSenderCertificate
);
235 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
236 unidentifiedAccessHelper
::getAccessFor
,
237 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
238 this::getOrCreateMessageReceiver
);
239 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
240 this::getRecipientProfile
,
241 account
::getSelfAddress
,
244 this::getGroupAuthForToday
);
247 public String
getUsername() {
248 return account
.getUsername();
251 public SignalServiceAddress
getSelfAddress() {
252 return account
.getSelfAddress();
255 private SignalServiceAccountManager
createSignalServiceAccountManager() {
256 return new SignalServiceAccountManager(serviceConfiguration
,
257 new DynamicCredentialsProvider(account
.getUuid(),
258 account
.getUsername(),
259 account
.getPassword(),
261 account
.getDeviceId()),
267 private IdentityKeyPair
getIdentityKeyPair() {
268 return account
.getSignalProtocolStore().getIdentityKeyPair();
271 public int getDeviceId() {
272 return account
.getDeviceId();
275 private File
getMessageCachePath() {
276 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
279 private File
getMessageCachePath(String sender
) {
280 if (sender
== null || sender
.isEmpty()) {
281 return getMessageCachePath();
284 return new File(getMessageCachePath(), sender
.replace("/", "_"));
287 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
288 File cachePath
= getMessageCachePath(sender
);
289 IOUtils
.createPrivateDirectories(cachePath
);
290 return new File(cachePath
, now
+ "_" + timestamp
);
293 public static Manager
init(
294 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
295 ) throws IOException
{
296 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
298 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
299 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
300 int registrationId
= KeyHelper
.generateRegistrationId(false);
302 ProfileKey profileKey
= KeyUtils
.createProfileKey();
303 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
310 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
313 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
315 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
317 m
.migrateLegacyConfigs();
322 private void migrateLegacyConfigs() {
323 if (account
.getProfileKey() == null && isRegistered()) {
324 // Old config file, creating new profile key
325 account
.setProfileKey(KeyUtils
.createProfileKey());
328 // Store profile keys only in profile store
329 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
330 String profileKeyString
= contact
.profileKey
;
331 if (profileKeyString
== null) {
334 final ProfileKey profileKey
;
336 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
337 } catch (InvalidInputException
| IOException e
) {
340 contact
.profileKey
= null;
341 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
343 // Ensure our profile key is stored in profile store
344 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
347 public void checkAccountState() throws IOException
{
348 if (account
.isRegistered()) {
349 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
353 if (account
.getUuid() == null) {
354 account
.setUuid(accountManager
.getOwnUuid());
357 updateAccountAttributes();
361 public boolean isRegistered() {
362 return account
.isRegistered();
365 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
366 account
.setPassword(KeyUtils
.createPassword());
368 // Resetting UUID, because registering doesn't work otherwise
369 account
.setUuid(null);
370 accountManager
= createSignalServiceAccountManager();
371 this.groupsV2Api
= accountManager
.getGroupsV2Api();
373 if (voiceVerification
) {
374 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
375 Optional
.fromNullable(captcha
),
378 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
381 account
.setRegistered(false);
385 public void updateAccountAttributes() throws IOException
{
386 accountManager
.setAccountAttributes(account
.getSignalingKey(),
387 account
.getSignalProtocolStore().getLocalRegistrationId(),
389 account
.getRegistrationLockPin(),
390 account
.getRegistrationLock(),
391 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
392 unrestrictedUnidentifiedAccess
,
394 discoverableByPhoneNumber
);
397 public void setProfile(String name
, File avatar
) throws IOException
{
398 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
399 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
403 public void unregister() throws IOException
{
404 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
405 // If this is the master device, other users can't send messages to this number anymore.
406 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
407 accountManager
.setGcmId(Optional
.absent());
409 account
.setRegistered(false);
413 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
414 List
<DeviceInfo
> devices
= accountManager
.getDevices();
415 account
.setMultiDevice(devices
.size() > 1);
420 public void removeLinkedDevices(int deviceId
) throws IOException
{
421 accountManager
.removeDevice(deviceId
);
422 List
<DeviceInfo
> devices
= accountManager
.getDevices();
423 account
.setMultiDevice(devices
.size() > 1);
427 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
428 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
430 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
433 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
434 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
435 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
437 accountManager
.addDevice(deviceIdentifier
,
440 Optional
.of(account
.getProfileKey().serialize()),
442 account
.setMultiDevice(true);
446 private List
<PreKeyRecord
> generatePreKeys() {
447 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
449 final int offset
= account
.getPreKeyIdOffset();
450 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
451 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
452 ECKeyPair keyPair
= Curve
.generateKeyPair();
453 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
458 account
.addPreKeys(records
);
464 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
466 ECKeyPair keyPair
= Curve
.generateKeyPair();
467 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
468 keyPair
.getPublicKey().serialize());
469 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
470 System
.currentTimeMillis(),
474 account
.addSignedPreKey(record);
478 } catch (InvalidKeyException e
) {
479 throw new AssertionError(e
);
483 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
484 verificationCode
= verificationCode
.replace("-", "");
485 account
.setSignalingKey(KeyUtils
.createSignalingKey());
486 // TODO make unrestricted unidentified access configurable
487 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
488 account
.getSignalingKey(),
489 account
.getSignalProtocolStore().getLocalRegistrationId(),
493 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
494 unrestrictedUnidentifiedAccess
,
496 discoverableByPhoneNumber
);
498 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
499 // TODO response.isStorageCapable()
500 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
501 account
.setRegistered(true);
502 account
.setUuid(uuid
);
503 account
.setRegistrationLockPin(pin
);
504 account
.getSignalProtocolStore()
505 .saveIdentity(account
.getSelfAddress(),
506 getIdentityKeyPair().getPublicKey(),
507 TrustLevel
.TRUSTED_VERIFIED
);
513 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
514 if (pin
.isPresent()) {
515 account
.setRegistrationLockPin(pin
.get());
516 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
518 account
.setRegistrationLockPin(null);
519 accountManager
.removeRegistrationLockV1();
524 void refreshPreKeys() throws IOException
{
525 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
526 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
527 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
529 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
532 private SignalServiceMessageReceiver
createMessageReceiver() {
533 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
534 serviceConfiguration
).getProfileOperations() : null;
535 return new SignalServiceMessageReceiver(serviceConfiguration
,
537 account
.getUsername(),
538 account
.getPassword(),
539 account
.getDeviceId(),
540 account
.getSignalingKey(),
544 clientZkProfileOperations
);
547 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
548 if (messageReceiver
== null) {
549 messageReceiver
= createMessageReceiver();
551 return messageReceiver
;
554 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
555 if (messagePipe
== null) {
556 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
561 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
562 if (unidentifiedMessagePipe
== null) {
563 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
565 return unidentifiedMessagePipe
;
568 private SignalServiceMessageSender
createMessageSender() {
569 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
570 serviceConfiguration
).getProfileOperations() : null;
571 final ExecutorService executor
= null;
572 return new SignalServiceMessageSender(serviceConfiguration
,
574 account
.getUsername(),
575 account
.getPassword(),
576 account
.getDeviceId(),
577 account
.getSignalProtocolStore(),
579 account
.isMultiDevice(),
580 Optional
.fromNullable(messagePipe
),
581 Optional
.fromNullable(unidentifiedMessagePipe
),
583 clientZkProfileOperations
,
585 ServiceConfig
.MAX_ENVELOPE_SIZE
);
588 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
589 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
592 private SignalProfile
getRecipientProfile(
593 SignalServiceAddress address
595 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
596 if (profileEntry
== null) {
599 long now
= new Date().getTime();
600 // Profiles are cache for 24h before retrieving them again
601 if (!profileEntry
.isRequestPending() && (
602 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
604 ProfileKey profileKey
= profileEntry
.getProfileKey();
605 profileEntry
.setRequestPending(true);
606 SignalProfile profile
;
608 profile
= retrieveRecipientProfile(address
, profileKey
);
609 } catch (IOException e
) {
610 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
611 profileEntry
.setRequestPending(false);
614 profileEntry
.setRequestPending(false);
615 account
.getProfileStore()
616 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
619 return profileEntry
.getProfile();
622 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
623 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
624 if (profileEntry
== null) {
627 if (profileEntry
.getProfileKeyCredential() == null) {
628 ProfileAndCredential profileAndCredential
;
630 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
631 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
632 } catch (IOException e
) {
633 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
637 long now
= new Date().getTime();
638 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
639 final SignalProfile profile
= decryptProfile(address
,
640 profileEntry
.getProfileKey(),
641 profileAndCredential
.getProfile());
642 account
.getProfileStore()
643 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
644 return profileKeyCredential
;
646 return profileEntry
.getProfileKeyCredential();
649 private SignalProfile
retrieveRecipientProfile(
650 SignalServiceAddress address
, ProfileKey profileKey
651 ) throws IOException
{
652 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
654 return decryptProfile(address
, profileKey
, encryptedProfile
);
657 private SignalProfile
decryptProfile(
658 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
660 File avatarFile
= null;
662 avatarFile
= encryptedProfile
.getAvatar() == null
664 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
665 } catch (Throwable e
) {
666 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
669 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
673 name
= encryptedProfile
.getName() == null
675 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
676 } catch (IOException e
) {
679 String unidentifiedAccess
;
681 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
682 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
684 : encryptedProfile
.getUnidentifiedAccess();
685 } catch (IOException e
) {
686 unidentifiedAccess
= null;
688 return new SignalProfile(encryptedProfile
.getIdentityKey(),
692 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
693 encryptedProfile
.getCapabilities());
694 } catch (InvalidCiphertextException e
) {
699 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
700 File file
= getGroupAvatarFile(groupId
);
701 if (!file
.exists()) {
702 return Optional
.absent();
705 return Optional
.of(AttachmentUtils
.createAttachment(file
));
708 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
709 File file
= getContactAvatarFile(number
);
710 if (!file
.exists()) {
711 return Optional
.absent();
714 return Optional
.of(AttachmentUtils
.createAttachment(file
));
717 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
718 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
720 throw new GroupNotFoundException(groupId
);
722 if (!g
.isMember(account
.getSelfAddress())) {
723 throw new NotAGroupMemberException(groupId
, g
.getTitle());
728 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
729 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
731 throw new GroupNotFoundException(groupId
);
733 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
734 throw new NotAGroupMemberException(groupId
, g
.getTitle());
739 public List
<GroupInfo
> getGroups() {
740 return account
.getGroupStore().getGroups();
743 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
744 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
745 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
746 final GroupInfo g
= getGroupForSending(groupId
);
748 GroupUtils
.setGroupContext(messageBuilder
, g
);
749 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
751 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
754 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
755 String messageText
, List
<String
> attachments
, GroupId groupId
756 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
757 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
758 .withBody(messageText
);
759 if (attachments
!= null) {
760 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
763 return sendGroupMessage(messageBuilder
, groupId
);
766 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
767 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
768 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
769 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
771 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
772 targetSentTimestamp
);
773 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
774 .withReaction(reaction
);
776 return sendGroupMessage(messageBuilder
, groupId
);
779 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
781 SignalServiceDataMessage
.Builder messageBuilder
;
783 final GroupInfo g
= getGroupForUpdating(groupId
);
784 if (g
instanceof GroupInfoV1
) {
785 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
786 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
787 .withId(groupId
.serialize())
789 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
790 groupInfoV1
.removeMember(account
.getSelfAddress());
791 account
.getGroupStore().updateGroup(groupInfoV1
);
793 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
794 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
795 groupInfoV2
.setGroup(groupGroupChangePair
.first());
796 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
797 account
.getGroupStore().updateGroup(groupInfoV2
);
800 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
803 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
804 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
805 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
807 SignalServiceDataMessage
.Builder messageBuilder
;
808 if (groupId
== null) {
810 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
812 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
813 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
814 updateGroupV1(gv1
, name
, members
, avatarFile
);
815 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
818 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
822 GroupInfo group
= getGroupForUpdating(groupId
);
823 if (group
instanceof GroupInfoV2
) {
824 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
826 Pair
<Long
, List
<SendMessageResult
>> result
= null;
827 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
828 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
829 result
= sendUpdateGroupMessage(groupInfoV2
,
830 groupGroupChangePair
.first(),
831 groupGroupChangePair
.second());
834 if (members
!= null) {
835 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
836 newMembers
.removeAll(group
.getMembers()
838 .map(this::resolveSignalServiceAddress
)
839 .collect(Collectors
.toSet()));
840 if (newMembers
.size() > 0) {
841 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
843 result
= sendUpdateGroupMessage(groupInfoV2
,
844 groupGroupChangePair
.first(),
845 groupGroupChangePair
.second());
848 if (result
== null || name
!= null || avatarFile
!= null) {
849 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
852 result
= sendUpdateGroupMessage(groupInfoV2
,
853 groupGroupChangePair
.first(),
854 groupGroupChangePair
.second());
857 return new Pair
<>(group
.getGroupId(), result
.second());
859 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
860 updateGroupV1(gv1
, name
, members
, avatarFile
);
861 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
866 account
.getGroupStore().updateGroup(g
);
868 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
869 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
870 return new Pair
<>(g
.getGroupId(), result
.second());
873 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
874 GroupInviteLinkUrl inviteLinkUrl
875 ) throws IOException
, GroupLinkNotActiveException
{
876 return sendJoinGroupMessage(inviteLinkUrl
);
879 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
880 GroupInviteLinkUrl inviteLinkUrl
881 ) throws IOException
, GroupLinkNotActiveException
{
882 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
883 inviteLinkUrl
.getPassword());
884 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
885 inviteLinkUrl
.getPassword(),
887 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
888 groupJoinInfo
.getRevision() + 1,
889 groupChange
.toByteArray());
891 if (group
.getGroup() == null) {
892 // Only requested member, can't send update to group members
893 return new Pair
<>(group
.getGroupId(), List
.of());
896 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
898 return new Pair
<>(group
.getGroupId(), result
.second());
901 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
902 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
903 ) throws IOException
{
904 group
.setGroup(newDecryptedGroup
);
905 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
906 groupChange
.toByteArray());
907 account
.getGroupStore().updateGroup(group
);
908 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
911 private void updateGroupV1(
914 final Collection
<SignalServiceAddress
> members
,
915 final String avatarFile
916 ) throws IOException
{
921 if (members
!= null) {
922 final Set
<String
> newE164Members
= new HashSet
<>();
923 for (SignalServiceAddress member
: members
) {
924 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
927 newE164Members
.add(member
.getNumber().get());
930 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
931 if (contacts
.size() != newE164Members
.size()) {
932 // Some of the new members are not registered on Signal
933 for (ContactTokenDetails contact
: contacts
) {
934 newE164Members
.remove(contact
.getNumber());
936 throw new IOException("Failed to add members "
937 + String
.join(", ", newE164Members
)
938 + " to group: Not registered on Signal");
941 g
.addMembers(members
);
944 if (avatarFile
!= null) {
945 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
946 File aFile
= getGroupAvatarFile(g
.getGroupId());
947 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
951 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
952 GroupIdV1 groupId
, SignalServiceAddress recipient
953 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
955 GroupInfo group
= getGroupForSending(groupId
);
956 if (!(group
instanceof GroupInfoV1
)) {
957 throw new RuntimeException("Received an invalid group request for a v2 group!");
959 g
= (GroupInfoV1
) group
;
961 if (!g
.isMember(recipient
)) {
962 throw new NotAGroupMemberException(groupId
, g
.name
);
965 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
967 // Send group message only to the recipient who requested it
968 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
971 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
972 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
973 .withId(g
.getGroupId().serialize())
975 .withMembers(new ArrayList
<>(g
.getMembers()));
977 File aFile
= getGroupAvatarFile(g
.getGroupId());
978 if (aFile
.exists()) {
980 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
981 } catch (IOException e
) {
982 throw new AttachmentInvalidException(aFile
.toString(), e
);
986 return SignalServiceDataMessage
.newBuilder()
987 .asGroupMessage(group
.build())
988 .withExpiration(g
.getMessageExpirationTime());
991 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
992 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
993 .withRevision(g
.getGroup().getRevision())
994 .withSignedGroupChange(signedGroupChange
);
995 return SignalServiceDataMessage
.newBuilder()
996 .asGroupMessage(group
.build())
997 .withExpiration(g
.getMessageExpirationTime());
1000 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
1001 GroupIdV1 groupId
, SignalServiceAddress recipient
1002 ) throws IOException
{
1003 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
1004 .withId(groupId
.serialize());
1006 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1007 .asGroupMessage(group
.build());
1009 // Send group info request message to the recipient who sent us a message with this groupId
1010 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
1014 SignalServiceAddress remoteAddress
, long messageId
1015 ) throws IOException
, UntrustedIdentityException
{
1016 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
1017 Collections
.singletonList(messageId
),
1018 System
.currentTimeMillis());
1020 createMessageSender().sendReceipt(remoteAddress
,
1021 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1025 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1026 String messageText
, List
<String
> attachments
, List
<String
> recipients
1027 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1028 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1029 .withBody(messageText
);
1030 if (attachments
!= null) {
1031 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
1033 // Upload attachments here, so we only upload once even for multiple recipients
1034 SignalServiceMessageSender messageSender
= createMessageSender();
1035 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1036 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1037 if (attachment
.isStream()) {
1038 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1039 } else if (attachment
.isPointer()) {
1040 attachmentPointers
.add(attachment
.asPointer());
1044 messageBuilder
.withAttachments(attachmentPointers
);
1046 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1049 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1050 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1051 ) throws IOException
, InvalidNumberException
{
1052 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1054 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1055 targetSentTimestamp
);
1056 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1057 .withReaction(reaction
);
1058 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1061 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1062 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1064 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1066 return sendMessage(messageBuilder
, signalServiceAddresses
);
1067 } catch (Exception e
) {
1068 for (SignalServiceAddress address
: signalServiceAddresses
) {
1069 handleEndSession(address
);
1076 public String
getContactName(String number
) throws InvalidNumberException
{
1077 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1078 if (contact
== null) {
1081 return contact
.name
;
1085 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1086 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1087 ContactInfo contact
= account
.getContactStore().getContact(address
);
1088 if (contact
== null) {
1089 contact
= new ContactInfo(address
);
1091 contact
.name
= name
;
1092 account
.getContactStore().updateContact(contact
);
1096 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1097 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1100 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1101 ContactInfo contact
= account
.getContactStore().getContact(address
);
1102 if (contact
== null) {
1103 contact
= new ContactInfo(address
);
1105 contact
.blocked
= blocked
;
1106 account
.getContactStore().updateContact(contact
);
1110 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1111 GroupInfo group
= getGroup(groupId
);
1112 if (group
== null) {
1113 throw new GroupNotFoundException(groupId
);
1116 group
.setBlocked(blocked
);
1117 account
.getGroupStore().updateGroup(group
);
1121 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1122 GroupId groupId
, String name
, List
<String
> members
, String avatar
1123 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1124 return sendUpdateGroupMessage(groupId
,
1126 members
== null ?
null : getSignalServiceAddresses(members
),
1131 * Change the expiration timer for a contact
1133 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1134 ContactInfo contact
= account
.getContactStore().getContact(address
);
1135 contact
.messageExpirationTime
= messageExpirationTimer
;
1136 account
.getContactStore().updateContact(contact
);
1137 sendExpirationTimerUpdate(address
);
1141 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1142 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1143 .asExpirationUpdate();
1144 sendMessage(messageBuilder
, Collections
.singleton(address
));
1148 * Change the expiration timer for a contact
1150 public void setExpirationTimer(
1151 String number
, int messageExpirationTimer
1152 ) throws IOException
, InvalidNumberException
{
1153 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1154 setExpirationTimer(address
, messageExpirationTimer
);
1158 * Change the expiration timer for a group
1160 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1161 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1162 if (g
instanceof GroupInfoV1
) {
1163 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1164 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1165 account
.getGroupStore().updateGroup(groupInfoV1
);
1167 throw new RuntimeException("TODO Not implemented!");
1172 * Upload the sticker pack from path.
1174 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1175 * @return if successful, returns the URL to install the sticker pack in the signal app
1177 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1178 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1180 SignalServiceMessageSender messageSender
= createMessageSender();
1182 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1183 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1185 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1186 account
.getStickerStore().updateSticker(sticker
);
1190 return new URI("https",
1193 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1194 Hex
.toStringCondensed(packKey
),
1195 StandardCharsets
.UTF_8
)).toString();
1196 } catch (URISyntaxException e
) {
1197 throw new AssertionError(e
);
1201 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1203 ) throws IOException
, StickerPackInvalidException
{
1205 String rootPath
= null;
1207 if (file
.getName().endsWith(".zip")) {
1208 zip
= new ZipFile(file
);
1209 } else if (file
.getName().equals("manifest.json")) {
1210 rootPath
= file
.getParent();
1212 throw new StickerPackInvalidException("Could not find manifest.json");
1215 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1217 if (pack
.stickers
== null) {
1218 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1221 if (pack
.stickers
.isEmpty()) {
1222 throw new StickerPackInvalidException("Must include stickers.");
1225 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1226 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1227 if (sticker
.file
== null) {
1228 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1231 Pair
<InputStream
, Long
> data
;
1233 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1234 } catch (IOException ignored
) {
1235 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1238 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1239 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1241 Optional
.fromNullable(sticker
.emoji
).or(""),
1243 stickers
.add(stickerInfo
);
1246 StickerInfo cover
= null;
1247 if (pack
.cover
!= null) {
1248 if (pack
.cover
.file
== null) {
1249 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1252 Pair
<InputStream
, Long
> data
;
1254 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1255 } catch (IOException ignored
) {
1256 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1259 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1260 cover
= new StickerInfo(data
.first(),
1262 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1266 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1269 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1270 InputStream inputStream
;
1272 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1274 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1276 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1279 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1280 final String rootPath
, final ZipFile zip
, final String subfile
1281 ) throws IOException
{
1283 final ZipEntry entry
= zip
.getEntry(subfile
);
1284 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1286 final File file
= new File(rootPath
, subfile
);
1287 return new Pair
<>(new FileInputStream(file
), file
.length());
1291 void requestSyncGroups() throws IOException
{
1292 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1293 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1295 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1297 sendSyncMessage(message
);
1298 } catch (UntrustedIdentityException e
) {
1299 e
.printStackTrace();
1303 void requestSyncContacts() throws IOException
{
1304 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1305 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1307 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1309 sendSyncMessage(message
);
1310 } catch (UntrustedIdentityException e
) {
1311 e
.printStackTrace();
1315 void requestSyncBlocked() throws IOException
{
1316 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1317 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1319 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1321 sendSyncMessage(message
);
1322 } catch (UntrustedIdentityException e
) {
1323 e
.printStackTrace();
1327 void requestSyncConfiguration() throws IOException
{
1328 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1329 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1331 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1333 sendSyncMessage(message
);
1334 } catch (UntrustedIdentityException e
) {
1335 e
.printStackTrace();
1339 private byte[] getSenderCertificate() {
1340 // TODO support UUID capable sender certificates
1341 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1344 certificate
= accountManager
.getSenderCertificate();
1345 } catch (IOException e
) {
1346 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1349 // TODO cache for a day
1353 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1354 SignalServiceMessageSender messageSender
= createMessageSender();
1356 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1357 } catch (UntrustedIdentityException e
) {
1358 account
.getSignalProtocolStore()
1359 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1361 TrustLevel
.UNTRUSTED
);
1366 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1367 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1368 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1370 for (String number
: numbers
) {
1371 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1372 if (resolvedAddress
.getUuid().isPresent()) {
1373 signalServiceAddresses
.add(resolvedAddress
);
1375 missingUuids
.add(resolvedAddress
);
1379 Map
<String
, UUID
> registeredUsers
;
1381 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1382 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1384 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1385 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1386 registeredUsers
= new HashMap
<>();
1389 for (SignalServiceAddress address
: missingUuids
) {
1390 final String number
= address
.getNumber().get();
1391 if (registeredUsers
.containsKey(number
)) {
1392 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1393 registeredUsers
.get(number
),
1395 signalServiceAddresses
.add(newAddress
);
1397 signalServiceAddresses
.add(address
);
1401 return signalServiceAddresses
;
1404 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1405 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1406 ) throws IOException
{
1407 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1408 final long timestamp
= System
.currentTimeMillis();
1409 messageBuilder
.withTimestamp(timestamp
);
1410 getOrCreateMessagePipe();
1411 getOrCreateUnidentifiedMessagePipe();
1412 SignalServiceDataMessage message
= null;
1414 message
= messageBuilder
.build();
1415 if (message
.getGroupContext().isPresent()) {
1417 SignalServiceMessageSender messageSender
= createMessageSender();
1418 final boolean isRecipientUpdate
= false;
1419 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1420 unidentifiedAccessHelper
.getAccessFor(recipients
),
1423 for (SendMessageResult r
: result
) {
1424 if (r
.getIdentityFailure() != null) {
1425 account
.getSignalProtocolStore()
1426 .saveIdentity(r
.getAddress(),
1427 r
.getIdentityFailure().getIdentityKey(),
1428 TrustLevel
.UNTRUSTED
);
1431 return new Pair
<>(timestamp
, result
);
1432 } catch (UntrustedIdentityException e
) {
1433 account
.getSignalProtocolStore()
1434 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1436 TrustLevel
.UNTRUSTED
);
1437 return new Pair
<>(timestamp
, Collections
.emptyList());
1440 // Send to all individually, so sync messages are sent correctly
1441 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1442 for (SignalServiceAddress address
: recipients
) {
1443 ContactInfo contact
= account
.getContactStore().getContact(address
);
1444 if (contact
!= null) {
1445 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1446 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1448 messageBuilder
.withExpiration(0);
1449 messageBuilder
.withProfileKey(null);
1451 message
= messageBuilder
.build();
1452 if (address
.matches(account
.getSelfAddress())) {
1453 results
.add(sendSelfMessage(message
));
1455 results
.add(sendMessage(address
, message
));
1458 return new Pair
<>(timestamp
, results
);
1461 if (message
!= null && message
.isEndSession()) {
1462 for (SignalServiceAddress recipient
: recipients
) {
1463 handleEndSession(recipient
);
1470 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1471 SignalServiceMessageSender messageSender
= createMessageSender();
1473 SignalServiceAddress recipient
= account
.getSelfAddress();
1475 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1476 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1477 message
.getTimestamp(),
1479 message
.getExpiresInSeconds(),
1480 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1482 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1485 long startTime
= System
.currentTimeMillis();
1486 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1487 return SendMessageResult
.success(recipient
,
1488 unidentifiedAccess
.isPresent(),
1490 System
.currentTimeMillis() - startTime
);
1491 } catch (UntrustedIdentityException e
) {
1492 account
.getSignalProtocolStore()
1493 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1495 TrustLevel
.UNTRUSTED
);
1496 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1500 private SendMessageResult
sendMessage(
1501 SignalServiceAddress address
, SignalServiceDataMessage message
1502 ) throws IOException
{
1503 SignalServiceMessageSender messageSender
= createMessageSender();
1506 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1507 } catch (UntrustedIdentityException e
) {
1508 account
.getSignalProtocolStore()
1509 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1511 TrustLevel
.UNTRUSTED
);
1512 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1516 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1517 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1518 account
.getSignalProtocolStore(),
1519 certificateValidator
);
1521 return cipher
.decrypt(envelope
);
1522 } catch (ProtocolUntrustedIdentityException e
) {
1523 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1524 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1526 account
.getSignalProtocolStore()
1527 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1528 identityException
.getUntrustedIdentity(),
1529 TrustLevel
.UNTRUSTED
);
1530 throw identityException
;
1532 throw new AssertionError(e
);
1536 private void handleEndSession(SignalServiceAddress source
) {
1537 account
.getSignalProtocolStore().deleteAllSessions(source
);
1540 private static int currentTimeDays() {
1541 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1544 private GroupsV2AuthorizationString
getGroupAuthForToday(
1545 final GroupSecretParams groupSecretParams
1546 ) throws IOException
{
1547 final int today
= currentTimeDays();
1548 // Returns credentials for the next 7 days
1549 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1550 // TODO cache credentials until they expire
1551 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1553 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1556 authCredentialResponse
);
1557 } catch (VerificationFailedException e
) {
1558 throw new IOException(e
);
1562 private List
<HandleAction
> handleSignalServiceDataMessage(
1563 SignalServiceDataMessage message
,
1565 SignalServiceAddress source
,
1566 SignalServiceAddress destination
,
1567 boolean ignoreAttachments
1569 List
<HandleAction
> actions
= new ArrayList
<>();
1570 if (message
.getGroupContext().isPresent()) {
1571 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1572 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1573 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1574 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1575 if (group
== null || group
instanceof GroupInfoV1
) {
1576 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1577 switch (groupInfo
.getType()) {
1579 if (groupV1
== null) {
1580 groupV1
= new GroupInfoV1(groupId
);
1583 if (groupInfo
.getAvatar().isPresent()) {
1584 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1585 if (avatar
.isPointer()) {
1587 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1588 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1589 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1596 if (groupInfo
.getName().isPresent()) {
1597 groupV1
.name
= groupInfo
.getName().get();
1600 if (groupInfo
.getMembers().isPresent()) {
1601 groupV1
.addMembers(groupInfo
.getMembers()
1604 .map(this::resolveSignalServiceAddress
)
1605 .collect(Collectors
.toSet()));
1608 account
.getGroupStore().updateGroup(groupV1
);
1612 if (groupV1
== null && !isSync
) {
1613 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1617 if (groupV1
!= null) {
1618 groupV1
.removeMember(source
);
1619 account
.getGroupStore().updateGroup(groupV1
);
1624 if (groupV1
!= null && !isSync
) {
1625 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1630 // Received a group v1 message for a v2 group
1633 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1634 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1635 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1637 getOrMigrateGroup(groupMasterKey
,
1638 groupContext
.getRevision(),
1639 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1643 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1644 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1645 handleEndSession(conversationPartnerAddress
);
1647 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1648 if (message
.getGroupContext().isPresent()) {
1649 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1650 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1651 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1652 if (group
!= null) {
1653 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1654 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1655 account
.getGroupStore().updateGroup(group
);
1658 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1659 // disappearing message timer already stored in the DecryptedGroup
1661 } else if (conversationPartnerAddress
!= null) {
1662 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1663 if (contact
== null) {
1664 contact
= new ContactInfo(conversationPartnerAddress
);
1666 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1667 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1668 account
.getContactStore().updateContact(contact
);
1672 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1673 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1674 if (attachment
.isPointer()) {
1676 retrieveAttachment(attachment
.asPointer());
1677 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1678 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1679 attachment
.asPointer().getRemoteId(),
1685 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1686 final ProfileKey profileKey
;
1688 profileKey
= new ProfileKey(message
.getProfileKey().get());
1689 } catch (InvalidInputException e
) {
1690 throw new AssertionError(e
);
1692 if (source
.matches(account
.getSelfAddress())) {
1693 this.account
.setProfileKey(profileKey
);
1695 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1697 if (message
.getPreviews().isPresent()) {
1698 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1699 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1700 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1701 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1703 retrieveAttachment(attachment
);
1704 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1705 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1706 attachment
.getRemoteId(),
1712 if (message
.getQuote().isPresent()) {
1713 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1715 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1716 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1717 if (attachment
!= null && attachment
.isPointer()) {
1719 retrieveAttachment(attachment
.asPointer());
1720 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1721 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1722 attachment
.asPointer().getRemoteId(),
1728 if (message
.getSticker().isPresent()) {
1729 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1730 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1731 if (sticker
== null) {
1732 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1733 account
.getStickerStore().updateSticker(sticker
);
1739 private GroupInfoV2
getOrMigrateGroup(
1740 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1742 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1744 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1745 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1746 final GroupInfoV2 groupInfoV2
;
1747 if (groupInfo
instanceof GroupInfoV1
) {
1748 // Received a v2 group message for a v1 group, we need to locally migrate the group
1749 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1750 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1751 logger
.info("Locally migrated group {} to group v2, id: {}",
1752 groupInfo
.getGroupId().toBase64(),
1753 groupInfoV2
.getGroupId().toBase64());
1754 } else if (groupInfo
instanceof GroupInfoV2
) {
1755 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1757 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1760 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1761 DecryptedGroup group
= null;
1762 if (signedGroupChange
!= null
1763 && groupInfoV2
.getGroup() != null
1764 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1765 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1767 if (group
== null) {
1768 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1770 if (group
!= null) {
1771 storeProfileKeysFromMembers(group
);
1772 final String avatar
= group
.getAvatar();
1773 if (avatar
!= null && !avatar
.isEmpty()) {
1775 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1776 } catch (IOException e
) {
1777 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1781 groupInfoV2
.setGroup(group
);
1782 account
.getGroupStore().updateGroup(groupInfoV2
);
1788 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1789 for (DecryptedMember member
: group
.getMembersList()) {
1790 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1791 member
.getUuid().toByteArray()), null));
1793 account
.getProfileStore()
1794 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1795 } catch (InvalidInputException ignored
) {
1800 private void retryFailedReceivedMessages(
1801 ReceiveMessageHandler handler
, boolean ignoreAttachments
1803 final File cachePath
= getMessageCachePath();
1804 if (!cachePath
.exists()) {
1807 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1808 if (!dir
.isDirectory()) {
1809 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1813 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1814 if (!fileEntry
.isFile()) {
1817 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1819 // Try to delete directory if empty
1824 private void retryFailedReceivedMessage(
1825 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1827 SignalServiceEnvelope envelope
;
1829 envelope
= MessageCacheUtils
.loadEnvelope(fileEntry
);
1830 if (envelope
== null) {
1833 } catch (IOException e
) {
1834 e
.printStackTrace();
1837 SignalServiceContent content
= null;
1838 if (!envelope
.isReceipt()) {
1840 content
= decryptMessage(envelope
);
1841 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1843 } catch (Exception er
) {
1844 // All other errors are not recoverable, so delete the cached message
1846 Files
.delete(fileEntry
.toPath());
1847 } catch (IOException e
) {
1848 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1852 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1853 for (HandleAction action
: actions
) {
1855 action
.execute(this);
1856 } catch (Throwable e
) {
1857 e
.printStackTrace();
1862 handler
.handleMessage(envelope
, content
, null);
1864 Files
.delete(fileEntry
.toPath());
1865 } catch (IOException e
) {
1866 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1870 public void receiveMessages(
1873 boolean returnOnTimeout
,
1874 boolean ignoreAttachments
,
1875 ReceiveMessageHandler handler
1876 ) throws IOException
{
1877 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1879 Set
<HandleAction
> queuedActions
= null;
1881 getOrCreateMessagePipe();
1883 boolean hasCaughtUpWithOldMessages
= false;
1886 SignalServiceEnvelope envelope
;
1887 SignalServiceContent content
= null;
1888 Exception exception
= null;
1889 final long now
= new Date().getTime();
1891 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1892 // store message on disk, before acknowledging receipt to the server
1894 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1895 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1896 MessageCacheUtils
.storeEnvelope(envelope1
, cacheFile
);
1897 } catch (IOException e
) {
1898 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1901 if (result
.isPresent()) {
1902 envelope
= result
.get();
1904 // Received indicator that server queue is empty
1905 hasCaughtUpWithOldMessages
= true;
1907 if (queuedActions
!= null) {
1908 for (HandleAction action
: queuedActions
) {
1910 action
.execute(this);
1911 } catch (Throwable e
) {
1912 e
.printStackTrace();
1916 queuedActions
.clear();
1917 queuedActions
= null;
1920 // Continue to wait another timeout for new messages
1923 } catch (TimeoutException e
) {
1924 if (returnOnTimeout
) return;
1926 } catch (InvalidVersionException e
) {
1927 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1931 if (envelope
.hasSource()) {
1932 // Store uuid if we don't have it already
1933 SignalServiceAddress source
= envelope
.getSourceAddress();
1934 resolveSignalServiceAddress(source
);
1936 if (!envelope
.isReceipt()) {
1938 content
= decryptMessage(envelope
);
1939 } catch (Exception e
) {
1942 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1943 if (hasCaughtUpWithOldMessages
) {
1944 for (HandleAction action
: actions
) {
1946 action
.execute(this);
1947 } catch (Throwable e
) {
1948 e
.printStackTrace();
1952 if (queuedActions
== null) {
1953 queuedActions
= new HashSet
<>();
1955 queuedActions
.addAll(actions
);
1959 if (!isMessageBlocked(envelope
, content
)) {
1960 handler
.handleMessage(envelope
, content
, exception
);
1962 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1963 File cacheFile
= null;
1965 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1966 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1967 Files
.delete(cacheFile
.toPath());
1968 // Try to delete directory if empty
1969 getMessageCachePath().delete();
1970 } catch (IOException e
) {
1971 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1977 private boolean isMessageBlocked(
1978 SignalServiceEnvelope envelope
, SignalServiceContent content
1980 SignalServiceAddress source
;
1981 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1982 source
= envelope
.getSourceAddress();
1983 } else if (content
!= null) {
1984 source
= content
.getSender();
1988 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1989 if (sourceContact
!= null && sourceContact
.blocked
) {
1993 if (content
!= null && content
.getDataMessage().isPresent()) {
1994 SignalServiceDataMessage message
= content
.getDataMessage().get();
1995 if (message
.getGroupContext().isPresent()) {
1996 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1997 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1998 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
2002 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
2003 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2004 if (group
!= null && group
.isBlocked()) {
2012 private List
<HandleAction
> handleMessage(
2013 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2015 List
<HandleAction
> actions
= new ArrayList
<>();
2016 if (content
!= null) {
2017 final SignalServiceAddress sender
;
2018 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2019 sender
= envelope
.getSourceAddress();
2021 sender
= content
.getSender();
2023 // Store uuid if we don't have it already
2024 resolveSignalServiceAddress(sender
);
2026 if (content
.getDataMessage().isPresent()) {
2027 SignalServiceDataMessage message
= content
.getDataMessage().get();
2029 if (content
.isNeedsReceipt()) {
2030 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2033 actions
.addAll(handleSignalServiceDataMessage(message
,
2036 account
.getSelfAddress(),
2037 ignoreAttachments
));
2039 if (content
.getSyncMessage().isPresent()) {
2040 account
.setMultiDevice(true);
2041 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2042 if (syncMessage
.getSent().isPresent()) {
2043 SentTranscriptMessage message
= syncMessage
.getSent().get();
2044 final SignalServiceAddress destination
= message
.getDestination().orNull();
2045 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2049 ignoreAttachments
));
2051 if (syncMessage
.getRequest().isPresent()) {
2052 RequestMessage rm
= syncMessage
.getRequest().get();
2053 if (rm
.isContactsRequest()) {
2054 actions
.add(SendSyncContactsAction
.create());
2056 if (rm
.isGroupsRequest()) {
2057 actions
.add(SendSyncGroupsAction
.create());
2059 if (rm
.isBlockedListRequest()) {
2060 actions
.add(SendSyncBlockedListAction
.create());
2062 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2064 if (syncMessage
.getGroups().isPresent()) {
2065 File tmpFile
= null;
2067 tmpFile
= IOUtils
.createTempFile();
2068 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2070 .asPointer(), tmpFile
)) {
2071 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2073 while ((g
= s
.read()) != null) {
2074 GroupInfoV1 syncGroup
= account
.getGroupStore()
2075 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2076 if (syncGroup
!= null) {
2077 if (g
.getName().isPresent()) {
2078 syncGroup
.name
= g
.getName().get();
2080 syncGroup
.addMembers(g
.getMembers()
2082 .map(this::resolveSignalServiceAddress
)
2083 .collect(Collectors
.toSet()));
2084 if (!g
.isActive()) {
2085 syncGroup
.removeMember(account
.getSelfAddress());
2087 // Add ourself to the member set as it's marked as active
2088 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2090 syncGroup
.blocked
= g
.isBlocked();
2091 if (g
.getColor().isPresent()) {
2092 syncGroup
.color
= g
.getColor().get();
2095 if (g
.getAvatar().isPresent()) {
2096 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2098 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2099 syncGroup
.archived
= g
.isArchived();
2100 account
.getGroupStore().updateGroup(syncGroup
);
2104 } catch (Exception e
) {
2105 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2108 e
.printStackTrace();
2110 if (tmpFile
!= null) {
2112 Files
.delete(tmpFile
.toPath());
2113 } catch (IOException e
) {
2114 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2121 if (syncMessage
.getBlockedList().isPresent()) {
2122 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2123 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2124 setContactBlocked(resolveSignalServiceAddress(address
), true);
2126 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2128 .map(GroupId
::unknownVersion
)
2129 .collect(Collectors
.toSet())) {
2131 setGroupBlocked(groupId
, true);
2132 } catch (GroupNotFoundException e
) {
2133 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2134 groupId
.toBase64());
2138 if (syncMessage
.getContacts().isPresent()) {
2139 File tmpFile
= null;
2141 tmpFile
= IOUtils
.createTempFile();
2142 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2143 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2144 .asPointer(), tmpFile
)) {
2145 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2146 if (contactsMessage
.isComplete()) {
2147 account
.getContactStore().clear();
2150 while ((c
= s
.read()) != null) {
2151 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2152 account
.setProfileKey(c
.getProfileKey().get());
2154 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2155 ContactInfo contact
= account
.getContactStore().getContact(address
);
2156 if (contact
== null) {
2157 contact
= new ContactInfo(address
);
2159 if (c
.getName().isPresent()) {
2160 contact
.name
= c
.getName().get();
2162 if (c
.getColor().isPresent()) {
2163 contact
.color
= c
.getColor().get();
2165 if (c
.getProfileKey().isPresent()) {
2166 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2168 if (c
.getVerified().isPresent()) {
2169 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2170 account
.getSignalProtocolStore()
2171 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2172 verifiedMessage
.getIdentityKey(),
2173 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2175 if (c
.getExpirationTimer().isPresent()) {
2176 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2178 contact
.blocked
= c
.isBlocked();
2179 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2180 contact
.archived
= c
.isArchived();
2181 account
.getContactStore().updateContact(contact
);
2183 if (c
.getAvatar().isPresent()) {
2184 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2188 } catch (Exception e
) {
2189 e
.printStackTrace();
2191 if (tmpFile
!= null) {
2193 Files
.delete(tmpFile
.toPath());
2194 } catch (IOException e
) {
2195 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2202 if (syncMessage
.getVerified().isPresent()) {
2203 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2204 account
.getSignalProtocolStore()
2205 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2206 verifiedMessage
.getIdentityKey(),
2207 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2209 if (syncMessage
.getStickerPackOperations().isPresent()) {
2210 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2212 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2213 if (!m
.getPackId().isPresent()) {
2216 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2217 if (sticker
== null) {
2218 if (!m
.getPackKey().isPresent()) {
2221 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2223 sticker
.setInstalled(!m
.getType().isPresent()
2224 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2225 account
.getStickerStore().updateSticker(sticker
);
2228 if (syncMessage
.getConfiguration().isPresent()) {
2236 private File
getContactAvatarFile(String number
) {
2237 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2240 private File
retrieveContactAvatarAttachment(
2241 SignalServiceAttachment attachment
, String number
2242 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2243 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2244 if (attachment
.isPointer()) {
2245 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2246 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2248 SignalServiceAttachmentStream stream
= attachment
.asStream();
2249 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2253 private File
getGroupAvatarFile(GroupId groupId
) {
2254 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2257 private File
retrieveGroupAvatarAttachment(
2258 SignalServiceAttachment attachment
, GroupId groupId
2259 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2260 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2261 if (attachment
.isPointer()) {
2262 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2263 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2265 SignalServiceAttachmentStream stream
= attachment
.asStream();
2266 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2270 private File
retrieveGroupAvatar(
2271 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2272 ) throws IOException
{
2273 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2274 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2275 File outputFile
= getGroupAvatarFile(groupId
);
2276 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2278 File tmpFile
= IOUtils
.createTempFile();
2279 tmpFile
.deleteOnExit();
2280 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2282 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2283 byte[] encryptedData
= IOUtils
.readFully(input
);
2285 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2286 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2287 output
.write(decryptedData
);
2291 Files
.delete(tmpFile
.toPath());
2292 } catch (IOException e
) {
2293 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2301 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2302 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2305 private File
retrieveProfileAvatar(
2306 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2307 ) throws IOException
{
2308 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2309 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2310 File outputFile
= getProfileAvatarFile(address
);
2312 File tmpFile
= IOUtils
.createTempFile();
2313 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2316 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2317 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2318 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2321 Files
.delete(tmpFile
.toPath());
2322 } catch (IOException e
) {
2323 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2331 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2332 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2335 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2336 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2337 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2340 private File
retrieveAttachment(
2341 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2342 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2343 if (storePreview
&& pointer
.getPreview().isPresent()) {
2344 File previewFile
= new File(outputFile
+ ".preview");
2345 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2346 byte[] preview
= pointer
.getPreview().get();
2347 output
.write(preview
, 0, preview
.length
);
2348 } catch (FileNotFoundException e
) {
2349 e
.printStackTrace();
2354 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2356 File tmpFile
= IOUtils
.createTempFile();
2357 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2359 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2360 IOUtils
.copyStreamToFile(input
, outputFile
);
2363 Files
.delete(tmpFile
.toPath());
2364 } catch (IOException e
) {
2365 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2373 private InputStream
retrieveAttachmentAsStream(
2374 SignalServiceAttachmentPointer pointer
, File tmpFile
2375 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2376 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2377 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2380 void sendGroups() throws IOException
, UntrustedIdentityException
{
2381 File groupsFile
= IOUtils
.createTempFile();
2384 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2385 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2386 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2387 if (record instanceof GroupInfoV1
) {
2388 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2389 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2390 Optional
.fromNullable(groupInfo
.name
),
2391 new ArrayList
<>(groupInfo
.getMembers()),
2392 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2393 groupInfo
.isMember(account
.getSelfAddress()),
2394 Optional
.of(groupInfo
.messageExpirationTime
),
2395 Optional
.fromNullable(groupInfo
.color
),
2397 Optional
.fromNullable(groupInfo
.inboxPosition
),
2398 groupInfo
.archived
));
2403 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2404 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2405 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2406 .withStream(groupsFileStream
)
2407 .withContentType("application/octet-stream")
2408 .withLength(groupsFile
.length())
2411 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2416 Files
.delete(groupsFile
.toPath());
2417 } catch (IOException e
) {
2418 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2423 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2424 File contactsFile
= IOUtils
.createTempFile();
2427 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2428 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2429 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2430 VerifiedMessage verifiedMessage
= null;
2431 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2432 if (currentIdentity
!= null) {
2433 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2434 currentIdentity
.getIdentityKey(),
2435 currentIdentity
.getTrustLevel().toVerifiedState(),
2436 currentIdentity
.getDateAdded().getTime());
2439 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2440 out
.write(new DeviceContact(record.getAddress(),
2441 Optional
.fromNullable(record.name
),
2442 createContactAvatarAttachment(record.number
),
2443 Optional
.fromNullable(record.color
),
2444 Optional
.fromNullable(verifiedMessage
),
2445 Optional
.fromNullable(profileKey
),
2447 Optional
.of(record.messageExpirationTime
),
2448 Optional
.fromNullable(record.inboxPosition
),
2452 if (account
.getProfileKey() != null) {
2453 // Send our own profile key as well
2454 out
.write(new DeviceContact(account
.getSelfAddress(),
2459 Optional
.of(account
.getProfileKey()),
2467 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2468 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2469 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2470 .withStream(contactsFileStream
)
2471 .withContentType("application/octet-stream")
2472 .withLength(contactsFile
.length())
2475 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2480 Files
.delete(contactsFile
.toPath());
2481 } catch (IOException e
) {
2482 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2487 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2488 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2489 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2490 if (record.blocked
) {
2491 addresses
.add(record.getAddress());
2494 List
<byte[]> groupIds
= new ArrayList
<>();
2495 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2496 if (record.isBlocked()) {
2497 groupIds
.add(record.getGroupId().serialize());
2500 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2503 private void sendVerifiedMessage(
2504 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2505 ) throws IOException
, UntrustedIdentityException
{
2506 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2508 trustLevel
.toVerifiedState(),
2509 System
.currentTimeMillis());
2510 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2513 public List
<ContactInfo
> getContacts() {
2514 return account
.getContactStore().getContacts();
2517 public ContactInfo
getContact(String number
) {
2518 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2521 public GroupInfo
getGroup(GroupId groupId
) {
2522 return account
.getGroupStore().getGroup(groupId
);
2525 public List
<IdentityInfo
> getIdentities() {
2526 return account
.getSignalProtocolStore().getIdentities();
2529 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2530 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2534 * Trust this the identity with this fingerprint
2536 * @param name username of the identity
2537 * @param fingerprint Fingerprint
2539 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2540 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2541 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2545 for (IdentityInfo id
: ids
) {
2546 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2550 account
.getSignalProtocolStore()
2551 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2553 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2554 } catch (IOException
| UntrustedIdentityException e
) {
2555 e
.printStackTrace();
2564 * Trust this the identity with this safety number
2566 * @param name username of the identity
2567 * @param safetyNumber Safety number
2569 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2570 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2571 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2575 for (IdentityInfo id
: ids
) {
2576 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2580 account
.getSignalProtocolStore()
2581 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2583 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2584 } catch (IOException
| UntrustedIdentityException e
) {
2585 e
.printStackTrace();
2594 * Trust all keys of this identity without verification
2596 * @param name username of the identity
2598 public boolean trustIdentityAllKeys(String name
) {
2599 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2600 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2604 for (IdentityInfo id
: ids
) {
2605 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2606 account
.getSignalProtocolStore()
2607 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2609 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2610 } catch (IOException
| UntrustedIdentityException e
) {
2611 e
.printStackTrace();
2619 public String
computeSafetyNumber(
2620 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2622 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2623 account
.getSelfAddress(),
2624 getIdentityKeyPair().getPublicKey(),
2629 void saveAccount() {
2633 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2634 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2636 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2637 return resolveSignalServiceAddress(canonicalizedNumber
);
2640 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2641 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2643 return resolveSignalServiceAddress(address
);
2646 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2647 if (address
.matches(account
.getSelfAddress())) {
2648 return account
.getSelfAddress();
2651 return account
.getRecipientStore().resolveServiceAddress(address
);
2655 public void close() throws IOException
{
2656 if (messagePipe
!= null) {
2657 messagePipe
.shutdown();
2661 if (unidentifiedMessagePipe
!= null) {
2662 unidentifiedMessagePipe
.shutdown();
2663 unidentifiedMessagePipe
= null;
2669 public interface ReceiveMessageHandler
{
2671 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);