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
.helper
.GroupHelper
;
22 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
23 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
24 import org
.asamk
.signal
.storage
.SignalAccount
;
25 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
26 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
27 import org
.asamk
.signal
.storage
.groups
.GroupInfoV1
;
28 import org
.asamk
.signal
.storage
.groups
.GroupInfoV2
;
29 import org
.asamk
.signal
.storage
.profiles
.SignalProfile
;
30 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
31 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
32 import org
.asamk
.signal
.storage
.stickers
.Sticker
;
33 import org
.asamk
.signal
.util
.IOUtils
;
34 import org
.asamk
.signal
.util
.Util
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
45 import org
.signal
.libsignal
.metadata
.SelfSendException
;
46 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
47 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
48 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
49 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
50 import org
.signal
.zkgroup
.InvalidInputException
;
51 import org
.signal
.zkgroup
.VerificationFailedException
;
52 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
53 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
54 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
55 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
56 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
57 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
58 import org
.whispersystems
.libsignal
.IdentityKey
;
59 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
60 import org
.whispersystems
.libsignal
.InvalidKeyException
;
61 import org
.whispersystems
.libsignal
.InvalidMessageException
;
62 import org
.whispersystems
.libsignal
.InvalidVersionException
;
63 import org
.whispersystems
.libsignal
.ecc
.Curve
;
64 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
65 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
66 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
67 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
68 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
69 import org
.whispersystems
.libsignal
.util
.Medium
;
70 import org
.whispersystems
.libsignal
.util
.Pair
;
71 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
72 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
73 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
74 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
75 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
77 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
79 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
80 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
84 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
85 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
98 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
113 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
114 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
115 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
116 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
117 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
118 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
119 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
120 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
121 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
122 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
123 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
124 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
125 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
126 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
127 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
128 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
129 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
130 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
131 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
132 import org
.whispersystems
.util
.Base64
;
134 import java
.io
.Closeable
;
136 import java
.io
.FileInputStream
;
137 import java
.io
.FileNotFoundException
;
138 import java
.io
.FileOutputStream
;
139 import java
.io
.IOException
;
140 import java
.io
.InputStream
;
141 import java
.io
.OutputStream
;
143 import java
.net
.URISyntaxException
;
144 import java
.net
.URLEncoder
;
145 import java
.nio
.charset
.StandardCharsets
;
146 import java
.nio
.file
.Files
;
147 import java
.nio
.file
.Paths
;
148 import java
.nio
.file
.StandardCopyOption
;
149 import java
.security
.SignatureException
;
150 import java
.util
.ArrayList
;
151 import java
.util
.Arrays
;
152 import java
.util
.Collection
;
153 import java
.util
.Collections
;
154 import java
.util
.Date
;
155 import java
.util
.HashMap
;
156 import java
.util
.HashSet
;
157 import java
.util
.List
;
158 import java
.util
.Locale
;
159 import java
.util
.Map
;
160 import java
.util
.Objects
;
161 import java
.util
.Set
;
162 import java
.util
.UUID
;
163 import java
.util
.concurrent
.ExecutorService
;
164 import java
.util
.concurrent
.TimeUnit
;
165 import java
.util
.concurrent
.TimeoutException
;
166 import java
.util
.stream
.Collectors
;
167 import java
.util
.zip
.ZipEntry
;
168 import java
.util
.zip
.ZipFile
;
170 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
171 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
172 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
174 public class Manager
implements Closeable
{
176 private final SleepTimer timer
= new UptimeSleepTimer();
178 private final SignalServiceConfiguration serviceConfiguration
;
179 private final String userAgent
;
180 private final boolean discoverableByPhoneNumber
= true;
181 private final boolean unrestrictedUnidentifiedAccess
= false;
183 private final SignalAccount account
;
184 private final PathConfig pathConfig
;
185 private SignalServiceAccountManager accountManager
;
186 private GroupsV2Api groupsV2Api
;
187 private final GroupsV2Operations groupsV2Operations
;
189 private SignalServiceMessageReceiver messageReceiver
= null;
190 private SignalServiceMessagePipe messagePipe
= null;
191 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
193 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
194 private final ProfileHelper profileHelper
;
195 private final GroupHelper groupHelper
;
198 SignalAccount account
,
199 PathConfig pathConfig
,
200 SignalServiceConfiguration serviceConfiguration
,
203 this.account
= account
;
204 this.pathConfig
= pathConfig
;
205 this.serviceConfiguration
= serviceConfiguration
;
206 this.userAgent
= userAgent
;
207 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
208 serviceConfiguration
)) : null;
209 this.accountManager
= createSignalServiceAccountManager();
210 this.groupsV2Api
= accountManager
.getGroupsV2Api();
212 this.account
.setResolver(this::resolveSignalServiceAddress
);
214 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
215 account
.getProfileStore()::getProfileKey
,
216 this::getRecipientProfile
,
217 this::getSenderCertificate
);
218 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
219 unidentifiedAccessHelper
::getAccessFor
,
220 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
221 this::getOrCreateMessageReceiver
);
222 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
223 this::getRecipientProfile
,
224 account
::getSelfAddress
,
227 this::getGroupAuthForToday
);
230 public String
getUsername() {
231 return account
.getUsername();
234 public SignalServiceAddress
getSelfAddress() {
235 return account
.getSelfAddress();
238 private SignalServiceAccountManager
createSignalServiceAccountManager() {
239 return new SignalServiceAccountManager(serviceConfiguration
,
240 new DynamicCredentialsProvider(account
.getUuid(),
241 account
.getUsername(),
242 account
.getPassword(),
244 account
.getDeviceId()),
250 private IdentityKeyPair
getIdentityKeyPair() {
251 return account
.getSignalProtocolStore().getIdentityKeyPair();
254 public int getDeviceId() {
255 return account
.getDeviceId();
258 private String
getMessageCachePath() {
259 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
262 private String
getMessageCachePath(String sender
) {
263 if (sender
== null || sender
.isEmpty()) {
264 return getMessageCachePath();
267 return getMessageCachePath() + "/" + sender
.replace("/", "_");
270 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
271 String cachePath
= getMessageCachePath(sender
);
272 IOUtils
.createPrivateDirectories(cachePath
);
273 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
276 public static Manager
init(
277 String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
278 ) throws IOException
{
279 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
281 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
282 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
283 int registrationId
= KeyHelper
.generateRegistrationId(false);
285 ProfileKey profileKey
= KeyUtils
.createProfileKey();
286 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
293 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
296 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
298 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
300 m
.migrateLegacyConfigs();
305 private void migrateLegacyConfigs() {
306 if (account
.getProfileKey() == null && isRegistered()) {
307 // Old config file, creating new profile key
308 account
.setProfileKey(KeyUtils
.createProfileKey());
311 // Store profile keys only in profile store
312 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
313 String profileKeyString
= contact
.profileKey
;
314 if (profileKeyString
== null) {
317 final ProfileKey profileKey
;
319 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
320 } catch (InvalidInputException
| IOException e
) {
323 contact
.profileKey
= null;
324 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
326 // Ensure our profile key is stored in profile store
327 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
330 public void checkAccountState() throws IOException
{
331 if (account
.isRegistered()) {
332 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
336 if (account
.getUuid() == null) {
337 account
.setUuid(accountManager
.getOwnUuid());
340 updateAccountAttributes();
344 public boolean isRegistered() {
345 return account
.isRegistered();
348 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
349 account
.setPassword(KeyUtils
.createPassword());
351 // Resetting UUID, because registering doesn't work otherwise
352 account
.setUuid(null);
353 accountManager
= createSignalServiceAccountManager();
354 this.groupsV2Api
= accountManager
.getGroupsV2Api();
356 if (voiceVerification
) {
357 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
358 Optional
.fromNullable(captcha
),
361 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
364 account
.setRegistered(false);
368 public void updateAccountAttributes() throws IOException
{
369 accountManager
.setAccountAttributes(account
.getSignalingKey(),
370 account
.getSignalProtocolStore().getLocalRegistrationId(),
372 account
.getRegistrationLockPin(),
373 account
.getRegistrationLock(),
374 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
375 unrestrictedUnidentifiedAccess
,
377 discoverableByPhoneNumber
);
380 public void setProfile(String name
, File avatar
) throws IOException
{
381 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
382 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
386 public void unregister() throws IOException
{
387 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
388 // If this is the master device, other users can't send messages to this number anymore.
389 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
390 accountManager
.setGcmId(Optional
.absent());
392 account
.setRegistered(false);
396 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
397 List
<DeviceInfo
> devices
= accountManager
.getDevices();
398 account
.setMultiDevice(devices
.size() > 1);
403 public void removeLinkedDevices(int deviceId
) throws IOException
{
404 accountManager
.removeDevice(deviceId
);
405 List
<DeviceInfo
> devices
= accountManager
.getDevices();
406 account
.setMultiDevice(devices
.size() > 1);
410 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
411 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
413 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
416 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
417 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
418 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
420 accountManager
.addDevice(deviceIdentifier
,
423 Optional
.of(account
.getProfileKey().serialize()),
425 account
.setMultiDevice(true);
429 private List
<PreKeyRecord
> generatePreKeys() {
430 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
432 final int offset
= account
.getPreKeyIdOffset();
433 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
434 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
435 ECKeyPair keyPair
= Curve
.generateKeyPair();
436 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
441 account
.addPreKeys(records
);
447 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
449 ECKeyPair keyPair
= Curve
.generateKeyPair();
450 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
451 keyPair
.getPublicKey().serialize());
452 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
453 System
.currentTimeMillis(),
457 account
.addSignedPreKey(record);
461 } catch (InvalidKeyException e
) {
462 throw new AssertionError(e
);
466 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
467 verificationCode
= verificationCode
.replace("-", "");
468 account
.setSignalingKey(KeyUtils
.createSignalingKey());
469 // TODO make unrestricted unidentified access configurable
470 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
471 account
.getSignalingKey(),
472 account
.getSignalProtocolStore().getLocalRegistrationId(),
476 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
477 unrestrictedUnidentifiedAccess
,
479 discoverableByPhoneNumber
);
481 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
482 // TODO response.isStorageCapable()
483 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
484 account
.setRegistered(true);
485 account
.setUuid(uuid
);
486 account
.setRegistrationLockPin(pin
);
487 account
.getSignalProtocolStore()
488 .saveIdentity(account
.getSelfAddress(),
489 getIdentityKeyPair().getPublicKey(),
490 TrustLevel
.TRUSTED_VERIFIED
);
496 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
497 if (pin
.isPresent()) {
498 account
.setRegistrationLockPin(pin
.get());
499 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
501 account
.setRegistrationLockPin(null);
502 accountManager
.removeRegistrationLockV1();
507 void refreshPreKeys() throws IOException
{
508 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
509 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
510 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
512 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
515 private SignalServiceMessageReceiver
createMessageReceiver() {
516 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
517 serviceConfiguration
).getProfileOperations() : null;
518 return new SignalServiceMessageReceiver(serviceConfiguration
,
520 account
.getUsername(),
521 account
.getPassword(),
522 account
.getDeviceId(),
523 account
.getSignalingKey(),
527 clientZkProfileOperations
);
530 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
531 if (messageReceiver
== null) {
532 messageReceiver
= createMessageReceiver();
534 return messageReceiver
;
537 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
538 if (messagePipe
== null) {
539 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
544 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
545 if (unidentifiedMessagePipe
== null) {
546 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
548 return unidentifiedMessagePipe
;
551 private SignalServiceMessageSender
createMessageSender() {
552 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
553 serviceConfiguration
).getProfileOperations() : null;
554 final ExecutorService executor
= null;
555 return new SignalServiceMessageSender(serviceConfiguration
,
557 account
.getUsername(),
558 account
.getPassword(),
559 account
.getDeviceId(),
560 account
.getSignalProtocolStore(),
562 account
.isMultiDevice(),
563 Optional
.fromNullable(messagePipe
),
564 Optional
.fromNullable(unidentifiedMessagePipe
),
566 clientZkProfileOperations
,
568 ServiceConfig
.MAX_ENVELOPE_SIZE
);
571 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
572 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
575 private SignalProfile
getRecipientProfile(
576 SignalServiceAddress address
578 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
579 if (profileEntry
== null) {
582 long now
= new Date().getTime();
583 // Profiles are cache for 24h before retrieving them again
584 if (!profileEntry
.isRequestPending() && (
585 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
587 ProfileKey profileKey
= profileEntry
.getProfileKey();
588 profileEntry
.setRequestPending(true);
589 SignalProfile profile
;
591 profile
= retrieveRecipientProfile(address
, profileKey
);
592 } catch (IOException e
) {
593 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
594 profileEntry
.setRequestPending(false);
597 profileEntry
.setRequestPending(false);
598 account
.getProfileStore()
599 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
602 return profileEntry
.getProfile();
605 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
606 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
607 if (profileEntry
== null) {
610 if (profileEntry
.getProfileKeyCredential() == null) {
611 ProfileAndCredential profileAndCredential
;
613 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
614 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
615 } catch (IOException e
) {
616 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
620 long now
= new Date().getTime();
621 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
622 final SignalProfile profile
= decryptProfile(address
,
623 profileEntry
.getProfileKey(),
624 profileAndCredential
.getProfile());
625 account
.getProfileStore()
626 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
627 return profileKeyCredential
;
629 return profileEntry
.getProfileKeyCredential();
632 private SignalProfile
retrieveRecipientProfile(
633 SignalServiceAddress address
, ProfileKey profileKey
634 ) throws IOException
{
635 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
637 return decryptProfile(address
, profileKey
, encryptedProfile
);
640 private SignalProfile
decryptProfile(
641 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
643 File avatarFile
= null;
645 avatarFile
= encryptedProfile
.getAvatar() == null
647 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
648 } catch (Throwable e
) {
649 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
652 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
656 name
= encryptedProfile
.getName() == null
658 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
659 } catch (IOException e
) {
662 String unidentifiedAccess
;
664 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
665 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
667 : encryptedProfile
.getUnidentifiedAccess();
668 } catch (IOException e
) {
669 unidentifiedAccess
= null;
671 return new SignalProfile(encryptedProfile
.getIdentityKey(),
675 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
676 encryptedProfile
.getCapabilities());
677 } catch (InvalidCiphertextException e
) {
682 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
683 File file
= getGroupAvatarFile(groupId
);
684 if (!file
.exists()) {
685 return Optional
.absent();
688 return Optional
.of(Utils
.createAttachment(file
));
691 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
692 File file
= getContactAvatarFile(number
);
693 if (!file
.exists()) {
694 return Optional
.absent();
697 return Optional
.of(Utils
.createAttachment(file
));
700 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
701 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
703 throw new GroupNotFoundException(groupId
);
705 if (!g
.isMember(account
.getSelfAddress())) {
706 throw new NotAGroupMemberException(groupId
, g
.getTitle());
711 private GroupInfo
getGroupForUpdating(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
712 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
714 throw new GroupNotFoundException(groupId
);
716 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
717 throw new NotAGroupMemberException(groupId
, g
.getTitle());
722 public List
<GroupInfo
> getGroups() {
723 return account
.getGroupStore().getGroups();
726 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
727 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
728 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
729 final GroupInfo g
= getGroupForSending(groupId
);
731 GroupUtils
.setGroupContext(messageBuilder
, g
);
732 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
734 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
737 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
738 String messageText
, List
<String
> attachments
, byte[] groupId
739 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
740 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
741 .withBody(messageText
);
742 if (attachments
!= null) {
743 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
746 return sendGroupMessage(messageBuilder
, groupId
);
749 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
750 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
751 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
752 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
754 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
755 targetSentTimestamp
);
756 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
757 .withReaction(reaction
);
759 return sendGroupMessage(messageBuilder
, groupId
);
762 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
764 SignalServiceDataMessage
.Builder messageBuilder
;
766 final GroupInfo g
= getGroupForUpdating(groupId
);
767 if (g
instanceof GroupInfoV1
) {
768 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
769 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
772 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
773 groupInfoV1
.removeMember(account
.getSelfAddress());
774 account
.getGroupStore().updateGroup(groupInfoV1
);
776 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
777 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
778 groupInfoV2
.setGroup(groupGroupChangePair
.first());
779 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
780 account
.getGroupStore().updateGroup(groupInfoV2
);
783 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
786 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
787 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
788 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
790 SignalServiceDataMessage
.Builder messageBuilder
;
791 if (groupId
== null) {
793 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
795 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
796 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
797 updateGroupV1(gv1
, name
, members
, avatarFile
);
798 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
801 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
805 GroupInfo group
= getGroupForUpdating(groupId
);
806 if (group
instanceof GroupInfoV2
) {
807 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
809 Pair
<Long
, List
<SendMessageResult
>> result
= null;
810 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
811 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
812 result
= sendUpdateGroupMessage(groupInfoV2
,
813 groupGroupChangePair
.first(),
814 groupGroupChangePair
.second());
817 if (members
!= null) {
818 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
819 newMembers
.removeAll(group
.getMembers());
820 if (newMembers
.size() > 0) {
821 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
823 result
= sendUpdateGroupMessage(groupInfoV2
,
824 groupGroupChangePair
.first(),
825 groupGroupChangePair
.second());
828 if (result
== null || name
!= null || avatarFile
!= null) {
829 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
832 result
= sendUpdateGroupMessage(groupInfoV2
,
833 groupGroupChangePair
.first(),
834 groupGroupChangePair
.second());
837 return new Pair
<>(group
.groupId
, result
.second());
839 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
840 updateGroupV1(gv1
, name
, members
, avatarFile
);
841 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
846 account
.getGroupStore().updateGroup(g
);
848 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
849 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
850 return new Pair
<>(g
.groupId
, result
.second());
853 public Pair
<byte[], List
<SendMessageResult
>> joinGroup(
854 GroupInviteLinkUrl inviteLinkUrl
855 ) throws IOException
, GroupLinkNotActiveException
{
856 return sendJoinGroupMessage(inviteLinkUrl
);
859 private Pair
<byte[], List
<SendMessageResult
>> sendJoinGroupMessage(
860 GroupInviteLinkUrl inviteLinkUrl
861 ) throws IOException
, GroupLinkNotActiveException
{
862 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
863 inviteLinkUrl
.getPassword());
864 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
865 inviteLinkUrl
.getPassword(),
867 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
868 groupJoinInfo
.getRevision() + 1,
869 groupChange
.toByteArray());
871 if (group
.getGroup() == null) {
872 // Only requested member, can't send update to group members
873 return new Pair
<>(group
.groupId
, List
.of());
876 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
878 return new Pair
<>(group
.groupId
, result
.second());
881 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
882 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
883 ) throws IOException
{
884 group
.setGroup(newDecryptedGroup
);
885 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
886 groupChange
.toByteArray());
887 account
.getGroupStore().updateGroup(group
);
888 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
891 private void updateGroupV1(
894 final Collection
<SignalServiceAddress
> members
,
895 final String avatarFile
896 ) throws IOException
{
901 if (members
!= null) {
902 final Set
<String
> newE164Members
= new HashSet
<>();
903 for (SignalServiceAddress member
: members
) {
904 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
907 newE164Members
.add(member
.getNumber().get());
910 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
911 if (contacts
.size() != newE164Members
.size()) {
912 // Some of the new members are not registered on Signal
913 for (ContactTokenDetails contact
: contacts
) {
914 newE164Members
.remove(contact
.getNumber());
916 throw new IOException("Failed to add members "
917 + Util
.join(", ", newE164Members
)
918 + " to group: Not registered on Signal");
921 g
.addMembers(members
);
924 if (avatarFile
!= null) {
925 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
926 File aFile
= getGroupAvatarFile(g
.groupId
);
927 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
931 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
932 byte[] groupId
, SignalServiceAddress recipient
933 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
935 GroupInfo group
= getGroupForSending(groupId
);
936 if (!(group
instanceof GroupInfoV1
)) {
937 throw new RuntimeException("Received an invalid group request for a v2 group!");
939 g
= (GroupInfoV1
) group
;
941 if (!g
.isMember(recipient
)) {
942 throw new NotAGroupMemberException(groupId
, g
.name
);
945 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
947 // Send group message only to the recipient who requested it
948 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
951 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
952 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
955 .withMembers(new ArrayList
<>(g
.getMembers()));
957 File aFile
= getGroupAvatarFile(g
.groupId
);
958 if (aFile
.exists()) {
960 group
.withAvatar(Utils
.createAttachment(aFile
));
961 } catch (IOException e
) {
962 throw new AttachmentInvalidException(aFile
.toString(), e
);
966 return SignalServiceDataMessage
.newBuilder()
967 .asGroupMessage(group
.build())
968 .withExpiration(g
.getMessageExpirationTime());
971 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
972 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
973 .withRevision(g
.getGroup().getRevision())
974 .withSignedGroupChange(signedGroupChange
);
975 return SignalServiceDataMessage
.newBuilder()
976 .asGroupMessage(group
.build())
977 .withExpiration(g
.getMessageExpirationTime());
980 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
981 byte[] groupId
, SignalServiceAddress recipient
982 ) throws IOException
{
983 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
986 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
987 .asGroupMessage(group
.build());
989 // Send group info request message to the recipient who sent us a message with this groupId
990 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
994 SignalServiceAddress remoteAddress
, long messageId
995 ) throws IOException
, UntrustedIdentityException
{
996 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
997 Collections
.singletonList(messageId
),
998 System
.currentTimeMillis());
1000 createMessageSender().sendReceipt(remoteAddress
,
1001 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1005 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1006 String messageText
, List
<String
> attachments
, List
<String
> recipients
1007 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1008 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1009 .withBody(messageText
);
1010 if (attachments
!= null) {
1011 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
1013 // Upload attachments here, so we only upload once even for multiple recipients
1014 SignalServiceMessageSender messageSender
= createMessageSender();
1015 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1016 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1017 if (attachment
.isStream()) {
1018 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1019 } else if (attachment
.isPointer()) {
1020 attachmentPointers
.add(attachment
.asPointer());
1024 messageBuilder
.withAttachments(attachmentPointers
);
1026 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1029 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1030 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1031 ) throws IOException
, InvalidNumberException
{
1032 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1034 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1035 targetSentTimestamp
);
1036 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1037 .withReaction(reaction
);
1038 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1041 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1042 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1044 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1046 return sendMessage(messageBuilder
, signalServiceAddresses
);
1047 } catch (Exception e
) {
1048 for (SignalServiceAddress address
: signalServiceAddresses
) {
1049 handleEndSession(address
);
1056 public String
getContactName(String number
) throws InvalidNumberException
{
1057 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1058 if (contact
== null) {
1061 return contact
.name
;
1065 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1066 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1067 ContactInfo contact
= account
.getContactStore().getContact(address
);
1068 if (contact
== null) {
1069 contact
= new ContactInfo(address
);
1071 contact
.name
= name
;
1072 account
.getContactStore().updateContact(contact
);
1076 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1077 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1080 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1081 ContactInfo contact
= account
.getContactStore().getContact(address
);
1082 if (contact
== null) {
1083 contact
= new ContactInfo(address
);
1085 contact
.blocked
= blocked
;
1086 account
.getContactStore().updateContact(contact
);
1090 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1091 GroupInfo group
= getGroup(groupId
);
1092 if (group
== null) {
1093 throw new GroupNotFoundException(groupId
);
1096 group
.setBlocked(blocked
);
1097 account
.getGroupStore().updateGroup(group
);
1101 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1102 byte[] groupId
, String name
, List
<String
> members
, String avatar
1103 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1104 return sendUpdateGroupMessage(groupId
,
1106 members
== null ?
null : getSignalServiceAddresses(members
),
1111 * Change the expiration timer for a contact
1113 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1114 ContactInfo contact
= account
.getContactStore().getContact(address
);
1115 contact
.messageExpirationTime
= messageExpirationTimer
;
1116 account
.getContactStore().updateContact(contact
);
1117 sendExpirationTimerUpdate(address
);
1121 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1122 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1123 .asExpirationUpdate();
1124 sendMessage(messageBuilder
, Collections
.singleton(address
));
1128 * Change the expiration timer for a contact
1130 public void setExpirationTimer(
1131 String number
, int messageExpirationTimer
1132 ) throws IOException
, InvalidNumberException
{
1133 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1134 setExpirationTimer(address
, messageExpirationTimer
);
1138 * Change the expiration timer for a group
1140 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1141 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1142 if (g
instanceof GroupInfoV1
) {
1143 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1144 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1145 account
.getGroupStore().updateGroup(groupInfoV1
);
1147 throw new RuntimeException("TODO Not implemented!");
1152 * Upload the sticker pack from path.
1154 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1155 * @return if successful, returns the URL to install the sticker pack in the signal app
1157 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1158 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1160 SignalServiceMessageSender messageSender
= createMessageSender();
1162 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1163 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1165 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1166 account
.getStickerStore().updateSticker(sticker
);
1170 return new URI("https",
1173 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1174 Hex
.toStringCondensed(packKey
),
1175 StandardCharsets
.UTF_8
)).toString();
1176 } catch (URISyntaxException e
) {
1177 throw new AssertionError(e
);
1181 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1183 ) throws IOException
, StickerPackInvalidException
{
1185 String rootPath
= null;
1187 final File file
= new File(path
);
1188 if (file
.getName().endsWith(".zip")) {
1189 zip
= new ZipFile(file
);
1190 } else if (file
.getName().equals("manifest.json")) {
1191 rootPath
= file
.getParent();
1193 throw new StickerPackInvalidException("Could not find manifest.json");
1196 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1198 if (pack
.stickers
== null) {
1199 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1202 if (pack
.stickers
.isEmpty()) {
1203 throw new StickerPackInvalidException("Must include stickers.");
1206 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1207 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1208 if (sticker
.file
== null) {
1209 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1212 Pair
<InputStream
, Long
> data
;
1214 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1215 } catch (IOException ignored
) {
1216 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1219 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1220 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1222 Optional
.fromNullable(sticker
.emoji
).or(""),
1224 stickers
.add(stickerInfo
);
1227 StickerInfo cover
= null;
1228 if (pack
.cover
!= null) {
1229 if (pack
.cover
.file
== null) {
1230 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1233 Pair
<InputStream
, Long
> data
;
1235 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1236 } catch (IOException ignored
) {
1237 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1240 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1241 cover
= new StickerInfo(data
.first(),
1243 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1247 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1250 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1251 InputStream inputStream
;
1253 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1255 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1257 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1260 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1261 final String rootPath
, final ZipFile zip
, final String subfile
1262 ) throws IOException
{
1264 final ZipEntry entry
= zip
.getEntry(subfile
);
1265 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1267 final File file
= new File(rootPath
, subfile
);
1268 return new Pair
<>(new FileInputStream(file
), file
.length());
1272 void requestSyncGroups() throws IOException
{
1273 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1274 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1276 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1278 sendSyncMessage(message
);
1279 } catch (UntrustedIdentityException e
) {
1280 e
.printStackTrace();
1284 void requestSyncContacts() throws IOException
{
1285 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1286 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1288 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1290 sendSyncMessage(message
);
1291 } catch (UntrustedIdentityException e
) {
1292 e
.printStackTrace();
1296 void requestSyncBlocked() throws IOException
{
1297 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1298 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1300 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1302 sendSyncMessage(message
);
1303 } catch (UntrustedIdentityException e
) {
1304 e
.printStackTrace();
1308 void requestSyncConfiguration() throws IOException
{
1309 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1310 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1312 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1314 sendSyncMessage(message
);
1315 } catch (UntrustedIdentityException e
) {
1316 e
.printStackTrace();
1320 private byte[] getSenderCertificate() {
1321 // TODO support UUID capable sender certificates
1322 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1325 certificate
= accountManager
.getSenderCertificate();
1326 } catch (IOException e
) {
1327 System
.err
.println("Failed to get sender certificate: " + e
);
1330 // TODO cache for a day
1334 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1335 SignalServiceMessageSender messageSender
= createMessageSender();
1337 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1338 } catch (UntrustedIdentityException e
) {
1339 account
.getSignalProtocolStore()
1340 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1342 TrustLevel
.UNTRUSTED
);
1347 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1348 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1349 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1351 for (String number
: numbers
) {
1352 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1353 if (resolvedAddress
.getUuid().isPresent()) {
1354 signalServiceAddresses
.add(resolvedAddress
);
1356 missingUuids
.add(resolvedAddress
);
1360 Map
<String
, UUID
> registeredUsers
;
1362 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1363 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1365 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1366 System
.err
.println("Failed to resolve uuids from server: " + e
.getMessage());
1367 registeredUsers
= new HashMap
<>();
1370 for (SignalServiceAddress address
: missingUuids
) {
1371 final String number
= address
.getNumber().get();
1372 if (registeredUsers
.containsKey(number
)) {
1373 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1374 registeredUsers
.get(number
),
1376 signalServiceAddresses
.add(newAddress
);
1378 signalServiceAddresses
.add(address
);
1382 return signalServiceAddresses
;
1385 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1386 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1387 ) throws IOException
{
1388 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1389 final long timestamp
= System
.currentTimeMillis();
1390 messageBuilder
.withTimestamp(timestamp
);
1391 getOrCreateMessagePipe();
1392 getOrCreateUnidentifiedMessagePipe();
1393 SignalServiceDataMessage message
= null;
1395 message
= messageBuilder
.build();
1396 if (message
.getGroupContext().isPresent()) {
1398 SignalServiceMessageSender messageSender
= createMessageSender();
1399 final boolean isRecipientUpdate
= false;
1400 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1401 unidentifiedAccessHelper
.getAccessFor(recipients
),
1404 for (SendMessageResult r
: result
) {
1405 if (r
.getIdentityFailure() != null) {
1406 account
.getSignalProtocolStore()
1407 .saveIdentity(r
.getAddress(),
1408 r
.getIdentityFailure().getIdentityKey(),
1409 TrustLevel
.UNTRUSTED
);
1412 return new Pair
<>(timestamp
, result
);
1413 } catch (UntrustedIdentityException e
) {
1414 account
.getSignalProtocolStore()
1415 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1417 TrustLevel
.UNTRUSTED
);
1418 return new Pair
<>(timestamp
, Collections
.emptyList());
1421 // Send to all individually, so sync messages are sent correctly
1422 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1423 for (SignalServiceAddress address
: recipients
) {
1424 ContactInfo contact
= account
.getContactStore().getContact(address
);
1425 if (contact
!= null) {
1426 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1427 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1429 messageBuilder
.withExpiration(0);
1430 messageBuilder
.withProfileKey(null);
1432 message
= messageBuilder
.build();
1433 if (address
.matches(account
.getSelfAddress())) {
1434 results
.add(sendSelfMessage(message
));
1436 results
.add(sendMessage(address
, message
));
1439 return new Pair
<>(timestamp
, results
);
1442 if (message
!= null && message
.isEndSession()) {
1443 for (SignalServiceAddress recipient
: recipients
) {
1444 handleEndSession(recipient
);
1451 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1452 SignalServiceMessageSender messageSender
= createMessageSender();
1454 SignalServiceAddress recipient
= account
.getSelfAddress();
1456 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1457 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1458 message
.getTimestamp(),
1460 message
.getExpiresInSeconds(),
1461 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1463 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1466 long startTime
= System
.currentTimeMillis();
1467 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1468 return SendMessageResult
.success(recipient
,
1469 unidentifiedAccess
.isPresent(),
1471 System
.currentTimeMillis() - startTime
);
1472 } catch (UntrustedIdentityException e
) {
1473 account
.getSignalProtocolStore()
1474 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1476 TrustLevel
.UNTRUSTED
);
1477 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1481 private SendMessageResult
sendMessage(
1482 SignalServiceAddress address
, SignalServiceDataMessage message
1483 ) throws IOException
{
1484 SignalServiceMessageSender messageSender
= createMessageSender();
1487 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1488 } catch (UntrustedIdentityException e
) {
1489 account
.getSignalProtocolStore()
1490 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1492 TrustLevel
.UNTRUSTED
);
1493 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1497 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1498 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1499 account
.getSignalProtocolStore(),
1500 Utils
.getCertificateValidator());
1502 return cipher
.decrypt(envelope
);
1503 } catch (ProtocolUntrustedIdentityException e
) {
1504 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1505 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1507 account
.getSignalProtocolStore()
1508 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1509 identityException
.getUntrustedIdentity(),
1510 TrustLevel
.UNTRUSTED
);
1511 throw identityException
;
1513 throw new AssertionError(e
);
1517 private void handleEndSession(SignalServiceAddress source
) {
1518 account
.getSignalProtocolStore().deleteAllSessions(source
);
1521 private static int currentTimeDays() {
1522 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1525 private GroupsV2AuthorizationString
getGroupAuthForToday(
1526 final GroupSecretParams groupSecretParams
1527 ) throws IOException
{
1528 final int today
= currentTimeDays();
1529 // Returns credentials for the next 7 days
1530 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1531 // TODO cache credentials until they expire
1532 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1534 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1537 authCredentialResponse
);
1538 } catch (VerificationFailedException e
) {
1539 throw new IOException(e
);
1543 private List
<HandleAction
> handleSignalServiceDataMessage(
1544 SignalServiceDataMessage message
,
1546 SignalServiceAddress source
,
1547 SignalServiceAddress destination
,
1548 boolean ignoreAttachments
1550 List
<HandleAction
> actions
= new ArrayList
<>();
1551 if (message
.getGroupContext().isPresent()) {
1552 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1553 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1554 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1555 if (group
== null || group
instanceof GroupInfoV1
) {
1556 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1557 switch (groupInfo
.getType()) {
1559 if (groupV1
== null) {
1560 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1563 if (groupInfo
.getAvatar().isPresent()) {
1564 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1565 if (avatar
.isPointer()) {
1567 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1568 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1569 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1570 .getRemoteId() + "): " + e
.getMessage());
1575 if (groupInfo
.getName().isPresent()) {
1576 groupV1
.name
= groupInfo
.getName().get();
1579 if (groupInfo
.getMembers().isPresent()) {
1580 groupV1
.addMembers(groupInfo
.getMembers()
1583 .map(this::resolveSignalServiceAddress
)
1584 .collect(Collectors
.toSet()));
1587 account
.getGroupStore().updateGroup(groupV1
);
1591 if (groupV1
== null && !isSync
) {
1592 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1596 if (groupV1
!= null) {
1597 groupV1
.removeMember(source
);
1598 account
.getGroupStore().updateGroup(groupV1
);
1603 if (groupV1
!= null && !isSync
) {
1604 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1609 // Received a group v1 message for a v2 group
1612 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1613 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1614 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1616 getOrMigrateGroup(groupMasterKey
,
1617 groupContext
.getRevision(),
1618 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1622 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1623 if (message
.isEndSession()) {
1624 handleEndSession(conversationPartnerAddress
);
1626 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1627 if (message
.getGroupContext().isPresent()) {
1628 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1629 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1630 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1631 if (group
!= null) {
1632 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1633 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1634 account
.getGroupStore().updateGroup(group
);
1637 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1638 // disappearing message timer already stored in the DecryptedGroup
1641 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1642 if (contact
== null) {
1643 contact
= new ContactInfo(conversationPartnerAddress
);
1645 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1646 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1647 account
.getContactStore().updateContact(contact
);
1651 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1652 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1653 if (attachment
.isPointer()) {
1655 retrieveAttachment(attachment
.asPointer());
1656 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1657 System
.err
.println("Failed to retrieve attachment ("
1658 + attachment
.asPointer().getRemoteId()
1665 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1666 final ProfileKey profileKey
;
1668 profileKey
= new ProfileKey(message
.getProfileKey().get());
1669 } catch (InvalidInputException e
) {
1670 throw new AssertionError(e
);
1672 if (source
.matches(account
.getSelfAddress())) {
1673 this.account
.setProfileKey(profileKey
);
1675 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1677 if (message
.getPreviews().isPresent()) {
1678 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1679 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1680 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1681 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1683 retrieveAttachment(attachment
);
1684 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1685 System
.err
.println("Failed to retrieve attachment ("
1686 + attachment
.getRemoteId()
1693 if (message
.getSticker().isPresent()) {
1694 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1695 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1696 if (sticker
== null) {
1697 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1698 account
.getStickerStore().updateSticker(sticker
);
1704 private GroupInfoV2
getOrMigrateGroup(
1705 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1707 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1709 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1710 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1711 final GroupInfoV2 groupInfoV2
;
1712 if (groupInfo
instanceof GroupInfoV1
) {
1713 // Received a v2 group message for a v1 group, we need to locally migrate the group
1714 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1715 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1716 System
.err
.println("Locally migrated group "
1717 + Base64
.encodeBytes(groupInfo
.groupId
)
1718 + " to group v2, id: "
1719 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1721 } else if (groupInfo
instanceof GroupInfoV2
) {
1722 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1724 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1727 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1728 DecryptedGroup group
= null;
1729 if (signedGroupChange
!= null
1730 && groupInfoV2
.getGroup() != null
1731 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1732 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1734 if (group
== null) {
1735 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1737 if (group
!= null) {
1738 storeProfileKeysFromMembers(group
);
1740 groupInfoV2
.setGroup(group
);
1741 account
.getGroupStore().updateGroup(groupInfoV2
);
1747 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1748 for (DecryptedMember member
: group
.getMembersList()) {
1749 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1750 member
.getUuid().toByteArray()), null));
1752 account
.getProfileStore()
1753 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1754 } catch (InvalidInputException ignored
) {
1759 private void retryFailedReceivedMessages(
1760 ReceiveMessageHandler handler
, boolean ignoreAttachments
1762 final File cachePath
= new File(getMessageCachePath());
1763 if (!cachePath
.exists()) {
1766 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1767 if (!dir
.isDirectory()) {
1768 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1772 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1773 if (!fileEntry
.isFile()) {
1776 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1778 // Try to delete directory if empty
1783 private void retryFailedReceivedMessage(
1784 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1786 SignalServiceEnvelope envelope
;
1788 envelope
= Utils
.loadEnvelope(fileEntry
);
1789 if (envelope
== null) {
1792 } catch (IOException e
) {
1793 e
.printStackTrace();
1796 SignalServiceContent content
= null;
1797 if (!envelope
.isReceipt()) {
1799 content
= decryptMessage(envelope
);
1800 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1802 } catch (Exception er
) {
1803 // All other errors are not recoverable, so delete the cached message
1805 Files
.delete(fileEntry
.toPath());
1806 } catch (IOException e
) {
1807 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1811 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1812 for (HandleAction action
: actions
) {
1814 action
.execute(this);
1815 } catch (Throwable e
) {
1816 e
.printStackTrace();
1821 handler
.handleMessage(envelope
, content
, null);
1823 Files
.delete(fileEntry
.toPath());
1824 } catch (IOException e
) {
1825 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1829 public void receiveMessages(
1832 boolean returnOnTimeout
,
1833 boolean ignoreAttachments
,
1834 ReceiveMessageHandler handler
1835 ) throws IOException
{
1836 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1838 Set
<HandleAction
> queuedActions
= null;
1840 getOrCreateMessagePipe();
1842 boolean hasCaughtUpWithOldMessages
= false;
1845 SignalServiceEnvelope envelope
;
1846 SignalServiceContent content
= null;
1847 Exception exception
= null;
1848 final long now
= new Date().getTime();
1850 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1851 // store message on disk, before acknowledging receipt to the server
1853 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1854 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1855 Utils
.storeEnvelope(envelope1
, cacheFile
);
1856 } catch (IOException e
) {
1857 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1861 if (result
.isPresent()) {
1862 envelope
= result
.get();
1864 // Received indicator that server queue is empty
1865 hasCaughtUpWithOldMessages
= true;
1867 if (queuedActions
!= null) {
1868 for (HandleAction action
: queuedActions
) {
1870 action
.execute(this);
1871 } catch (Throwable e
) {
1872 e
.printStackTrace();
1876 queuedActions
.clear();
1877 queuedActions
= null;
1880 // Continue to wait another timeout for new messages
1883 } catch (TimeoutException e
) {
1884 if (returnOnTimeout
) return;
1886 } catch (InvalidVersionException e
) {
1887 System
.err
.println("Ignoring error: " + e
.getMessage());
1891 if (envelope
.hasSource()) {
1892 // Store uuid if we don't have it already
1893 SignalServiceAddress source
= envelope
.getSourceAddress();
1894 resolveSignalServiceAddress(source
);
1896 if (!envelope
.isReceipt()) {
1898 content
= decryptMessage(envelope
);
1899 } catch (Exception e
) {
1902 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1903 if (hasCaughtUpWithOldMessages
) {
1904 for (HandleAction action
: actions
) {
1906 action
.execute(this);
1907 } catch (Throwable e
) {
1908 e
.printStackTrace();
1912 if (queuedActions
== null) {
1913 queuedActions
= new HashSet
<>();
1915 queuedActions
.addAll(actions
);
1919 if (!isMessageBlocked(envelope
, content
)) {
1920 handler
.handleMessage(envelope
, content
, exception
);
1922 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1923 File cacheFile
= null;
1925 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1926 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1927 Files
.delete(cacheFile
.toPath());
1928 // Try to delete directory if empty
1929 new File(getMessageCachePath()).delete();
1930 } catch (IOException e
) {
1931 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1937 private boolean isMessageBlocked(
1938 SignalServiceEnvelope envelope
, SignalServiceContent content
1940 SignalServiceAddress source
;
1941 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1942 source
= envelope
.getSourceAddress();
1943 } else if (content
!= null) {
1944 source
= content
.getSender();
1948 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1949 if (sourceContact
!= null && sourceContact
.blocked
) {
1953 if (content
!= null && content
.getDataMessage().isPresent()) {
1954 SignalServiceDataMessage message
= content
.getDataMessage().get();
1955 if (message
.getGroupContext().isPresent()) {
1956 GroupInfo group
= null;
1957 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1958 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1959 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
) {
1960 group
= getGroup(groupInfo
.getGroupId());
1963 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1964 SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1965 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1966 byte[] groupId
= GroupUtils
.getGroupId(groupMasterKey
);
1967 group
= account
.getGroupStore().getGroupByV2Id(groupId
);
1969 if (group
!= null && group
.isBlocked()) {
1977 private List
<HandleAction
> handleMessage(
1978 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1980 List
<HandleAction
> actions
= new ArrayList
<>();
1981 if (content
!= null) {
1982 final SignalServiceAddress sender
;
1983 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1984 sender
= envelope
.getSourceAddress();
1986 sender
= content
.getSender();
1988 // Store uuid if we don't have it already
1989 resolveSignalServiceAddress(sender
);
1991 if (content
.getDataMessage().isPresent()) {
1992 SignalServiceDataMessage message
= content
.getDataMessage().get();
1994 if (content
.isNeedsReceipt()) {
1995 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1998 actions
.addAll(handleSignalServiceDataMessage(message
,
2001 account
.getSelfAddress(),
2002 ignoreAttachments
));
2004 if (content
.getSyncMessage().isPresent()) {
2005 account
.setMultiDevice(true);
2006 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2007 if (syncMessage
.getSent().isPresent()) {
2008 SentTranscriptMessage message
= syncMessage
.getSent().get();
2009 final SignalServiceAddress destination
= message
.getDestination().orNull();
2010 if (destination
!= null) {
2011 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2015 ignoreAttachments
));
2018 if (syncMessage
.getRequest().isPresent()) {
2019 RequestMessage rm
= syncMessage
.getRequest().get();
2020 if (rm
.isContactsRequest()) {
2021 actions
.add(SendSyncContactsAction
.create());
2023 if (rm
.isGroupsRequest()) {
2024 actions
.add(SendSyncGroupsAction
.create());
2026 if (rm
.isBlockedListRequest()) {
2027 actions
.add(SendSyncBlockedListAction
.create());
2029 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2031 if (syncMessage
.getGroups().isPresent()) {
2032 File tmpFile
= null;
2034 tmpFile
= IOUtils
.createTempFile();
2035 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2037 .asPointer(), tmpFile
)) {
2038 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2040 while ((g
= s
.read()) != null) {
2041 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
2042 if (syncGroup
!= null) {
2043 if (g
.getName().isPresent()) {
2044 syncGroup
.name
= g
.getName().get();
2046 syncGroup
.addMembers(g
.getMembers()
2048 .map(this::resolveSignalServiceAddress
)
2049 .collect(Collectors
.toSet()));
2050 if (!g
.isActive()) {
2051 syncGroup
.removeMember(account
.getSelfAddress());
2053 // Add ourself to the member set as it's marked as active
2054 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2056 syncGroup
.blocked
= g
.isBlocked();
2057 if (g
.getColor().isPresent()) {
2058 syncGroup
.color
= g
.getColor().get();
2061 if (g
.getAvatar().isPresent()) {
2062 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
2064 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2065 syncGroup
.archived
= g
.isArchived();
2066 account
.getGroupStore().updateGroup(syncGroup
);
2070 } catch (Exception e
) {
2071 e
.printStackTrace();
2073 if (tmpFile
!= null) {
2075 Files
.delete(tmpFile
.toPath());
2076 } catch (IOException e
) {
2077 System
.err
.println("Failed to delete received groups temp file “"
2085 if (syncMessage
.getBlockedList().isPresent()) {
2086 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2087 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2088 setContactBlocked(resolveSignalServiceAddress(address
), true);
2090 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
2092 setGroupBlocked(groupId
, true);
2093 } catch (GroupNotFoundException e
) {
2094 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
2095 + Base64
.encodeBytes(groupId
));
2099 if (syncMessage
.getContacts().isPresent()) {
2100 File tmpFile
= null;
2102 tmpFile
= IOUtils
.createTempFile();
2103 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2104 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2105 .asPointer(), tmpFile
)) {
2106 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2107 if (contactsMessage
.isComplete()) {
2108 account
.getContactStore().clear();
2111 while ((c
= s
.read()) != null) {
2112 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2113 account
.setProfileKey(c
.getProfileKey().get());
2115 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2116 ContactInfo contact
= account
.getContactStore().getContact(address
);
2117 if (contact
== null) {
2118 contact
= new ContactInfo(address
);
2120 if (c
.getName().isPresent()) {
2121 contact
.name
= c
.getName().get();
2123 if (c
.getColor().isPresent()) {
2124 contact
.color
= c
.getColor().get();
2126 if (c
.getProfileKey().isPresent()) {
2127 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2129 if (c
.getVerified().isPresent()) {
2130 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2131 account
.getSignalProtocolStore()
2132 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2133 verifiedMessage
.getIdentityKey(),
2134 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2136 if (c
.getExpirationTimer().isPresent()) {
2137 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2139 contact
.blocked
= c
.isBlocked();
2140 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2141 contact
.archived
= c
.isArchived();
2142 account
.getContactStore().updateContact(contact
);
2144 if (c
.getAvatar().isPresent()) {
2145 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2149 } catch (Exception e
) {
2150 e
.printStackTrace();
2152 if (tmpFile
!= null) {
2154 Files
.delete(tmpFile
.toPath());
2155 } catch (IOException e
) {
2156 System
.err
.println("Failed to delete received contacts temp file “"
2164 if (syncMessage
.getVerified().isPresent()) {
2165 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2166 account
.getSignalProtocolStore()
2167 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2168 verifiedMessage
.getIdentityKey(),
2169 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2171 if (syncMessage
.getStickerPackOperations().isPresent()) {
2172 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2174 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2175 if (!m
.getPackId().isPresent()) {
2178 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2179 if (sticker
== null) {
2180 if (!m
.getPackKey().isPresent()) {
2183 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2185 sticker
.setInstalled(!m
.getType().isPresent()
2186 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2187 account
.getStickerStore().updateSticker(sticker
);
2190 if (syncMessage
.getConfiguration().isPresent()) {
2198 private File
getContactAvatarFile(String number
) {
2199 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2202 private File
retrieveContactAvatarAttachment(
2203 SignalServiceAttachment attachment
, String number
2204 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2205 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2206 if (attachment
.isPointer()) {
2207 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2208 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2210 SignalServiceAttachmentStream stream
= attachment
.asStream();
2211 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2215 private File
getGroupAvatarFile(byte[] groupId
) {
2216 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2219 private File
retrieveGroupAvatarAttachment(
2220 SignalServiceAttachment attachment
, byte[] groupId
2221 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2222 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2223 if (attachment
.isPointer()) {
2224 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2225 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2227 SignalServiceAttachmentStream stream
= attachment
.asStream();
2228 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2232 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2233 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2236 private File
retrieveProfileAvatar(
2237 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2238 ) throws IOException
{
2239 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2240 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2241 File outputFile
= getProfileAvatarFile(address
);
2243 File tmpFile
= IOUtils
.createTempFile();
2244 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2247 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2248 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2249 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2252 Files
.delete(tmpFile
.toPath());
2253 } catch (IOException e
) {
2254 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2260 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2261 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2264 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2265 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2266 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2269 private File
retrieveAttachment(
2270 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2271 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2272 if (storePreview
&& pointer
.getPreview().isPresent()) {
2273 File previewFile
= new File(outputFile
+ ".preview");
2274 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2275 byte[] preview
= pointer
.getPreview().get();
2276 output
.write(preview
, 0, preview
.length
);
2277 } catch (FileNotFoundException e
) {
2278 e
.printStackTrace();
2283 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2285 File tmpFile
= IOUtils
.createTempFile();
2286 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2288 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2289 IOUtils
.copyStreamToFile(input
, outputFile
);
2292 Files
.delete(tmpFile
.toPath());
2293 } catch (IOException e
) {
2294 System
.err
.println("Failed to delete received attachment temp file “"
2303 private InputStream
retrieveAttachmentAsStream(
2304 SignalServiceAttachmentPointer pointer
, File tmpFile
2305 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2306 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2307 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2310 void sendGroups() throws IOException
, UntrustedIdentityException
{
2311 File groupsFile
= IOUtils
.createTempFile();
2314 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2315 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2316 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2317 if (record instanceof GroupInfoV1
) {
2318 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2319 out
.write(new DeviceGroup(groupInfo
.groupId
,
2320 Optional
.fromNullable(groupInfo
.name
),
2321 new ArrayList
<>(groupInfo
.getMembers()),
2322 createGroupAvatarAttachment(groupInfo
.groupId
),
2323 groupInfo
.isMember(account
.getSelfAddress()),
2324 Optional
.of(groupInfo
.messageExpirationTime
),
2325 Optional
.fromNullable(groupInfo
.color
),
2327 Optional
.fromNullable(groupInfo
.inboxPosition
),
2328 groupInfo
.archived
));
2333 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2334 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2335 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2336 .withStream(groupsFileStream
)
2337 .withContentType("application/octet-stream")
2338 .withLength(groupsFile
.length())
2341 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2346 Files
.delete(groupsFile
.toPath());
2347 } catch (IOException e
) {
2348 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2353 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2354 File contactsFile
= IOUtils
.createTempFile();
2357 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2358 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2359 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2360 VerifiedMessage verifiedMessage
= null;
2361 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2362 .getIdentity(record.getAddress());
2363 if (currentIdentity
!= null) {
2364 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2365 currentIdentity
.getIdentityKey(),
2366 currentIdentity
.getTrustLevel().toVerifiedState(),
2367 currentIdentity
.getDateAdded().getTime());
2370 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2371 out
.write(new DeviceContact(record.getAddress(),
2372 Optional
.fromNullable(record.name
),
2373 createContactAvatarAttachment(record.number
),
2374 Optional
.fromNullable(record.color
),
2375 Optional
.fromNullable(verifiedMessage
),
2376 Optional
.fromNullable(profileKey
),
2378 Optional
.of(record.messageExpirationTime
),
2379 Optional
.fromNullable(record.inboxPosition
),
2383 if (account
.getProfileKey() != null) {
2384 // Send our own profile key as well
2385 out
.write(new DeviceContact(account
.getSelfAddress(),
2390 Optional
.of(account
.getProfileKey()),
2398 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2399 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2400 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2401 .withStream(contactsFileStream
)
2402 .withContentType("application/octet-stream")
2403 .withLength(contactsFile
.length())
2406 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2411 Files
.delete(contactsFile
.toPath());
2412 } catch (IOException e
) {
2413 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2418 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2419 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2420 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2421 if (record.blocked
) {
2422 addresses
.add(record.getAddress());
2425 List
<byte[]> groupIds
= new ArrayList
<>();
2426 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2427 if (record.isBlocked()) {
2428 groupIds
.add(record.groupId
);
2431 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2434 private void sendVerifiedMessage(
2435 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2436 ) throws IOException
, UntrustedIdentityException
{
2437 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2439 trustLevel
.toVerifiedState(),
2440 System
.currentTimeMillis());
2441 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2444 public List
<ContactInfo
> getContacts() {
2445 return account
.getContactStore().getContacts();
2448 public ContactInfo
getContact(String number
) {
2449 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2452 public GroupInfo
getGroup(byte[] groupId
) {
2453 return account
.getGroupStore().getGroup(groupId
);
2456 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2457 return account
.getSignalProtocolStore().getIdentities();
2460 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2461 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2465 * Trust this the identity with this fingerprint
2467 * @param name username of the identity
2468 * @param fingerprint Fingerprint
2470 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2471 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2472 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2476 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2477 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2481 account
.getSignalProtocolStore()
2482 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2484 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2485 } catch (IOException
| UntrustedIdentityException e
) {
2486 e
.printStackTrace();
2495 * Trust this the identity with this safety number
2497 * @param name username of the identity
2498 * @param safetyNumber Safety number
2500 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2501 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2502 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2506 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2507 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2511 account
.getSignalProtocolStore()
2512 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2514 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2515 } catch (IOException
| UntrustedIdentityException e
) {
2516 e
.printStackTrace();
2525 * Trust all keys of this identity without verification
2527 * @param name username of the identity
2529 public boolean trustIdentityAllKeys(String name
) {
2530 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2531 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2535 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2536 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2537 account
.getSignalProtocolStore()
2538 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2540 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2541 } catch (IOException
| UntrustedIdentityException e
) {
2542 e
.printStackTrace();
2550 public String
computeSafetyNumber(
2551 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2553 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2554 getIdentityKeyPair().getPublicKey(),
2559 void saveAccount() {
2563 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2564 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2566 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2567 return resolveSignalServiceAddress(canonicalizedNumber
);
2570 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2571 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2573 return resolveSignalServiceAddress(address
);
2576 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2577 if (address
.matches(account
.getSelfAddress())) {
2578 return account
.getSelfAddress();
2581 return account
.getRecipientStore().resolveServiceAddress(address
);
2585 public void close() throws IOException
{
2586 if (messagePipe
!= null) {
2587 messagePipe
.shutdown();
2591 if (unidentifiedMessagePipe
!= null) {
2592 unidentifiedMessagePipe
.shutdown();
2593 unidentifiedMessagePipe
= null;
2599 public interface ReceiveMessageHandler
{
2601 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);