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
.util
.IOUtils
;
41 import org
.asamk
.signal
.util
.Util
;
42 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
43 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
46 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
47 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
52 import org
.signal
.libsignal
.metadata
.SelfSendException
;
53 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
54 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
55 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
56 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
57 import org
.signal
.zkgroup
.InvalidInputException
;
58 import org
.signal
.zkgroup
.VerificationFailedException
;
59 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
60 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
61 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
62 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
63 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
64 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
65 import org
.slf4j
.Logger
;
66 import org
.slf4j
.LoggerFactory
;
67 import org
.whispersystems
.libsignal
.IdentityKey
;
68 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
69 import org
.whispersystems
.libsignal
.InvalidKeyException
;
70 import org
.whispersystems
.libsignal
.InvalidMessageException
;
71 import org
.whispersystems
.libsignal
.InvalidVersionException
;
72 import org
.whispersystems
.libsignal
.ecc
.Curve
;
73 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
74 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
75 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
76 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
77 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
78 import org
.whispersystems
.libsignal
.util
.Medium
;
79 import org
.whispersystems
.libsignal
.util
.Pair
;
80 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
81 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
82 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
83 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
84 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
85 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
86 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
87 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
88 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
89 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
90 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
91 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
92 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
93 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
94 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
97 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
98 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
99 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
100 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
101 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
102 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
103 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
104 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
112 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
113 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
114 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
115 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
116 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
117 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
122 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
123 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
124 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
125 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
126 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
127 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
128 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
129 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
130 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
131 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
132 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
133 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
134 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
135 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
136 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
137 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
138 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
139 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
140 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
141 import org
.whispersystems
.util
.Base64
;
143 import java
.io
.Closeable
;
145 import java
.io
.FileInputStream
;
146 import java
.io
.FileNotFoundException
;
147 import java
.io
.FileOutputStream
;
148 import java
.io
.IOException
;
149 import java
.io
.InputStream
;
150 import java
.io
.OutputStream
;
152 import java
.net
.URISyntaxException
;
153 import java
.net
.URLEncoder
;
154 import java
.nio
.charset
.StandardCharsets
;
155 import java
.nio
.file
.Files
;
156 import java
.nio
.file
.Paths
;
157 import java
.nio
.file
.StandardCopyOption
;
158 import java
.security
.SignatureException
;
159 import java
.util
.ArrayList
;
160 import java
.util
.Arrays
;
161 import java
.util
.Collection
;
162 import java
.util
.Collections
;
163 import java
.util
.Date
;
164 import java
.util
.HashMap
;
165 import java
.util
.HashSet
;
166 import java
.util
.List
;
167 import java
.util
.Locale
;
168 import java
.util
.Map
;
169 import java
.util
.Objects
;
170 import java
.util
.Set
;
171 import java
.util
.UUID
;
172 import java
.util
.concurrent
.ExecutorService
;
173 import java
.util
.concurrent
.TimeUnit
;
174 import java
.util
.concurrent
.TimeoutException
;
175 import java
.util
.stream
.Collectors
;
176 import java
.util
.zip
.ZipEntry
;
177 import java
.util
.zip
.ZipFile
;
179 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
180 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
181 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
183 public class Manager
implements Closeable
{
185 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
187 private final SleepTimer timer
= new UptimeSleepTimer();
189 private final SignalServiceConfiguration serviceConfiguration
;
190 private final String userAgent
;
191 private final boolean discoverableByPhoneNumber
= true;
192 private final boolean unrestrictedUnidentifiedAccess
= false;
194 private final SignalAccount account
;
195 private final PathConfig pathConfig
;
196 private SignalServiceAccountManager accountManager
;
197 private GroupsV2Api groupsV2Api
;
198 private final GroupsV2Operations groupsV2Operations
;
200 private SignalServiceMessageReceiver messageReceiver
= null;
201 private SignalServiceMessagePipe messagePipe
= null;
202 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
204 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
205 private final ProfileHelper profileHelper
;
206 private final GroupHelper groupHelper
;
209 SignalAccount account
,
210 PathConfig pathConfig
,
211 SignalServiceConfiguration serviceConfiguration
,
214 this.account
= account
;
215 this.pathConfig
= pathConfig
;
216 this.serviceConfiguration
= serviceConfiguration
;
217 this.userAgent
= userAgent
;
218 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
219 serviceConfiguration
)) : null;
220 this.accountManager
= createSignalServiceAccountManager();
221 this.groupsV2Api
= accountManager
.getGroupsV2Api();
223 this.account
.setResolver(this::resolveSignalServiceAddress
);
225 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
226 account
.getProfileStore()::getProfileKey
,
227 this::getRecipientProfile
,
228 this::getSenderCertificate
);
229 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
230 unidentifiedAccessHelper
::getAccessFor
,
231 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
232 this::getOrCreateMessageReceiver
);
233 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
234 this::getRecipientProfile
,
235 account
::getSelfAddress
,
238 this::getGroupAuthForToday
);
241 public String
getUsername() {
242 return account
.getUsername();
245 public SignalServiceAddress
getSelfAddress() {
246 return account
.getSelfAddress();
249 private SignalServiceAccountManager
createSignalServiceAccountManager() {
250 return new SignalServiceAccountManager(serviceConfiguration
,
251 new DynamicCredentialsProvider(account
.getUuid(),
252 account
.getUsername(),
253 account
.getPassword(),
255 account
.getDeviceId()),
261 private IdentityKeyPair
getIdentityKeyPair() {
262 return account
.getSignalProtocolStore().getIdentityKeyPair();
265 public int getDeviceId() {
266 return account
.getDeviceId();
269 private File
getMessageCachePath() {
270 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
273 private File
getMessageCachePath(String sender
) {
274 if (sender
== null || sender
.isEmpty()) {
275 return getMessageCachePath();
278 return new File(getMessageCachePath(), sender
.replace("/", "_"));
281 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
282 File cachePath
= getMessageCachePath(sender
);
283 IOUtils
.createPrivateDirectories(cachePath
);
284 return new File(cachePath
, now
+ "_" + timestamp
);
287 public static Manager
init(
288 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
289 ) throws IOException
{
290 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
292 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
293 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
294 int registrationId
= KeyHelper
.generateRegistrationId(false);
296 ProfileKey profileKey
= KeyUtils
.createProfileKey();
297 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
304 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
307 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
309 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
311 m
.migrateLegacyConfigs();
316 private void migrateLegacyConfigs() {
317 if (account
.getProfileKey() == null && isRegistered()) {
318 // Old config file, creating new profile key
319 account
.setProfileKey(KeyUtils
.createProfileKey());
322 // Store profile keys only in profile store
323 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
324 String profileKeyString
= contact
.profileKey
;
325 if (profileKeyString
== null) {
328 final ProfileKey profileKey
;
330 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
331 } catch (InvalidInputException
| IOException e
) {
334 contact
.profileKey
= null;
335 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
337 // Ensure our profile key is stored in profile store
338 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
341 public void checkAccountState() throws IOException
{
342 if (account
.isRegistered()) {
343 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
347 if (account
.getUuid() == null) {
348 account
.setUuid(accountManager
.getOwnUuid());
351 updateAccountAttributes();
355 public boolean isRegistered() {
356 return account
.isRegistered();
359 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
360 account
.setPassword(KeyUtils
.createPassword());
362 // Resetting UUID, because registering doesn't work otherwise
363 account
.setUuid(null);
364 accountManager
= createSignalServiceAccountManager();
365 this.groupsV2Api
= accountManager
.getGroupsV2Api();
367 if (voiceVerification
) {
368 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
369 Optional
.fromNullable(captcha
),
372 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
375 account
.setRegistered(false);
379 public void updateAccountAttributes() throws IOException
{
380 accountManager
.setAccountAttributes(account
.getSignalingKey(),
381 account
.getSignalProtocolStore().getLocalRegistrationId(),
383 account
.getRegistrationLockPin(),
384 account
.getRegistrationLock(),
385 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
386 unrestrictedUnidentifiedAccess
,
388 discoverableByPhoneNumber
);
391 public void setProfile(String name
, File avatar
) throws IOException
{
392 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
393 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
397 public void unregister() throws IOException
{
398 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
399 // If this is the master device, other users can't send messages to this number anymore.
400 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
401 accountManager
.setGcmId(Optional
.absent());
403 account
.setRegistered(false);
407 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
408 List
<DeviceInfo
> devices
= accountManager
.getDevices();
409 account
.setMultiDevice(devices
.size() > 1);
414 public void removeLinkedDevices(int deviceId
) throws IOException
{
415 accountManager
.removeDevice(deviceId
);
416 List
<DeviceInfo
> devices
= accountManager
.getDevices();
417 account
.setMultiDevice(devices
.size() > 1);
421 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
422 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
424 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
427 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
428 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
429 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
431 accountManager
.addDevice(deviceIdentifier
,
434 Optional
.of(account
.getProfileKey().serialize()),
436 account
.setMultiDevice(true);
440 private List
<PreKeyRecord
> generatePreKeys() {
441 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
443 final int offset
= account
.getPreKeyIdOffset();
444 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
445 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
446 ECKeyPair keyPair
= Curve
.generateKeyPair();
447 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
452 account
.addPreKeys(records
);
458 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
460 ECKeyPair keyPair
= Curve
.generateKeyPair();
461 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
462 keyPair
.getPublicKey().serialize());
463 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
464 System
.currentTimeMillis(),
468 account
.addSignedPreKey(record);
472 } catch (InvalidKeyException e
) {
473 throw new AssertionError(e
);
477 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
478 verificationCode
= verificationCode
.replace("-", "");
479 account
.setSignalingKey(KeyUtils
.createSignalingKey());
480 // TODO make unrestricted unidentified access configurable
481 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
482 account
.getSignalingKey(),
483 account
.getSignalProtocolStore().getLocalRegistrationId(),
487 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
488 unrestrictedUnidentifiedAccess
,
490 discoverableByPhoneNumber
);
492 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
493 // TODO response.isStorageCapable()
494 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
495 account
.setRegistered(true);
496 account
.setUuid(uuid
);
497 account
.setRegistrationLockPin(pin
);
498 account
.getSignalProtocolStore()
499 .saveIdentity(account
.getSelfAddress(),
500 getIdentityKeyPair().getPublicKey(),
501 TrustLevel
.TRUSTED_VERIFIED
);
507 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
508 if (pin
.isPresent()) {
509 account
.setRegistrationLockPin(pin
.get());
510 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
512 account
.setRegistrationLockPin(null);
513 accountManager
.removeRegistrationLockV1();
518 void refreshPreKeys() throws IOException
{
519 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
520 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
521 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
523 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
526 private SignalServiceMessageReceiver
createMessageReceiver() {
527 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
528 serviceConfiguration
).getProfileOperations() : null;
529 return new SignalServiceMessageReceiver(serviceConfiguration
,
531 account
.getUsername(),
532 account
.getPassword(),
533 account
.getDeviceId(),
534 account
.getSignalingKey(),
538 clientZkProfileOperations
);
541 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
542 if (messageReceiver
== null) {
543 messageReceiver
= createMessageReceiver();
545 return messageReceiver
;
548 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
549 if (messagePipe
== null) {
550 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
555 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
556 if (unidentifiedMessagePipe
== null) {
557 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
559 return unidentifiedMessagePipe
;
562 private SignalServiceMessageSender
createMessageSender() {
563 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
564 serviceConfiguration
).getProfileOperations() : null;
565 final ExecutorService executor
= null;
566 return new SignalServiceMessageSender(serviceConfiguration
,
568 account
.getUsername(),
569 account
.getPassword(),
570 account
.getDeviceId(),
571 account
.getSignalProtocolStore(),
573 account
.isMultiDevice(),
574 Optional
.fromNullable(messagePipe
),
575 Optional
.fromNullable(unidentifiedMessagePipe
),
577 clientZkProfileOperations
,
579 ServiceConfig
.MAX_ENVELOPE_SIZE
);
582 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
583 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
586 private SignalProfile
getRecipientProfile(
587 SignalServiceAddress address
589 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
590 if (profileEntry
== null) {
593 long now
= new Date().getTime();
594 // Profiles are cache for 24h before retrieving them again
595 if (!profileEntry
.isRequestPending() && (
596 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
598 ProfileKey profileKey
= profileEntry
.getProfileKey();
599 profileEntry
.setRequestPending(true);
600 SignalProfile profile
;
602 profile
= retrieveRecipientProfile(address
, profileKey
);
603 } catch (IOException e
) {
604 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
605 profileEntry
.setRequestPending(false);
608 profileEntry
.setRequestPending(false);
609 account
.getProfileStore()
610 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
613 return profileEntry
.getProfile();
616 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
617 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
618 if (profileEntry
== null) {
621 if (profileEntry
.getProfileKeyCredential() == null) {
622 ProfileAndCredential profileAndCredential
;
624 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
625 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
626 } catch (IOException e
) {
627 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
631 long now
= new Date().getTime();
632 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
633 final SignalProfile profile
= decryptProfile(address
,
634 profileEntry
.getProfileKey(),
635 profileAndCredential
.getProfile());
636 account
.getProfileStore()
637 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
638 return profileKeyCredential
;
640 return profileEntry
.getProfileKeyCredential();
643 private SignalProfile
retrieveRecipientProfile(
644 SignalServiceAddress address
, ProfileKey profileKey
645 ) throws IOException
{
646 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
648 return decryptProfile(address
, profileKey
, encryptedProfile
);
651 private SignalProfile
decryptProfile(
652 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
654 File avatarFile
= null;
656 avatarFile
= encryptedProfile
.getAvatar() == null
658 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
659 } catch (Throwable e
) {
660 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
663 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
667 name
= encryptedProfile
.getName() == null
669 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
670 } catch (IOException e
) {
673 String unidentifiedAccess
;
675 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
676 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
678 : encryptedProfile
.getUnidentifiedAccess();
679 } catch (IOException e
) {
680 unidentifiedAccess
= null;
682 return new SignalProfile(encryptedProfile
.getIdentityKey(),
686 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
687 encryptedProfile
.getCapabilities());
688 } catch (InvalidCiphertextException e
) {
693 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
694 File file
= getGroupAvatarFile(groupId
);
695 if (!file
.exists()) {
696 return Optional
.absent();
699 return Optional
.of(Utils
.createAttachment(file
));
702 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
703 File file
= getContactAvatarFile(number
);
704 if (!file
.exists()) {
705 return Optional
.absent();
708 return Optional
.of(Utils
.createAttachment(file
));
711 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
712 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
714 throw new GroupNotFoundException(groupId
);
716 if (!g
.isMember(account
.getSelfAddress())) {
717 throw new NotAGroupMemberException(groupId
, g
.getTitle());
722 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
723 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
725 throw new GroupNotFoundException(groupId
);
727 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
728 throw new NotAGroupMemberException(groupId
, g
.getTitle());
733 public List
<GroupInfo
> getGroups() {
734 return account
.getGroupStore().getGroups();
737 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
738 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
739 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
740 final GroupInfo g
= getGroupForSending(groupId
);
742 GroupUtils
.setGroupContext(messageBuilder
, g
);
743 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
745 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
748 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
749 String messageText
, List
<String
> attachments
, GroupId groupId
750 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
751 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
752 .withBody(messageText
);
753 if (attachments
!= null) {
754 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
757 return sendGroupMessage(messageBuilder
, groupId
);
760 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
761 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
762 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
763 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
765 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
766 targetSentTimestamp
);
767 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
768 .withReaction(reaction
);
770 return sendGroupMessage(messageBuilder
, groupId
);
773 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
775 SignalServiceDataMessage
.Builder messageBuilder
;
777 final GroupInfo g
= getGroupForUpdating(groupId
);
778 if (g
instanceof GroupInfoV1
) {
779 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
780 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
781 .withId(groupId
.serialize())
783 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
784 groupInfoV1
.removeMember(account
.getSelfAddress());
785 account
.getGroupStore().updateGroup(groupInfoV1
);
787 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
788 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
789 groupInfoV2
.setGroup(groupGroupChangePair
.first());
790 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
791 account
.getGroupStore().updateGroup(groupInfoV2
);
794 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
797 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
798 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
799 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
801 SignalServiceDataMessage
.Builder messageBuilder
;
802 if (groupId
== null) {
804 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
806 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
807 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
808 updateGroupV1(gv1
, name
, members
, avatarFile
);
809 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
812 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
816 GroupInfo group
= getGroupForUpdating(groupId
);
817 if (group
instanceof GroupInfoV2
) {
818 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
820 Pair
<Long
, List
<SendMessageResult
>> result
= null;
821 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
822 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
823 result
= sendUpdateGroupMessage(groupInfoV2
,
824 groupGroupChangePair
.first(),
825 groupGroupChangePair
.second());
828 if (members
!= null) {
829 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
830 newMembers
.removeAll(group
.getMembers()
832 .map(this::resolveSignalServiceAddress
)
833 .collect(Collectors
.toSet()));
834 if (newMembers
.size() > 0) {
835 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
837 result
= sendUpdateGroupMessage(groupInfoV2
,
838 groupGroupChangePair
.first(),
839 groupGroupChangePair
.second());
842 if (result
== null || name
!= null || avatarFile
!= null) {
843 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
846 result
= sendUpdateGroupMessage(groupInfoV2
,
847 groupGroupChangePair
.first(),
848 groupGroupChangePair
.second());
851 return new Pair
<>(group
.getGroupId(), result
.second());
853 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
854 updateGroupV1(gv1
, name
, members
, avatarFile
);
855 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
860 account
.getGroupStore().updateGroup(g
);
862 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
863 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
864 return new Pair
<>(g
.getGroupId(), result
.second());
867 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
868 GroupInviteLinkUrl inviteLinkUrl
869 ) throws IOException
, GroupLinkNotActiveException
{
870 return sendJoinGroupMessage(inviteLinkUrl
);
873 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
874 GroupInviteLinkUrl inviteLinkUrl
875 ) throws IOException
, GroupLinkNotActiveException
{
876 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
877 inviteLinkUrl
.getPassword());
878 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
879 inviteLinkUrl
.getPassword(),
881 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
882 groupJoinInfo
.getRevision() + 1,
883 groupChange
.toByteArray());
885 if (group
.getGroup() == null) {
886 // Only requested member, can't send update to group members
887 return new Pair
<>(group
.getGroupId(), List
.of());
890 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
892 return new Pair
<>(group
.getGroupId(), result
.second());
895 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
896 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
897 ) throws IOException
{
898 group
.setGroup(newDecryptedGroup
);
899 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
900 groupChange
.toByteArray());
901 account
.getGroupStore().updateGroup(group
);
902 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
905 private void updateGroupV1(
908 final Collection
<SignalServiceAddress
> members
,
909 final String avatarFile
910 ) throws IOException
{
915 if (members
!= null) {
916 final Set
<String
> newE164Members
= new HashSet
<>();
917 for (SignalServiceAddress member
: members
) {
918 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
921 newE164Members
.add(member
.getNumber().get());
924 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
925 if (contacts
.size() != newE164Members
.size()) {
926 // Some of the new members are not registered on Signal
927 for (ContactTokenDetails contact
: contacts
) {
928 newE164Members
.remove(contact
.getNumber());
930 throw new IOException("Failed to add members "
931 + Util
.join(", ", newE164Members
)
932 + " to group: Not registered on Signal");
935 g
.addMembers(members
);
938 if (avatarFile
!= null) {
939 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
940 File aFile
= getGroupAvatarFile(g
.getGroupId());
941 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
945 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
946 GroupIdV1 groupId
, SignalServiceAddress recipient
947 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
949 GroupInfo group
= getGroupForSending(groupId
);
950 if (!(group
instanceof GroupInfoV1
)) {
951 throw new RuntimeException("Received an invalid group request for a v2 group!");
953 g
= (GroupInfoV1
) group
;
955 if (!g
.isMember(recipient
)) {
956 throw new NotAGroupMemberException(groupId
, g
.name
);
959 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
961 // Send group message only to the recipient who requested it
962 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
965 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
966 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
967 .withId(g
.getGroupId().serialize())
969 .withMembers(new ArrayList
<>(g
.getMembers()));
971 File aFile
= getGroupAvatarFile(g
.getGroupId());
972 if (aFile
.exists()) {
974 group
.withAvatar(Utils
.createAttachment(aFile
));
975 } catch (IOException e
) {
976 throw new AttachmentInvalidException(aFile
.toString(), e
);
980 return SignalServiceDataMessage
.newBuilder()
981 .asGroupMessage(group
.build())
982 .withExpiration(g
.getMessageExpirationTime());
985 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
986 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
987 .withRevision(g
.getGroup().getRevision())
988 .withSignedGroupChange(signedGroupChange
);
989 return SignalServiceDataMessage
.newBuilder()
990 .asGroupMessage(group
.build())
991 .withExpiration(g
.getMessageExpirationTime());
994 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
995 GroupIdV1 groupId
, SignalServiceAddress recipient
996 ) throws IOException
{
997 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
998 .withId(groupId
.serialize());
1000 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1001 .asGroupMessage(group
.build());
1003 // Send group info request message to the recipient who sent us a message with this groupId
1004 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
1008 SignalServiceAddress remoteAddress
, long messageId
1009 ) throws IOException
, UntrustedIdentityException
{
1010 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
1011 Collections
.singletonList(messageId
),
1012 System
.currentTimeMillis());
1014 createMessageSender().sendReceipt(remoteAddress
,
1015 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1019 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1020 String messageText
, List
<String
> attachments
, List
<String
> recipients
1021 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1022 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1023 .withBody(messageText
);
1024 if (attachments
!= null) {
1025 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
1027 // Upload attachments here, so we only upload once even for multiple recipients
1028 SignalServiceMessageSender messageSender
= createMessageSender();
1029 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1030 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1031 if (attachment
.isStream()) {
1032 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1033 } else if (attachment
.isPointer()) {
1034 attachmentPointers
.add(attachment
.asPointer());
1038 messageBuilder
.withAttachments(attachmentPointers
);
1040 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1043 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1044 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1045 ) throws IOException
, InvalidNumberException
{
1046 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1048 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1049 targetSentTimestamp
);
1050 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1051 .withReaction(reaction
);
1052 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1055 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1056 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1058 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1060 return sendMessage(messageBuilder
, signalServiceAddresses
);
1061 } catch (Exception e
) {
1062 for (SignalServiceAddress address
: signalServiceAddresses
) {
1063 handleEndSession(address
);
1070 public String
getContactName(String number
) throws InvalidNumberException
{
1071 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1072 if (contact
== null) {
1075 return contact
.name
;
1079 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1080 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1081 ContactInfo contact
= account
.getContactStore().getContact(address
);
1082 if (contact
== null) {
1083 contact
= new ContactInfo(address
);
1085 contact
.name
= name
;
1086 account
.getContactStore().updateContact(contact
);
1090 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1091 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1094 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1095 ContactInfo contact
= account
.getContactStore().getContact(address
);
1096 if (contact
== null) {
1097 contact
= new ContactInfo(address
);
1099 contact
.blocked
= blocked
;
1100 account
.getContactStore().updateContact(contact
);
1104 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1105 GroupInfo group
= getGroup(groupId
);
1106 if (group
== null) {
1107 throw new GroupNotFoundException(groupId
);
1110 group
.setBlocked(blocked
);
1111 account
.getGroupStore().updateGroup(group
);
1115 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1116 GroupId groupId
, String name
, List
<String
> members
, String avatar
1117 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1118 return sendUpdateGroupMessage(groupId
,
1120 members
== null ?
null : getSignalServiceAddresses(members
),
1125 * Change the expiration timer for a contact
1127 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1128 ContactInfo contact
= account
.getContactStore().getContact(address
);
1129 contact
.messageExpirationTime
= messageExpirationTimer
;
1130 account
.getContactStore().updateContact(contact
);
1131 sendExpirationTimerUpdate(address
);
1135 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1136 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1137 .asExpirationUpdate();
1138 sendMessage(messageBuilder
, Collections
.singleton(address
));
1142 * Change the expiration timer for a contact
1144 public void setExpirationTimer(
1145 String number
, int messageExpirationTimer
1146 ) throws IOException
, InvalidNumberException
{
1147 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1148 setExpirationTimer(address
, messageExpirationTimer
);
1152 * Change the expiration timer for a group
1154 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1155 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1156 if (g
instanceof GroupInfoV1
) {
1157 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1158 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1159 account
.getGroupStore().updateGroup(groupInfoV1
);
1161 throw new RuntimeException("TODO Not implemented!");
1166 * Upload the sticker pack from path.
1168 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1169 * @return if successful, returns the URL to install the sticker pack in the signal app
1171 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1172 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1174 SignalServiceMessageSender messageSender
= createMessageSender();
1176 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1177 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1179 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1180 account
.getStickerStore().updateSticker(sticker
);
1184 return new URI("https",
1187 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1188 Hex
.toStringCondensed(packKey
),
1189 StandardCharsets
.UTF_8
)).toString();
1190 } catch (URISyntaxException e
) {
1191 throw new AssertionError(e
);
1195 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1197 ) throws IOException
, StickerPackInvalidException
{
1199 String rootPath
= null;
1201 if (file
.getName().endsWith(".zip")) {
1202 zip
= new ZipFile(file
);
1203 } else if (file
.getName().equals("manifest.json")) {
1204 rootPath
= file
.getParent();
1206 throw new StickerPackInvalidException("Could not find manifest.json");
1209 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1211 if (pack
.stickers
== null) {
1212 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1215 if (pack
.stickers
.isEmpty()) {
1216 throw new StickerPackInvalidException("Must include stickers.");
1219 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1220 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1221 if (sticker
.file
== null) {
1222 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1225 Pair
<InputStream
, Long
> data
;
1227 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1228 } catch (IOException ignored
) {
1229 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1232 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1233 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1235 Optional
.fromNullable(sticker
.emoji
).or(""),
1237 stickers
.add(stickerInfo
);
1240 StickerInfo cover
= null;
1241 if (pack
.cover
!= null) {
1242 if (pack
.cover
.file
== null) {
1243 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1246 Pair
<InputStream
, Long
> data
;
1248 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1249 } catch (IOException ignored
) {
1250 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1253 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1254 cover
= new StickerInfo(data
.first(),
1256 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1260 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1263 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1264 InputStream inputStream
;
1266 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1268 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1270 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1273 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1274 final String rootPath
, final ZipFile zip
, final String subfile
1275 ) throws IOException
{
1277 final ZipEntry entry
= zip
.getEntry(subfile
);
1278 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1280 final File file
= new File(rootPath
, subfile
);
1281 return new Pair
<>(new FileInputStream(file
), file
.length());
1285 void requestSyncGroups() throws IOException
{
1286 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1287 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1289 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1291 sendSyncMessage(message
);
1292 } catch (UntrustedIdentityException e
) {
1293 e
.printStackTrace();
1297 void requestSyncContacts() throws IOException
{
1298 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1299 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1301 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1303 sendSyncMessage(message
);
1304 } catch (UntrustedIdentityException e
) {
1305 e
.printStackTrace();
1309 void requestSyncBlocked() throws IOException
{
1310 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1311 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1313 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1315 sendSyncMessage(message
);
1316 } catch (UntrustedIdentityException e
) {
1317 e
.printStackTrace();
1321 void requestSyncConfiguration() throws IOException
{
1322 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1323 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1325 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1327 sendSyncMessage(message
);
1328 } catch (UntrustedIdentityException e
) {
1329 e
.printStackTrace();
1333 private byte[] getSenderCertificate() {
1334 // TODO support UUID capable sender certificates
1335 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1338 certificate
= accountManager
.getSenderCertificate();
1339 } catch (IOException e
) {
1340 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1343 // TODO cache for a day
1347 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1348 SignalServiceMessageSender messageSender
= createMessageSender();
1350 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1351 } catch (UntrustedIdentityException e
) {
1352 account
.getSignalProtocolStore()
1353 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1355 TrustLevel
.UNTRUSTED
);
1360 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1361 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1362 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1364 for (String number
: numbers
) {
1365 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1366 if (resolvedAddress
.getUuid().isPresent()) {
1367 signalServiceAddresses
.add(resolvedAddress
);
1369 missingUuids
.add(resolvedAddress
);
1373 Map
<String
, UUID
> registeredUsers
;
1375 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1376 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1378 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1379 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1380 registeredUsers
= new HashMap
<>();
1383 for (SignalServiceAddress address
: missingUuids
) {
1384 final String number
= address
.getNumber().get();
1385 if (registeredUsers
.containsKey(number
)) {
1386 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1387 registeredUsers
.get(number
),
1389 signalServiceAddresses
.add(newAddress
);
1391 signalServiceAddresses
.add(address
);
1395 return signalServiceAddresses
;
1398 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1399 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1400 ) throws IOException
{
1401 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1402 final long timestamp
= System
.currentTimeMillis();
1403 messageBuilder
.withTimestamp(timestamp
);
1404 getOrCreateMessagePipe();
1405 getOrCreateUnidentifiedMessagePipe();
1406 SignalServiceDataMessage message
= null;
1408 message
= messageBuilder
.build();
1409 if (message
.getGroupContext().isPresent()) {
1411 SignalServiceMessageSender messageSender
= createMessageSender();
1412 final boolean isRecipientUpdate
= false;
1413 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1414 unidentifiedAccessHelper
.getAccessFor(recipients
),
1417 for (SendMessageResult r
: result
) {
1418 if (r
.getIdentityFailure() != null) {
1419 account
.getSignalProtocolStore()
1420 .saveIdentity(r
.getAddress(),
1421 r
.getIdentityFailure().getIdentityKey(),
1422 TrustLevel
.UNTRUSTED
);
1425 return new Pair
<>(timestamp
, result
);
1426 } catch (UntrustedIdentityException e
) {
1427 account
.getSignalProtocolStore()
1428 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1430 TrustLevel
.UNTRUSTED
);
1431 return new Pair
<>(timestamp
, Collections
.emptyList());
1434 // Send to all individually, so sync messages are sent correctly
1435 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1436 for (SignalServiceAddress address
: recipients
) {
1437 ContactInfo contact
= account
.getContactStore().getContact(address
);
1438 if (contact
!= null) {
1439 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1440 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1442 messageBuilder
.withExpiration(0);
1443 messageBuilder
.withProfileKey(null);
1445 message
= messageBuilder
.build();
1446 if (address
.matches(account
.getSelfAddress())) {
1447 results
.add(sendSelfMessage(message
));
1449 results
.add(sendMessage(address
, message
));
1452 return new Pair
<>(timestamp
, results
);
1455 if (message
!= null && message
.isEndSession()) {
1456 for (SignalServiceAddress recipient
: recipients
) {
1457 handleEndSession(recipient
);
1464 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1465 SignalServiceMessageSender messageSender
= createMessageSender();
1467 SignalServiceAddress recipient
= account
.getSelfAddress();
1469 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1470 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1471 message
.getTimestamp(),
1473 message
.getExpiresInSeconds(),
1474 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1476 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1479 long startTime
= System
.currentTimeMillis();
1480 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1481 return SendMessageResult
.success(recipient
,
1482 unidentifiedAccess
.isPresent(),
1484 System
.currentTimeMillis() - startTime
);
1485 } catch (UntrustedIdentityException e
) {
1486 account
.getSignalProtocolStore()
1487 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1489 TrustLevel
.UNTRUSTED
);
1490 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1494 private SendMessageResult
sendMessage(
1495 SignalServiceAddress address
, SignalServiceDataMessage message
1496 ) throws IOException
{
1497 SignalServiceMessageSender messageSender
= createMessageSender();
1500 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1501 } catch (UntrustedIdentityException e
) {
1502 account
.getSignalProtocolStore()
1503 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1505 TrustLevel
.UNTRUSTED
);
1506 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1510 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1511 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1512 account
.getSignalProtocolStore(),
1513 Utils
.getCertificateValidator());
1515 return cipher
.decrypt(envelope
);
1516 } catch (ProtocolUntrustedIdentityException e
) {
1517 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1518 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1520 account
.getSignalProtocolStore()
1521 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1522 identityException
.getUntrustedIdentity(),
1523 TrustLevel
.UNTRUSTED
);
1524 throw identityException
;
1526 throw new AssertionError(e
);
1530 private void handleEndSession(SignalServiceAddress source
) {
1531 account
.getSignalProtocolStore().deleteAllSessions(source
);
1534 private static int currentTimeDays() {
1535 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1538 private GroupsV2AuthorizationString
getGroupAuthForToday(
1539 final GroupSecretParams groupSecretParams
1540 ) throws IOException
{
1541 final int today
= currentTimeDays();
1542 // Returns credentials for the next 7 days
1543 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1544 // TODO cache credentials until they expire
1545 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1547 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1550 authCredentialResponse
);
1551 } catch (VerificationFailedException e
) {
1552 throw new IOException(e
);
1556 private List
<HandleAction
> handleSignalServiceDataMessage(
1557 SignalServiceDataMessage message
,
1559 SignalServiceAddress source
,
1560 SignalServiceAddress destination
,
1561 boolean ignoreAttachments
1563 List
<HandleAction
> actions
= new ArrayList
<>();
1564 if (message
.getGroupContext().isPresent()) {
1565 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1566 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1567 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1568 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1569 if (group
== null || group
instanceof GroupInfoV1
) {
1570 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1571 switch (groupInfo
.getType()) {
1573 if (groupV1
== null) {
1574 groupV1
= new GroupInfoV1(groupId
);
1577 if (groupInfo
.getAvatar().isPresent()) {
1578 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1579 if (avatar
.isPointer()) {
1581 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1582 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1583 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1590 if (groupInfo
.getName().isPresent()) {
1591 groupV1
.name
= groupInfo
.getName().get();
1594 if (groupInfo
.getMembers().isPresent()) {
1595 groupV1
.addMembers(groupInfo
.getMembers()
1598 .map(this::resolveSignalServiceAddress
)
1599 .collect(Collectors
.toSet()));
1602 account
.getGroupStore().updateGroup(groupV1
);
1606 if (groupV1
== null && !isSync
) {
1607 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1611 if (groupV1
!= null) {
1612 groupV1
.removeMember(source
);
1613 account
.getGroupStore().updateGroup(groupV1
);
1618 if (groupV1
!= null && !isSync
) {
1619 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1624 // Received a group v1 message for a v2 group
1627 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1628 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1629 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1631 getOrMigrateGroup(groupMasterKey
,
1632 groupContext
.getRevision(),
1633 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1637 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1638 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1639 handleEndSession(conversationPartnerAddress
);
1641 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1642 if (message
.getGroupContext().isPresent()) {
1643 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1644 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1645 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1646 if (group
!= null) {
1647 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1648 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1649 account
.getGroupStore().updateGroup(group
);
1652 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1653 // disappearing message timer already stored in the DecryptedGroup
1655 } else if (conversationPartnerAddress
!= null) {
1656 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1657 if (contact
== null) {
1658 contact
= new ContactInfo(conversationPartnerAddress
);
1660 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1661 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1662 account
.getContactStore().updateContact(contact
);
1666 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1667 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1668 if (attachment
.isPointer()) {
1670 retrieveAttachment(attachment
.asPointer());
1671 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1672 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1673 attachment
.asPointer().getRemoteId(),
1679 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1680 final ProfileKey profileKey
;
1682 profileKey
= new ProfileKey(message
.getProfileKey().get());
1683 } catch (InvalidInputException e
) {
1684 throw new AssertionError(e
);
1686 if (source
.matches(account
.getSelfAddress())) {
1687 this.account
.setProfileKey(profileKey
);
1689 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1691 if (message
.getPreviews().isPresent()) {
1692 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1693 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1694 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1695 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1697 retrieveAttachment(attachment
);
1698 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1699 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1700 attachment
.getRemoteId(),
1706 if (message
.getQuote().isPresent()) {
1707 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1709 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1710 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1711 if (attachment
!= null && attachment
.isPointer()) {
1713 retrieveAttachment(attachment
.asPointer());
1714 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1715 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1716 attachment
.asPointer().getRemoteId(),
1722 if (message
.getSticker().isPresent()) {
1723 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1724 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1725 if (sticker
== null) {
1726 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1727 account
.getStickerStore().updateSticker(sticker
);
1733 private GroupInfoV2
getOrMigrateGroup(
1734 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1736 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1738 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1739 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1740 final GroupInfoV2 groupInfoV2
;
1741 if (groupInfo
instanceof GroupInfoV1
) {
1742 // Received a v2 group message for a v1 group, we need to locally migrate the group
1743 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1744 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1745 logger
.info("Locally migrated group {} to group v2, id: {}",
1746 groupInfo
.getGroupId().toBase64(),
1747 groupInfoV2
.getGroupId().toBase64());
1748 } else if (groupInfo
instanceof GroupInfoV2
) {
1749 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1751 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1754 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1755 DecryptedGroup group
= null;
1756 if (signedGroupChange
!= null
1757 && groupInfoV2
.getGroup() != null
1758 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1759 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1761 if (group
== null) {
1762 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1764 if (group
!= null) {
1765 storeProfileKeysFromMembers(group
);
1766 final String avatar
= group
.getAvatar();
1767 if (avatar
!= null && !avatar
.isEmpty()) {
1769 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1770 } catch (IOException e
) {
1771 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1775 groupInfoV2
.setGroup(group
);
1776 account
.getGroupStore().updateGroup(groupInfoV2
);
1782 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1783 for (DecryptedMember member
: group
.getMembersList()) {
1784 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1785 member
.getUuid().toByteArray()), null));
1787 account
.getProfileStore()
1788 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1789 } catch (InvalidInputException ignored
) {
1794 private void retryFailedReceivedMessages(
1795 ReceiveMessageHandler handler
, boolean ignoreAttachments
1797 final File cachePath
= getMessageCachePath();
1798 if (!cachePath
.exists()) {
1801 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1802 if (!dir
.isDirectory()) {
1803 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1807 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1808 if (!fileEntry
.isFile()) {
1811 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1813 // Try to delete directory if empty
1818 private void retryFailedReceivedMessage(
1819 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1821 SignalServiceEnvelope envelope
;
1823 envelope
= Utils
.loadEnvelope(fileEntry
);
1824 if (envelope
== null) {
1827 } catch (IOException e
) {
1828 e
.printStackTrace();
1831 SignalServiceContent content
= null;
1832 if (!envelope
.isReceipt()) {
1834 content
= decryptMessage(envelope
);
1835 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1837 } catch (Exception er
) {
1838 // All other errors are not recoverable, so delete the cached message
1840 Files
.delete(fileEntry
.toPath());
1841 } catch (IOException e
) {
1842 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1846 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1847 for (HandleAction action
: actions
) {
1849 action
.execute(this);
1850 } catch (Throwable e
) {
1851 e
.printStackTrace();
1856 handler
.handleMessage(envelope
, content
, null);
1858 Files
.delete(fileEntry
.toPath());
1859 } catch (IOException e
) {
1860 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1864 public void receiveMessages(
1867 boolean returnOnTimeout
,
1868 boolean ignoreAttachments
,
1869 ReceiveMessageHandler handler
1870 ) throws IOException
{
1871 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1873 Set
<HandleAction
> queuedActions
= null;
1875 getOrCreateMessagePipe();
1877 boolean hasCaughtUpWithOldMessages
= false;
1880 SignalServiceEnvelope envelope
;
1881 SignalServiceContent content
= null;
1882 Exception exception
= null;
1883 final long now
= new Date().getTime();
1885 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1886 // store message on disk, before acknowledging receipt to the server
1888 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1889 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1890 Utils
.storeEnvelope(envelope1
, cacheFile
);
1891 } catch (IOException e
) {
1892 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1895 if (result
.isPresent()) {
1896 envelope
= result
.get();
1898 // Received indicator that server queue is empty
1899 hasCaughtUpWithOldMessages
= true;
1901 if (queuedActions
!= null) {
1902 for (HandleAction action
: queuedActions
) {
1904 action
.execute(this);
1905 } catch (Throwable e
) {
1906 e
.printStackTrace();
1910 queuedActions
.clear();
1911 queuedActions
= null;
1914 // Continue to wait another timeout for new messages
1917 } catch (TimeoutException e
) {
1918 if (returnOnTimeout
) return;
1920 } catch (InvalidVersionException e
) {
1921 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1925 if (envelope
.hasSource()) {
1926 // Store uuid if we don't have it already
1927 SignalServiceAddress source
= envelope
.getSourceAddress();
1928 resolveSignalServiceAddress(source
);
1930 if (!envelope
.isReceipt()) {
1932 content
= decryptMessage(envelope
);
1933 } catch (Exception e
) {
1936 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1937 if (hasCaughtUpWithOldMessages
) {
1938 for (HandleAction action
: actions
) {
1940 action
.execute(this);
1941 } catch (Throwable e
) {
1942 e
.printStackTrace();
1946 if (queuedActions
== null) {
1947 queuedActions
= new HashSet
<>();
1949 queuedActions
.addAll(actions
);
1953 if (!isMessageBlocked(envelope
, content
)) {
1954 handler
.handleMessage(envelope
, content
, exception
);
1956 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1957 File cacheFile
= null;
1959 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1960 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1961 Files
.delete(cacheFile
.toPath());
1962 // Try to delete directory if empty
1963 getMessageCachePath().delete();
1964 } catch (IOException e
) {
1965 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
1971 private boolean isMessageBlocked(
1972 SignalServiceEnvelope envelope
, SignalServiceContent content
1974 SignalServiceAddress source
;
1975 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1976 source
= envelope
.getSourceAddress();
1977 } else if (content
!= null) {
1978 source
= content
.getSender();
1982 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1983 if (sourceContact
!= null && sourceContact
.blocked
) {
1987 if (content
!= null && content
.getDataMessage().isPresent()) {
1988 SignalServiceDataMessage message
= content
.getDataMessage().get();
1989 if (message
.getGroupContext().isPresent()) {
1990 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1991 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1992 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
1996 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
1997 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1998 if (group
!= null && group
.isBlocked()) {
2006 private List
<HandleAction
> handleMessage(
2007 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2009 List
<HandleAction
> actions
= new ArrayList
<>();
2010 if (content
!= null) {
2011 final SignalServiceAddress sender
;
2012 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2013 sender
= envelope
.getSourceAddress();
2015 sender
= content
.getSender();
2017 // Store uuid if we don't have it already
2018 resolveSignalServiceAddress(sender
);
2020 if (content
.getDataMessage().isPresent()) {
2021 SignalServiceDataMessage message
= content
.getDataMessage().get();
2023 if (content
.isNeedsReceipt()) {
2024 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2027 actions
.addAll(handleSignalServiceDataMessage(message
,
2030 account
.getSelfAddress(),
2031 ignoreAttachments
));
2033 if (content
.getSyncMessage().isPresent()) {
2034 account
.setMultiDevice(true);
2035 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2036 if (syncMessage
.getSent().isPresent()) {
2037 SentTranscriptMessage message
= syncMessage
.getSent().get();
2038 final SignalServiceAddress destination
= message
.getDestination().orNull();
2039 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2043 ignoreAttachments
));
2045 if (syncMessage
.getRequest().isPresent()) {
2046 RequestMessage rm
= syncMessage
.getRequest().get();
2047 if (rm
.isContactsRequest()) {
2048 actions
.add(SendSyncContactsAction
.create());
2050 if (rm
.isGroupsRequest()) {
2051 actions
.add(SendSyncGroupsAction
.create());
2053 if (rm
.isBlockedListRequest()) {
2054 actions
.add(SendSyncBlockedListAction
.create());
2056 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2058 if (syncMessage
.getGroups().isPresent()) {
2059 File tmpFile
= null;
2061 tmpFile
= IOUtils
.createTempFile();
2062 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2064 .asPointer(), tmpFile
)) {
2065 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2067 while ((g
= s
.read()) != null) {
2068 GroupInfoV1 syncGroup
= account
.getGroupStore()
2069 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2070 if (syncGroup
!= null) {
2071 if (g
.getName().isPresent()) {
2072 syncGroup
.name
= g
.getName().get();
2074 syncGroup
.addMembers(g
.getMembers()
2076 .map(this::resolveSignalServiceAddress
)
2077 .collect(Collectors
.toSet()));
2078 if (!g
.isActive()) {
2079 syncGroup
.removeMember(account
.getSelfAddress());
2081 // Add ourself to the member set as it's marked as active
2082 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2084 syncGroup
.blocked
= g
.isBlocked();
2085 if (g
.getColor().isPresent()) {
2086 syncGroup
.color
= g
.getColor().get();
2089 if (g
.getAvatar().isPresent()) {
2090 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2092 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2093 syncGroup
.archived
= g
.isArchived();
2094 account
.getGroupStore().updateGroup(syncGroup
);
2098 } catch (Exception e
) {
2099 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2102 e
.printStackTrace();
2104 if (tmpFile
!= null) {
2106 Files
.delete(tmpFile
.toPath());
2107 } catch (IOException e
) {
2108 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2115 if (syncMessage
.getBlockedList().isPresent()) {
2116 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2117 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2118 setContactBlocked(resolveSignalServiceAddress(address
), true);
2120 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2122 .map(GroupId
::unknownVersion
)
2123 .collect(Collectors
.toSet())) {
2125 setGroupBlocked(groupId
, true);
2126 } catch (GroupNotFoundException e
) {
2127 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2128 groupId
.toBase64());
2132 if (syncMessage
.getContacts().isPresent()) {
2133 File tmpFile
= null;
2135 tmpFile
= IOUtils
.createTempFile();
2136 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2137 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2138 .asPointer(), tmpFile
)) {
2139 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2140 if (contactsMessage
.isComplete()) {
2141 account
.getContactStore().clear();
2144 while ((c
= s
.read()) != null) {
2145 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2146 account
.setProfileKey(c
.getProfileKey().get());
2148 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2149 ContactInfo contact
= account
.getContactStore().getContact(address
);
2150 if (contact
== null) {
2151 contact
= new ContactInfo(address
);
2153 if (c
.getName().isPresent()) {
2154 contact
.name
= c
.getName().get();
2156 if (c
.getColor().isPresent()) {
2157 contact
.color
= c
.getColor().get();
2159 if (c
.getProfileKey().isPresent()) {
2160 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2162 if (c
.getVerified().isPresent()) {
2163 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2164 account
.getSignalProtocolStore()
2165 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2166 verifiedMessage
.getIdentityKey(),
2167 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2169 if (c
.getExpirationTimer().isPresent()) {
2170 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2172 contact
.blocked
= c
.isBlocked();
2173 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2174 contact
.archived
= c
.isArchived();
2175 account
.getContactStore().updateContact(contact
);
2177 if (c
.getAvatar().isPresent()) {
2178 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2182 } catch (Exception e
) {
2183 e
.printStackTrace();
2185 if (tmpFile
!= null) {
2187 Files
.delete(tmpFile
.toPath());
2188 } catch (IOException e
) {
2189 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2196 if (syncMessage
.getVerified().isPresent()) {
2197 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2198 account
.getSignalProtocolStore()
2199 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2200 verifiedMessage
.getIdentityKey(),
2201 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2203 if (syncMessage
.getStickerPackOperations().isPresent()) {
2204 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2206 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2207 if (!m
.getPackId().isPresent()) {
2210 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2211 if (sticker
== null) {
2212 if (!m
.getPackKey().isPresent()) {
2215 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2217 sticker
.setInstalled(!m
.getType().isPresent()
2218 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2219 account
.getStickerStore().updateSticker(sticker
);
2222 if (syncMessage
.getConfiguration().isPresent()) {
2230 private File
getContactAvatarFile(String number
) {
2231 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2234 private File
retrieveContactAvatarAttachment(
2235 SignalServiceAttachment attachment
, String number
2236 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2237 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2238 if (attachment
.isPointer()) {
2239 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2240 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2242 SignalServiceAttachmentStream stream
= attachment
.asStream();
2243 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2247 private File
getGroupAvatarFile(GroupId groupId
) {
2248 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2251 private File
retrieveGroupAvatarAttachment(
2252 SignalServiceAttachment attachment
, GroupId groupId
2253 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2254 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2255 if (attachment
.isPointer()) {
2256 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2257 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2259 SignalServiceAttachmentStream stream
= attachment
.asStream();
2260 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2264 private File
retrieveGroupAvatar(
2265 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2266 ) throws IOException
{
2267 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2268 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2269 File outputFile
= getGroupAvatarFile(groupId
);
2270 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2272 File tmpFile
= IOUtils
.createTempFile();
2273 tmpFile
.deleteOnExit();
2274 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2276 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2277 byte[] encryptedData
= IOUtils
.readFully(input
);
2279 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2280 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2281 output
.write(decryptedData
);
2285 Files
.delete(tmpFile
.toPath());
2286 } catch (IOException e
) {
2287 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2295 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2296 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2299 private File
retrieveProfileAvatar(
2300 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2301 ) throws IOException
{
2302 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2303 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2304 File outputFile
= getProfileAvatarFile(address
);
2306 File tmpFile
= IOUtils
.createTempFile();
2307 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2310 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2311 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2312 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2315 Files
.delete(tmpFile
.toPath());
2316 } catch (IOException e
) {
2317 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2325 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2326 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2329 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2330 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2331 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2334 private File
retrieveAttachment(
2335 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2336 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2337 if (storePreview
&& pointer
.getPreview().isPresent()) {
2338 File previewFile
= new File(outputFile
+ ".preview");
2339 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2340 byte[] preview
= pointer
.getPreview().get();
2341 output
.write(preview
, 0, preview
.length
);
2342 } catch (FileNotFoundException e
) {
2343 e
.printStackTrace();
2348 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2350 File tmpFile
= IOUtils
.createTempFile();
2351 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2353 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2354 IOUtils
.copyStreamToFile(input
, outputFile
);
2357 Files
.delete(tmpFile
.toPath());
2358 } catch (IOException e
) {
2359 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2367 private InputStream
retrieveAttachmentAsStream(
2368 SignalServiceAttachmentPointer pointer
, File tmpFile
2369 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2370 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2371 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2374 void sendGroups() throws IOException
, UntrustedIdentityException
{
2375 File groupsFile
= IOUtils
.createTempFile();
2378 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2379 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2380 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2381 if (record instanceof GroupInfoV1
) {
2382 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2383 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2384 Optional
.fromNullable(groupInfo
.name
),
2385 new ArrayList
<>(groupInfo
.getMembers()),
2386 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2387 groupInfo
.isMember(account
.getSelfAddress()),
2388 Optional
.of(groupInfo
.messageExpirationTime
),
2389 Optional
.fromNullable(groupInfo
.color
),
2391 Optional
.fromNullable(groupInfo
.inboxPosition
),
2392 groupInfo
.archived
));
2397 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2398 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2399 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2400 .withStream(groupsFileStream
)
2401 .withContentType("application/octet-stream")
2402 .withLength(groupsFile
.length())
2405 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2410 Files
.delete(groupsFile
.toPath());
2411 } catch (IOException e
) {
2412 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2417 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2418 File contactsFile
= IOUtils
.createTempFile();
2421 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2422 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2423 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2424 VerifiedMessage verifiedMessage
= null;
2425 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2426 if (currentIdentity
!= null) {
2427 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2428 currentIdentity
.getIdentityKey(),
2429 currentIdentity
.getTrustLevel().toVerifiedState(),
2430 currentIdentity
.getDateAdded().getTime());
2433 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2434 out
.write(new DeviceContact(record.getAddress(),
2435 Optional
.fromNullable(record.name
),
2436 createContactAvatarAttachment(record.number
),
2437 Optional
.fromNullable(record.color
),
2438 Optional
.fromNullable(verifiedMessage
),
2439 Optional
.fromNullable(profileKey
),
2441 Optional
.of(record.messageExpirationTime
),
2442 Optional
.fromNullable(record.inboxPosition
),
2446 if (account
.getProfileKey() != null) {
2447 // Send our own profile key as well
2448 out
.write(new DeviceContact(account
.getSelfAddress(),
2453 Optional
.of(account
.getProfileKey()),
2461 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2462 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2463 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2464 .withStream(contactsFileStream
)
2465 .withContentType("application/octet-stream")
2466 .withLength(contactsFile
.length())
2469 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2474 Files
.delete(contactsFile
.toPath());
2475 } catch (IOException e
) {
2476 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2481 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2482 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2483 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2484 if (record.blocked
) {
2485 addresses
.add(record.getAddress());
2488 List
<byte[]> groupIds
= new ArrayList
<>();
2489 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2490 if (record.isBlocked()) {
2491 groupIds
.add(record.getGroupId().serialize());
2494 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2497 private void sendVerifiedMessage(
2498 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2499 ) throws IOException
, UntrustedIdentityException
{
2500 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2502 trustLevel
.toVerifiedState(),
2503 System
.currentTimeMillis());
2504 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2507 public List
<ContactInfo
> getContacts() {
2508 return account
.getContactStore().getContacts();
2511 public ContactInfo
getContact(String number
) {
2512 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2515 public GroupInfo
getGroup(GroupId groupId
) {
2516 return account
.getGroupStore().getGroup(groupId
);
2519 public List
<IdentityInfo
> getIdentities() {
2520 return account
.getSignalProtocolStore().getIdentities();
2523 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2524 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2528 * Trust this the identity with this fingerprint
2530 * @param name username of the identity
2531 * @param fingerprint Fingerprint
2533 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2534 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2535 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2539 for (IdentityInfo id
: ids
) {
2540 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2544 account
.getSignalProtocolStore()
2545 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2547 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2548 } catch (IOException
| UntrustedIdentityException e
) {
2549 e
.printStackTrace();
2558 * Trust this the identity with this safety number
2560 * @param name username of the identity
2561 * @param safetyNumber Safety number
2563 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2564 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2565 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2569 for (IdentityInfo id
: ids
) {
2570 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2574 account
.getSignalProtocolStore()
2575 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2577 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2578 } catch (IOException
| UntrustedIdentityException e
) {
2579 e
.printStackTrace();
2588 * Trust all keys of this identity without verification
2590 * @param name username of the identity
2592 public boolean trustIdentityAllKeys(String name
) {
2593 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2594 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2598 for (IdentityInfo id
: ids
) {
2599 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2600 account
.getSignalProtocolStore()
2601 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2603 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2604 } catch (IOException
| UntrustedIdentityException e
) {
2605 e
.printStackTrace();
2613 public String
computeSafetyNumber(
2614 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2616 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2617 getIdentityKeyPair().getPublicKey(),
2622 void saveAccount() {
2626 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2627 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2629 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2630 return resolveSignalServiceAddress(canonicalizedNumber
);
2633 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2634 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2636 return resolveSignalServiceAddress(address
);
2639 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2640 if (address
.matches(account
.getSelfAddress())) {
2641 return account
.getSelfAddress();
2644 return account
.getRecipientStore().resolveServiceAddress(address
);
2648 public void close() throws IOException
{
2649 if (messagePipe
!= null) {
2650 messagePipe
.shutdown();
2654 if (unidentifiedMessagePipe
!= null) {
2655 unidentifiedMessagePipe
.shutdown();
2656 unidentifiedMessagePipe
= null;
2662 public interface ReceiveMessageHandler
{
2664 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);