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()) {
1932 GroupInfo group
= null;
1933 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1934 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1935 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
) {
1936 group
= getGroup(groupInfo
.getGroupId());
1939 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1940 SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1941 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1942 byte[] groupId
= GroupUtils
.getGroupId(groupMasterKey
);
1943 group
= account
.getGroupStore().getGroupByV2Id(groupId
);
1945 if (group
!= null && group
.isBlocked()) {
1953 private List
<HandleAction
> handleMessage(
1954 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1956 List
<HandleAction
> actions
= new ArrayList
<>();
1957 if (content
!= null) {
1958 final SignalServiceAddress sender
;
1959 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1960 sender
= envelope
.getSourceAddress();
1962 sender
= content
.getSender();
1964 // Store uuid if we don't have it already
1965 resolveSignalServiceAddress(sender
);
1967 if (content
.getDataMessage().isPresent()) {
1968 SignalServiceDataMessage message
= content
.getDataMessage().get();
1970 if (content
.isNeedsReceipt()) {
1971 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1974 actions
.addAll(handleSignalServiceDataMessage(message
,
1977 account
.getSelfAddress(),
1978 ignoreAttachments
));
1980 if (content
.getSyncMessage().isPresent()) {
1981 account
.setMultiDevice(true);
1982 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1983 if (syncMessage
.getSent().isPresent()) {
1984 SentTranscriptMessage message
= syncMessage
.getSent().get();
1985 final SignalServiceAddress destination
= message
.getDestination().orNull();
1986 if (destination
!= null) {
1987 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1991 ignoreAttachments
));
1994 if (syncMessage
.getRequest().isPresent()) {
1995 RequestMessage rm
= syncMessage
.getRequest().get();
1996 if (rm
.isContactsRequest()) {
1997 actions
.add(SendSyncContactsAction
.create());
1999 if (rm
.isGroupsRequest()) {
2000 actions
.add(SendSyncGroupsAction
.create());
2002 if (rm
.isBlockedListRequest()) {
2003 actions
.add(SendSyncBlockedListAction
.create());
2005 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2007 if (syncMessage
.getGroups().isPresent()) {
2008 File tmpFile
= null;
2010 tmpFile
= IOUtils
.createTempFile();
2011 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2013 .asPointer(), tmpFile
)) {
2014 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2016 while ((g
= s
.read()) != null) {
2017 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
2018 if (syncGroup
!= null) {
2019 if (g
.getName().isPresent()) {
2020 syncGroup
.name
= g
.getName().get();
2022 syncGroup
.addMembers(g
.getMembers()
2024 .map(this::resolveSignalServiceAddress
)
2025 .collect(Collectors
.toSet()));
2026 if (!g
.isActive()) {
2027 syncGroup
.removeMember(account
.getSelfAddress());
2029 // Add ourself to the member set as it's marked as active
2030 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
2032 syncGroup
.blocked
= g
.isBlocked();
2033 if (g
.getColor().isPresent()) {
2034 syncGroup
.color
= g
.getColor().get();
2037 if (g
.getAvatar().isPresent()) {
2038 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
2040 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2041 syncGroup
.archived
= g
.isArchived();
2042 account
.getGroupStore().updateGroup(syncGroup
);
2046 } catch (Exception e
) {
2047 e
.printStackTrace();
2049 if (tmpFile
!= null) {
2051 Files
.delete(tmpFile
.toPath());
2052 } catch (IOException e
) {
2053 System
.err
.println("Failed to delete received groups temp file “"
2061 if (syncMessage
.getBlockedList().isPresent()) {
2062 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2063 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2064 setContactBlocked(resolveSignalServiceAddress(address
), true);
2066 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
2068 setGroupBlocked(groupId
, true);
2069 } catch (GroupNotFoundException e
) {
2070 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
2071 + Base64
.encodeBytes(groupId
));
2075 if (syncMessage
.getContacts().isPresent()) {
2076 File tmpFile
= null;
2078 tmpFile
= IOUtils
.createTempFile();
2079 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2080 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2081 .asPointer(), tmpFile
)) {
2082 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2083 if (contactsMessage
.isComplete()) {
2084 account
.getContactStore().clear();
2087 while ((c
= s
.read()) != null) {
2088 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2089 account
.setProfileKey(c
.getProfileKey().get());
2091 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2092 ContactInfo contact
= account
.getContactStore().getContact(address
);
2093 if (contact
== null) {
2094 contact
= new ContactInfo(address
);
2096 if (c
.getName().isPresent()) {
2097 contact
.name
= c
.getName().get();
2099 if (c
.getColor().isPresent()) {
2100 contact
.color
= c
.getColor().get();
2102 if (c
.getProfileKey().isPresent()) {
2103 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2105 if (c
.getVerified().isPresent()) {
2106 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2107 account
.getSignalProtocolStore()
2108 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2109 verifiedMessage
.getIdentityKey(),
2110 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2112 if (c
.getExpirationTimer().isPresent()) {
2113 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2115 contact
.blocked
= c
.isBlocked();
2116 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2117 contact
.archived
= c
.isArchived();
2118 account
.getContactStore().updateContact(contact
);
2120 if (c
.getAvatar().isPresent()) {
2121 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2125 } catch (Exception e
) {
2126 e
.printStackTrace();
2128 if (tmpFile
!= null) {
2130 Files
.delete(tmpFile
.toPath());
2131 } catch (IOException e
) {
2132 System
.err
.println("Failed to delete received contacts temp file “"
2140 if (syncMessage
.getVerified().isPresent()) {
2141 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2142 account
.getSignalProtocolStore()
2143 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2144 verifiedMessage
.getIdentityKey(),
2145 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2147 if (syncMessage
.getStickerPackOperations().isPresent()) {
2148 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2150 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2151 if (!m
.getPackId().isPresent()) {
2154 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2155 if (sticker
== null) {
2156 if (!m
.getPackKey().isPresent()) {
2159 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2161 sticker
.setInstalled(!m
.getType().isPresent()
2162 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2163 account
.getStickerStore().updateSticker(sticker
);
2166 if (syncMessage
.getConfiguration().isPresent()) {
2174 private File
getContactAvatarFile(String number
) {
2175 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2178 private File
retrieveContactAvatarAttachment(
2179 SignalServiceAttachment attachment
, String number
2180 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2181 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2182 if (attachment
.isPointer()) {
2183 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2184 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2186 SignalServiceAttachmentStream stream
= attachment
.asStream();
2187 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2191 private File
getGroupAvatarFile(byte[] groupId
) {
2192 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2195 private File
retrieveGroupAvatarAttachment(
2196 SignalServiceAttachment attachment
, byte[] groupId
2197 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2198 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2199 if (attachment
.isPointer()) {
2200 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2201 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2203 SignalServiceAttachmentStream stream
= attachment
.asStream();
2204 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2208 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2209 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2212 private File
retrieveProfileAvatar(
2213 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2214 ) throws IOException
{
2215 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2216 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2217 File outputFile
= getProfileAvatarFile(address
);
2219 File tmpFile
= IOUtils
.createTempFile();
2220 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2223 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2224 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2225 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2228 Files
.delete(tmpFile
.toPath());
2229 } catch (IOException e
) {
2230 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2236 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2237 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2240 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2241 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2242 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2245 private File
retrieveAttachment(
2246 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2247 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2248 if (storePreview
&& pointer
.getPreview().isPresent()) {
2249 File previewFile
= new File(outputFile
+ ".preview");
2250 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2251 byte[] preview
= pointer
.getPreview().get();
2252 output
.write(preview
, 0, preview
.length
);
2253 } catch (FileNotFoundException e
) {
2254 e
.printStackTrace();
2259 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2261 File tmpFile
= IOUtils
.createTempFile();
2262 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2264 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2265 IOUtils
.copyStreamToFile(input
, outputFile
);
2268 Files
.delete(tmpFile
.toPath());
2269 } catch (IOException e
) {
2270 System
.err
.println("Failed to delete received attachment temp file “"
2279 private InputStream
retrieveAttachmentAsStream(
2280 SignalServiceAttachmentPointer pointer
, File tmpFile
2281 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2282 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2283 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2286 void sendGroups() throws IOException
, UntrustedIdentityException
{
2287 File groupsFile
= IOUtils
.createTempFile();
2290 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2291 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2292 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2293 if (record instanceof GroupInfoV1
) {
2294 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2295 out
.write(new DeviceGroup(groupInfo
.groupId
,
2296 Optional
.fromNullable(groupInfo
.name
),
2297 new ArrayList
<>(groupInfo
.getMembers()),
2298 createGroupAvatarAttachment(groupInfo
.groupId
),
2299 groupInfo
.isMember(account
.getSelfAddress()),
2300 Optional
.of(groupInfo
.messageExpirationTime
),
2301 Optional
.fromNullable(groupInfo
.color
),
2303 Optional
.fromNullable(groupInfo
.inboxPosition
),
2304 groupInfo
.archived
));
2309 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2310 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2311 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2312 .withStream(groupsFileStream
)
2313 .withContentType("application/octet-stream")
2314 .withLength(groupsFile
.length())
2317 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2322 Files
.delete(groupsFile
.toPath());
2323 } catch (IOException e
) {
2324 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2329 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2330 File contactsFile
= IOUtils
.createTempFile();
2333 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2334 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2335 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2336 VerifiedMessage verifiedMessage
= null;
2337 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2338 .getIdentity(record.getAddress());
2339 if (currentIdentity
!= null) {
2340 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2341 currentIdentity
.getIdentityKey(),
2342 currentIdentity
.getTrustLevel().toVerifiedState(),
2343 currentIdentity
.getDateAdded().getTime());
2346 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2347 out
.write(new DeviceContact(record.getAddress(),
2348 Optional
.fromNullable(record.name
),
2349 createContactAvatarAttachment(record.number
),
2350 Optional
.fromNullable(record.color
),
2351 Optional
.fromNullable(verifiedMessage
),
2352 Optional
.fromNullable(profileKey
),
2354 Optional
.of(record.messageExpirationTime
),
2355 Optional
.fromNullable(record.inboxPosition
),
2359 if (account
.getProfileKey() != null) {
2360 // Send our own profile key as well
2361 out
.write(new DeviceContact(account
.getSelfAddress(),
2366 Optional
.of(account
.getProfileKey()),
2374 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2375 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2376 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2377 .withStream(contactsFileStream
)
2378 .withContentType("application/octet-stream")
2379 .withLength(contactsFile
.length())
2382 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2387 Files
.delete(contactsFile
.toPath());
2388 } catch (IOException e
) {
2389 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2394 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2395 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2396 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2397 if (record.blocked
) {
2398 addresses
.add(record.getAddress());
2401 List
<byte[]> groupIds
= new ArrayList
<>();
2402 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2403 if (record.isBlocked()) {
2404 groupIds
.add(record.groupId
);
2407 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2410 private void sendVerifiedMessage(
2411 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2412 ) throws IOException
, UntrustedIdentityException
{
2413 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2415 trustLevel
.toVerifiedState(),
2416 System
.currentTimeMillis());
2417 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2420 public List
<ContactInfo
> getContacts() {
2421 return account
.getContactStore().getContacts();
2424 public ContactInfo
getContact(String number
) {
2425 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2428 public GroupInfo
getGroup(byte[] groupId
) {
2429 return account
.getGroupStore().getGroup(groupId
);
2432 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2433 return account
.getSignalProtocolStore().getIdentities();
2436 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2437 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2441 * Trust this the identity with this fingerprint
2443 * @param name username of the identity
2444 * @param fingerprint Fingerprint
2446 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2447 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2448 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2452 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2453 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2457 account
.getSignalProtocolStore()
2458 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2460 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2461 } catch (IOException
| UntrustedIdentityException e
) {
2462 e
.printStackTrace();
2471 * Trust this the identity with this safety number
2473 * @param name username of the identity
2474 * @param safetyNumber Safety number
2476 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2477 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2478 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2482 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2483 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2487 account
.getSignalProtocolStore()
2488 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2490 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2491 } catch (IOException
| UntrustedIdentityException e
) {
2492 e
.printStackTrace();
2501 * Trust all keys of this identity without verification
2503 * @param name username of the identity
2505 public boolean trustIdentityAllKeys(String name
) {
2506 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2507 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2511 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2512 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2513 account
.getSignalProtocolStore()
2514 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2516 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2517 } catch (IOException
| UntrustedIdentityException e
) {
2518 e
.printStackTrace();
2526 public String
computeSafetyNumber(
2527 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2529 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2530 getIdentityKeyPair().getPublicKey(),
2535 void saveAccount() {
2539 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2540 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2542 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2543 return resolveSignalServiceAddress(canonicalizedNumber
);
2546 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2547 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2549 return resolveSignalServiceAddress(address
);
2552 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2553 if (address
.matches(account
.getSelfAddress())) {
2554 return account
.getSelfAddress();
2557 return account
.getRecipientStore().resolveServiceAddress(address
);
2561 public void close() throws IOException
{
2562 if (messagePipe
!= null) {
2563 messagePipe
.shutdown();
2567 if (unidentifiedMessagePipe
!= null) {
2568 unidentifiedMessagePipe
.shutdown();
2569 unidentifiedMessagePipe
= null;
2575 public interface ReceiveMessageHandler
{
2577 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);