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
.DecryptedMember
;
49 import org
.signal
.zkgroup
.InvalidInputException
;
50 import org
.signal
.zkgroup
.VerificationFailedException
;
51 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
52 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
53 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
54 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
55 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
56 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
57 import org
.whispersystems
.libsignal
.IdentityKey
;
58 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
59 import org
.whispersystems
.libsignal
.InvalidKeyException
;
60 import org
.whispersystems
.libsignal
.InvalidMessageException
;
61 import org
.whispersystems
.libsignal
.InvalidVersionException
;
62 import org
.whispersystems
.libsignal
.ecc
.Curve
;
63 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
64 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
65 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
66 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
67 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
68 import org
.whispersystems
.libsignal
.util
.Medium
;
69 import org
.whispersystems
.libsignal
.util
.Pair
;
70 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
71 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
72 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
73 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
74 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
77 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
79 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
80 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
84 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
97 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
111 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
112 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
113 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
114 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
115 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
116 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
117 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
118 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
119 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
120 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
121 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
122 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
123 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
124 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
125 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
126 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
127 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
128 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
129 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
130 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
131 import org
.whispersystems
.util
.Base64
;
133 import java
.io
.Closeable
;
135 import java
.io
.FileInputStream
;
136 import java
.io
.FileNotFoundException
;
137 import java
.io
.FileOutputStream
;
138 import java
.io
.IOException
;
139 import java
.io
.InputStream
;
140 import java
.io
.OutputStream
;
142 import java
.net
.URISyntaxException
;
143 import java
.net
.URLEncoder
;
144 import java
.nio
.charset
.StandardCharsets
;
145 import java
.nio
.file
.Files
;
146 import java
.nio
.file
.Paths
;
147 import java
.nio
.file
.StandardCopyOption
;
148 import java
.security
.SignatureException
;
149 import java
.util
.ArrayList
;
150 import java
.util
.Arrays
;
151 import java
.util
.Collection
;
152 import java
.util
.Collections
;
153 import java
.util
.Date
;
154 import java
.util
.HashMap
;
155 import java
.util
.HashSet
;
156 import java
.util
.List
;
157 import java
.util
.Locale
;
158 import java
.util
.Map
;
159 import java
.util
.Objects
;
160 import java
.util
.Set
;
161 import java
.util
.UUID
;
162 import java
.util
.concurrent
.ExecutorService
;
163 import java
.util
.concurrent
.TimeUnit
;
164 import java
.util
.concurrent
.TimeoutException
;
165 import java
.util
.stream
.Collectors
;
166 import java
.util
.zip
.ZipEntry
;
167 import java
.util
.zip
.ZipFile
;
169 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
170 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
171 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
173 public class Manager
implements Closeable
{
175 private final SleepTimer timer
= new UptimeSleepTimer();
177 private final SignalServiceConfiguration serviceConfiguration
;
178 private final String userAgent
;
179 private final boolean discoverableByPhoneNumber
= true;
180 private final boolean unrestrictedUnidentifiedAccess
= false;
182 private final SignalAccount account
;
183 private final PathConfig pathConfig
;
184 private SignalServiceAccountManager accountManager
;
185 private GroupsV2Api groupsV2Api
;
186 private final GroupsV2Operations groupsV2Operations
;
188 private SignalServiceMessageReceiver messageReceiver
= null;
189 private SignalServiceMessagePipe messagePipe
= null;
190 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
192 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
193 private final ProfileHelper profileHelper
;
194 private final GroupHelper groupHelper
;
197 SignalAccount account
,
198 PathConfig pathConfig
,
199 SignalServiceConfiguration serviceConfiguration
,
202 this.account
= account
;
203 this.pathConfig
= pathConfig
;
204 this.serviceConfiguration
= serviceConfiguration
;
205 this.userAgent
= userAgent
;
206 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
207 serviceConfiguration
)) : null;
208 this.accountManager
= createSignalServiceAccountManager();
209 this.groupsV2Api
= accountManager
.getGroupsV2Api();
211 this.account
.setResolver(this::resolveSignalServiceAddress
);
213 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
214 account
.getProfileStore()::getProfileKey
,
215 this::getRecipientProfile
,
216 this::getSenderCertificate
);
217 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
218 unidentifiedAccessHelper
::getAccessFor
,
219 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
220 this::getOrCreateMessageReceiver
);
221 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
222 this::getRecipientProfile
,
223 account
::getSelfAddress
,
226 this::getGroupAuthForToday
);
229 public String
getUsername() {
230 return account
.getUsername();
233 public SignalServiceAddress
getSelfAddress() {
234 return account
.getSelfAddress();
237 private SignalServiceAccountManager
createSignalServiceAccountManager() {
238 return new SignalServiceAccountManager(serviceConfiguration
,
239 new DynamicCredentialsProvider(account
.getUuid(),
240 account
.getUsername(),
241 account
.getPassword(),
243 account
.getDeviceId()),
249 private IdentityKeyPair
getIdentityKeyPair() {
250 return account
.getSignalProtocolStore().getIdentityKeyPair();
253 public int getDeviceId() {
254 return account
.getDeviceId();
257 private String
getMessageCachePath() {
258 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
261 private String
getMessageCachePath(String sender
) {
262 if (sender
== null || sender
.isEmpty()) {
263 return getMessageCachePath();
266 return getMessageCachePath() + "/" + sender
.replace("/", "_");
269 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
270 String cachePath
= getMessageCachePath(sender
);
271 IOUtils
.createPrivateDirectories(cachePath
);
272 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
275 public static Manager
init(
276 String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
277 ) throws IOException
{
278 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
280 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
281 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
282 int registrationId
= KeyHelper
.generateRegistrationId(false);
284 ProfileKey profileKey
= KeyUtils
.createProfileKey();
285 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
292 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
295 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
297 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
299 m
.migrateLegacyConfigs();
304 private void migrateLegacyConfigs() {
305 if (account
.getProfileKey() == null && isRegistered()) {
306 // Old config file, creating new profile key
307 account
.setProfileKey(KeyUtils
.createProfileKey());
310 // Store profile keys only in profile store
311 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
312 String profileKeyString
= contact
.profileKey
;
313 if (profileKeyString
== null) {
316 final ProfileKey profileKey
;
318 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
319 } catch (InvalidInputException
| IOException e
) {
322 contact
.profileKey
= null;
323 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
325 // Ensure our profile key is stored in profile store
326 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
329 public void checkAccountState() throws IOException
{
330 if (account
.isRegistered()) {
331 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
335 if (account
.getUuid() == null) {
336 account
.setUuid(accountManager
.getOwnUuid());
339 updateAccountAttributes();
343 public boolean isRegistered() {
344 return account
.isRegistered();
347 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
348 account
.setPassword(KeyUtils
.createPassword());
350 // Resetting UUID, because registering doesn't work otherwise
351 account
.setUuid(null);
352 accountManager
= createSignalServiceAccountManager();
353 this.groupsV2Api
= accountManager
.getGroupsV2Api();
355 if (voiceVerification
) {
356 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
357 Optional
.fromNullable(captcha
),
360 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
363 account
.setRegistered(false);
367 public void updateAccountAttributes() throws IOException
{
368 accountManager
.setAccountAttributes(account
.getSignalingKey(),
369 account
.getSignalProtocolStore().getLocalRegistrationId(),
371 account
.getRegistrationLockPin(),
372 account
.getRegistrationLock(),
373 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
374 unrestrictedUnidentifiedAccess
,
376 discoverableByPhoneNumber
);
379 public void setProfile(String name
, File avatar
) throws IOException
{
380 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
381 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
385 public void unregister() throws IOException
{
386 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
387 // If this is the master device, other users can't send messages to this number anymore.
388 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
389 accountManager
.setGcmId(Optional
.absent());
391 account
.setRegistered(false);
395 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
396 List
<DeviceInfo
> devices
= accountManager
.getDevices();
397 account
.setMultiDevice(devices
.size() > 1);
402 public void removeLinkedDevices(int deviceId
) throws IOException
{
403 accountManager
.removeDevice(deviceId
);
404 List
<DeviceInfo
> devices
= accountManager
.getDevices();
405 account
.setMultiDevice(devices
.size() > 1);
409 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
410 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
412 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
415 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
416 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
417 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
419 accountManager
.addDevice(deviceIdentifier
,
422 Optional
.of(account
.getProfileKey().serialize()),
424 account
.setMultiDevice(true);
428 private List
<PreKeyRecord
> generatePreKeys() {
429 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
431 final int offset
= account
.getPreKeyIdOffset();
432 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
433 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
434 ECKeyPair keyPair
= Curve
.generateKeyPair();
435 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
440 account
.addPreKeys(records
);
446 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
448 ECKeyPair keyPair
= Curve
.generateKeyPair();
449 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
450 keyPair
.getPublicKey().serialize());
451 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
452 System
.currentTimeMillis(),
456 account
.addSignedPreKey(record);
460 } catch (InvalidKeyException e
) {
461 throw new AssertionError(e
);
465 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
466 verificationCode
= verificationCode
.replace("-", "");
467 account
.setSignalingKey(KeyUtils
.createSignalingKey());
468 // TODO make unrestricted unidentified access configurable
469 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
470 account
.getSignalingKey(),
471 account
.getSignalProtocolStore().getLocalRegistrationId(),
475 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
476 unrestrictedUnidentifiedAccess
,
478 discoverableByPhoneNumber
);
480 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
481 // TODO response.isStorageCapable()
482 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
483 account
.setRegistered(true);
484 account
.setUuid(uuid
);
485 account
.setRegistrationLockPin(pin
);
486 account
.getSignalProtocolStore()
487 .saveIdentity(account
.getSelfAddress(),
488 getIdentityKeyPair().getPublicKey(),
489 TrustLevel
.TRUSTED_VERIFIED
);
495 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
496 if (pin
.isPresent()) {
497 account
.setRegistrationLockPin(pin
.get());
498 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
500 account
.setRegistrationLockPin(null);
501 accountManager
.removeRegistrationLockV1();
506 void refreshPreKeys() throws IOException
{
507 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
508 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
509 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
511 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
514 private SignalServiceMessageReceiver
createMessageReceiver() {
515 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
516 serviceConfiguration
).getProfileOperations() : null;
517 return new SignalServiceMessageReceiver(serviceConfiguration
,
519 account
.getUsername(),
520 account
.getPassword(),
521 account
.getDeviceId(),
522 account
.getSignalingKey(),
526 clientZkProfileOperations
);
529 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
530 if (messageReceiver
== null) {
531 messageReceiver
= createMessageReceiver();
533 return messageReceiver
;
536 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
537 if (messagePipe
== null) {
538 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
543 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
544 if (unidentifiedMessagePipe
== null) {
545 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
547 return unidentifiedMessagePipe
;
550 private SignalServiceMessageSender
createMessageSender() {
551 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
552 serviceConfiguration
).getProfileOperations() : null;
553 final ExecutorService executor
= null;
554 return new SignalServiceMessageSender(serviceConfiguration
,
556 account
.getUsername(),
557 account
.getPassword(),
558 account
.getDeviceId(),
559 account
.getSignalProtocolStore(),
561 account
.isMultiDevice(),
562 Optional
.fromNullable(messagePipe
),
563 Optional
.fromNullable(unidentifiedMessagePipe
),
565 clientZkProfileOperations
,
567 ServiceConfig
.MAX_ENVELOPE_SIZE
);
570 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
571 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
574 private SignalProfile
getRecipientProfile(
575 SignalServiceAddress address
577 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
578 if (profileEntry
== null) {
581 long now
= new Date().getTime();
582 // Profiles are cache for 24h before retrieving them again
583 if (!profileEntry
.isRequestPending() && (
584 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
586 ProfileKey profileKey
= profileEntry
.getProfileKey();
587 profileEntry
.setRequestPending(true);
588 SignalProfile profile
;
590 profile
= retrieveRecipientProfile(address
, profileKey
);
591 } catch (IOException e
) {
592 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
593 profileEntry
.setRequestPending(false);
596 profileEntry
.setRequestPending(false);
597 account
.getProfileStore()
598 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
601 return profileEntry
.getProfile();
604 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
605 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
606 if (profileEntry
== null) {
609 if (profileEntry
.getProfileKeyCredential() == null) {
610 ProfileAndCredential profileAndCredential
;
612 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
613 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
614 } catch (IOException e
) {
615 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
619 long now
= new Date().getTime();
620 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
621 final SignalProfile profile
= decryptProfile(address
,
622 profileEntry
.getProfileKey(),
623 profileAndCredential
.getProfile());
624 account
.getProfileStore()
625 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
626 return profileKeyCredential
;
628 return profileEntry
.getProfileKeyCredential();
631 private SignalProfile
retrieveRecipientProfile(
632 SignalServiceAddress address
, ProfileKey profileKey
633 ) throws IOException
{
634 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
636 return decryptProfile(address
, profileKey
, encryptedProfile
);
639 private SignalProfile
decryptProfile(
640 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
642 File avatarFile
= null;
644 avatarFile
= encryptedProfile
.getAvatar() == null
646 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
647 } catch (Throwable e
) {
648 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
651 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
655 name
= encryptedProfile
.getName() == null
657 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
658 } catch (IOException e
) {
661 String unidentifiedAccess
;
663 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
664 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
666 : encryptedProfile
.getUnidentifiedAccess();
667 } catch (IOException e
) {
668 unidentifiedAccess
= null;
670 return new SignalProfile(encryptedProfile
.getIdentityKey(),
674 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
675 encryptedProfile
.getCapabilities());
676 } catch (InvalidCiphertextException e
) {
681 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
682 File file
= getGroupAvatarFile(groupId
);
683 if (!file
.exists()) {
684 return Optional
.absent();
687 return Optional
.of(Utils
.createAttachment(file
));
690 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
691 File file
= getContactAvatarFile(number
);
692 if (!file
.exists()) {
693 return Optional
.absent();
696 return Optional
.of(Utils
.createAttachment(file
));
699 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
700 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
702 throw new GroupNotFoundException(groupId
);
704 if (!g
.isMember(account
.getSelfAddress())) {
705 throw new NotAGroupMemberException(groupId
, g
.getTitle());
710 private GroupInfo
getGroupForUpdating(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
711 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
713 throw new GroupNotFoundException(groupId
);
715 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
716 throw new NotAGroupMemberException(groupId
, g
.getTitle());
721 public List
<GroupInfo
> getGroups() {
722 return account
.getGroupStore().getGroups();
725 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
726 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
727 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
728 final GroupInfo g
= getGroupForSending(groupId
);
730 GroupUtils
.setGroupContext(messageBuilder
, g
);
731 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
733 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
736 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
737 String messageText
, List
<String
> attachments
, byte[] groupId
738 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
739 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
740 .withBody(messageText
);
741 if (attachments
!= null) {
742 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
745 return sendGroupMessage(messageBuilder
, groupId
);
748 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
749 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
750 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
751 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
753 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
754 targetSentTimestamp
);
755 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
756 .withReaction(reaction
);
758 return sendGroupMessage(messageBuilder
, groupId
);
761 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
763 SignalServiceDataMessage
.Builder messageBuilder
;
765 final GroupInfo g
= getGroupForUpdating(groupId
);
766 if (g
instanceof GroupInfoV1
) {
767 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
768 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
771 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
772 groupInfoV1
.removeMember(account
.getSelfAddress());
773 account
.getGroupStore().updateGroup(groupInfoV1
);
775 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
776 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
777 groupInfoV2
.setGroup(groupGroupChangePair
.first());
778 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
779 account
.getGroupStore().updateGroup(groupInfoV2
);
782 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
785 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
786 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
787 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
789 SignalServiceDataMessage
.Builder messageBuilder
;
790 if (groupId
== null) {
792 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
794 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
795 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
796 updateGroupV1(gv1
, name
, members
, avatarFile
);
797 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
800 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
804 GroupInfo group
= getGroupForUpdating(groupId
);
805 if (group
instanceof GroupInfoV2
) {
806 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
808 Pair
<Long
, List
<SendMessageResult
>> result
= null;
809 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
810 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
811 result
= sendUpdateGroupMessage(groupInfoV2
,
812 groupGroupChangePair
.first(),
813 groupGroupChangePair
.second());
816 if (members
!= null) {
817 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
818 newMembers
.removeAll(group
.getMembers());
819 if (newMembers
.size() > 0) {
820 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
822 result
= sendUpdateGroupMessage(groupInfoV2
,
823 groupGroupChangePair
.first(),
824 groupGroupChangePair
.second());
827 if (result
== null || name
!= null || avatarFile
!= null) {
828 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
831 result
= sendUpdateGroupMessage(groupInfoV2
,
832 groupGroupChangePair
.first(),
833 groupGroupChangePair
.second());
836 return new Pair
<>(group
.groupId
, result
.second());
838 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
839 updateGroupV1(gv1
, name
, members
, avatarFile
);
840 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
845 account
.getGroupStore().updateGroup(g
);
847 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
848 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
849 return new Pair
<>(g
.groupId
, result
.second());
852 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
853 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
854 ) throws IOException
{
855 group
.setGroup(newDecryptedGroup
);
856 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
857 groupChange
.toByteArray());
858 account
.getGroupStore().updateGroup(group
);
859 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
862 private void updateGroupV1(
865 final Collection
<SignalServiceAddress
> members
,
866 final String avatarFile
867 ) throws IOException
{
872 if (members
!= null) {
873 final Set
<String
> newE164Members
= new HashSet
<>();
874 for (SignalServiceAddress member
: members
) {
875 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
878 newE164Members
.add(member
.getNumber().get());
881 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
882 if (contacts
.size() != newE164Members
.size()) {
883 // Some of the new members are not registered on Signal
884 for (ContactTokenDetails contact
: contacts
) {
885 newE164Members
.remove(contact
.getNumber());
887 throw new IOException("Failed to add members "
888 + Util
.join(", ", newE164Members
)
889 + " to group: Not registered on Signal");
892 g
.addMembers(members
);
895 if (avatarFile
!= null) {
896 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
897 File aFile
= getGroupAvatarFile(g
.groupId
);
898 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
902 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
903 byte[] groupId
, SignalServiceAddress recipient
904 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
906 GroupInfo group
= getGroupForSending(groupId
);
907 if (!(group
instanceof GroupInfoV1
)) {
908 throw new RuntimeException("Received an invalid group request for a v2 group!");
910 g
= (GroupInfoV1
) group
;
912 if (!g
.isMember(recipient
)) {
913 throw new NotAGroupMemberException(groupId
, g
.name
);
916 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
918 // Send group message only to the recipient who requested it
919 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
922 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
923 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
926 .withMembers(new ArrayList
<>(g
.getMembers()));
928 File aFile
= getGroupAvatarFile(g
.groupId
);
929 if (aFile
.exists()) {
931 group
.withAvatar(Utils
.createAttachment(aFile
));
932 } catch (IOException e
) {
933 throw new AttachmentInvalidException(aFile
.toString(), e
);
937 return SignalServiceDataMessage
.newBuilder()
938 .asGroupMessage(group
.build())
939 .withExpiration(g
.getMessageExpirationTime());
942 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
943 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
944 .withRevision(g
.getGroup().getRevision())
945 .withSignedGroupChange(signedGroupChange
);
946 return SignalServiceDataMessage
.newBuilder()
947 .asGroupMessage(group
.build())
948 .withExpiration(g
.getMessageExpirationTime());
951 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
952 byte[] groupId
, SignalServiceAddress recipient
953 ) throws IOException
{
954 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
957 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
958 .asGroupMessage(group
.build());
960 // Send group info request message to the recipient who sent us a message with this groupId
961 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
965 SignalServiceAddress remoteAddress
, long messageId
966 ) throws IOException
, UntrustedIdentityException
{
967 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
968 Collections
.singletonList(messageId
),
969 System
.currentTimeMillis());
971 createMessageSender().sendReceipt(remoteAddress
,
972 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
976 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
977 String messageText
, List
<String
> attachments
, List
<String
> recipients
978 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
979 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
980 .withBody(messageText
);
981 if (attachments
!= null) {
982 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
984 // Upload attachments here, so we only upload once even for multiple recipients
985 SignalServiceMessageSender messageSender
= createMessageSender();
986 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
987 for (SignalServiceAttachment attachment
: attachmentStreams
) {
988 if (attachment
.isStream()) {
989 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
990 } else if (attachment
.isPointer()) {
991 attachmentPointers
.add(attachment
.asPointer());
995 messageBuilder
.withAttachments(attachmentPointers
);
997 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1000 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1001 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1002 ) throws IOException
, InvalidNumberException
{
1003 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1005 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1006 targetSentTimestamp
);
1007 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1008 .withReaction(reaction
);
1009 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1012 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1013 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1015 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1017 return sendMessage(messageBuilder
, signalServiceAddresses
);
1018 } catch (Exception e
) {
1019 for (SignalServiceAddress address
: signalServiceAddresses
) {
1020 handleEndSession(address
);
1027 public String
getContactName(String number
) throws InvalidNumberException
{
1028 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1029 if (contact
== null) {
1032 return contact
.name
;
1036 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1037 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1038 ContactInfo contact
= account
.getContactStore().getContact(address
);
1039 if (contact
== null) {
1040 contact
= new ContactInfo(address
);
1042 contact
.name
= name
;
1043 account
.getContactStore().updateContact(contact
);
1047 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1048 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1051 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1052 ContactInfo contact
= account
.getContactStore().getContact(address
);
1053 if (contact
== null) {
1054 contact
= new ContactInfo(address
);
1056 contact
.blocked
= blocked
;
1057 account
.getContactStore().updateContact(contact
);
1061 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1062 GroupInfo group
= getGroup(groupId
);
1063 if (group
== null) {
1064 throw new GroupNotFoundException(groupId
);
1067 group
.setBlocked(blocked
);
1068 account
.getGroupStore().updateGroup(group
);
1072 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1073 byte[] groupId
, String name
, List
<String
> members
, String avatar
1074 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1075 return sendUpdateGroupMessage(groupId
,
1077 members
== null ?
null : getSignalServiceAddresses(members
),
1082 * Change the expiration timer for a contact
1084 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1085 ContactInfo contact
= account
.getContactStore().getContact(address
);
1086 contact
.messageExpirationTime
= messageExpirationTimer
;
1087 account
.getContactStore().updateContact(contact
);
1088 sendExpirationTimerUpdate(address
);
1092 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1093 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1094 .asExpirationUpdate();
1095 sendMessage(messageBuilder
, Collections
.singleton(address
));
1099 * Change the expiration timer for a contact
1101 public void setExpirationTimer(
1102 String number
, int messageExpirationTimer
1103 ) throws IOException
, InvalidNumberException
{
1104 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1105 setExpirationTimer(address
, messageExpirationTimer
);
1109 * Change the expiration timer for a group
1111 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1112 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1113 if (g
instanceof GroupInfoV1
) {
1114 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1115 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1116 account
.getGroupStore().updateGroup(groupInfoV1
);
1118 throw new RuntimeException("TODO Not implemented!");
1123 * Upload the sticker pack from path.
1125 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1126 * @return if successful, returns the URL to install the sticker pack in the signal app
1128 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1129 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1131 SignalServiceMessageSender messageSender
= createMessageSender();
1133 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1134 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1136 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1137 account
.getStickerStore().updateSticker(sticker
);
1141 return new URI("https",
1144 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1145 Hex
.toStringCondensed(packKey
),
1146 StandardCharsets
.UTF_8
)).toString();
1147 } catch (URISyntaxException e
) {
1148 throw new AssertionError(e
);
1152 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1154 ) throws IOException
, StickerPackInvalidException
{
1156 String rootPath
= null;
1158 final File file
= new File(path
);
1159 if (file
.getName().endsWith(".zip")) {
1160 zip
= new ZipFile(file
);
1161 } else if (file
.getName().equals("manifest.json")) {
1162 rootPath
= file
.getParent();
1164 throw new StickerPackInvalidException("Could not find manifest.json");
1167 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1169 if (pack
.stickers
== null) {
1170 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1173 if (pack
.stickers
.isEmpty()) {
1174 throw new StickerPackInvalidException("Must include stickers.");
1177 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1178 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1179 if (sticker
.file
== null) {
1180 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1183 Pair
<InputStream
, Long
> data
;
1185 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1186 } catch (IOException ignored
) {
1187 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1190 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1191 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1193 Optional
.fromNullable(sticker
.emoji
).or(""),
1195 stickers
.add(stickerInfo
);
1198 StickerInfo cover
= null;
1199 if (pack
.cover
!= null) {
1200 if (pack
.cover
.file
== null) {
1201 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1204 Pair
<InputStream
, Long
> data
;
1206 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1207 } catch (IOException ignored
) {
1208 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1211 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1212 cover
= new StickerInfo(data
.first(),
1214 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1218 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1221 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1222 InputStream inputStream
;
1224 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1226 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1228 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1231 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1232 final String rootPath
, final ZipFile zip
, final String subfile
1233 ) throws IOException
{
1235 final ZipEntry entry
= zip
.getEntry(subfile
);
1236 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1238 final File file
= new File(rootPath
, subfile
);
1239 return new Pair
<>(new FileInputStream(file
), file
.length());
1243 void requestSyncGroups() throws IOException
{
1244 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1245 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1247 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1249 sendSyncMessage(message
);
1250 } catch (UntrustedIdentityException e
) {
1251 e
.printStackTrace();
1255 void requestSyncContacts() throws IOException
{
1256 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1257 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1259 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1261 sendSyncMessage(message
);
1262 } catch (UntrustedIdentityException e
) {
1263 e
.printStackTrace();
1267 void requestSyncBlocked() throws IOException
{
1268 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1269 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1271 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1273 sendSyncMessage(message
);
1274 } catch (UntrustedIdentityException e
) {
1275 e
.printStackTrace();
1279 void requestSyncConfiguration() throws IOException
{
1280 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1281 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1283 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1285 sendSyncMessage(message
);
1286 } catch (UntrustedIdentityException e
) {
1287 e
.printStackTrace();
1291 private byte[] getSenderCertificate() {
1292 // TODO support UUID capable sender certificates
1293 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1296 certificate
= accountManager
.getSenderCertificate();
1297 } catch (IOException e
) {
1298 System
.err
.println("Failed to get sender certificate: " + e
);
1301 // TODO cache for a day
1305 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1306 SignalServiceMessageSender messageSender
= createMessageSender();
1308 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1309 } catch (UntrustedIdentityException e
) {
1310 account
.getSignalProtocolStore()
1311 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1313 TrustLevel
.UNTRUSTED
);
1318 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1319 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1320 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1322 for (String number
: numbers
) {
1323 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1324 if (resolvedAddress
.getUuid().isPresent()) {
1325 signalServiceAddresses
.add(resolvedAddress
);
1327 missingUuids
.add(resolvedAddress
);
1331 Map
<String
, UUID
> registeredUsers
;
1333 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1334 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1336 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1337 System
.err
.println("Failed to resolve uuids from server: " + e
.getMessage());
1338 registeredUsers
= new HashMap
<>();
1341 for (SignalServiceAddress address
: missingUuids
) {
1342 final String number
= address
.getNumber().get();
1343 if (registeredUsers
.containsKey(number
)) {
1344 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1345 registeredUsers
.get(number
),
1347 signalServiceAddresses
.add(newAddress
);
1349 signalServiceAddresses
.add(address
);
1353 return signalServiceAddresses
;
1356 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1357 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1358 ) throws IOException
{
1359 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1360 final long timestamp
= System
.currentTimeMillis();
1361 messageBuilder
.withTimestamp(timestamp
);
1362 getOrCreateMessagePipe();
1363 getOrCreateUnidentifiedMessagePipe();
1364 SignalServiceDataMessage message
= null;
1366 message
= messageBuilder
.build();
1367 if (message
.getGroupContext().isPresent()) {
1369 SignalServiceMessageSender messageSender
= createMessageSender();
1370 final boolean isRecipientUpdate
= false;
1371 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1372 unidentifiedAccessHelper
.getAccessFor(recipients
),
1375 for (SendMessageResult r
: result
) {
1376 if (r
.getIdentityFailure() != null) {
1377 account
.getSignalProtocolStore()
1378 .saveIdentity(r
.getAddress(),
1379 r
.getIdentityFailure().getIdentityKey(),
1380 TrustLevel
.UNTRUSTED
);
1383 return new Pair
<>(timestamp
, result
);
1384 } catch (UntrustedIdentityException e
) {
1385 account
.getSignalProtocolStore()
1386 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1388 TrustLevel
.UNTRUSTED
);
1389 return new Pair
<>(timestamp
, Collections
.emptyList());
1392 // Send to all individually, so sync messages are sent correctly
1393 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1394 for (SignalServiceAddress address
: recipients
) {
1395 ContactInfo contact
= account
.getContactStore().getContact(address
);
1396 if (contact
!= null) {
1397 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1398 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1400 messageBuilder
.withExpiration(0);
1401 messageBuilder
.withProfileKey(null);
1403 message
= messageBuilder
.build();
1404 if (address
.matches(account
.getSelfAddress())) {
1405 results
.add(sendSelfMessage(message
));
1407 results
.add(sendMessage(address
, message
));
1410 return new Pair
<>(timestamp
, results
);
1413 if (message
!= null && message
.isEndSession()) {
1414 for (SignalServiceAddress recipient
: recipients
) {
1415 handleEndSession(recipient
);
1422 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1423 SignalServiceMessageSender messageSender
= createMessageSender();
1425 SignalServiceAddress recipient
= account
.getSelfAddress();
1427 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1428 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1429 message
.getTimestamp(),
1431 message
.getExpiresInSeconds(),
1432 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1434 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1437 long startTime
= System
.currentTimeMillis();
1438 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1439 return SendMessageResult
.success(recipient
,
1440 unidentifiedAccess
.isPresent(),
1442 System
.currentTimeMillis() - startTime
);
1443 } catch (UntrustedIdentityException e
) {
1444 account
.getSignalProtocolStore()
1445 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1447 TrustLevel
.UNTRUSTED
);
1448 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1452 private SendMessageResult
sendMessage(
1453 SignalServiceAddress address
, SignalServiceDataMessage message
1454 ) throws IOException
{
1455 SignalServiceMessageSender messageSender
= createMessageSender();
1458 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1459 } catch (UntrustedIdentityException e
) {
1460 account
.getSignalProtocolStore()
1461 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1463 TrustLevel
.UNTRUSTED
);
1464 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1468 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1469 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1470 account
.getSignalProtocolStore(),
1471 Utils
.getCertificateValidator());
1473 return cipher
.decrypt(envelope
);
1474 } catch (ProtocolUntrustedIdentityException e
) {
1475 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1476 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1478 account
.getSignalProtocolStore()
1479 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1480 identityException
.getUntrustedIdentity(),
1481 TrustLevel
.UNTRUSTED
);
1482 throw identityException
;
1484 throw new AssertionError(e
);
1488 private void handleEndSession(SignalServiceAddress source
) {
1489 account
.getSignalProtocolStore().deleteAllSessions(source
);
1492 private static int currentTimeDays() {
1493 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1496 private GroupsV2AuthorizationString
getGroupAuthForToday(
1497 final GroupSecretParams groupSecretParams
1498 ) throws IOException
{
1499 final int today
= currentTimeDays();
1500 // Returns credentials for the next 7 days
1501 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1502 // TODO cache credentials until they expire
1503 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1505 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1508 authCredentialResponse
);
1509 } catch (VerificationFailedException e
) {
1510 throw new IOException(e
);
1514 private List
<HandleAction
> handleSignalServiceDataMessage(
1515 SignalServiceDataMessage message
,
1517 SignalServiceAddress source
,
1518 SignalServiceAddress destination
,
1519 boolean ignoreAttachments
1521 List
<HandleAction
> actions
= new ArrayList
<>();
1522 if (message
.getGroupContext().isPresent()) {
1523 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1524 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1525 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1526 if (group
== null || group
instanceof GroupInfoV1
) {
1527 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1528 switch (groupInfo
.getType()) {
1530 if (groupV1
== null) {
1531 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1534 if (groupInfo
.getAvatar().isPresent()) {
1535 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1536 if (avatar
.isPointer()) {
1538 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1539 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1540 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1541 .getRemoteId() + "): " + e
.getMessage());
1546 if (groupInfo
.getName().isPresent()) {
1547 groupV1
.name
= groupInfo
.getName().get();
1550 if (groupInfo
.getMembers().isPresent()) {
1551 groupV1
.addMembers(groupInfo
.getMembers()
1554 .map(this::resolveSignalServiceAddress
)
1555 .collect(Collectors
.toSet()));
1558 account
.getGroupStore().updateGroup(groupV1
);
1562 if (groupV1
== null && !isSync
) {
1563 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1567 if (groupV1
!= null) {
1568 groupV1
.removeMember(source
);
1569 account
.getGroupStore().updateGroup(groupV1
);
1574 if (groupV1
!= null && !isSync
) {
1575 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1580 // Received a group v1 message for a v2 group
1583 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1584 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1585 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1587 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1589 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1590 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1591 if (groupInfo
instanceof GroupInfoV1
) {
1592 // Received a v2 group message for a v2 group, we need to locally migrate the group
1593 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1594 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1595 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1596 account
.getGroupStore().updateGroup(groupInfoV2
);
1597 System
.err
.println("Locally migrated group "
1598 + Base64
.encodeBytes(groupInfo
.groupId
)
1599 + " to group v2, id: "
1600 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1602 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1603 GroupInfoV2 groupInfoV2
= groupInfo
== null
1604 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1605 : (GroupInfoV2
) groupInfo
;
1607 if (groupInfoV2
.getGroup() == null
1608 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1609 DecryptedGroup group
= null;
1610 if (groupContext
.hasSignedGroupChange()
1611 && groupInfoV2
.getGroup() != null
1612 && groupInfoV2
.getGroup().getRevision() + 1 == groupContext
.getRevision()) {
1613 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(),
1614 groupContext
.getSignedGroupChange(),
1616 if (group
!= null) {
1617 storeProfileKeysFromMembers(group
);
1620 if (group
== null) {
1621 group
= getDecryptedGroup(groupSecretParams
);
1623 groupInfoV2
.setGroup(group
);
1624 account
.getGroupStore().updateGroup(groupInfoV2
);
1629 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1630 if (message
.isEndSession()) {
1631 handleEndSession(conversationPartnerAddress
);
1633 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1634 if (message
.getGroupContext().isPresent()) {
1635 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1636 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1637 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1638 if (group
!= null) {
1639 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1640 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1641 account
.getGroupStore().updateGroup(group
);
1644 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1645 // disappearing message timer already stored in the DecryptedGroup
1648 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1649 if (contact
== null) {
1650 contact
= new ContactInfo(conversationPartnerAddress
);
1652 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1653 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1654 account
.getContactStore().updateContact(contact
);
1658 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1659 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1660 if (attachment
.isPointer()) {
1662 retrieveAttachment(attachment
.asPointer());
1663 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1664 System
.err
.println("Failed to retrieve attachment ("
1665 + attachment
.asPointer().getRemoteId()
1672 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1673 final ProfileKey profileKey
;
1675 profileKey
= new ProfileKey(message
.getProfileKey().get());
1676 } catch (InvalidInputException e
) {
1677 throw new AssertionError(e
);
1679 if (source
.matches(account
.getSelfAddress())) {
1680 this.account
.setProfileKey(profileKey
);
1682 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1684 if (message
.getPreviews().isPresent()) {
1685 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1686 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1687 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1688 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1690 retrieveAttachment(attachment
);
1691 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1692 System
.err
.println("Failed to retrieve attachment ("
1693 + attachment
.getRemoteId()
1700 if (message
.getSticker().isPresent()) {
1701 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1702 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1703 if (sticker
== null) {
1704 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1705 account
.getStickerStore().updateSticker(sticker
);
1711 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1713 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1714 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1715 storeProfileKeysFromMembers(group
);
1717 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1718 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1723 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1724 for (DecryptedMember member
: group
.getMembersList()) {
1725 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1726 member
.getUuid().toByteArray()), null));
1728 account
.getProfileStore()
1729 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1730 } catch (InvalidInputException ignored
) {
1735 private void retryFailedReceivedMessages(
1736 ReceiveMessageHandler handler
, boolean ignoreAttachments
1738 final File cachePath
= new File(getMessageCachePath());
1739 if (!cachePath
.exists()) {
1742 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1743 if (!dir
.isDirectory()) {
1744 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1748 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1749 if (!fileEntry
.isFile()) {
1752 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1754 // Try to delete directory if empty
1759 private void retryFailedReceivedMessage(
1760 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1762 SignalServiceEnvelope envelope
;
1764 envelope
= Utils
.loadEnvelope(fileEntry
);
1765 if (envelope
== null) {
1768 } catch (IOException e
) {
1769 e
.printStackTrace();
1772 SignalServiceContent content
= null;
1773 if (!envelope
.isReceipt()) {
1775 content
= decryptMessage(envelope
);
1776 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1778 } catch (Exception er
) {
1779 // All other errors are not recoverable, so delete the cached message
1781 Files
.delete(fileEntry
.toPath());
1782 } catch (IOException e
) {
1783 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1787 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1788 for (HandleAction action
: actions
) {
1790 action
.execute(this);
1791 } catch (Throwable e
) {
1792 e
.printStackTrace();
1797 handler
.handleMessage(envelope
, content
, null);
1799 Files
.delete(fileEntry
.toPath());
1800 } catch (IOException e
) {
1801 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1805 public void receiveMessages(
1808 boolean returnOnTimeout
,
1809 boolean ignoreAttachments
,
1810 ReceiveMessageHandler handler
1811 ) throws IOException
{
1812 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1814 Set
<HandleAction
> queuedActions
= null;
1816 getOrCreateMessagePipe();
1818 boolean hasCaughtUpWithOldMessages
= false;
1821 SignalServiceEnvelope envelope
;
1822 SignalServiceContent content
= null;
1823 Exception exception
= null;
1824 final long now
= new Date().getTime();
1826 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1827 // store message on disk, before acknowledging receipt to the server
1829 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1830 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1831 Utils
.storeEnvelope(envelope1
, cacheFile
);
1832 } catch (IOException e
) {
1833 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1837 if (result
.isPresent()) {
1838 envelope
= result
.get();
1840 // Received indicator that server queue is empty
1841 hasCaughtUpWithOldMessages
= true;
1843 if (queuedActions
!= null) {
1844 for (HandleAction action
: queuedActions
) {
1846 action
.execute(this);
1847 } catch (Throwable e
) {
1848 e
.printStackTrace();
1852 queuedActions
.clear();
1853 queuedActions
= null;
1856 // Continue to wait another timeout for new messages
1859 } catch (TimeoutException e
) {
1860 if (returnOnTimeout
) return;
1862 } catch (InvalidVersionException e
) {
1863 System
.err
.println("Ignoring error: " + e
.getMessage());
1867 if (envelope
.hasSource()) {
1868 // Store uuid if we don't have it already
1869 SignalServiceAddress source
= envelope
.getSourceAddress();
1870 resolveSignalServiceAddress(source
);
1872 if (!envelope
.isReceipt()) {
1874 content
= decryptMessage(envelope
);
1875 } catch (Exception e
) {
1878 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1879 if (hasCaughtUpWithOldMessages
) {
1880 for (HandleAction action
: actions
) {
1882 action
.execute(this);
1883 } catch (Throwable e
) {
1884 e
.printStackTrace();
1888 if (queuedActions
== null) {
1889 queuedActions
= new HashSet
<>();
1891 queuedActions
.addAll(actions
);
1895 if (!isMessageBlocked(envelope
, content
)) {
1896 handler
.handleMessage(envelope
, content
, exception
);
1898 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1899 File cacheFile
= null;
1901 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1902 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1903 Files
.delete(cacheFile
.toPath());
1904 // Try to delete directory if empty
1905 new File(getMessageCachePath()).delete();
1906 } catch (IOException e
) {
1907 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1913 private boolean isMessageBlocked(
1914 SignalServiceEnvelope envelope
, SignalServiceContent content
1916 SignalServiceAddress source
;
1917 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1918 source
= envelope
.getSourceAddress();
1919 } else if (content
!= null) {
1920 source
= content
.getSender();
1924 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1925 if (sourceContact
!= null && sourceContact
.blocked
) {
1929 if (content
!= null && content
.getDataMessage().isPresent()) {
1930 SignalServiceDataMessage message
= content
.getDataMessage().get();
1931 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1932 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1933 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1934 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1940 private List
<HandleAction
> handleMessage(
1941 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1943 List
<HandleAction
> actions
= new ArrayList
<>();
1944 if (content
!= null) {
1945 final SignalServiceAddress sender
;
1946 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1947 sender
= envelope
.getSourceAddress();
1949 sender
= content
.getSender();
1951 // Store uuid if we don't have it already
1952 resolveSignalServiceAddress(sender
);
1954 if (content
.getDataMessage().isPresent()) {
1955 SignalServiceDataMessage message
= content
.getDataMessage().get();
1957 if (content
.isNeedsReceipt()) {
1958 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1961 actions
.addAll(handleSignalServiceDataMessage(message
,
1964 account
.getSelfAddress(),
1965 ignoreAttachments
));
1967 if (content
.getSyncMessage().isPresent()) {
1968 account
.setMultiDevice(true);
1969 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1970 if (syncMessage
.getSent().isPresent()) {
1971 SentTranscriptMessage message
= syncMessage
.getSent().get();
1972 final SignalServiceAddress destination
= message
.getDestination().orNull();
1973 if (destination
!= null) {
1974 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1978 ignoreAttachments
));
1981 if (syncMessage
.getRequest().isPresent()) {
1982 RequestMessage rm
= syncMessage
.getRequest().get();
1983 if (rm
.isContactsRequest()) {
1984 actions
.add(SendSyncContactsAction
.create());
1986 if (rm
.isGroupsRequest()) {
1987 actions
.add(SendSyncGroupsAction
.create());
1989 if (rm
.isBlockedListRequest()) {
1990 actions
.add(SendSyncBlockedListAction
.create());
1992 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1994 if (syncMessage
.getGroups().isPresent()) {
1995 File tmpFile
= null;
1997 tmpFile
= IOUtils
.createTempFile();
1998 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2000 .asPointer(), tmpFile
)) {
2001 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2003 while ((g
= s
.read()) != null) {
2004 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
2005 if (syncGroup
!= null) {
2006 if (g
.getName().isPresent()) {
2007 syncGroup
.name
= g
.getName().get();
2009 syncGroup
.addMembers(g
.getMembers()
2011 .map(this::resolveSignalServiceAddress
)
2012 .collect(Collectors
.toSet()));
2013 if (!g
.isActive()) {
2014 syncGroup
.removeMember(account
.getSelfAddress());
2016 // Add ourself to the member set as it's marked as active
2017 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2019 syncGroup
.blocked
= g
.isBlocked();
2020 if (g
.getColor().isPresent()) {
2021 syncGroup
.color
= g
.getColor().get();
2024 if (g
.getAvatar().isPresent()) {
2025 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
2027 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2028 syncGroup
.archived
= g
.isArchived();
2029 account
.getGroupStore().updateGroup(syncGroup
);
2033 } catch (Exception e
) {
2034 e
.printStackTrace();
2036 if (tmpFile
!= null) {
2038 Files
.delete(tmpFile
.toPath());
2039 } catch (IOException e
) {
2040 System
.err
.println("Failed to delete received groups temp file “"
2048 if (syncMessage
.getBlockedList().isPresent()) {
2049 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2050 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2051 setContactBlocked(resolveSignalServiceAddress(address
), true);
2053 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
2055 setGroupBlocked(groupId
, true);
2056 } catch (GroupNotFoundException e
) {
2057 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
2058 + Base64
.encodeBytes(groupId
));
2062 if (syncMessage
.getContacts().isPresent()) {
2063 File tmpFile
= null;
2065 tmpFile
= IOUtils
.createTempFile();
2066 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2067 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2068 .asPointer(), tmpFile
)) {
2069 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2070 if (contactsMessage
.isComplete()) {
2071 account
.getContactStore().clear();
2074 while ((c
= s
.read()) != null) {
2075 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2076 account
.setProfileKey(c
.getProfileKey().get());
2078 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2079 ContactInfo contact
= account
.getContactStore().getContact(address
);
2080 if (contact
== null) {
2081 contact
= new ContactInfo(address
);
2083 if (c
.getName().isPresent()) {
2084 contact
.name
= c
.getName().get();
2086 if (c
.getColor().isPresent()) {
2087 contact
.color
= c
.getColor().get();
2089 if (c
.getProfileKey().isPresent()) {
2090 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2092 if (c
.getVerified().isPresent()) {
2093 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2094 account
.getSignalProtocolStore()
2095 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2096 verifiedMessage
.getIdentityKey(),
2097 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2099 if (c
.getExpirationTimer().isPresent()) {
2100 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2102 contact
.blocked
= c
.isBlocked();
2103 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2104 contact
.archived
= c
.isArchived();
2105 account
.getContactStore().updateContact(contact
);
2107 if (c
.getAvatar().isPresent()) {
2108 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2112 } catch (Exception e
) {
2113 e
.printStackTrace();
2115 if (tmpFile
!= null) {
2117 Files
.delete(tmpFile
.toPath());
2118 } catch (IOException e
) {
2119 System
.err
.println("Failed to delete received contacts temp file “"
2127 if (syncMessage
.getVerified().isPresent()) {
2128 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2129 account
.getSignalProtocolStore()
2130 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2131 verifiedMessage
.getIdentityKey(),
2132 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2134 if (syncMessage
.getStickerPackOperations().isPresent()) {
2135 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2137 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2138 if (!m
.getPackId().isPresent()) {
2141 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2142 if (sticker
== null) {
2143 if (!m
.getPackKey().isPresent()) {
2146 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2148 sticker
.setInstalled(!m
.getType().isPresent()
2149 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2150 account
.getStickerStore().updateSticker(sticker
);
2153 if (syncMessage
.getConfiguration().isPresent()) {
2161 private File
getContactAvatarFile(String number
) {
2162 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2165 private File
retrieveContactAvatarAttachment(
2166 SignalServiceAttachment attachment
, String number
2167 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2168 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2169 if (attachment
.isPointer()) {
2170 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2171 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2173 SignalServiceAttachmentStream stream
= attachment
.asStream();
2174 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2178 private File
getGroupAvatarFile(byte[] groupId
) {
2179 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2182 private File
retrieveGroupAvatarAttachment(
2183 SignalServiceAttachment attachment
, byte[] groupId
2184 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2185 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2186 if (attachment
.isPointer()) {
2187 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2188 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2190 SignalServiceAttachmentStream stream
= attachment
.asStream();
2191 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2195 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2196 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2199 private File
retrieveProfileAvatar(
2200 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2201 ) throws IOException
{
2202 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2203 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2204 File outputFile
= getProfileAvatarFile(address
);
2206 File tmpFile
= IOUtils
.createTempFile();
2207 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2210 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2211 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2212 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2215 Files
.delete(tmpFile
.toPath());
2216 } catch (IOException e
) {
2217 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2223 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2224 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2227 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2228 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2229 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2232 private File
retrieveAttachment(
2233 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2234 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2235 if (storePreview
&& pointer
.getPreview().isPresent()) {
2236 File previewFile
= new File(outputFile
+ ".preview");
2237 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2238 byte[] preview
= pointer
.getPreview().get();
2239 output
.write(preview
, 0, preview
.length
);
2240 } catch (FileNotFoundException e
) {
2241 e
.printStackTrace();
2246 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2248 File tmpFile
= IOUtils
.createTempFile();
2249 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2251 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2252 IOUtils
.copyStreamToFile(input
, outputFile
);
2255 Files
.delete(tmpFile
.toPath());
2256 } catch (IOException e
) {
2257 System
.err
.println("Failed to delete received attachment temp file “"
2266 private InputStream
retrieveAttachmentAsStream(
2267 SignalServiceAttachmentPointer pointer
, File tmpFile
2268 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2269 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2270 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2273 void sendGroups() throws IOException
, UntrustedIdentityException
{
2274 File groupsFile
= IOUtils
.createTempFile();
2277 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2278 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2279 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2280 if (record instanceof GroupInfoV1
) {
2281 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2282 out
.write(new DeviceGroup(groupInfo
.groupId
,
2283 Optional
.fromNullable(groupInfo
.name
),
2284 new ArrayList
<>(groupInfo
.getMembers()),
2285 createGroupAvatarAttachment(groupInfo
.groupId
),
2286 groupInfo
.isMember(account
.getSelfAddress()),
2287 Optional
.of(groupInfo
.messageExpirationTime
),
2288 Optional
.fromNullable(groupInfo
.color
),
2290 Optional
.fromNullable(groupInfo
.inboxPosition
),
2291 groupInfo
.archived
));
2296 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2297 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2298 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2299 .withStream(groupsFileStream
)
2300 .withContentType("application/octet-stream")
2301 .withLength(groupsFile
.length())
2304 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2309 Files
.delete(groupsFile
.toPath());
2310 } catch (IOException e
) {
2311 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2316 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2317 File contactsFile
= IOUtils
.createTempFile();
2320 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2321 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2322 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2323 VerifiedMessage verifiedMessage
= null;
2324 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2325 .getIdentity(record.getAddress());
2326 if (currentIdentity
!= null) {
2327 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2328 currentIdentity
.getIdentityKey(),
2329 currentIdentity
.getTrustLevel().toVerifiedState(),
2330 currentIdentity
.getDateAdded().getTime());
2333 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2334 out
.write(new DeviceContact(record.getAddress(),
2335 Optional
.fromNullable(record.name
),
2336 createContactAvatarAttachment(record.number
),
2337 Optional
.fromNullable(record.color
),
2338 Optional
.fromNullable(verifiedMessage
),
2339 Optional
.fromNullable(profileKey
),
2341 Optional
.of(record.messageExpirationTime
),
2342 Optional
.fromNullable(record.inboxPosition
),
2346 if (account
.getProfileKey() != null) {
2347 // Send our own profile key as well
2348 out
.write(new DeviceContact(account
.getSelfAddress(),
2353 Optional
.of(account
.getProfileKey()),
2361 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2362 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2363 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2364 .withStream(contactsFileStream
)
2365 .withContentType("application/octet-stream")
2366 .withLength(contactsFile
.length())
2369 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2374 Files
.delete(contactsFile
.toPath());
2375 } catch (IOException e
) {
2376 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2381 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2382 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2383 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2384 if (record.blocked
) {
2385 addresses
.add(record.getAddress());
2388 List
<byte[]> groupIds
= new ArrayList
<>();
2389 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2390 if (record.isBlocked()) {
2391 groupIds
.add(record.groupId
);
2394 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2397 private void sendVerifiedMessage(
2398 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2399 ) throws IOException
, UntrustedIdentityException
{
2400 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2402 trustLevel
.toVerifiedState(),
2403 System
.currentTimeMillis());
2404 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2407 public List
<ContactInfo
> getContacts() {
2408 return account
.getContactStore().getContacts();
2411 public ContactInfo
getContact(String number
) {
2412 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2415 public GroupInfo
getGroup(byte[] groupId
) {
2416 return account
.getGroupStore().getGroup(groupId
);
2419 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2420 return account
.getSignalProtocolStore().getIdentities();
2423 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2424 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2428 * Trust this the identity with this fingerprint
2430 * @param name username of the identity
2431 * @param fingerprint Fingerprint
2433 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2434 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2435 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2439 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2440 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2444 account
.getSignalProtocolStore()
2445 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2447 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2448 } catch (IOException
| UntrustedIdentityException e
) {
2449 e
.printStackTrace();
2458 * Trust this the identity with this safety number
2460 * @param name username of the identity
2461 * @param safetyNumber Safety number
2463 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2464 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2465 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2469 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2470 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2474 account
.getSignalProtocolStore()
2475 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2477 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2478 } catch (IOException
| UntrustedIdentityException e
) {
2479 e
.printStackTrace();
2488 * Trust all keys of this identity without verification
2490 * @param name username of the identity
2492 public boolean trustIdentityAllKeys(String name
) {
2493 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2494 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2498 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2499 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2500 account
.getSignalProtocolStore()
2501 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2503 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2504 } catch (IOException
| UntrustedIdentityException e
) {
2505 e
.printStackTrace();
2513 public String
computeSafetyNumber(
2514 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2516 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2517 getIdentityKeyPair().getPublicKey(),
2522 void saveAccount() {
2526 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2527 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2529 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2530 return resolveSignalServiceAddress(canonicalizedNumber
);
2533 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2534 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2536 return resolveSignalServiceAddress(address
);
2539 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2540 if (address
.matches(account
.getSelfAddress())) {
2541 return account
.getSelfAddress();
2544 return account
.getRecipientStore().resolveServiceAddress(address
);
2548 public void close() throws IOException
{
2549 if (messagePipe
!= null) {
2550 messagePipe
.shutdown();
2554 if (unidentifiedMessagePipe
!= null) {
2555 unidentifiedMessagePipe
.shutdown();
2556 unidentifiedMessagePipe
= null;
2562 public interface ReceiveMessageHandler
{
2564 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);