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
.local
.DecryptedGroup
;
47 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
51 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
52 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
53 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
54 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
55 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
56 import org
.whispersystems
.libsignal
.IdentityKey
;
57 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
58 import org
.whispersystems
.libsignal
.InvalidKeyException
;
59 import org
.whispersystems
.libsignal
.InvalidMessageException
;
60 import org
.whispersystems
.libsignal
.InvalidVersionException
;
61 import org
.whispersystems
.libsignal
.ecc
.Curve
;
62 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
63 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
64 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
65 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
66 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
67 import org
.whispersystems
.libsignal
.util
.Medium
;
68 import org
.whispersystems
.libsignal
.util
.Pair
;
69 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
70 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
71 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
72 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
73 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
75 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
76 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
77 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
78 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
79 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
80 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
81 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
82 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
83 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
87 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
88 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
89 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
90 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
92 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
93 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
94 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
96 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
100 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
101 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
102 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
103 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
104 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
105 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
106 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
107 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
108 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
109 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
110 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
111 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
112 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
113 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
114 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
115 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
116 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
117 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
118 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
119 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
120 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
121 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
122 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
123 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
124 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
125 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
126 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
127 import org
.whispersystems
.util
.Base64
;
129 import java
.io
.Closeable
;
131 import java
.io
.FileInputStream
;
132 import java
.io
.FileNotFoundException
;
133 import java
.io
.FileOutputStream
;
134 import java
.io
.IOException
;
135 import java
.io
.InputStream
;
136 import java
.io
.OutputStream
;
138 import java
.net
.URISyntaxException
;
139 import java
.net
.URLEncoder
;
140 import java
.nio
.charset
.StandardCharsets
;
141 import java
.nio
.file
.Files
;
142 import java
.nio
.file
.Paths
;
143 import java
.nio
.file
.StandardCopyOption
;
144 import java
.util
.ArrayList
;
145 import java
.util
.Arrays
;
146 import java
.util
.Collection
;
147 import java
.util
.Collections
;
148 import java
.util
.Date
;
149 import java
.util
.HashMap
;
150 import java
.util
.HashSet
;
151 import java
.util
.List
;
152 import java
.util
.Locale
;
153 import java
.util
.Objects
;
154 import java
.util
.Set
;
155 import java
.util
.UUID
;
156 import java
.util
.concurrent
.ExecutorService
;
157 import java
.util
.concurrent
.TimeUnit
;
158 import java
.util
.concurrent
.TimeoutException
;
159 import java
.util
.stream
.Collectors
;
160 import java
.util
.zip
.ZipEntry
;
161 import java
.util
.zip
.ZipFile
;
163 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
165 public class Manager
implements Closeable
{
167 private final SleepTimer timer
= new UptimeSleepTimer();
169 private final SignalServiceConfiguration serviceConfiguration
;
170 private final String userAgent
;
171 private final boolean discoverableByPhoneNumber
= true;
172 private final boolean unrestrictedUnidentifiedAccess
= false;
174 private final SignalAccount account
;
175 private final PathConfig pathConfig
;
176 private SignalServiceAccountManager accountManager
;
177 private GroupsV2Api groupsV2Api
;
178 private final GroupsV2Operations groupsV2Operations
;
180 private SignalServiceMessageReceiver messageReceiver
= null;
181 private SignalServiceMessagePipe messagePipe
= null;
182 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
184 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
185 private final ProfileHelper profileHelper
;
186 private final GroupHelper groupHelper
;
189 SignalAccount account
,
190 PathConfig pathConfig
,
191 SignalServiceConfiguration serviceConfiguration
,
194 this.account
= account
;
195 this.pathConfig
= pathConfig
;
196 this.serviceConfiguration
= serviceConfiguration
;
197 this.userAgent
= userAgent
;
198 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
199 serviceConfiguration
)) : null;
200 this.accountManager
= createSignalServiceAccountManager();
201 this.groupsV2Api
= accountManager
.getGroupsV2Api();
203 this.account
.setResolver(this::resolveSignalServiceAddress
);
205 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
206 account
.getProfileStore()::getProfileKey
,
207 this::getRecipientProfile
,
208 this::getSenderCertificate
);
209 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
210 unidentifiedAccessHelper
::getAccessFor
,
211 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
212 this::getOrCreateMessageReceiver
);
213 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
214 this::getRecipientProfile
,
215 account
::getSelfAddress
,
219 public String
getUsername() {
220 return account
.getUsername();
223 public SignalServiceAddress
getSelfAddress() {
224 return account
.getSelfAddress();
227 private SignalServiceAccountManager
createSignalServiceAccountManager() {
228 return new SignalServiceAccountManager(serviceConfiguration
,
229 new DynamicCredentialsProvider(account
.getUuid(),
230 account
.getUsername(),
231 account
.getPassword(),
233 account
.getDeviceId()),
239 private IdentityKeyPair
getIdentityKeyPair() {
240 return account
.getSignalProtocolStore().getIdentityKeyPair();
243 public int getDeviceId() {
244 return account
.getDeviceId();
247 private String
getMessageCachePath() {
248 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
251 private String
getMessageCachePath(String sender
) {
252 if (sender
== null || sender
.isEmpty()) {
253 return getMessageCachePath();
256 return getMessageCachePath() + "/" + sender
.replace("/", "_");
259 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
260 String cachePath
= getMessageCachePath(sender
);
261 IOUtils
.createPrivateDirectories(cachePath
);
262 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
265 public static Manager
init(
266 String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
267 ) throws IOException
{
268 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
270 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
271 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
272 int registrationId
= KeyHelper
.generateRegistrationId(false);
274 ProfileKey profileKey
= KeyUtils
.createProfileKey();
275 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
282 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
285 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
287 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
289 m
.migrateLegacyConfigs();
294 private void migrateLegacyConfigs() {
295 if (account
.getProfileKey() == null && isRegistered()) {
296 // Old config file, creating new profile key
297 account
.setProfileKey(KeyUtils
.createProfileKey());
300 // Store profile keys only in profile store
301 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
302 String profileKeyString
= contact
.profileKey
;
303 if (profileKeyString
== null) {
306 final ProfileKey profileKey
;
308 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
309 } catch (InvalidInputException
| IOException e
) {
312 contact
.profileKey
= null;
313 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
317 public void checkAccountState() throws IOException
{
318 if (account
.isRegistered()) {
319 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
323 if (account
.getUuid() == null) {
324 account
.setUuid(accountManager
.getOwnUuid());
327 updateAccountAttributes();
331 public boolean isRegistered() {
332 return account
.isRegistered();
335 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
336 account
.setPassword(KeyUtils
.createPassword());
338 // Resetting UUID, because registering doesn't work otherwise
339 account
.setUuid(null);
340 accountManager
= createSignalServiceAccountManager();
341 this.groupsV2Api
= accountManager
.getGroupsV2Api();
343 if (voiceVerification
) {
344 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
345 Optional
.fromNullable(captcha
),
348 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
351 account
.setRegistered(false);
355 public void updateAccountAttributes() throws IOException
{
356 accountManager
.setAccountAttributes(account
.getSignalingKey(),
357 account
.getSignalProtocolStore().getLocalRegistrationId(),
359 account
.getRegistrationLockPin(),
360 account
.getRegistrationLock(),
361 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
362 unrestrictedUnidentifiedAccess
,
364 discoverableByPhoneNumber
);
367 public void setProfile(String name
, File avatar
) throws IOException
{
368 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
369 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
373 public void unregister() throws IOException
{
374 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
375 // If this is the master device, other users can't send messages to this number anymore.
376 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
377 accountManager
.setGcmId(Optional
.absent());
379 account
.setRegistered(false);
383 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
384 List
<DeviceInfo
> devices
= accountManager
.getDevices();
385 account
.setMultiDevice(devices
.size() > 1);
390 public void removeLinkedDevices(int deviceId
) throws IOException
{
391 accountManager
.removeDevice(deviceId
);
392 List
<DeviceInfo
> devices
= accountManager
.getDevices();
393 account
.setMultiDevice(devices
.size() > 1);
397 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
398 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
400 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
403 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
404 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
405 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
407 accountManager
.addDevice(deviceIdentifier
,
410 Optional
.of(account
.getProfileKey().serialize()),
412 account
.setMultiDevice(true);
416 private List
<PreKeyRecord
> generatePreKeys() {
417 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
419 final int offset
= account
.getPreKeyIdOffset();
420 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
421 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
422 ECKeyPair keyPair
= Curve
.generateKeyPair();
423 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
428 account
.addPreKeys(records
);
434 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
436 ECKeyPair keyPair
= Curve
.generateKeyPair();
437 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
438 keyPair
.getPublicKey().serialize());
439 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
440 System
.currentTimeMillis(),
444 account
.addSignedPreKey(record);
448 } catch (InvalidKeyException e
) {
449 throw new AssertionError(e
);
453 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
454 verificationCode
= verificationCode
.replace("-", "");
455 account
.setSignalingKey(KeyUtils
.createSignalingKey());
456 // TODO make unrestricted unidentified access configurable
457 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
,
458 account
.getSignalingKey(),
459 account
.getSignalProtocolStore().getLocalRegistrationId(),
463 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
464 unrestrictedUnidentifiedAccess
,
466 discoverableByPhoneNumber
);
468 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
469 // TODO response.isStorageCapable()
470 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
471 account
.setRegistered(true);
472 account
.setUuid(uuid
);
473 account
.setRegistrationLockPin(pin
);
474 account
.getSignalProtocolStore()
475 .saveIdentity(account
.getSelfAddress(),
476 getIdentityKeyPair().getPublicKey(),
477 TrustLevel
.TRUSTED_VERIFIED
);
483 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
484 if (pin
.isPresent()) {
485 account
.setRegistrationLockPin(pin
.get());
486 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
488 account
.setRegistrationLockPin(null);
489 accountManager
.removeRegistrationLockV1();
494 void refreshPreKeys() throws IOException
{
495 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
496 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
497 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
499 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
502 private SignalServiceMessageReceiver
createMessageReceiver() {
503 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
504 serviceConfiguration
).getProfileOperations() : null;
505 return new SignalServiceMessageReceiver(serviceConfiguration
,
507 account
.getUsername(),
508 account
.getPassword(),
509 account
.getDeviceId(),
510 account
.getSignalingKey(),
514 clientZkProfileOperations
);
517 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
518 if (messageReceiver
== null) {
519 messageReceiver
= createMessageReceiver();
521 return messageReceiver
;
524 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
525 if (messagePipe
== null) {
526 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
531 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
532 if (unidentifiedMessagePipe
== null) {
533 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
535 return unidentifiedMessagePipe
;
538 private SignalServiceMessageSender
createMessageSender() {
539 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
540 serviceConfiguration
).getProfileOperations() : null;
541 final ExecutorService executor
= null;
542 return new SignalServiceMessageSender(serviceConfiguration
,
544 account
.getUsername(),
545 account
.getPassword(),
546 account
.getDeviceId(),
547 account
.getSignalProtocolStore(),
549 account
.isMultiDevice(),
550 Optional
.fromNullable(messagePipe
),
551 Optional
.fromNullable(unidentifiedMessagePipe
),
553 clientZkProfileOperations
,
555 ServiceConfig
.MAX_ENVELOPE_SIZE
);
558 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
559 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
562 private SignalProfile
getRecipientProfile(
563 SignalServiceAddress address
565 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
566 if (profileEntry
== null) {
569 long now
= new Date().getTime();
570 // Profiles are cache for 24h before retrieving them again
571 if (!profileEntry
.isRequestPending() && (
572 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
574 ProfileKey profileKey
= profileEntry
.getProfileKey();
575 profileEntry
.setRequestPending(true);
576 SignalProfile profile
;
578 profile
= retrieveRecipientProfile(address
, profileKey
);
579 } catch (IOException e
) {
580 System
.err
.println("Failed to retrieve profile, ignoring: " + e
.getMessage());
581 profileEntry
.setRequestPending(false);
584 profileEntry
.setRequestPending(false);
585 account
.getProfileStore()
586 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
589 return profileEntry
.getProfile();
592 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
593 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
594 if (profileEntry
== null) {
597 if (profileEntry
.getProfileKeyCredential() == null) {
598 ProfileAndCredential profileAndCredential
;
600 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
601 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
602 } catch (IOException e
) {
603 System
.err
.println("Failed to retrieve profile key credential, ignoring: " + e
.getMessage());
607 long now
= new Date().getTime();
608 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
609 final SignalProfile profile
= decryptProfile(address
,
610 profileEntry
.getProfileKey(),
611 profileAndCredential
.getProfile());
612 account
.getProfileStore()
613 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
614 return profileKeyCredential
;
616 return profileEntry
.getProfileKeyCredential();
619 private SignalProfile
retrieveRecipientProfile(
620 SignalServiceAddress address
, ProfileKey profileKey
621 ) throws IOException
{
622 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
624 return decryptProfile(address
, profileKey
, encryptedProfile
);
627 private SignalProfile
decryptProfile(
628 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
630 File avatarFile
= null;
632 avatarFile
= encryptedProfile
.getAvatar() == null
634 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
635 } catch (Throwable e
) {
636 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
639 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
643 name
= encryptedProfile
.getName() == null
645 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
646 } catch (IOException e
) {
649 String unidentifiedAccess
;
651 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
652 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
654 : encryptedProfile
.getUnidentifiedAccess();
655 } catch (IOException e
) {
656 unidentifiedAccess
= null;
658 return new SignalProfile(encryptedProfile
.getIdentityKey(),
662 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
663 encryptedProfile
.getCapabilities());
664 } catch (InvalidCiphertextException e
) {
669 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
670 File file
= getGroupAvatarFile(groupId
);
671 if (!file
.exists()) {
672 return Optional
.absent();
675 return Optional
.of(Utils
.createAttachment(file
));
678 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
679 File file
= getContactAvatarFile(number
);
680 if (!file
.exists()) {
681 return Optional
.absent();
684 return Optional
.of(Utils
.createAttachment(file
));
687 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
688 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
690 throw new GroupNotFoundException(groupId
);
692 if (!g
.isMember(account
.getSelfAddress())) {
693 throw new NotAGroupMemberException(groupId
, g
.getTitle());
698 public List
<GroupInfo
> getGroups() {
699 return account
.getGroupStore().getGroups();
702 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
703 SignalServiceDataMessage
.Builder messageBuilder
, byte[] groupId
704 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
705 final GroupInfo g
= getGroupForSending(groupId
);
707 GroupUtils
.setGroupContext(messageBuilder
, g
);
708 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
710 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
713 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
714 String messageText
, List
<String
> attachments
, byte[] groupId
715 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
716 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
717 .withBody(messageText
);
718 if (attachments
!= null) {
719 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
722 return sendGroupMessage(messageBuilder
, groupId
);
725 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
726 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, byte[] groupId
727 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
728 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
730 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
731 targetSentTimestamp
);
732 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
733 .withReaction(reaction
);
735 return sendGroupMessage(messageBuilder
, groupId
);
738 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
739 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
).withId(groupId
).build();
741 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
743 final GroupInfo g
= getGroupForSending(groupId
);
744 if (g
instanceof GroupInfoV1
) {
745 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
746 groupInfoV1
.removeMember(account
.getSelfAddress());
747 account
.getGroupStore().updateGroup(groupInfoV1
);
749 throw new RuntimeException("TODO Not implemented!");
752 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
755 private GroupInfoV2
createGroupV2(
756 String name
, Collection
<SignalServiceAddress
> members
, InputStream avatar
757 ) throws IOException
{
758 byte[] avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
759 final GroupsV2Operations
.NewGroup newGroup
= groupHelper
.createGroupV2(name
, members
, avatarBytes
);
760 final GroupSecretParams groupSecretParams
= newGroup
.getGroupSecretParams();
762 final GroupsV2AuthorizationString groupAuthForToday
;
763 final DecryptedGroup decryptedGroup
;
765 groupAuthForToday
= getGroupAuthForToday(groupSecretParams
);
766 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
767 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
768 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
769 System
.err
.println("Failed to create V2 group: " + e
.getMessage());
772 if (decryptedGroup
== null) {
773 System
.err
.println("Failed to create V2 group!");
777 final byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
778 final GroupMasterKey masterKey
= groupSecretParams
.getMasterKey();
779 GroupInfoV2 g
= new GroupInfoV2(groupId
, masterKey
);
780 g
.setGroup(decryptedGroup
);
785 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(
786 byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
787 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
789 SignalServiceDataMessage
.Builder messageBuilder
;
790 if (groupId
== null) {
792 InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
);
793 GroupInfoV2 gv2
= createGroupV2(name
, members
, avatar
);
795 GroupInfoV1 gv1
= new GroupInfoV1(KeyUtils
.createGroupId());
796 gv1
.addMembers(Collections
.singleton(account
.getSelfAddress()));
797 updateGroupV1(gv1
, name
, members
, avatarFile
);
798 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
801 messageBuilder
= getGroupUpdateMessageBuilder(gv2
);
805 GroupInfo group
= getGroupForSending(groupId
);
806 if (!(group
instanceof GroupInfoV1
)) {
807 throw new RuntimeException("TODO Not implemented!");
809 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
810 updateGroupV1(gv1
, name
, members
, avatarFile
);
811 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
815 account
.getGroupStore().updateGroup(g
);
817 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
818 g
.getMembersWithout(account
.getSelfAddress()));
819 return new Pair
<>(g
.groupId
, result
.second());
822 private void updateGroupV1(
825 final Collection
<SignalServiceAddress
> members
,
826 final String avatarFile
827 ) throws IOException
{
832 if (members
!= null) {
833 final Set
<String
> newE164Members
= new HashSet
<>();
834 for (SignalServiceAddress member
: members
) {
835 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
838 newE164Members
.add(member
.getNumber().get());
841 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
842 if (contacts
.size() != newE164Members
.size()) {
843 // Some of the new members are not registered on Signal
844 for (ContactTokenDetails contact
: contacts
) {
845 newE164Members
.remove(contact
.getNumber());
847 throw new IOException("Failed to add members "
848 + Util
.join(", ", newE164Members
)
849 + " to group: Not registered on Signal");
852 g
.addMembers(members
);
855 if (avatarFile
!= null) {
856 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
857 File aFile
= getGroupAvatarFile(g
.groupId
);
858 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
862 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
863 byte[] groupId
, SignalServiceAddress recipient
864 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
866 GroupInfo group
= getGroupForSending(groupId
);
867 if (!(group
instanceof GroupInfoV1
)) {
868 throw new RuntimeException("Received an invalid group request for a v2 group!");
870 g
= (GroupInfoV1
) group
;
872 if (!g
.isMember(recipient
)) {
873 throw new NotAGroupMemberException(groupId
, g
.name
);
876 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
878 // Send group message only to the recipient who requested it
879 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
882 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
883 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
886 .withMembers(new ArrayList
<>(g
.getMembers()));
888 File aFile
= getGroupAvatarFile(g
.groupId
);
889 if (aFile
.exists()) {
891 group
.withAvatar(Utils
.createAttachment(aFile
));
892 } catch (IOException e
) {
893 throw new AttachmentInvalidException(aFile
.toString(), e
);
897 return SignalServiceDataMessage
.newBuilder()
898 .asGroupMessage(group
.build())
899 .withExpiration(g
.getMessageExpirationTime());
902 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
) {
903 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
904 .withRevision(g
.getGroup().getRevision())
905 // .withSignedGroupChange() // TODO
907 return SignalServiceDataMessage
.newBuilder()
908 .asGroupMessage(group
.build())
909 .withExpiration(g
.getMessageExpirationTime());
912 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
913 byte[] groupId
, SignalServiceAddress recipient
914 ) throws IOException
{
915 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
918 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
919 .asGroupMessage(group
.build());
921 // Send group info request message to the recipient who sent us a message with this groupId
922 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
926 SignalServiceAddress remoteAddress
, long messageId
927 ) throws IOException
, UntrustedIdentityException
{
928 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
929 Collections
.singletonList(messageId
),
930 System
.currentTimeMillis());
932 createMessageSender().sendReceipt(remoteAddress
,
933 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
937 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
938 String messageText
, List
<String
> attachments
, List
<String
> recipients
939 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
940 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
941 .withBody(messageText
);
942 if (attachments
!= null) {
943 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
945 // Upload attachments here, so we only upload once even for multiple recipients
946 SignalServiceMessageSender messageSender
= createMessageSender();
947 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
948 for (SignalServiceAttachment attachment
: attachmentStreams
) {
949 if (attachment
.isStream()) {
950 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
951 } else if (attachment
.isPointer()) {
952 attachmentPointers
.add(attachment
.asPointer());
956 messageBuilder
.withAttachments(attachmentPointers
);
958 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
961 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
962 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
963 ) throws IOException
, InvalidNumberException
{
964 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
966 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
967 targetSentTimestamp
);
968 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
969 .withReaction(reaction
);
970 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
973 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
974 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
976 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
978 return sendMessage(messageBuilder
, signalServiceAddresses
);
979 } catch (Exception e
) {
980 for (SignalServiceAddress address
: signalServiceAddresses
) {
981 handleEndSession(address
);
988 public String
getContactName(String number
) throws InvalidNumberException
{
989 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
990 if (contact
== null) {
997 public void setContactName(String number
, String name
) throws InvalidNumberException
{
998 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
999 ContactInfo contact
= account
.getContactStore().getContact(address
);
1000 if (contact
== null) {
1001 contact
= new ContactInfo(address
);
1003 contact
.name
= name
;
1004 account
.getContactStore().updateContact(contact
);
1008 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1009 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1012 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1013 ContactInfo contact
= account
.getContactStore().getContact(address
);
1014 if (contact
== null) {
1015 contact
= new ContactInfo(address
);
1017 contact
.blocked
= blocked
;
1018 account
.getContactStore().updateContact(contact
);
1022 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
1023 GroupInfo group
= getGroup(groupId
);
1024 if (group
== null) {
1025 throw new GroupNotFoundException(groupId
);
1028 group
.setBlocked(blocked
);
1029 account
.getGroupStore().updateGroup(group
);
1033 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(
1034 byte[] groupId
, String name
, List
<String
> members
, String avatar
1035 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1036 return sendUpdateGroupMessage(groupId
,
1038 members
== null ?
null : getSignalServiceAddresses(members
),
1043 * Change the expiration timer for a contact
1045 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1046 ContactInfo contact
= account
.getContactStore().getContact(address
);
1047 contact
.messageExpirationTime
= messageExpirationTimer
;
1048 account
.getContactStore().updateContact(contact
);
1049 sendExpirationTimerUpdate(address
);
1053 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1054 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1055 .asExpirationUpdate();
1056 sendMessage(messageBuilder
, Collections
.singleton(address
));
1060 * Change the expiration timer for a contact
1062 public void setExpirationTimer(
1063 String number
, int messageExpirationTimer
1064 ) throws IOException
, InvalidNumberException
{
1065 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1066 setExpirationTimer(address
, messageExpirationTimer
);
1070 * Change the expiration timer for a group
1072 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
1073 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1074 if (g
instanceof GroupInfoV1
) {
1075 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1076 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1077 account
.getGroupStore().updateGroup(groupInfoV1
);
1079 throw new RuntimeException("TODO Not implemented!");
1084 * Upload the sticker pack from path.
1086 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1087 * @return if successful, returns the URL to install the sticker pack in the signal app
1089 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
1090 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1092 SignalServiceMessageSender messageSender
= createMessageSender();
1094 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1095 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1097 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1098 account
.getStickerStore().updateSticker(sticker
);
1102 return new URI("https",
1105 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1106 Hex
.toStringCondensed(packKey
),
1107 StandardCharsets
.UTF_8
)).toString();
1108 } catch (URISyntaxException e
) {
1109 throw new AssertionError(e
);
1113 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1115 ) throws IOException
, StickerPackInvalidException
{
1117 String rootPath
= null;
1119 final File file
= new File(path
);
1120 if (file
.getName().endsWith(".zip")) {
1121 zip
= new ZipFile(file
);
1122 } else if (file
.getName().equals("manifest.json")) {
1123 rootPath
= file
.getParent();
1125 throw new StickerPackInvalidException("Could not find manifest.json");
1128 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1130 if (pack
.stickers
== null) {
1131 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1134 if (pack
.stickers
.isEmpty()) {
1135 throw new StickerPackInvalidException("Must include stickers.");
1138 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1139 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1140 if (sticker
.file
== null) {
1141 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1144 Pair
<InputStream
, Long
> data
;
1146 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1147 } catch (IOException ignored
) {
1148 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1151 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1152 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1154 Optional
.fromNullable(sticker
.emoji
).or(""),
1156 stickers
.add(stickerInfo
);
1159 StickerInfo cover
= null;
1160 if (pack
.cover
!= null) {
1161 if (pack
.cover
.file
== null) {
1162 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1165 Pair
<InputStream
, Long
> data
;
1167 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1168 } catch (IOException ignored
) {
1169 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1172 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1173 cover
= new StickerInfo(data
.first(),
1175 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1179 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1182 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1183 InputStream inputStream
;
1185 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1187 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1189 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1192 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1193 final String rootPath
, final ZipFile zip
, final String subfile
1194 ) throws IOException
{
1196 final ZipEntry entry
= zip
.getEntry(subfile
);
1197 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1199 final File file
= new File(rootPath
, subfile
);
1200 return new Pair
<>(new FileInputStream(file
), file
.length());
1204 void requestSyncGroups() throws IOException
{
1205 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1206 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1208 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1210 sendSyncMessage(message
);
1211 } catch (UntrustedIdentityException e
) {
1212 e
.printStackTrace();
1216 void requestSyncContacts() throws IOException
{
1217 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1218 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1220 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1222 sendSyncMessage(message
);
1223 } catch (UntrustedIdentityException e
) {
1224 e
.printStackTrace();
1228 void requestSyncBlocked() throws IOException
{
1229 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1230 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1232 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1234 sendSyncMessage(message
);
1235 } catch (UntrustedIdentityException e
) {
1236 e
.printStackTrace();
1240 void requestSyncConfiguration() throws IOException
{
1241 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1242 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1244 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1246 sendSyncMessage(message
);
1247 } catch (UntrustedIdentityException e
) {
1248 e
.printStackTrace();
1252 private byte[] getSenderCertificate() {
1253 // TODO support UUID capable sender certificates
1254 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1257 certificate
= accountManager
.getSenderCertificate();
1258 } catch (IOException e
) {
1259 System
.err
.println("Failed to get sender certificate: " + e
);
1262 // TODO cache for a day
1266 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1267 SignalServiceMessageSender messageSender
= createMessageSender();
1269 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1270 } catch (UntrustedIdentityException e
) {
1271 account
.getSignalProtocolStore()
1272 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1274 TrustLevel
.UNTRUSTED
);
1279 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1280 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1282 for (String number
: numbers
) {
1283 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1285 return signalServiceAddresses
;
1288 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1289 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1290 ) throws IOException
{
1291 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1292 final long timestamp
= System
.currentTimeMillis();
1293 messageBuilder
.withTimestamp(timestamp
);
1294 getOrCreateMessagePipe();
1295 getOrCreateUnidentifiedMessagePipe();
1296 SignalServiceDataMessage message
= null;
1298 message
= messageBuilder
.build();
1299 if (message
.getGroupContext().isPresent()) {
1301 SignalServiceMessageSender messageSender
= createMessageSender();
1302 final boolean isRecipientUpdate
= false;
1303 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1304 unidentifiedAccessHelper
.getAccessFor(recipients
),
1307 for (SendMessageResult r
: result
) {
1308 if (r
.getIdentityFailure() != null) {
1309 account
.getSignalProtocolStore()
1310 .saveIdentity(r
.getAddress(),
1311 r
.getIdentityFailure().getIdentityKey(),
1312 TrustLevel
.UNTRUSTED
);
1315 return new Pair
<>(timestamp
, result
);
1316 } catch (UntrustedIdentityException e
) {
1317 account
.getSignalProtocolStore()
1318 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1320 TrustLevel
.UNTRUSTED
);
1321 return new Pair
<>(timestamp
, Collections
.emptyList());
1324 // Send to all individually, so sync messages are sent correctly
1325 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1326 for (SignalServiceAddress address
: recipients
) {
1327 ContactInfo contact
= account
.getContactStore().getContact(address
);
1328 if (contact
!= null) {
1329 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1330 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1332 messageBuilder
.withExpiration(0);
1333 messageBuilder
.withProfileKey(null);
1335 message
= messageBuilder
.build();
1336 if (address
.matches(account
.getSelfAddress())) {
1337 results
.add(sendSelfMessage(message
));
1339 results
.add(sendMessage(address
, message
));
1342 return new Pair
<>(timestamp
, results
);
1345 if (message
!= null && message
.isEndSession()) {
1346 for (SignalServiceAddress recipient
: recipients
) {
1347 handleEndSession(recipient
);
1354 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1355 SignalServiceMessageSender messageSender
= createMessageSender();
1357 SignalServiceAddress recipient
= account
.getSelfAddress();
1359 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1360 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1361 message
.getTimestamp(),
1363 message
.getExpiresInSeconds(),
1364 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1366 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1369 long startTime
= System
.currentTimeMillis();
1370 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1371 return SendMessageResult
.success(recipient
,
1372 unidentifiedAccess
.isPresent(),
1374 System
.currentTimeMillis() - startTime
);
1375 } catch (UntrustedIdentityException e
) {
1376 account
.getSignalProtocolStore()
1377 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1379 TrustLevel
.UNTRUSTED
);
1380 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1384 private SendMessageResult
sendMessage(
1385 SignalServiceAddress address
, SignalServiceDataMessage message
1386 ) throws IOException
{
1387 SignalServiceMessageSender messageSender
= createMessageSender();
1390 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1391 } catch (UntrustedIdentityException e
) {
1392 account
.getSignalProtocolStore()
1393 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1395 TrustLevel
.UNTRUSTED
);
1396 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1400 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1401 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1402 account
.getSignalProtocolStore(),
1403 Utils
.getCertificateValidator());
1405 return cipher
.decrypt(envelope
);
1406 } catch (ProtocolUntrustedIdentityException e
) {
1407 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1408 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1410 account
.getSignalProtocolStore()
1411 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1412 identityException
.getUntrustedIdentity(),
1413 TrustLevel
.UNTRUSTED
);
1414 throw identityException
;
1416 throw new AssertionError(e
);
1420 private void handleEndSession(SignalServiceAddress source
) {
1421 account
.getSignalProtocolStore().deleteAllSessions(source
);
1424 private static int currentTimeDays() {
1425 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1428 private GroupsV2AuthorizationString
getGroupAuthForToday(
1429 final GroupSecretParams groupSecretParams
1430 ) throws IOException
, VerificationFailedException
{
1431 final int today
= currentTimeDays();
1432 // Returns credentials for the next 7 days
1433 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1434 // TODO cache credentials until they expire
1435 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1436 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1439 authCredentialResponse
);
1442 private List
<HandleAction
> handleSignalServiceDataMessage(
1443 SignalServiceDataMessage message
,
1445 SignalServiceAddress source
,
1446 SignalServiceAddress destination
,
1447 boolean ignoreAttachments
1449 List
<HandleAction
> actions
= new ArrayList
<>();
1450 if (message
.getGroupContext().isPresent()) {
1451 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1452 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1453 GroupInfo group
= account
.getGroupStore().getGroupByV1Id(groupInfo
.getGroupId());
1454 if (group
== null || group
instanceof GroupInfoV1
) {
1455 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1456 switch (groupInfo
.getType()) {
1458 if (groupV1
== null) {
1459 groupV1
= new GroupInfoV1(groupInfo
.getGroupId());
1462 if (groupInfo
.getAvatar().isPresent()) {
1463 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1464 if (avatar
.isPointer()) {
1466 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.groupId
);
1467 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1468 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer()
1469 .getRemoteId() + "): " + e
.getMessage());
1474 if (groupInfo
.getName().isPresent()) {
1475 groupV1
.name
= groupInfo
.getName().get();
1478 if (groupInfo
.getMembers().isPresent()) {
1479 groupV1
.addMembers(groupInfo
.getMembers()
1482 .map(this::resolveSignalServiceAddress
)
1483 .collect(Collectors
.toSet()));
1486 account
.getGroupStore().updateGroup(groupV1
);
1490 if (groupV1
== null && !isSync
) {
1491 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1495 if (groupV1
!= null) {
1496 groupV1
.removeMember(source
);
1497 account
.getGroupStore().updateGroup(groupV1
);
1502 if (groupV1
!= null && !isSync
) {
1503 actions
.add(new SendGroupUpdateAction(source
, groupV1
.groupId
));
1508 // Received a group v1 message for a v2 group
1511 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1512 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1513 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1515 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1517 byte[] groupId
= groupSecretParams
.getPublicParams().getGroupIdentifier().serialize();
1518 GroupInfo groupInfo
= account
.getGroupStore().getGroupByV2Id(groupId
);
1519 if (groupInfo
instanceof GroupInfoV1
) {
1520 // Received a v2 group message for a v2 group, we need to locally migrate the group
1521 account
.getGroupStore().deleteGroup(groupInfo
.groupId
);
1522 GroupInfoV2 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1523 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1524 account
.getGroupStore().updateGroup(groupInfoV2
);
1525 System
.err
.println("Locally migrated group "
1526 + Base64
.encodeBytes(groupInfo
.groupId
)
1527 + " to group v2, id: "
1528 + Base64
.encodeBytes(groupInfoV2
.groupId
)
1530 } else if (groupInfo
== null || groupInfo
instanceof GroupInfoV2
) {
1531 GroupInfoV2 groupInfoV2
= groupInfo
== null
1532 ?
new GroupInfoV2(groupId
, groupMasterKey
)
1533 : (GroupInfoV2
) groupInfo
;
1535 if (groupInfoV2
.getGroup() == null
1536 || groupInfoV2
.getGroup().getRevision() < groupContext
.getRevision()) {
1537 // TODO check if revision is only 1 behind and a signedGroupChange is available
1538 groupInfoV2
.setGroup(getDecryptedGroup(groupSecretParams
));
1539 account
.getGroupStore().updateGroup(groupInfoV2
);
1544 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1545 if (message
.isEndSession()) {
1546 handleEndSession(conversationPartnerAddress
);
1548 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1549 if (message
.getGroupContext().isPresent()) {
1550 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1551 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1552 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(groupInfo
.getGroupId());
1553 if (group
!= null) {
1554 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1555 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1556 account
.getGroupStore().updateGroup(group
);
1559 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1560 // disappearing message timer already stored in the DecryptedGroup
1563 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1564 if (contact
== null) {
1565 contact
= new ContactInfo(conversationPartnerAddress
);
1567 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1568 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1569 account
.getContactStore().updateContact(contact
);
1573 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1574 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1575 if (attachment
.isPointer()) {
1577 retrieveAttachment(attachment
.asPointer());
1578 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1579 System
.err
.println("Failed to retrieve attachment ("
1580 + attachment
.asPointer().getRemoteId()
1587 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1588 final ProfileKey profileKey
;
1590 profileKey
= new ProfileKey(message
.getProfileKey().get());
1591 } catch (InvalidInputException e
) {
1592 throw new AssertionError(e
);
1594 if (source
.matches(account
.getSelfAddress())) {
1595 this.account
.setProfileKey(profileKey
);
1597 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1599 if (message
.getPreviews().isPresent()) {
1600 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1601 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1602 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1603 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1605 retrieveAttachment(attachment
);
1606 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1607 System
.err
.println("Failed to retrieve attachment ("
1608 + attachment
.getRemoteId()
1615 if (message
.getSticker().isPresent()) {
1616 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1617 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1618 if (sticker
== null) {
1619 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1620 account
.getStickerStore().updateSticker(sticker
);
1626 private DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
1628 final GroupsV2AuthorizationString groupsV2AuthorizationString
= getGroupAuthForToday(groupSecretParams
);
1629 DecryptedGroup group
= groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
1630 for (DecryptedMember member
: group
.getMembersList()) {
1631 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1632 member
.getUuid().toByteArray()), null));
1634 account
.getProfileStore()
1635 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1636 } catch (InvalidInputException ignored
) {
1640 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
1641 System
.err
.println("Failed to retrieve Group V2 info, ignoring ...");
1646 private void retryFailedReceivedMessages(
1647 ReceiveMessageHandler handler
, boolean ignoreAttachments
1649 final File cachePath
= new File(getMessageCachePath());
1650 if (!cachePath
.exists()) {
1653 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1654 if (!dir
.isDirectory()) {
1655 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1659 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1660 if (!fileEntry
.isFile()) {
1663 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1665 // Try to delete directory if empty
1670 private void retryFailedReceivedMessage(
1671 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1673 SignalServiceEnvelope envelope
;
1675 envelope
= Utils
.loadEnvelope(fileEntry
);
1676 if (envelope
== null) {
1679 } catch (IOException e
) {
1680 e
.printStackTrace();
1683 SignalServiceContent content
= null;
1684 if (!envelope
.isReceipt()) {
1686 content
= decryptMessage(envelope
);
1687 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1689 } catch (Exception er
) {
1690 // All other errors are not recoverable, so delete the cached message
1692 Files
.delete(fileEntry
.toPath());
1693 } catch (IOException e
) {
1694 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1698 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1699 for (HandleAction action
: actions
) {
1701 action
.execute(this);
1702 } catch (Throwable e
) {
1703 e
.printStackTrace();
1708 handler
.handleMessage(envelope
, content
, null);
1710 Files
.delete(fileEntry
.toPath());
1711 } catch (IOException e
) {
1712 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1716 public void receiveMessages(
1719 boolean returnOnTimeout
,
1720 boolean ignoreAttachments
,
1721 ReceiveMessageHandler handler
1722 ) throws IOException
{
1723 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1725 Set
<HandleAction
> queuedActions
= null;
1727 getOrCreateMessagePipe();
1729 boolean hasCaughtUpWithOldMessages
= false;
1732 SignalServiceEnvelope envelope
;
1733 SignalServiceContent content
= null;
1734 Exception exception
= null;
1735 final long now
= new Date().getTime();
1737 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1738 // store message on disk, before acknowledging receipt to the server
1740 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1741 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1742 Utils
.storeEnvelope(envelope1
, cacheFile
);
1743 } catch (IOException e
) {
1744 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: "
1748 if (result
.isPresent()) {
1749 envelope
= result
.get();
1751 // Received indicator that server queue is empty
1752 hasCaughtUpWithOldMessages
= true;
1754 if (queuedActions
!= null) {
1755 for (HandleAction action
: queuedActions
) {
1757 action
.execute(this);
1758 } catch (Throwable e
) {
1759 e
.printStackTrace();
1763 queuedActions
.clear();
1764 queuedActions
= null;
1767 // Continue to wait another timeout for new messages
1770 } catch (TimeoutException e
) {
1771 if (returnOnTimeout
) return;
1773 } catch (InvalidVersionException e
) {
1774 System
.err
.println("Ignoring error: " + e
.getMessage());
1778 if (envelope
.hasSource()) {
1779 // Store uuid if we don't have it already
1780 SignalServiceAddress source
= envelope
.getSourceAddress();
1781 resolveSignalServiceAddress(source
);
1783 if (!envelope
.isReceipt()) {
1785 content
= decryptMessage(envelope
);
1786 } catch (Exception e
) {
1789 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1790 if (hasCaughtUpWithOldMessages
) {
1791 for (HandleAction action
: actions
) {
1793 action
.execute(this);
1794 } catch (Throwable e
) {
1795 e
.printStackTrace();
1799 if (queuedActions
== null) {
1800 queuedActions
= new HashSet
<>();
1802 queuedActions
.addAll(actions
);
1806 if (!isMessageBlocked(envelope
, content
)) {
1807 handler
.handleMessage(envelope
, content
, exception
);
1809 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1810 File cacheFile
= null;
1812 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1813 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1814 Files
.delete(cacheFile
.toPath());
1815 // Try to delete directory if empty
1816 new File(getMessageCachePath()).delete();
1817 } catch (IOException e
) {
1818 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1824 private boolean isMessageBlocked(
1825 SignalServiceEnvelope envelope
, SignalServiceContent content
1827 SignalServiceAddress source
;
1828 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1829 source
= envelope
.getSourceAddress();
1830 } else if (content
!= null) {
1831 source
= content
.getSender();
1835 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1836 if (sourceContact
!= null && sourceContact
.blocked
) {
1840 if (content
!= null && content
.getDataMessage().isPresent()) {
1841 SignalServiceDataMessage message
= content
.getDataMessage().get();
1842 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1843 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1844 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1845 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.isBlocked();
1851 private List
<HandleAction
> handleMessage(
1852 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
1854 List
<HandleAction
> actions
= new ArrayList
<>();
1855 if (content
!= null) {
1856 SignalServiceAddress sender
;
1857 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1858 sender
= envelope
.getSourceAddress();
1860 sender
= content
.getSender();
1862 // Store uuid if we don't have it already
1863 resolveSignalServiceAddress(sender
);
1865 if (content
.getDataMessage().isPresent()) {
1866 SignalServiceDataMessage message
= content
.getDataMessage().get();
1868 if (content
.isNeedsReceipt()) {
1869 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1872 actions
.addAll(handleSignalServiceDataMessage(message
,
1875 account
.getSelfAddress(),
1876 ignoreAttachments
));
1878 if (content
.getSyncMessage().isPresent()) {
1879 account
.setMultiDevice(true);
1880 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1881 if (syncMessage
.getSent().isPresent()) {
1882 SentTranscriptMessage message
= syncMessage
.getSent().get();
1883 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
1886 message
.getDestination().orNull(),
1887 ignoreAttachments
));
1889 if (syncMessage
.getRequest().isPresent()) {
1890 RequestMessage rm
= syncMessage
.getRequest().get();
1891 if (rm
.isContactsRequest()) {
1892 actions
.add(SendSyncContactsAction
.create());
1894 if (rm
.isGroupsRequest()) {
1895 actions
.add(SendSyncGroupsAction
.create());
1897 if (rm
.isBlockedListRequest()) {
1898 actions
.add(SendSyncBlockedListAction
.create());
1900 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
1902 if (syncMessage
.getGroups().isPresent()) {
1903 File tmpFile
= null;
1905 tmpFile
= IOUtils
.createTempFile();
1906 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
1908 .asPointer(), tmpFile
)) {
1909 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1911 while ((g
= s
.read()) != null) {
1912 GroupInfoV1 syncGroup
= account
.getGroupStore().getOrCreateGroupV1(g
.getId());
1913 if (syncGroup
!= null) {
1914 if (g
.getName().isPresent()) {
1915 syncGroup
.name
= g
.getName().get();
1917 syncGroup
.addMembers(g
.getMembers()
1919 .map(this::resolveSignalServiceAddress
)
1920 .collect(Collectors
.toSet()));
1921 if (!g
.isActive()) {
1922 syncGroup
.removeMember(account
.getSelfAddress());
1924 // Add ourself to the member set as it's marked as active
1925 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1927 syncGroup
.blocked
= g
.isBlocked();
1928 if (g
.getColor().isPresent()) {
1929 syncGroup
.color
= g
.getColor().get();
1932 if (g
.getAvatar().isPresent()) {
1933 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1935 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1936 syncGroup
.archived
= g
.isArchived();
1937 account
.getGroupStore().updateGroup(syncGroup
);
1941 } catch (Exception e
) {
1942 e
.printStackTrace();
1944 if (tmpFile
!= null) {
1946 Files
.delete(tmpFile
.toPath());
1947 } catch (IOException e
) {
1948 System
.err
.println("Failed to delete received groups temp file “"
1956 if (syncMessage
.getBlockedList().isPresent()) {
1957 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1958 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1959 setContactBlocked(resolveSignalServiceAddress(address
), true);
1961 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1963 setGroupBlocked(groupId
, true);
1964 } catch (GroupNotFoundException e
) {
1965 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: "
1966 + Base64
.encodeBytes(groupId
));
1970 if (syncMessage
.getContacts().isPresent()) {
1971 File tmpFile
= null;
1973 tmpFile
= IOUtils
.createTempFile();
1974 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1975 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
1976 .asPointer(), tmpFile
)) {
1977 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1978 if (contactsMessage
.isComplete()) {
1979 account
.getContactStore().clear();
1982 while ((c
= s
.read()) != null) {
1983 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1984 account
.setProfileKey(c
.getProfileKey().get());
1986 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1987 ContactInfo contact
= account
.getContactStore().getContact(address
);
1988 if (contact
== null) {
1989 contact
= new ContactInfo(address
);
1991 if (c
.getName().isPresent()) {
1992 contact
.name
= c
.getName().get();
1994 if (c
.getColor().isPresent()) {
1995 contact
.color
= c
.getColor().get();
1997 if (c
.getProfileKey().isPresent()) {
1998 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2000 if (c
.getVerified().isPresent()) {
2001 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2002 account
.getSignalProtocolStore()
2003 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2004 verifiedMessage
.getIdentityKey(),
2005 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2007 if (c
.getExpirationTimer().isPresent()) {
2008 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2010 contact
.blocked
= c
.isBlocked();
2011 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2012 contact
.archived
= c
.isArchived();
2013 account
.getContactStore().updateContact(contact
);
2015 if (c
.getAvatar().isPresent()) {
2016 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2020 } catch (Exception e
) {
2021 e
.printStackTrace();
2023 if (tmpFile
!= null) {
2025 Files
.delete(tmpFile
.toPath());
2026 } catch (IOException e
) {
2027 System
.err
.println("Failed to delete received contacts temp file “"
2035 if (syncMessage
.getVerified().isPresent()) {
2036 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2037 account
.getSignalProtocolStore()
2038 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2039 verifiedMessage
.getIdentityKey(),
2040 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2042 if (syncMessage
.getStickerPackOperations().isPresent()) {
2043 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2045 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2046 if (!m
.getPackId().isPresent()) {
2049 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2050 if (sticker
== null) {
2051 if (!m
.getPackKey().isPresent()) {
2054 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2056 sticker
.setInstalled(!m
.getType().isPresent()
2057 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2058 account
.getStickerStore().updateSticker(sticker
);
2061 if (syncMessage
.getConfiguration().isPresent()) {
2069 private File
getContactAvatarFile(String number
) {
2070 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2073 private File
retrieveContactAvatarAttachment(
2074 SignalServiceAttachment attachment
, String number
2075 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2076 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2077 if (attachment
.isPointer()) {
2078 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2079 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2081 SignalServiceAttachmentStream stream
= attachment
.asStream();
2082 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2086 private File
getGroupAvatarFile(byte[] groupId
) {
2087 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
2090 private File
retrieveGroupAvatarAttachment(
2091 SignalServiceAttachment attachment
, byte[] groupId
2092 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2093 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2094 if (attachment
.isPointer()) {
2095 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2096 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2098 SignalServiceAttachmentStream stream
= attachment
.asStream();
2099 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2103 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2104 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2107 private File
retrieveProfileAvatar(
2108 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2109 ) throws IOException
{
2110 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2111 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2112 File outputFile
= getProfileAvatarFile(address
);
2114 File tmpFile
= IOUtils
.createTempFile();
2115 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2118 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2119 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2120 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2123 Files
.delete(tmpFile
.toPath());
2124 } catch (IOException e
) {
2125 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
2131 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2132 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2135 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2136 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2137 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2140 private File
retrieveAttachment(
2141 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2142 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2143 if (storePreview
&& pointer
.getPreview().isPresent()) {
2144 File previewFile
= new File(outputFile
+ ".preview");
2145 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2146 byte[] preview
= pointer
.getPreview().get();
2147 output
.write(preview
, 0, preview
.length
);
2148 } catch (FileNotFoundException e
) {
2149 e
.printStackTrace();
2154 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2156 File tmpFile
= IOUtils
.createTempFile();
2157 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2159 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2160 IOUtils
.copyStreamToFile(input
, outputFile
);
2163 Files
.delete(tmpFile
.toPath());
2164 } catch (IOException e
) {
2165 System
.err
.println("Failed to delete received attachment temp file “"
2174 private InputStream
retrieveAttachmentAsStream(
2175 SignalServiceAttachmentPointer pointer
, File tmpFile
2176 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2177 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2178 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2181 void sendGroups() throws IOException
, UntrustedIdentityException
{
2182 File groupsFile
= IOUtils
.createTempFile();
2185 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2186 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2187 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2188 if (record instanceof GroupInfoV1
) {
2189 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2190 out
.write(new DeviceGroup(groupInfo
.groupId
,
2191 Optional
.fromNullable(groupInfo
.name
),
2192 new ArrayList
<>(groupInfo
.getMembers()),
2193 createGroupAvatarAttachment(groupInfo
.groupId
),
2194 groupInfo
.isMember(account
.getSelfAddress()),
2195 Optional
.of(groupInfo
.messageExpirationTime
),
2196 Optional
.fromNullable(groupInfo
.color
),
2198 Optional
.fromNullable(groupInfo
.inboxPosition
),
2199 groupInfo
.archived
));
2204 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2205 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2206 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2207 .withStream(groupsFileStream
)
2208 .withContentType("application/octet-stream")
2209 .withLength(groupsFile
.length())
2212 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2217 Files
.delete(groupsFile
.toPath());
2218 } catch (IOException e
) {
2219 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
2224 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2225 File contactsFile
= IOUtils
.createTempFile();
2228 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2229 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2230 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2231 VerifiedMessage verifiedMessage
= null;
2232 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore()
2233 .getIdentity(record.getAddress());
2234 if (currentIdentity
!= null) {
2235 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2236 currentIdentity
.getIdentityKey(),
2237 currentIdentity
.getTrustLevel().toVerifiedState(),
2238 currentIdentity
.getDateAdded().getTime());
2241 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2242 out
.write(new DeviceContact(record.getAddress(),
2243 Optional
.fromNullable(record.name
),
2244 createContactAvatarAttachment(record.number
),
2245 Optional
.fromNullable(record.color
),
2246 Optional
.fromNullable(verifiedMessage
),
2247 Optional
.fromNullable(profileKey
),
2249 Optional
.of(record.messageExpirationTime
),
2250 Optional
.fromNullable(record.inboxPosition
),
2254 if (account
.getProfileKey() != null) {
2255 // Send our own profile key as well
2256 out
.write(new DeviceContact(account
.getSelfAddress(),
2261 Optional
.of(account
.getProfileKey()),
2269 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2270 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2271 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2272 .withStream(contactsFileStream
)
2273 .withContentType("application/octet-stream")
2274 .withLength(contactsFile
.length())
2277 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2282 Files
.delete(contactsFile
.toPath());
2283 } catch (IOException e
) {
2284 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
2289 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2290 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2291 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2292 if (record.blocked
) {
2293 addresses
.add(record.getAddress());
2296 List
<byte[]> groupIds
= new ArrayList
<>();
2297 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2298 if (record.isBlocked()) {
2299 groupIds
.add(record.groupId
);
2302 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2305 private void sendVerifiedMessage(
2306 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2307 ) throws IOException
, UntrustedIdentityException
{
2308 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2310 trustLevel
.toVerifiedState(),
2311 System
.currentTimeMillis());
2312 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2315 public List
<ContactInfo
> getContacts() {
2316 return account
.getContactStore().getContacts();
2319 public ContactInfo
getContact(String number
) {
2320 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
2323 public GroupInfo
getGroup(byte[] groupId
) {
2324 return account
.getGroupStore().getGroup(groupId
);
2327 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
2328 return account
.getSignalProtocolStore().getIdentities();
2331 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
2332 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2336 * Trust this the identity with this fingerprint
2338 * @param name username of the identity
2339 * @param fingerprint Fingerprint
2341 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2342 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2343 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2347 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2348 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2352 account
.getSignalProtocolStore()
2353 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2355 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2356 } catch (IOException
| UntrustedIdentityException e
) {
2357 e
.printStackTrace();
2366 * Trust this the identity with this safety number
2368 * @param name username of the identity
2369 * @param safetyNumber Safety number
2371 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2372 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2373 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2377 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2378 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2382 account
.getSignalProtocolStore()
2383 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2385 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2386 } catch (IOException
| UntrustedIdentityException e
) {
2387 e
.printStackTrace();
2396 * Trust all keys of this identity without verification
2398 * @param name username of the identity
2400 public boolean trustIdentityAllKeys(String name
) {
2401 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2402 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2406 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2407 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2408 account
.getSignalProtocolStore()
2409 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2411 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2412 } catch (IOException
| UntrustedIdentityException e
) {
2413 e
.printStackTrace();
2421 public String
computeSafetyNumber(
2422 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2424 return Utils
.computeSafetyNumber(account
.getSelfAddress(),
2425 getIdentityKeyPair().getPublicKey(),
2430 void saveAccount() {
2434 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2435 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2437 : Util
.canonicalizeNumber(identifier
, account
.getUsername());
2438 return resolveSignalServiceAddress(canonicalizedNumber
);
2441 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2442 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2444 return resolveSignalServiceAddress(address
);
2447 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2448 if (address
.matches(account
.getSelfAddress())) {
2449 return account
.getSelfAddress();
2452 return account
.getRecipientStore().resolveServiceAddress(address
);
2456 public void close() throws IOException
{
2457 if (messagePipe
!= null) {
2458 messagePipe
.shutdown();
2462 if (unidentifiedMessagePipe
!= null) {
2463 unidentifiedMessagePipe
.shutdown();
2464 unidentifiedMessagePipe
= null;
2470 public interface ReceiveMessageHandler
{
2472 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);