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
);
327 public void checkAccountState() throws IOException
{
328 if (account
.isRegistered()) {
329 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
333 if (account
.getUuid() == null) {
334 account
.setUuid(accountManager
.getOwnUuid());
337 updateAccountAttributes();
341 public boolean isRegistered() {
342 return account
.isRegistered();
345 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
346 account
.setPassword(KeyUtils
.createPassword());
348 // Resetting UUID, because registering doesn't work otherwise
349 account
.setUuid(null);
350 accountManager
= createSignalServiceAccountManager();
351 this.groupsV2Api
= accountManager
.getGroupsV2Api();
353 if (voiceVerification
) {
354 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
355 Optional
.fromNullable(captcha
),
358 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
361 account
.setRegistered(false);
365 public void updateAccountAttributes() throws IOException
{
366 accountManager
.setAccountAttributes(account
.getSignalingKey(),
367 account
.getSignalProtocolStore().getLocalRegistrationId(),
369 account
.getRegistrationLockPin(),
370 account
.getRegistrationLock(),
371 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
372 unrestrictedUnidentifiedAccess
,
374 discoverableByPhoneNumber
);
377 public void setProfile(String name
, File avatar
) throws IOException
{
378 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
379 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
383 public void unregister() throws IOException
{
384 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
385 // If this is the master device, other users can't send messages to this number anymore.
386 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
387 accountManager
.setGcmId(Optional
.absent());
389 account
.setRegistered(false);
393 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
394 List
<DeviceInfo
> devices
= accountManager
.getDevices();
395 account
.setMultiDevice(devices
.size() > 1);
400 public void removeLinkedDevices(int deviceId
) throws IOException
{
401 accountManager
.removeDevice(deviceId
);
402 List
<DeviceInfo
> devices
= accountManager
.getDevices();
403 account
.setMultiDevice(devices
.size() > 1);
407 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
408 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
410 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
413 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
414 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
415 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
417 accountManager
.addDevice(deviceIdentifier
,
420 Optional
.of(account
.getProfileKey().serialize()),
422 account
.setMultiDevice(true);
426 private List
<PreKeyRecord
> generatePreKeys() {
427 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
429 final int offset
= account
.getPreKeyIdOffset();
430 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
431 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
432 ECKeyPair keyPair
= Curve
.generateKeyPair();
433 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
438 account
.addPreKeys(records
);
444 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
446 ECKeyPair keyPair
= Curve
.generateKeyPair();
447 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
448 keyPair
.getPublicKey().serialize());
449 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
450 System
.currentTimeMillis(),
454 account
.addSignedPreKey(record);
458 } catch (InvalidKeyException e
) {
459 throw new AssertionError(e
);
463 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
464 verificationCode
= verificationCode
.replace("-", "");
465 account
.setSignalingKey(KeyUtils
.createSignalingKey());
466 // TODO make unrestricted unidentified access configurable
467 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
468 account
.getSignalingKey(),
469 account
.getSignalProtocolStore().getLocalRegistrationId(),
473 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
474 unrestrictedUnidentifiedAccess
,
476 discoverableByPhoneNumber
);
478 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
479 // TODO response.isStorageCapable()
480 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
481 account
.setRegistered(true);
482 account
.setUuid(uuid
);
483 account
.setRegistrationLockPin(pin
);
484 account
.getSignalProtocolStore()
485 .saveIdentity(account
.getSelfAddress(),
486 getIdentityKeyPair().getPublicKey(),
487 TrustLevel
.TRUSTED_VERIFIED
);
493 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
494 if (pin
.isPresent()) {
495 account
.setRegistrationLockPin(pin
.get());
496 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
498 account
.setRegistrationLockPin(null);
499 accountManager
.removeRegistrationLockV1();
504 void refreshPreKeys() throws IOException
{
505 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
506 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
507 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
509 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
512 private SignalServiceMessageReceiver
createMessageReceiver() {
513 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
514 serviceConfiguration
).getProfileOperations() : null;
515 return new SignalServiceMessageReceiver(serviceConfiguration
,
517 account
.getUsername(),
518 account
.getPassword(),
519 account
.getDeviceId(),
520 account
.getSignalingKey(),
524 clientZkProfileOperations
);
527 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
528 if (messageReceiver
== null) {
529 messageReceiver
= createMessageReceiver();
531 return messageReceiver
;
534 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
535 if (messagePipe
== null) {
536 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
541 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
542 if (unidentifiedMessagePipe
== null) {
543 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
545 return unidentifiedMessagePipe
;
548 private SignalServiceMessageSender
createMessageSender() {
549 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
550 serviceConfiguration
).getProfileOperations() : null;
551 final ExecutorService executor
= null;
552 return new SignalServiceMessageSender(serviceConfiguration
,
554 account
.getUsername(),
555 account
.getPassword(),
556 account
.getDeviceId(),
557 account
.getSignalProtocolStore(),
559 account
.isMultiDevice(),
560 Optional
.fromNullable(messagePipe
),
561 Optional
.fromNullable(unidentifiedMessagePipe
),
563 clientZkProfileOperations
,
565 ServiceConfig
.MAX_ENVELOPE_SIZE
);
568 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
569 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
572 private SignalProfile
getRecipientProfile(
573 SignalServiceAddress address
575 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
576 if (profileEntry
== null) {
579 long now
= new Date().getTime();
580 // Profiles are cache for 24h before retrieving them again
581 if (!profileEntry
.isRequestPending() && (
582 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
584 ProfileKey profileKey
= profileEntry
.getProfileKey();
585 profileEntry
.setRequestPending(true);
586 SignalProfile profile
;
588 profile
= retrieveRecipientProfile(address
, profileKey
);
589 } catch (IOException e
) {
590 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
591 profileEntry
.setRequestPending(false);
594 profileEntry
.setRequestPending(false);
595 account
.getProfileStore()
596 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
599 return profileEntry
.getProfile();
602 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
603 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
604 if (profileEntry
== null) {
607 if (profileEntry
.getProfileKeyCredential() == null) {
608 ProfileAndCredential profileAndCredential
;
610 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
611 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
612 } catch (IOException e
) {
613 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
617 long now
= new Date().getTime();
618 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
619 final SignalProfile profile
= decryptProfile(address
,
620 profileEntry
.getProfileKey(),
621 profileAndCredential
.getProfile());
622 account
.getProfileStore()
623 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
624 return profileKeyCredential
;
626 return profileEntry
.getProfileKeyCredential();
629 private SignalProfile
retrieveRecipientProfile(
630 SignalServiceAddress address
, ProfileKey profileKey
631 ) throws IOException
{
632 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
634 return decryptProfile(address
, profileKey
, encryptedProfile
);
637 private SignalProfile
decryptProfile(
638 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
640 File avatarFile
= null;
642 avatarFile
= encryptedProfile
.getAvatar() == null
644 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
645 } catch (Throwable e
) {
646 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
649 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
653 name
= encryptedProfile
.getName() == null
655 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
656 } catch (IOException e
) {
659 String unidentifiedAccess
;
661 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
662 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
664 : encryptedProfile
.getUnidentifiedAccess();
665 } catch (IOException e
) {
666 unidentifiedAccess
= null;
668 return new SignalProfile(encryptedProfile
.getIdentityKey(),
672 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
673 encryptedProfile
.getCapabilities());
674 } catch (InvalidCiphertextException e
) {
679 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
680 File file
= getGroupAvatarFile(groupId
);
681 if (!file
.exists()) {
682 return Optional
.absent();
685 return Optional
.of(Utils
.createAttachment(file
));
688 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
689 File file
= getContactAvatarFile(number
);
690 if (!file
.exists()) {
691 return Optional
.absent();
694 return Optional
.of(Utils
.createAttachment(file
));
697 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
698 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
700 throw new GroupNotFoundException(groupId
);
702 if (!g
.isMember(account
.getSelfAddress())) {
703 throw new NotAGroupMemberException(groupId
, g
.getTitle());
708 public List
<GroupInfo
> getGroups() {
709 return account
.getGroupStore().getGroups();
712 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
713 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
714 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
715 final GroupInfo g
= getGroupForSending(groupId
);
717 GroupUtils
.setGroupContext(messageBuilder
, g
);
718 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
720 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
723 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
724 String messageText
, List
<String
> attachments
, byte[] groupId
725 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
726 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
727 .withBody(messageText
);
728 if (attachments
!= null) {
729 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
732 return sendGroupMessage(messageBuilder
, groupId
);
735 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
736 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
737 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
738 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
740 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
741 targetSentTimestamp
);
742 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
743 .withReaction(reaction
);
745 return sendGroupMessage(messageBuilder
, groupId
);
748 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
750 SignalServiceDataMessage
.Builder messageBuilder
;
752 final GroupInfo g
= getGroupForSending(groupId
);
753 if (g
instanceof GroupInfoV1
) {
754 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
755 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
758 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
759 groupInfoV1
.removeMember(account
.getSelfAddress());
760 account
.getGroupStore().updateGroup(groupInfoV1
);
762 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
763 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
764 groupInfoV2
.setGroup(groupGroupChangePair
.first());
765 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
766 account
.getGroupStore().updateGroup(groupInfoV2
);
769 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
772 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
773 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
774 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
776 SignalServiceDataMessage
.Builder messageBuilder
;
777 if (groupId
== null) {
779 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
781 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
782 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
783 updateGroupV1(gv1
, name
, members
, avatarFile
);
784 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
787 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
791 GroupInfo group
= getGroupForSending(groupId
);
792 if (group
instanceof GroupInfoV2
) {
793 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= null;
794 if (members
!= null) {
795 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
796 newMembers
.removeAll(group
.getMembers());
797 if (newMembers
.size() > 0) {
798 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, newMembers
);
801 if (groupGroupChangePair
== null || name
!= null || avatarFile
!= null) {
802 if (groupGroupChangePair
!= null) {
803 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
804 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
805 groupGroupChangePair
.second().toByteArray());
806 sendMessage(messageBuilder
, group
.getMembersWithout(account
.getSelfAddress()));
809 groupGroupChangePair
= groupHelper
.updateGroupV2((GroupInfoV2
) group
, name
, avatarFile
);
812 ((GroupInfoV2
) group
).setGroup(groupGroupChangePair
.first());
813 messageBuilder
= getGroupUpdateMessageBuilder((GroupInfoV2
) group
,
814 groupGroupChangePair
.second().toByteArray());
817 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
818 updateGroupV1(gv1
, name
, members
, avatarFile
);
819 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
824 account
.getGroupStore().updateGroup(g
);
826 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
827 g
.getMembersWithout(account
.getSelfAddress()));
828 return new Pair
<>(g
.groupId
, result
.second());
831 private void updateGroupV1(
834 final Collection
<SignalServiceAddress
> members
,
835 final String avatarFile
836 ) throws IOException
{
841 if (members
!= null) {
842 final Set
<String
> newE164Members
= new HashSet
<>();
843 for (SignalServiceAddress member
: members
) {
844 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
847 newE164Members
.add(member
.getNumber().get());
850 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
851 if (contacts
.size() != newE164Members
.size()) {
852 // Some of the new members are not registered on Signal
853 for (ContactTokenDetails contact
: contacts
) {
854 newE164Members
.remove(contact
.getNumber());
856 throw new IOException("Failed to add members "
857 + Util
.join(", ", newE164Members
)
858 + " to group: Not registered on Signal");
861 g
.addMembers(members
);
864 if (avatarFile
!= null) {
865 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
866 File aFile
= getGroupAvatarFile(g
.groupId
);
867 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
871 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
872 byte[] groupId
, SignalServiceAddress recipient
873 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
875 GroupInfo group
= getGroupForSending(groupId
);
876 if (!(group
instanceof GroupInfoV1
)) {
877 throw new RuntimeException("Received an invalid group request for a v2 group!");
879 g
= (GroupInfoV1
) group
;
881 if (!g
.isMember(recipient
)) {
882 throw new NotAGroupMemberException(groupId
, g
.name
);
885 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
887 // Send group message only to the recipient who requested it
888 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
891 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
892 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
895 .withMembers(new ArrayList
<>(g
.getMembers()));
897 File aFile
= getGroupAvatarFile(g
.groupId
);
898 if (aFile
.exists()) {
900 group
.withAvatar(Utils
.createAttachment(aFile
));
901 } catch (IOException e
) {
902 throw new AttachmentInvalidException(aFile
.toString(), e
);
906 return SignalServiceDataMessage
.newBuilder()
907 .asGroupMessage(group
.build())
908 .withExpiration(g
.getMessageExpirationTime());
911 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
912 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
913 .withRevision(g
.getGroup().getRevision())
914 .withSignedGroupChange(signedGroupChange
);
915 return SignalServiceDataMessage
.newBuilder()
916 .asGroupMessage(group
.build())
917 .withExpiration(g
.getMessageExpirationTime());
920 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
921 byte[] groupId
, SignalServiceAddress recipient
922 ) throws IOException
{
923 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
926 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
927 .asGroupMessage(group
.build());
929 // Send group info request message to the recipient who sent us a message with this groupId
930 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
934 SignalServiceAddress remoteAddress
, long messageId
935 ) throws IOException
, UntrustedIdentityException
{
936 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
937 Collections
.singletonList(messageId
),
938 System
.currentTimeMillis());
940 createMessageSender().sendReceipt(remoteAddress
,
941 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
945 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
946 String messageText
, List
<String
> attachments
, List
<String
> recipients
947 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
948 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
949 .withBody(messageText
);
950 if (attachments
!= null) {
951 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
953 // Upload attachments here, so we only upload once even for multiple recipients
954 SignalServiceMessageSender messageSender
= createMessageSender();
955 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
956 for (SignalServiceAttachment attachment
: attachmentStreams
) {
957 if (attachment
.isStream()) {
958 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
959 } else if (attachment
.isPointer()) {
960 attachmentPointers
.add(attachment
.asPointer());
964 messageBuilder
.withAttachments(attachmentPointers
);
966 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
969 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
970 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
971 ) throws IOException
, InvalidNumberException
{
972 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
974 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
975 targetSentTimestamp
);
976 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
977 .withReaction(reaction
);
978 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
981 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
982 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
984 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
986 return sendMessage(messageBuilder
, signalServiceAddresses
);
987 } catch (Exception e
) {
988 for (SignalServiceAddress address
: signalServiceAddresses
) {
989 handleEndSession(address
);
996 public String
getContactName(String number
) throws InvalidNumberException
{
997 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
998 if (contact
== null) {
1001 return contact
.name
;
1005 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1006 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1007 ContactInfo contact
= account
.getContactStore().getContact(address
);
1008 if (contact
== null) {
1009 contact
= new ContactInfo(address
);
1011 contact
.name
= name
;
1012 account
.getContactStore().updateContact(contact
);
1016 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1017 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1020 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1021 ContactInfo contact
= account
.getContactStore().getContact(address
);
1022 if (contact
== null) {
1023 contact
= new ContactInfo(address
);
1025 contact
.blocked
= blocked
;
1026 account
.getContactStore().updateContact(contact
);
1030 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1031 GroupInfo group
= getGroup(groupId
);
1032 if (group
== null) {
1033 throw new GroupNotFoundException(groupId
);
1036 group
.setBlocked(blocked
);
1037 account
.getGroupStore().updateGroup(group
);
1041 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1042 byte[] groupId
, String name
, List
<String
> members
, String avatar
1043 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1044 return sendUpdateGroupMessage(groupId
,
1046 members
== null ?
null : getSignalServiceAddresses(members
),
1051 * Change the expiration timer for a contact
1053 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1054 ContactInfo contact
= account
.getContactStore().getContact(address
);
1055 contact
.messageExpirationTime
= messageExpirationTimer
;
1056 account
.getContactStore().updateContact(contact
);
1057 sendExpirationTimerUpdate(address
);
1061 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1062 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1063 .asExpirationUpdate();
1064 sendMessage(messageBuilder
, Collections
.singleton(address
));
1068 * Change the expiration timer for a contact
1070 public void setExpirationTimer(
1071 String number
, int messageExpirationTimer
1072 ) throws IOException
, InvalidNumberException
{
1073 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1074 setExpirationTimer(address
, messageExpirationTimer
);
1078 * Change the expiration timer for a group
1080 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1081 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1082 if (g
instanceof GroupInfoV1
) {
1083 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1084 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1085 account
.getGroupStore().updateGroup(groupInfoV1
);
1087 throw new RuntimeException("TODO Not implemented!");
1092 * Upload the sticker pack from path.
1094 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1095 * @return if successful, returns the URL to install the sticker pack in the signal app
1097 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1098 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1100 SignalServiceMessageSender messageSender
= createMessageSender();
1102 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1103 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1105 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1106 account
.getStickerStore().updateSticker(sticker
);
1110 return new URI("https",
1113 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1114 Hex
.toStringCondensed(packKey
),
1115 StandardCharsets
.UTF_8
)).toString();
1116 } catch (URISyntaxException e
) {
1117 throw new AssertionError(e
);
1121 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1123 ) throws IOException
, StickerPackInvalidException
{
1125 String rootPath
= null;
1127 final File file
= new File(path
);
1128 if (file
.getName().endsWith(".zip")) {
1129 zip
= new ZipFile(file
);
1130 } else if (file
.getName().equals("manifest.json")) {
1131 rootPath
= file
.getParent();
1133 throw new StickerPackInvalidException("Could not find manifest.json");
1136 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1138 if (pack
.stickers
== null) {
1139 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1142 if (pack
.stickers
.isEmpty()) {
1143 throw new StickerPackInvalidException("Must include stickers.");
1146 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1147 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1148 if (sticker
.file
== null) {
1149 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1152 Pair
<InputStream
, Long
> data
;
1154 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1155 } catch (IOException ignored
) {
1156 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1159 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1160 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1162 Optional
.fromNullable(sticker
.emoji
).or(""),
1164 stickers
.add(stickerInfo
);
1167 StickerInfo cover
= null;
1168 if (pack
.cover
!= null) {
1169 if (pack
.cover
.file
== null) {
1170 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1173 Pair
<InputStream
, Long
> data
;
1175 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1176 } catch (IOException ignored
) {
1177 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1180 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1181 cover
= new StickerInfo(data
.first(),
1183 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1187 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1190 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1191 InputStream inputStream
;
1193 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1195 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1197 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1200 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1201 final String rootPath
, final ZipFile zip
, final String subfile
1202 ) throws IOException
{
1204 final ZipEntry entry
= zip
.getEntry(subfile
);
1205 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1207 final File file
= new File(rootPath
, subfile
);
1208 return new Pair
<>(new FileInputStream(file
), file
.length());
1212 void requestSyncGroups() throws IOException
{
1213 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1214 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1216 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1218 sendSyncMessage(message
);
1219 } catch (UntrustedIdentityException e
) {
1220 e
.printStackTrace();
1224 void requestSyncContacts() throws IOException
{
1225 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1226 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1228 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1230 sendSyncMessage(message
);
1231 } catch (UntrustedIdentityException e
) {
1232 e
.printStackTrace();
1236 void requestSyncBlocked() throws IOException
{
1237 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1238 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1240 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1242 sendSyncMessage(message
);
1243 } catch (UntrustedIdentityException e
) {
1244 e
.printStackTrace();
1248 void requestSyncConfiguration() throws IOException
{
1249 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1250 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1252 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1254 sendSyncMessage(message
);
1255 } catch (UntrustedIdentityException e
) {
1256 e
.printStackTrace();
1260 private byte[] getSenderCertificate() {
1261 // TODO support UUID capable sender certificates
1262 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1265 certificate
= accountManager
.getSenderCertificate();
1266 } catch (IOException e
) {
1267 System
.err
.println("Failed to get sender certificate: " + e
);
1270 // TODO cache for a day
1274 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1275 SignalServiceMessageSender messageSender
= createMessageSender();
1277 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1278 } catch (UntrustedIdentityException e
) {
1279 account
.getSignalProtocolStore()
1280 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1282 TrustLevel
.UNTRUSTED
);
1287 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1288 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1289 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1291 for (String number
: numbers
) {
1292 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1293 if (resolvedAddress
.getUuid().isPresent()) {
1294 signalServiceAddresses
.add(resolvedAddress
);
1296 missingUuids
.add(resolvedAddress
);
1300 Map
<String
, UUID
> registeredUsers
;
1302 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1303 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1305 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1306 System
.err
.println("Failed to resolve uuids from server: " + e
.getMessage());
1307 registeredUsers
= new HashMap
<>();
1310 for (SignalServiceAddress address
: missingUuids
) {
1311 final String number
= address
.getNumber().get();
1312 if (registeredUsers
.containsKey(number
)) {
1313 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1314 registeredUsers
.get(number
),
1316 signalServiceAddresses
.add(newAddress
);
1318 signalServiceAddresses
.add(address
);
1322 return signalServiceAddresses
;
1325 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1326 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1327 ) throws IOException
{
1328 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1329 final long timestamp
= System
.currentTimeMillis();
1330 messageBuilder
.withTimestamp(timestamp
);
1331 getOrCreateMessagePipe();
1332 getOrCreateUnidentifiedMessagePipe();
1333 SignalServiceDataMessage message
= null;
1335 message
= messageBuilder
.build();
1336 if (message
.getGroupContext().isPresent()) {
1338 SignalServiceMessageSender messageSender
= createMessageSender();
1339 final boolean isRecipientUpdate
= false;
1340 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1341 unidentifiedAccessHelper
.getAccessFor(recipients
),
1344 for (SendMessageResult r
: result
) {
1345 if (r
.getIdentityFailure() != null) {
1346 account
.getSignalProtocolStore()
1347 .saveIdentity(r
.getAddress(),
1348 r
.getIdentityFailure().getIdentityKey(),
1349 TrustLevel
.UNTRUSTED
);
1352 return new Pair
<>(timestamp
, result
);
1353 } catch (UntrustedIdentityException e
) {
1354 account
.getSignalProtocolStore()
1355 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1357 TrustLevel
.UNTRUSTED
);
1358 return new Pair
<>(timestamp
, Collections
.emptyList());
1361 // Send to all individually, so sync messages are sent correctly
1362 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1363 for (SignalServiceAddress address
: recipients
) {
1364 ContactInfo contact
= account
.getContactStore().getContact(address
);
1365 if (contact
!= null) {
1366 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1367 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1369 messageBuilder
.withExpiration(0);
1370 messageBuilder
.withProfileKey(null);
1372 message
= messageBuilder
.build();
1373 if (address
.matches(account
.getSelfAddress())) {
1374 results
.add(sendSelfMessage(message
));
1376 results
.add(sendMessage(address
, message
));
1379 return new Pair
<>(timestamp
, results
);
1382 if (message
!= null && message
.isEndSession()) {
1383 for (SignalServiceAddress recipient
: recipients
) {
1384 handleEndSession(recipient
);
1391 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1392 SignalServiceMessageSender messageSender
= createMessageSender();
1394 SignalServiceAddress recipient
= account
.getSelfAddress();
1396 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1397 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1398 message
.getTimestamp(),
1400 message
.getExpiresInSeconds(),
1401 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1403 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1406 long startTime
= System
.currentTimeMillis();
1407 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1408 return SendMessageResult
.success(recipient
,
1409 unidentifiedAccess
.isPresent(),
1411 System
.currentTimeMillis() - startTime
);
1412 } catch (UntrustedIdentityException e
) {
1413 account
.getSignalProtocolStore()
1414 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1416 TrustLevel
.UNTRUSTED
);
1417 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1421 private SendMessageResult
sendMessage(
1422 SignalServiceAddress address
, SignalServiceDataMessage message
1423 ) throws IOException
{
1424 SignalServiceMessageSender messageSender
= createMessageSender();
1427 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1428 } catch (UntrustedIdentityException e
) {
1429 account
.getSignalProtocolStore()
1430 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1432 TrustLevel
.UNTRUSTED
);
1433 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1437 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1438 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1439 account
.getSignalProtocolStore(),
1440 Utils
.getCertificateValidator());
1442 return cipher
.decrypt(envelope
);
1443 } catch (ProtocolUntrustedIdentityException e
) {
1444 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1445 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1447 account
.getSignalProtocolStore()
1448 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1449 identityException
.getUntrustedIdentity(),
1450 TrustLevel
.UNTRUSTED
);
1451 throw identityException
;
1453 throw new AssertionError(e
);
1457 private void handleEndSession(SignalServiceAddress source
) {
1458 account
.getSignalProtocolStore().deleteAllSessions(source
);
1461 private static int currentTimeDays() {
1462 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1465 private GroupsV2AuthorizationString
getGroupAuthForToday(
1466 final GroupSecretParams groupSecretParams
1467 ) throws IOException
{
1468 final int today
= currentTimeDays();
1469 // Returns credentials for the next 7 days
1470 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1471 // TODO cache credentials until they expire
1472 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1474 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1477 authCredentialResponse
);
1478 } catch (VerificationFailedException e
) {
1479 throw new IOException(e
);
1483 private List
<HandleAction
> handleSignalServiceDataMessage(
1484 SignalServiceDataMessage message
,
1486 SignalServiceAddress source
,
1487 SignalServiceAddress destination
,
1488 boolean ignoreAttachments
1490 List
<HandleAction
> actions
= new ArrayList
<>();
1491 if (message
.getGroupContext().isPresent()) {
1492 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1493 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1494 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1495 if (group
== null || group
instanceof GroupInfoV1
) {
1496 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1497 switch (groupInfo
.getType()) {
1499 if (groupV1
== null) {
1500 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1503 if (groupInfo
.getAvatar().isPresent()) {
1504 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1505 if (avatar
.isPointer()) {
1507 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1508 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1509 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1510 .getRemoteId() + "): " + e
.getMessage());
1515 if (groupInfo
.getName().isPresent()) {
1516 groupV1
.name
= groupInfo
.getName().get();
1519 if (groupInfo
.getMembers().isPresent()) {
1520 groupV1
.addMembers(groupInfo
.getMembers()
1523 .map(this::resolveSignalServiceAddress
)
1524 .collect(Collectors
.toSet()));
1527 account
.getGroupStore().updateGroup(groupV1
);
1531 if (groupV1
== null && !isSync
) {
1532 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1536 if (groupV1
!= null) {
1537 groupV1
.removeMember(source
);
1538 account
.getGroupStore().updateGroup(groupV1
);
1543 if (groupV1
!= null && !isSync
) {
1544 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1549 // Received a group v1 message for a v2 group
1552 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1553 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1554 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1556 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1558 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1559 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1560 if (groupInfo
instanceof GroupInfoV1
) {
1561 // Received a v2 group message for a v2 group, we need to locally migrate the group
1562 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1563 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1564 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1565 account
.getGroupStore().updateGroup(groupInfoV2
);
1566 System
.err
.println("Locally migrated group "
1567 + Base64
.encodeBytes(groupInfo
.groupId
)
1568 + " to group v2, id: "
1569 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1571 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1572 GroupInfoV2 groupInfoV2
= groupInfo
== null
1573 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1574 : (GroupInfoV2
) groupInfo
;
1576 if (groupInfoV2
.getGroup() == null
1577 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1578 DecryptedGroup group
= null;
1579 if (groupContext
.hasSignedGroupChange()
1580 && groupInfoV2
.getGroup() != null
1581 && groupInfoV2
.getGroup().getRevision() + 1 == groupContext
.getRevision()) {
1582 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(),
1583 groupContext
.getSignedGroupChange(),
1586 if (group
== null) {
1587 group
= getDecryptedGroup(groupSecretParams
);
1589 groupInfoV2
.setGroup(group
);
1590 account
.getGroupStore().updateGroup(groupInfoV2
);
1595 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1596 if (message
.isEndSession()) {
1597 handleEndSession(conversationPartnerAddress
);
1599 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1600 if (message
.getGroupContext().isPresent()) {
1601 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1602 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1603 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1604 if (group
!= null) {
1605 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1606 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1607 account
.getGroupStore().updateGroup(group
);
1610 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1611 // disappearing message timer already stored in the DecryptedGroup
1614 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1615 if (contact
== null) {
1616 contact
= new ContactInfo(conversationPartnerAddress
);
1618 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1619 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1620 account
.getContactStore().updateContact(contact
);
1624 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1625 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1626 if (attachment
.isPointer()) {
1628 retrieveAttachment(attachment
.asPointer());
1629 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1630 System
.err
.println("Failed to retrieve attachment ("
1631 + attachment
.asPointer().getRemoteId()
1638 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1639 final ProfileKey profileKey
;
1641 profileKey
= new ProfileKey(message
.getProfileKey().get());
1642 } catch (InvalidInputException e
) {
1643 throw new AssertionError(e
);
1645 if (source
.matches(account
.getSelfAddress())) {
1646 this.account
.setProfileKey(profileKey
);
1648 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1650 if (message
.getPreviews().isPresent()) {
1651 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1652 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1653 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1654 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1656 retrieveAttachment(attachment
);
1657 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1658 System
.err
.println("Failed to retrieve attachment ("
1659 + attachment
.getRemoteId()
1666 if (message
.getSticker().isPresent()) {
1667 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1668 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1669 if (sticker
== null) {
1670 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1671 account
.getStickerStore().updateSticker(sticker
);
1677 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1679 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1680 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1681 for (DecryptedMember member
: group
.getMembersList()) {
1682 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1683 member
.getUuid().toByteArray()), null));
1685 account
.getProfileStore()
1686 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1687 } catch (InvalidInputException ignored
) {
1691 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1692 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1697 private void retryFailedReceivedMessages(
1698 ReceiveMessageHandler handler
, boolean ignoreAttachments
1700 final File cachePath
= new File(getMessageCachePath());
1701 if (!cachePath
.exists()) {
1704 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1705 if (!dir
.isDirectory()) {
1706 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1710 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1711 if (!fileEntry
.isFile()) {
1714 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1716 // Try to delete directory if empty
1721 private void retryFailedReceivedMessage(
1722 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1724 SignalServiceEnvelope envelope
;
1726 envelope
= Utils
.loadEnvelope(fileEntry
);
1727 if (envelope
== null) {
1730 } catch (IOException e
) {
1731 e
.printStackTrace();
1734 SignalServiceContent content
= null;
1735 if (!envelope
.isReceipt()) {
1737 content
= decryptMessage(envelope
);
1738 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1740 } catch (Exception er
) {
1741 // All other errors are not recoverable, so delete the cached message
1743 Files
.delete(fileEntry
.toPath());
1744 } catch (IOException e
) {
1745 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1749 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1750 for (HandleAction action
: actions
) {
1752 action
.execute(this);
1753 } catch (Throwable e
) {
1754 e
.printStackTrace();
1759 handler
.handleMessage(envelope
, content
, null);
1761 Files
.delete(fileEntry
.toPath());
1762 } catch (IOException e
) {
1763 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1767 public void receiveMessages(
1770 boolean returnOnTimeout
,
1771 boolean ignoreAttachments
,
1772 ReceiveMessageHandler handler
1773 ) throws IOException
{
1774 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1776 Set
<HandleAction
> queuedActions
= null;
1778 getOrCreateMessagePipe();
1780 boolean hasCaughtUpWithOldMessages
= false;
1783 SignalServiceEnvelope envelope
;
1784 SignalServiceContent content
= null;
1785 Exception exception
= null;
1786 final long now
= new Date().getTime();
1788 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1789 // store message on disk, before acknowledging receipt to the server
1791 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1792 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1793 Utils
.storeEnvelope(envelope1
, cacheFile
);
1794 } catch (IOException e
) {
1795 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1799 if (result
.isPresent()) {
1800 envelope
= result
.get();
1802 // Received indicator that server queue is empty
1803 hasCaughtUpWithOldMessages
= true;
1805 if (queuedActions
!= null) {
1806 for (HandleAction action
: queuedActions
) {
1808 action
.execute(this);
1809 } catch (Throwable e
) {
1810 e
.printStackTrace();
1814 queuedActions
.clear();
1815 queuedActions
= null;
1818 // Continue to wait another timeout for new messages
1821 } catch (TimeoutException e
) {
1822 if (returnOnTimeout
) return;
1824 } catch (InvalidVersionException e
) {
1825 System
.err
.println("Ignoring error: " + e
.getMessage());
1829 if (envelope
.hasSource()) {
1830 // Store uuid if we don't have it already
1831 SignalServiceAddress source
= envelope
.getSourceAddress();
1832 resolveSignalServiceAddress(source
);
1834 if (!envelope
.isReceipt()) {
1836 content
= decryptMessage(envelope
);
1837 } catch (Exception e
) {
1840 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1841 if (hasCaughtUpWithOldMessages
) {
1842 for (HandleAction action
: actions
) {
1844 action
.execute(this);
1845 } catch (Throwable e
) {
1846 e
.printStackTrace();
1850 if (queuedActions
== null) {
1851 queuedActions
= new HashSet
<>();
1853 queuedActions
.addAll(actions
);
1857 if (!isMessageBlocked(envelope
, content
)) {
1858 handler
.handleMessage(envelope
, content
, exception
);
1860 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1861 File cacheFile
= null;
1863 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1864 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1865 Files
.delete(cacheFile
.toPath());
1866 // Try to delete directory if empty
1867 new File(getMessageCachePath()).delete();
1868 } catch (IOException e
) {
1869 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1875 private boolean isMessageBlocked(
1876 SignalServiceEnvelope envelope
, SignalServiceContent content
1878 SignalServiceAddress source
;
1879 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1880 source
= envelope
.getSourceAddress();
1881 } else if (content
!= null) {
1882 source
= content
.getSender();
1886 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1887 if (sourceContact
!= null && sourceContact
.blocked
) {
1891 if (content
!= null && content
.getDataMessage().isPresent()) {
1892 SignalServiceDataMessage message
= content
.getDataMessage().get();
1893 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1894 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1895 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1896 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1902 private List
<HandleAction
> handleMessage(
1903 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1905 List
<HandleAction
> actions
= new ArrayList
<>();
1906 if (content
!= null) {
1907 final SignalServiceAddress sender
;
1908 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1909 sender
= envelope
.getSourceAddress();
1911 sender
= content
.getSender();
1913 // Store uuid if we don't have it already
1914 resolveSignalServiceAddress(sender
);
1916 if (content
.getDataMessage().isPresent()) {
1917 SignalServiceDataMessage message
= content
.getDataMessage().get();
1919 if (content
.isNeedsReceipt()) {
1920 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1923 actions
.addAll(handleSignalServiceDataMessage(message
,
1926 account
.getSelfAddress(),
1927 ignoreAttachments
));
1929 if (content
.getSyncMessage().isPresent()) {
1930 account
.setMultiDevice(true);
1931 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1932 if (syncMessage
.getSent().isPresent()) {
1933 SentTranscriptMessage message
= syncMessage
.getSent().get();
1934 final SignalServiceAddress destination
= message
.getDestination().orNull();
1935 if (destination
!= null) {
1936 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1940 ignoreAttachments
));
1943 if (syncMessage
.getRequest().isPresent()) {
1944 RequestMessage rm
= syncMessage
.getRequest().get();
1945 if (rm
.isContactsRequest()) {
1946 actions
.add(SendSyncContactsAction
.create());
1948 if (rm
.isGroupsRequest()) {
1949 actions
.add(SendSyncGroupsAction
.create());
1951 if (rm
.isBlockedListRequest()) {
1952 actions
.add(SendSyncBlockedListAction
.create());
1954 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1956 if (syncMessage
.getGroups().isPresent()) {
1957 File tmpFile
= null;
1959 tmpFile
= IOUtils
.createTempFile();
1960 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1962 .asPointer(), tmpFile
)) {
1963 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1965 while ((g
= s
.read()) != null) {
1966 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1967 if (syncGroup
!= null) {
1968 if (g
.getName().isPresent()) {
1969 syncGroup
.name
= g
.getName().get();
1971 syncGroup
.addMembers(g
.getMembers()
1973 .map(this::resolveSignalServiceAddress
)
1974 .collect(Collectors
.toSet()));
1975 if (!g
.isActive()) {
1976 syncGroup
.removeMember(account
.getSelfAddress());
1978 // Add ourself to the member set as it's marked as active
1979 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1981 syncGroup
.blocked
= g
.isBlocked();
1982 if (g
.getColor().isPresent()) {
1983 syncGroup
.color
= g
.getColor().get();
1986 if (g
.getAvatar().isPresent()) {
1987 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1989 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1990 syncGroup
.archived
= g
.isArchived();
1991 account
.getGroupStore().updateGroup(syncGroup
);
1995 } catch (Exception e
) {
1996 e
.printStackTrace();
1998 if (tmpFile
!= null) {
2000 Files
.delete(tmpFile
.toPath());
2001 } catch (IOException e
) {
2002 System
.err
.println("Failed to delete received groups temp file “"
2010 if (syncMessage
.getBlockedList().isPresent()) {
2011 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2012 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2013 setContactBlocked(resolveSignalServiceAddress(address
), true);
2015 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
2017 setGroupBlocked(groupId
, true);
2018 } catch (GroupNotFoundException e
) {
2019 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
2020 + Base64
.encodeBytes(groupId
));
2024 if (syncMessage
.getContacts().isPresent()) {
2025 File tmpFile
= null;
2027 tmpFile
= IOUtils
.createTempFile();
2028 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2029 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2030 .asPointer(), tmpFile
)) {
2031 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2032 if (contactsMessage
.isComplete()) {
2033 account
.getContactStore().clear();
2036 while ((c
= s
.read()) != null) {
2037 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2038 account
.setProfileKey(c
.getProfileKey().get());
2040 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2041 ContactInfo contact
= account
.getContactStore().getContact(address
);
2042 if (contact
== null) {
2043 contact
= new ContactInfo(address
);
2045 if (c
.getName().isPresent()) {
2046 contact
.name
= c
.getName().get();
2048 if (c
.getColor().isPresent()) {
2049 contact
.color
= c
.getColor().get();
2051 if (c
.getProfileKey().isPresent()) {
2052 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2054 if (c
.getVerified().isPresent()) {
2055 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2056 account
.getSignalProtocolStore()
2057 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2058 verifiedMessage
.getIdentityKey(),
2059 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2061 if (c
.getExpirationTimer().isPresent()) {
2062 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2064 contact
.blocked
= c
.isBlocked();
2065 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2066 contact
.archived
= c
.isArchived();
2067 account
.getContactStore().updateContact(contact
);
2069 if (c
.getAvatar().isPresent()) {
2070 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2074 } catch (Exception e
) {
2075 e
.printStackTrace();
2077 if (tmpFile
!= null) {
2079 Files
.delete(tmpFile
.toPath());
2080 } catch (IOException e
) {
2081 System
.err
.println("Failed to delete received contacts temp file “"
2089 if (syncMessage
.getVerified().isPresent()) {
2090 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2091 account
.getSignalProtocolStore()
2092 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2093 verifiedMessage
.getIdentityKey(),
2094 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2096 if (syncMessage
.getStickerPackOperations().isPresent()) {
2097 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2099 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2100 if (!m
.getPackId().isPresent()) {
2103 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2104 if (sticker
== null) {
2105 if (!m
.getPackKey().isPresent()) {
2108 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2110 sticker
.setInstalled(!m
.getType().isPresent()
2111 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2112 account
.getStickerStore().updateSticker(sticker
);
2115 if (syncMessage
.getConfiguration().isPresent()) {
2123 private File
getContactAvatarFile(String number
) {
2124 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2127 private File
retrieveContactAvatarAttachment(
2128 SignalServiceAttachment attachment
, String number
2129 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2130 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2131 if (attachment
.isPointer()) {
2132 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2133 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2135 SignalServiceAttachmentStream stream
= attachment
.asStream();
2136 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2140 private File
getGroupAvatarFile(byte[] groupId
) {
2141 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2144 private File
retrieveGroupAvatarAttachment(
2145 SignalServiceAttachment attachment
, byte[] groupId
2146 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2147 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2148 if (attachment
.isPointer()) {
2149 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2150 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2152 SignalServiceAttachmentStream stream
= attachment
.asStream();
2153 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2157 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2158 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2161 private File
retrieveProfileAvatar(
2162 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2163 ) throws IOException
{
2164 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2165 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2166 File outputFile
= getProfileAvatarFile(address
);
2168 File tmpFile
= IOUtils
.createTempFile();
2169 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2172 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2173 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2174 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2177 Files
.delete(tmpFile
.toPath());
2178 } catch (IOException e
) {
2179 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2185 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2186 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2189 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2190 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2191 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2194 private File
retrieveAttachment(
2195 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2196 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2197 if (storePreview
&& pointer
.getPreview().isPresent()) {
2198 File previewFile
= new File(outputFile
+ ".preview");
2199 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2200 byte[] preview
= pointer
.getPreview().get();
2201 output
.write(preview
, 0, preview
.length
);
2202 } catch (FileNotFoundException e
) {
2203 e
.printStackTrace();
2208 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2210 File tmpFile
= IOUtils
.createTempFile();
2211 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2213 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2214 IOUtils
.copyStreamToFile(input
, outputFile
);
2217 Files
.delete(tmpFile
.toPath());
2218 } catch (IOException e
) {
2219 System
.err
.println("Failed to delete received attachment temp file “"
2228 private InputStream
retrieveAttachmentAsStream(
2229 SignalServiceAttachmentPointer pointer
, File tmpFile
2230 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2231 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2232 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2235 void sendGroups() throws IOException
, UntrustedIdentityException
{
2236 File groupsFile
= IOUtils
.createTempFile();
2239 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2240 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2241 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2242 if (record instanceof GroupInfoV1
) {
2243 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2244 out
.write(new DeviceGroup(groupInfo
.groupId
,
2245 Optional
.fromNullable(groupInfo
.name
),
2246 new ArrayList
<>(groupInfo
.getMembers()),
2247 createGroupAvatarAttachment(groupInfo
.groupId
),
2248 groupInfo
.isMember(account
.getSelfAddress()),
2249 Optional
.of(groupInfo
.messageExpirationTime
),
2250 Optional
.fromNullable(groupInfo
.color
),
2252 Optional
.fromNullable(groupInfo
.inboxPosition
),
2253 groupInfo
.archived
));
2258 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2259 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2260 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2261 .withStream(groupsFileStream
)
2262 .withContentType("application/octet-stream")
2263 .withLength(groupsFile
.length())
2266 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2271 Files
.delete(groupsFile
.toPath());
2272 } catch (IOException e
) {
2273 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2278 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2279 File contactsFile
= IOUtils
.createTempFile();
2282 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2283 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2284 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2285 VerifiedMessage verifiedMessage
= null;
2286 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2287 .getIdentity(record.getAddress());
2288 if (currentIdentity
!= null) {
2289 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2290 currentIdentity
.getIdentityKey(),
2291 currentIdentity
.getTrustLevel().toVerifiedState(),
2292 currentIdentity
.getDateAdded().getTime());
2295 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2296 out
.write(new DeviceContact(record.getAddress(),
2297 Optional
.fromNullable(record.name
),
2298 createContactAvatarAttachment(record.number
),
2299 Optional
.fromNullable(record.color
),
2300 Optional
.fromNullable(verifiedMessage
),
2301 Optional
.fromNullable(profileKey
),
2303 Optional
.of(record.messageExpirationTime
),
2304 Optional
.fromNullable(record.inboxPosition
),
2308 if (account
.getProfileKey() != null) {
2309 // Send our own profile key as well
2310 out
.write(new DeviceContact(account
.getSelfAddress(),
2315 Optional
.of(account
.getProfileKey()),
2323 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2324 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2325 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2326 .withStream(contactsFileStream
)
2327 .withContentType("application/octet-stream")
2328 .withLength(contactsFile
.length())
2331 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2336 Files
.delete(contactsFile
.toPath());
2337 } catch (IOException e
) {
2338 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2343 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2344 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2345 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2346 if (record.blocked
) {
2347 addresses
.add(record.getAddress());
2350 List
<byte[]> groupIds
= new ArrayList
<>();
2351 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2352 if (record.isBlocked()) {
2353 groupIds
.add(record.groupId
);
2356 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2359 private void sendVerifiedMessage(
2360 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2361 ) throws IOException
, UntrustedIdentityException
{
2362 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2364 trustLevel
.toVerifiedState(),
2365 System
.currentTimeMillis());
2366 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2369 public List
<ContactInfo
> getContacts() {
2370 return account
.getContactStore().getContacts();
2373 public ContactInfo
getContact(String number
) {
2374 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2377 public GroupInfo
getGroup(byte[] groupId
) {
2378 return account
.getGroupStore().getGroup(groupId
);
2381 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2382 return account
.getSignalProtocolStore().getIdentities();
2385 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2386 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2390 * Trust this the identity with this fingerprint
2392 * @param name username of the identity
2393 * @param fingerprint Fingerprint
2395 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2396 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2397 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2401 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2402 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2406 account
.getSignalProtocolStore()
2407 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2409 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2410 } catch (IOException
| UntrustedIdentityException e
) {
2411 e
.printStackTrace();
2420 * Trust this the identity with this safety number
2422 * @param name username of the identity
2423 * @param safetyNumber Safety number
2425 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2426 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2427 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2431 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2432 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2436 account
.getSignalProtocolStore()
2437 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2439 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2440 } catch (IOException
| UntrustedIdentityException e
) {
2441 e
.printStackTrace();
2450 * Trust all keys of this identity without verification
2452 * @param name username of the identity
2454 public boolean trustIdentityAllKeys(String name
) {
2455 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2456 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2460 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2461 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2462 account
.getSignalProtocolStore()
2463 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2465 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2466 } catch (IOException
| UntrustedIdentityException e
) {
2467 e
.printStackTrace();
2475 public String
computeSafetyNumber(
2476 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2478 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2479 getIdentityKeyPair().getPublicKey(),
2484 void saveAccount() {
2488 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2489 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2491 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2492 return resolveSignalServiceAddress(canonicalizedNumber
);
2495 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2496 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2498 return resolveSignalServiceAddress(address
);
2501 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2502 if (address
.matches(account
.getSelfAddress())) {
2503 return account
.getSelfAddress();
2506 return account
.getRecipientStore().resolveServiceAddress(address
);
2510 public void close() throws IOException
{
2511 if (messagePipe
!= null) {
2512 messagePipe
.shutdown();
2516 if (unidentifiedMessagePipe
!= null) {
2517 unidentifiedMessagePipe
.shutdown();
2518 unidentifiedMessagePipe
= null;
2524 public interface ReceiveMessageHandler
{
2526 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);