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
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
25 import org
.asamk
.signal
.storage
.profiles
.SignalProfile
;
26 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
27 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
28 import org
.asamk
.signal
.util
.IOUtils
;
29 import org
.asamk
.signal
.util
.Util
;
30 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
31 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
40 import org
.signal
.libsignal
.metadata
.SelfSendException
;
41 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
42 import org
.signal
.zkgroup
.InvalidInputException
;
43 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
44 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
45 import org
.whispersystems
.libsignal
.IdentityKey
;
46 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
47 import org
.whispersystems
.libsignal
.InvalidKeyException
;
48 import org
.whispersystems
.libsignal
.InvalidMessageException
;
49 import org
.whispersystems
.libsignal
.InvalidVersionException
;
50 import org
.whispersystems
.libsignal
.ecc
.Curve
;
51 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
52 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
53 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
54 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
55 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
56 import org
.whispersystems
.libsignal
.util
.Medium
;
57 import org
.whispersystems
.libsignal
.util
.Pair
;
58 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
69 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
70 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
96 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
97 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
98 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
100 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
101 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
103 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
104 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
105 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
106 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
108 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
112 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
113 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
114 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.Closeable
;
118 import java
.io
.FileInputStream
;
119 import java
.io
.FileNotFoundException
;
120 import java
.io
.FileOutputStream
;
121 import java
.io
.IOException
;
122 import java
.io
.InputStream
;
123 import java
.io
.OutputStream
;
125 import java
.net
.URISyntaxException
;
126 import java
.net
.URLEncoder
;
127 import java
.nio
.file
.Files
;
128 import java
.nio
.file
.Paths
;
129 import java
.nio
.file
.StandardCopyOption
;
130 import java
.util
.ArrayList
;
131 import java
.util
.Arrays
;
132 import java
.util
.Collection
;
133 import java
.util
.Collections
;
134 import java
.util
.Date
;
135 import java
.util
.HashSet
;
136 import java
.util
.LinkedList
;
137 import java
.util
.List
;
138 import java
.util
.Locale
;
139 import java
.util
.Objects
;
140 import java
.util
.Set
;
141 import java
.util
.UUID
;
142 import java
.util
.concurrent
.ExecutionException
;
143 import java
.util
.concurrent
.ExecutorService
;
144 import java
.util
.concurrent
.TimeUnit
;
145 import java
.util
.concurrent
.TimeoutException
;
146 import java
.util
.stream
.Collectors
;
147 import java
.util
.zip
.ZipEntry
;
148 import java
.util
.zip
.ZipFile
;
150 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
152 public class Manager
implements Closeable
{
154 private final SleepTimer timer
= new UptimeSleepTimer();
155 private final SignalServiceConfiguration serviceConfiguration
;
156 private final String userAgent
;
158 private final SignalAccount account
;
159 private final PathConfig pathConfig
;
160 private SignalServiceAccountManager accountManager
;
161 private SignalServiceMessagePipe messagePipe
= null;
162 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
163 private boolean discoverableByPhoneNumber
= true;
165 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
166 this.account
= account
;
167 this.pathConfig
= pathConfig
;
168 this.serviceConfiguration
= serviceConfiguration
;
169 this.userAgent
= userAgent
;
170 this.accountManager
= createSignalServiceAccountManager();
172 this.account
.setResolver(this::resolveSignalServiceAddress
);
175 public String
getUsername() {
176 return account
.getUsername();
179 public SignalServiceAddress
getSelfAddress() {
180 return account
.getSelfAddress();
183 private SignalServiceAccountManager
createSignalServiceAccountManager() {
184 GroupsV2Operations groupsV2Operations
;
186 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceConfiguration
));
187 } catch (Throwable ignored
) {
188 groupsV2Operations
= null;
190 return new SignalServiceAccountManager(serviceConfiguration
,
191 new DynamicCredentialsProvider(account
.getUuid(), account
.getUsername(), account
.getPassword(), null, account
.getDeviceId()),
197 private IdentityKeyPair
getIdentityKeyPair() {
198 return account
.getSignalProtocolStore().getIdentityKeyPair();
201 public int getDeviceId() {
202 return account
.getDeviceId();
205 private String
getMessageCachePath() {
206 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
209 private String
getMessageCachePath(String sender
) {
210 if (sender
== null || sender
.isEmpty()) {
211 return getMessageCachePath();
214 return getMessageCachePath() + "/" + sender
.replace("/", "_");
217 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
218 String cachePath
= getMessageCachePath(sender
);
219 IOUtils
.createPrivateDirectories(cachePath
);
220 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
223 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
224 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
226 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
227 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
228 int registrationId
= KeyHelper
.generateRegistrationId(false);
230 ProfileKey profileKey
= KeyUtils
.createProfileKey();
231 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
234 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
237 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
239 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
241 m
.migrateLegacyConfigs();
246 private void migrateLegacyConfigs() {
247 // Copy group avatars that were previously stored in the attachments folder
248 // to the new avatar folder
249 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
250 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
251 File avatarFile
= getGroupAvatarFile(g
.groupId
);
252 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
253 if (!avatarFile
.exists() && attachmentFile
.exists()) {
255 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
256 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
257 } catch (Exception e
) {
262 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
265 if (account
.getProfileKey() == null) {
266 // Old config file, creating new profile key
267 account
.setProfileKey(KeyUtils
.createProfileKey());
272 public void checkAccountState() throws IOException
{
273 if (account
.isRegistered()) {
274 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
278 if (account
.getUuid() == null) {
279 account
.setUuid(accountManager
.getOwnUuid());
285 public boolean isRegistered() {
286 return account
.isRegistered();
289 public void register(boolean voiceVerification
) throws IOException
{
290 account
.setPassword(KeyUtils
.createPassword());
292 // Resetting UUID, because registering doesn't work otherwise
293 account
.setUuid(null);
294 accountManager
= createSignalServiceAccountManager();
296 if (voiceVerification
) {
297 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
299 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
302 account
.setRegistered(false);
306 public void updateAccountAttributes() throws IOException
{
307 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
310 public void setProfile(String name
, File avatar
) throws IOException
{
311 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
312 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
316 public void unregister() throws IOException
{
317 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
318 // If this is the master device, other users can't send messages to this number anymore.
319 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
320 accountManager
.setGcmId(Optional
.absent());
322 account
.setRegistered(false);
326 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
327 List
<DeviceInfo
> devices
= accountManager
.getDevices();
328 account
.setMultiDevice(devices
.size() > 1);
333 public void removeLinkedDevices(int deviceId
) throws IOException
{
334 accountManager
.removeDevice(deviceId
);
335 List
<DeviceInfo
> devices
= accountManager
.getDevices();
336 account
.setMultiDevice(devices
.size() > 1);
340 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
341 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
343 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
346 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
347 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
348 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
350 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
351 account
.setMultiDevice(true);
355 private List
<PreKeyRecord
> generatePreKeys() {
356 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
358 final int offset
= account
.getPreKeyIdOffset();
359 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
360 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
361 ECKeyPair keyPair
= Curve
.generateKeyPair();
362 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
367 account
.addPreKeys(records
);
373 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
375 ECKeyPair keyPair
= Curve
.generateKeyPair();
376 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
377 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
379 account
.addSignedPreKey(record);
383 } catch (InvalidKeyException e
) {
384 throw new AssertionError(e
);
388 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
389 verificationCode
= verificationCode
.replace("-", "");
390 account
.setSignalingKey(KeyUtils
.createSignalingKey());
391 // TODO make unrestricted unidentified access configurable
392 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
394 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
395 // TODO response.isStorageCapable()
396 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
397 account
.setRegistered(true);
398 account
.setUuid(uuid
);
399 account
.setRegistrationLockPin(pin
);
400 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
406 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
407 if (pin
.isPresent()) {
408 account
.setRegistrationLockPin(pin
.get());
409 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
411 account
.setRegistrationLockPin(null);
412 accountManager
.removeRegistrationLockV1();
417 void refreshPreKeys() throws IOException
{
418 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
419 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
420 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
422 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
425 private SignalServiceMessageReceiver
getMessageReceiver() {
426 // TODO implement ZkGroup support
427 final ClientZkProfileOperations clientZkProfileOperations
= null;
428 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
431 private SignalServiceMessageSender
getMessageSender() {
432 // TODO implement ZkGroup support
433 final ClientZkProfileOperations clientZkProfileOperations
= null;
434 final ExecutorService executor
= null;
435 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
436 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
439 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
440 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
445 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
446 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
450 SignalServiceMessageReceiver receiver
= getMessageReceiver();
452 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
453 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
454 throw new IOException("Failed to retrieve profile", e
);
458 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
459 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
460 long now
= new Date().getTime();
461 // Profiles are cache for 24h before retrieving them again
462 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
463 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
464 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
467 return profileEntry
.getProfile();
470 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
471 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
473 File avatarFile
= null;
475 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
476 } catch (Throwable e
) {
477 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
480 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
482 return new SignalProfile(
483 encryptedProfile
.getIdentityKey(),
484 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
486 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
487 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
488 encryptedProfile
.getCapabilities());
489 } catch (InvalidCiphertextException e
) {
494 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
495 File file
= getGroupAvatarFile(groupId
);
496 if (!file
.exists()) {
497 return Optional
.absent();
500 return Optional
.of(Utils
.createAttachment(file
));
503 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
504 File file
= getContactAvatarFile(number
);
505 if (!file
.exists()) {
506 return Optional
.absent();
509 return Optional
.of(Utils
.createAttachment(file
));
512 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
513 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
515 throw new GroupNotFoundException(groupId
);
517 if (!g
.isMember(account
.getSelfAddress())) {
518 throw new NotAGroupMemberException(groupId
, g
.name
);
523 public List
<GroupInfo
> getGroups() {
524 return account
.getGroupStore().getGroups();
527 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
529 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
530 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
531 if (attachments
!= null) {
532 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
534 if (groupId
!= null) {
535 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
538 messageBuilder
.asGroupMessage(group
);
541 final GroupInfo g
= getGroupForSending(groupId
);
543 messageBuilder
.withExpiration(g
.messageExpirationTime
);
545 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
548 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
549 long targetSentTimestamp
, byte[] groupId
)
550 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
551 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
552 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
553 .withReaction(reaction
);
554 if (groupId
!= null) {
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
558 messageBuilder
.asGroupMessage(group
);
560 final GroupInfo g
= getGroupForSending(groupId
);
561 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
564 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
565 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
569 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
570 .asGroupMessage(group
);
572 final GroupInfo g
= getGroupForSending(groupId
);
573 g
.removeMember(account
.getSelfAddress());
574 account
.getGroupStore().updateGroup(g
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
581 if (groupId
== null) {
583 g
= new GroupInfo(KeyUtils
.createGroupId());
584 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
586 g
= getGroupForSending(groupId
);
593 if (members
!= null) {
594 final Set
<String
> newE164Members
= new HashSet
<>();
595 for (SignalServiceAddress member
: members
) {
596 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
599 newE164Members
.add(member
.getNumber().get());
602 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
603 if (contacts
.size() != newE164Members
.size()) {
604 // Some of the new members are not registered on Signal
605 for (ContactTokenDetails contact
: contacts
) {
606 newE164Members
.remove(contact
.getNumber());
608 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
611 g
.addMembers(members
);
614 if (avatarFile
!= null) {
615 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
616 File aFile
= getGroupAvatarFile(g
.groupId
);
617 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
620 account
.getGroupStore().updateGroup(g
);
622 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
624 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
628 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
629 if (groupId
== null) {
632 GroupInfo g
= getGroupForSending(groupId
);
634 if (!g
.isMember(recipient
)) {
638 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
640 // Send group message only to the recipient who requested it
641 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
644 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
645 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
648 .withMembers(new ArrayList
<>(g
.getMembers()));
650 File aFile
= getGroupAvatarFile(g
.groupId
);
651 if (aFile
.exists()) {
653 group
.withAvatar(Utils
.createAttachment(aFile
));
654 } catch (IOException e
) {
655 throw new AttachmentInvalidException(aFile
.toString(), e
);
659 return SignalServiceDataMessage
.newBuilder()
660 .asGroupMessage(group
.build())
661 .withExpiration(g
.messageExpirationTime
);
664 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
665 if (groupId
== null) {
669 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
672 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
673 .asGroupMessage(group
.build());
675 // Send group info request message to the recipient who sent us a message with this groupId
676 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
679 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
680 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
681 Collections
.singletonList(messageId
),
682 System
.currentTimeMillis());
684 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
687 public long sendMessage(String messageText
, List
<String
> attachments
,
688 List
<String
> recipients
)
689 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
690 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
691 if (attachments
!= null) {
692 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
694 // Upload attachments here, so we only upload once even for multiple recipients
695 SignalServiceMessageSender messageSender
= getMessageSender();
696 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
697 for (SignalServiceAttachment attachment
: attachmentStreams
) {
698 if (attachment
.isStream()) {
699 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
700 } else if (attachment
.isPointer()) {
701 attachmentPointers
.add(attachment
.asPointer());
705 messageBuilder
.withAttachments(attachmentPointers
);
707 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
710 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
711 long targetSentTimestamp
, List
<String
> recipients
)
712 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
713 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
714 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
715 .withReaction(reaction
);
716 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
719 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
720 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
721 .asEndSessionMessage();
723 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
725 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
726 } catch (Exception e
) {
727 for (SignalServiceAddress address
: signalServiceAddresses
) {
728 handleEndSession(address
);
735 public String
getContactName(String number
) throws InvalidNumberException
{
736 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
737 if (contact
== null) {
744 public void setContactName(String number
, String name
) throws InvalidNumberException
{
745 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
746 ContactInfo contact
= account
.getContactStore().getContact(address
);
747 if (contact
== null) {
748 contact
= new ContactInfo(address
);
751 account
.getContactStore().updateContact(contact
);
755 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
756 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
759 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
760 ContactInfo contact
= account
.getContactStore().getContact(address
);
761 if (contact
== null) {
762 contact
= new ContactInfo(address
);
764 contact
.blocked
= blocked
;
765 account
.getContactStore().updateContact(contact
);
769 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
770 GroupInfo group
= getGroup(groupId
);
772 throw new GroupNotFoundException(groupId
);
775 group
.blocked
= blocked
;
776 account
.getGroupStore().updateGroup(group
);
780 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
781 if (groupId
.length
== 0) {
784 if (name
.isEmpty()) {
787 if (members
.isEmpty()) {
790 if (avatar
.isEmpty()) {
793 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
797 * Change the expiration timer for a contact
799 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
800 ContactInfo contact
= account
.getContactStore().getContact(address
);
801 contact
.messageExpirationTime
= messageExpirationTimer
;
802 account
.getContactStore().updateContact(contact
);
803 sendExpirationTimerUpdate(address
);
807 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
808 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
809 .asExpirationUpdate();
810 sendMessage(messageBuilder
, Collections
.singleton(address
));
814 * Change the expiration timer for a contact
816 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
817 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
818 setExpirationTimer(address
, messageExpirationTimer
);
822 * Change the expiration timer for a group
824 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
825 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
826 g
.messageExpirationTime
= messageExpirationTimer
;
827 account
.getGroupStore().updateGroup(g
);
831 * Upload the sticker pack from path.
833 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
834 * @return if successful, returns the URL to install the sticker pack in the signal app
836 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
837 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
839 SignalServiceMessageSender messageSender
= getMessageSender();
841 byte[] packKey
= KeyUtils
.createStickerUploadKey();
842 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
845 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
847 } catch (URISyntaxException e
) {
848 throw new AssertionError(e
);
852 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
854 String rootPath
= null;
856 final File file
= new File(path
);
857 if (file
.getName().endsWith(".zip")) {
858 zip
= new ZipFile(file
);
859 } else if (file
.getName().equals("manifest.json")) {
860 rootPath
= file
.getParent();
862 throw new StickerPackInvalidException("Could not find manifest.json");
865 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
867 if (pack
.stickers
== null) {
868 throw new StickerPackInvalidException("Must set a 'stickers' field.");
871 if (pack
.stickers
.isEmpty()) {
872 throw new StickerPackInvalidException("Must include stickers.");
875 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
876 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
877 if (sticker
.file
== null) {
878 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
881 Pair
<InputStream
, Long
> data
;
883 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
884 } catch (IOException ignored
) {
885 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
888 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
889 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
890 stickers
.add(stickerInfo
);
893 StickerInfo cover
= null;
894 if (pack
.cover
!= null) {
895 if (pack
.cover
.file
== null) {
896 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
899 Pair
<InputStream
, Long
> data
;
901 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
902 } catch (IOException ignored
) {
903 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
906 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
907 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
910 return new SignalServiceStickerManifestUpload(
917 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
918 InputStream inputStream
;
920 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
922 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
924 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
927 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
929 final ZipEntry entry
= zip
.getEntry(subfile
);
930 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
932 final File file
= new File(rootPath
, subfile
);
933 return new Pair
<>(new FileInputStream(file
), file
.length());
937 void requestSyncGroups() throws IOException
{
938 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
939 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
941 sendSyncMessage(message
);
942 } catch (UntrustedIdentityException e
) {
947 void requestSyncContacts() throws IOException
{
948 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
949 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
951 sendSyncMessage(message
);
952 } catch (UntrustedIdentityException e
) {
957 void requestSyncBlocked() throws IOException
{
958 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
959 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
961 sendSyncMessage(message
);
962 } catch (UntrustedIdentityException e
) {
967 void requestSyncConfiguration() throws IOException
{
968 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
969 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
971 sendSyncMessage(message
);
972 } catch (UntrustedIdentityException e
) {
977 private byte[] getSenderCertificate() {
978 // TODO support UUID capable sender certificates
979 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
982 certificate
= accountManager
.getSenderCertificate();
983 } catch (IOException e
) {
984 System
.err
.println("Failed to get sender certificate: " + e
);
987 // TODO cache for a day
991 private byte[] getSelfUnidentifiedAccessKey() {
992 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
995 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
996 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
997 if (contact
== null || contact
.profileKey
== null) {
1000 ProfileKey theirProfileKey
;
1002 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1003 } catch (InvalidInputException
| IOException e
) {
1004 throw new AssertionError(e
);
1006 SignalProfile targetProfile
;
1008 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1009 } catch (IOException e
) {
1010 System
.err
.println("Failed to get recipient profile: " + e
);
1014 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1018 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1019 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1022 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1025 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1026 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1027 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1029 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1030 return Optional
.absent();
1034 return Optional
.of(new UnidentifiedAccessPair(
1035 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1036 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1038 } catch (InvalidCertificateException e
) {
1039 return Optional
.absent();
1043 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1044 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1045 for (SignalServiceAddress recipient
: recipients
) {
1046 result
.add(getAccessFor(recipient
));
1051 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1052 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1053 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1054 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1056 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1057 return Optional
.absent();
1061 return Optional
.of(new UnidentifiedAccessPair(
1062 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1063 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1065 } catch (InvalidCertificateException e
) {
1066 return Optional
.absent();
1070 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1071 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1073 if (unidentifiedAccess
.isPresent()) {
1074 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1077 return Optional
.absent();
1080 private void sendSyncMessage(SignalServiceSyncMessage message
)
1081 throws IOException
, UntrustedIdentityException
{
1082 SignalServiceMessageSender messageSender
= getMessageSender();
1084 messageSender
.sendMessage(message
, getAccessForSync());
1085 } catch (UntrustedIdentityException e
) {
1086 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1092 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1094 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1095 throws EncapsulatedExceptions
, IOException
{
1096 final long timestamp
= System
.currentTimeMillis();
1097 messageBuilder
.withTimestamp(timestamp
);
1098 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1100 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1101 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1102 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1104 for (SendMessageResult result
: results
) {
1105 if (result
.isUnregisteredFailure()) {
1106 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1107 } else if (result
.isNetworkFailure()) {
1108 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1109 } else if (result
.getIdentityFailure() != null) {
1110 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1113 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1114 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1119 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1120 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1122 for (String number
: numbers
) {
1123 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1125 return signalServiceAddresses
;
1128 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1129 throws IOException
{
1130 if (messagePipe
== null) {
1131 messagePipe
= getMessageReceiver().createMessagePipe();
1133 if (unidentifiedMessagePipe
== null) {
1134 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1136 SignalServiceDataMessage message
= null;
1138 message
= messageBuilder
.build();
1139 if (message
.getGroupContext().isPresent()) {
1141 SignalServiceMessageSender messageSender
= getMessageSender();
1142 final boolean isRecipientUpdate
= false;
1143 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1144 for (SendMessageResult r
: result
) {
1145 if (r
.getIdentityFailure() != null) {
1146 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1150 } catch (UntrustedIdentityException e
) {
1151 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1152 return Collections
.emptyList();
1155 // Send to all individually, so sync messages are sent correctly
1156 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1157 for (SignalServiceAddress address
: recipients
) {
1158 ContactInfo contact
= account
.getContactStore().getContact(address
);
1159 if (contact
!= null) {
1160 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1161 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1163 messageBuilder
.withExpiration(0);
1164 messageBuilder
.withProfileKey(null);
1166 message
= messageBuilder
.build();
1167 if (address
.matches(account
.getSelfAddress())) {
1168 results
.add(sendSelfMessage(message
));
1170 results
.add(sendMessage(address
, message
));
1176 if (message
!= null && message
.isEndSession()) {
1177 for (SignalServiceAddress recipient
: recipients
) {
1178 handleEndSession(recipient
);
1185 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1186 SignalServiceMessageSender messageSender
= getMessageSender();
1188 SignalServiceAddress recipient
= account
.getSelfAddress();
1190 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1191 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1192 message
.getTimestamp(),
1194 message
.getExpiresInSeconds(),
1195 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1197 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1200 long startTime
= System
.currentTimeMillis();
1201 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1202 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1203 } catch (UntrustedIdentityException e
) {
1204 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1205 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1209 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1210 SignalServiceMessageSender messageSender
= getMessageSender();
1213 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1214 } catch (UntrustedIdentityException e
) {
1215 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1216 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1220 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1221 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1223 return cipher
.decrypt(envelope
);
1224 } catch (ProtocolUntrustedIdentityException e
) {
1225 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1226 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1227 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1228 throw identityException
;
1230 throw new AssertionError(e
);
1234 private void handleEndSession(SignalServiceAddress source
) {
1235 account
.getSignalProtocolStore().deleteAllSessions(source
);
1238 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1239 List
<HandleAction
> actions
= new ArrayList
<>();
1240 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1241 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1242 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1243 switch (groupInfo
.getType()) {
1245 if (group
== null) {
1246 group
= new GroupInfo(groupInfo
.getGroupId());
1249 if (groupInfo
.getAvatar().isPresent()) {
1250 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1251 if (avatar
.isPointer()) {
1253 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1254 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1255 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1260 if (groupInfo
.getName().isPresent()) {
1261 group
.name
= groupInfo
.getName().get();
1264 if (groupInfo
.getMembers().isPresent()) {
1265 group
.addMembers(groupInfo
.getMembers().get()
1267 .map(this::resolveSignalServiceAddress
)
1268 .collect(Collectors
.toSet()));
1271 account
.getGroupStore().updateGroup(group
);
1274 if (group
== null && !isSync
) {
1275 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1279 if (group
!= null) {
1280 group
.removeMember(source
);
1281 account
.getGroupStore().updateGroup(group
);
1285 if (group
!= null && !isSync
) {
1286 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1291 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1292 if (message
.isEndSession()) {
1293 handleEndSession(conversationPartnerAddress
);
1295 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1296 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1297 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1298 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1299 if (group
== null) {
1300 group
= new GroupInfo(groupInfo
.getGroupId());
1302 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1303 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1304 account
.getGroupStore().updateGroup(group
);
1307 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1308 if (contact
== null) {
1309 contact
= new ContactInfo(conversationPartnerAddress
);
1311 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1312 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1313 account
.getContactStore().updateContact(contact
);
1317 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1318 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1319 if (attachment
.isPointer()) {
1321 retrieveAttachment(attachment
.asPointer());
1322 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1323 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1328 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1329 if (source
.matches(account
.getSelfAddress())) {
1331 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1332 } catch (InvalidInputException ignored
) {
1334 ContactInfo contact
= account
.getContactStore().getContact(source
);
1335 if (contact
!= null) {
1336 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1337 account
.getContactStore().updateContact(contact
);
1340 ContactInfo contact
= account
.getContactStore().getContact(source
);
1341 if (contact
== null) {
1342 contact
= new ContactInfo(source
);
1344 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1345 account
.getContactStore().updateContact(contact
);
1348 if (message
.getPreviews().isPresent()) {
1349 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1350 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1351 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1352 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1354 retrieveAttachment(attachment
);
1355 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1356 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1364 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1365 final File cachePath
= new File(getMessageCachePath());
1366 if (!cachePath
.exists()) {
1369 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1370 if (!dir
.isDirectory()) {
1371 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1375 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1376 if (!fileEntry
.isFile()) {
1379 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1381 // Try to delete directory if empty
1386 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1387 SignalServiceEnvelope envelope
;
1389 envelope
= Utils
.loadEnvelope(fileEntry
);
1390 if (envelope
== null) {
1393 } catch (IOException e
) {
1394 e
.printStackTrace();
1397 SignalServiceContent content
= null;
1398 if (!envelope
.isReceipt()) {
1400 content
= decryptMessage(envelope
);
1401 } catch (Exception e
) {
1404 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1405 for (HandleAction action
: actions
) {
1407 action
.execute(this);
1408 } catch (Throwable e
) {
1409 e
.printStackTrace();
1414 handler
.handleMessage(envelope
, content
, null);
1416 Files
.delete(fileEntry
.toPath());
1417 } catch (IOException e
) {
1418 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1422 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1423 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1424 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1426 Set
<HandleAction
> queuedActions
= null;
1428 if (messagePipe
== null) {
1429 messagePipe
= messageReceiver
.createMessagePipe();
1432 boolean hasCaughtUpWithOldMessages
= false;
1435 SignalServiceEnvelope envelope
;
1436 SignalServiceContent content
= null;
1437 Exception exception
= null;
1438 final long now
= new Date().getTime();
1440 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1441 // store message on disk, before acknowledging receipt to the server
1443 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1444 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1445 Utils
.storeEnvelope(envelope1
, cacheFile
);
1446 } catch (IOException e
) {
1447 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1450 if (result
.isPresent()) {
1451 envelope
= result
.get();
1453 // Received indicator that server queue is empty
1454 hasCaughtUpWithOldMessages
= true;
1456 if (queuedActions
!= null) {
1457 for (HandleAction action
: queuedActions
) {
1459 action
.execute(this);
1460 } catch (Throwable e
) {
1461 e
.printStackTrace();
1464 queuedActions
.clear();
1465 queuedActions
= null;
1468 // Continue to wait another timeout for new messages
1471 } catch (TimeoutException e
) {
1472 if (returnOnTimeout
)
1475 } catch (InvalidVersionException e
) {
1476 System
.err
.println("Ignoring error: " + e
.getMessage());
1479 if (envelope
.hasSource()) {
1480 // Store uuid if we don't have it already
1481 SignalServiceAddress source
= envelope
.getSourceAddress();
1482 resolveSignalServiceAddress(source
);
1484 if (!envelope
.isReceipt()) {
1486 content
= decryptMessage(envelope
);
1487 } catch (Exception e
) {
1490 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1491 if (hasCaughtUpWithOldMessages
) {
1492 for (HandleAction action
: actions
) {
1494 action
.execute(this);
1495 } catch (Throwable e
) {
1496 e
.printStackTrace();
1500 if (queuedActions
== null) {
1501 queuedActions
= new HashSet
<>();
1503 queuedActions
.addAll(actions
);
1507 if (!isMessageBlocked(envelope
, content
)) {
1508 handler
.handleMessage(envelope
, content
, exception
);
1510 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1511 File cacheFile
= null;
1513 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1514 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1515 Files
.delete(cacheFile
.toPath());
1516 // Try to delete directory if empty
1517 new File(getMessageCachePath()).delete();
1518 } catch (IOException e
) {
1519 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1525 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1526 SignalServiceAddress source
;
1527 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1528 source
= envelope
.getSourceAddress();
1529 } else if (content
!= null) {
1530 source
= content
.getSender();
1534 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1535 if (sourceContact
!= null && sourceContact
.blocked
) {
1539 if (content
!= null && content
.getDataMessage().isPresent()) {
1540 SignalServiceDataMessage message
= content
.getDataMessage().get();
1541 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1542 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1543 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1544 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1552 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1553 List
<HandleAction
> actions
= new ArrayList
<>();
1554 if (content
!= null) {
1555 SignalServiceAddress sender
;
1556 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1557 sender
= envelope
.getSourceAddress();
1559 sender
= content
.getSender();
1561 // Store uuid if we don't have it already
1562 resolveSignalServiceAddress(sender
);
1564 if (content
.getDataMessage().isPresent()) {
1565 SignalServiceDataMessage message
= content
.getDataMessage().get();
1567 if (content
.isNeedsReceipt()) {
1568 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1571 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1573 if (content
.getSyncMessage().isPresent()) {
1574 account
.setMultiDevice(true);
1575 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1576 if (syncMessage
.getSent().isPresent()) {
1577 SentTranscriptMessage message
= syncMessage
.getSent().get();
1578 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1580 if (syncMessage
.getRequest().isPresent()) {
1581 RequestMessage rm
= syncMessage
.getRequest().get();
1582 if (rm
.isContactsRequest()) {
1583 actions
.add(SendSyncContactsAction
.create());
1585 if (rm
.isGroupsRequest()) {
1586 actions
.add(SendSyncGroupsAction
.create());
1588 if (rm
.isBlockedListRequest()) {
1589 actions
.add(SendSyncBlockedListAction
.create());
1591 // TODO Handle rm.isConfigurationRequest();
1593 if (syncMessage
.getGroups().isPresent()) {
1594 File tmpFile
= null;
1596 tmpFile
= IOUtils
.createTempFile();
1597 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1598 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1600 while ((g
= s
.read()) != null) {
1601 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1602 if (syncGroup
== null) {
1603 syncGroup
= new GroupInfo(g
.getId());
1605 if (g
.getName().isPresent()) {
1606 syncGroup
.name
= g
.getName().get();
1608 syncGroup
.addMembers(g
.getMembers()
1610 .map(this::resolveSignalServiceAddress
)
1611 .collect(Collectors
.toSet()));
1612 if (!g
.isActive()) {
1613 syncGroup
.removeMember(account
.getSelfAddress());
1615 // Add ourself to the member set as it's marked as active
1616 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1618 syncGroup
.blocked
= g
.isBlocked();
1619 if (g
.getColor().isPresent()) {
1620 syncGroup
.color
= g
.getColor().get();
1623 if (g
.getAvatar().isPresent()) {
1624 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1626 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1627 syncGroup
.archived
= g
.isArchived();
1628 account
.getGroupStore().updateGroup(syncGroup
);
1631 } catch (Exception e
) {
1632 e
.printStackTrace();
1634 if (tmpFile
!= null) {
1636 Files
.delete(tmpFile
.toPath());
1637 } catch (IOException e
) {
1638 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1643 if (syncMessage
.getBlockedList().isPresent()) {
1644 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1645 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1646 setContactBlocked(resolveSignalServiceAddress(address
), true);
1648 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1650 setGroupBlocked(groupId
, true);
1651 } catch (GroupNotFoundException e
) {
1652 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1656 if (syncMessage
.getContacts().isPresent()) {
1657 File tmpFile
= null;
1659 tmpFile
= IOUtils
.createTempFile();
1660 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1661 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1662 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1663 if (contactsMessage
.isComplete()) {
1664 account
.getContactStore().clear();
1667 while ((c
= s
.read()) != null) {
1668 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1669 account
.setProfileKey(c
.getProfileKey().get());
1671 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1672 ContactInfo contact
= account
.getContactStore().getContact(address
);
1673 if (contact
== null) {
1674 contact
= new ContactInfo(address
);
1676 if (c
.getName().isPresent()) {
1677 contact
.name
= c
.getName().get();
1679 if (c
.getColor().isPresent()) {
1680 contact
.color
= c
.getColor().get();
1682 if (c
.getProfileKey().isPresent()) {
1683 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1685 if (c
.getVerified().isPresent()) {
1686 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1687 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1689 if (c
.getExpirationTimer().isPresent()) {
1690 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1692 contact
.blocked
= c
.isBlocked();
1693 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1694 contact
.archived
= c
.isArchived();
1695 account
.getContactStore().updateContact(contact
);
1697 if (c
.getAvatar().isPresent()) {
1698 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1702 } catch (Exception e
) {
1703 e
.printStackTrace();
1705 if (tmpFile
!= null) {
1707 Files
.delete(tmpFile
.toPath());
1708 } catch (IOException e
) {
1709 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1714 if (syncMessage
.getVerified().isPresent()) {
1715 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1716 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1718 if (syncMessage
.getConfiguration().isPresent()) {
1726 private File
getContactAvatarFile(String number
) {
1727 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1730 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1731 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1732 if (attachment
.isPointer()) {
1733 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1734 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1736 SignalServiceAttachmentStream stream
= attachment
.asStream();
1737 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1741 private File
getGroupAvatarFile(byte[] groupId
) {
1742 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1745 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1746 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1747 if (attachment
.isPointer()) {
1748 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1749 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1751 SignalServiceAttachmentStream stream
= attachment
.asStream();
1752 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1756 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1757 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1760 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1761 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1762 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1763 File outputFile
= getProfileAvatarFile(address
);
1765 File tmpFile
= IOUtils
.createTempFile();
1766 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1767 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1768 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1771 Files
.delete(tmpFile
.toPath());
1772 } catch (IOException e
) {
1773 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1779 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1780 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1783 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1784 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1785 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1788 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1789 if (storePreview
&& pointer
.getPreview().isPresent()) {
1790 File previewFile
= new File(outputFile
+ ".preview");
1791 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1792 byte[] preview
= pointer
.getPreview().get();
1793 output
.write(preview
, 0, preview
.length
);
1794 } catch (FileNotFoundException e
) {
1795 e
.printStackTrace();
1800 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1802 File tmpFile
= IOUtils
.createTempFile();
1803 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1804 IOUtils
.copyStreamToFile(input
, outputFile
);
1807 Files
.delete(tmpFile
.toPath());
1808 } catch (IOException e
) {
1809 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1815 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1816 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1817 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1820 void sendGroups() throws IOException
, UntrustedIdentityException
{
1821 File groupsFile
= IOUtils
.createTempFile();
1824 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1825 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1826 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1827 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1828 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1829 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1830 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1834 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1835 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1836 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1837 .withStream(groupsFileStream
)
1838 .withContentType("application/octet-stream")
1839 .withLength(groupsFile
.length())
1842 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1847 Files
.delete(groupsFile
.toPath());
1848 } catch (IOException e
) {
1849 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1854 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1855 File contactsFile
= IOUtils
.createTempFile();
1858 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1859 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1860 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1861 VerifiedMessage verifiedMessage
= null;
1862 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1863 if (currentIdentity
!= null) {
1864 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1867 ProfileKey profileKey
= null;
1869 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1870 } catch (InvalidInputException ignored
) {
1872 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1873 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1874 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1875 Optional
.of(record.messageExpirationTime
),
1876 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1879 if (account
.getProfileKey() != null) {
1880 // Send our own profile key as well
1881 out
.write(new DeviceContact(account
.getSelfAddress(),
1882 Optional
.absent(), Optional
.absent(),
1883 Optional
.absent(), Optional
.absent(),
1884 Optional
.of(account
.getProfileKey()),
1885 false, Optional
.absent(), Optional
.absent(), false));
1889 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1890 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1891 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1892 .withStream(contactsFileStream
)
1893 .withContentType("application/octet-stream")
1894 .withLength(contactsFile
.length())
1897 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1902 Files
.delete(contactsFile
.toPath());
1903 } catch (IOException e
) {
1904 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1909 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1910 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1911 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1912 if (record.blocked
) {
1913 addresses
.add(record.getAddress());
1916 List
<byte[]> groupIds
= new ArrayList
<>();
1917 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1918 if (record.blocked
) {
1919 groupIds
.add(record.groupId
);
1922 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1925 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1926 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1927 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1930 public List
<ContactInfo
> getContacts() {
1931 return account
.getContactStore().getContacts();
1934 public ContactInfo
getContact(String number
) {
1935 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1938 public GroupInfo
getGroup(byte[] groupId
) {
1939 return account
.getGroupStore().getGroup(groupId
);
1942 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1943 return account
.getSignalProtocolStore().getIdentities();
1946 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1947 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1951 * Trust this the identity with this fingerprint
1953 * @param name username of the identity
1954 * @param fingerprint Fingerprint
1956 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1957 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1958 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1962 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1963 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1967 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1969 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 } catch (IOException
| UntrustedIdentityException e
) {
1971 e
.printStackTrace();
1980 * Trust this the identity with this safety number
1982 * @param name username of the identity
1983 * @param safetyNumber Safety number
1985 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1986 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1987 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1991 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1992 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1996 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1998 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1999 } catch (IOException
| UntrustedIdentityException e
) {
2000 e
.printStackTrace();
2009 * Trust all keys of this identity without verification
2011 * @param name username of the identity
2013 public boolean trustIdentityAllKeys(String name
) {
2014 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2015 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2019 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2020 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2021 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2023 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2024 } catch (IOException
| UntrustedIdentityException e
) {
2025 e
.printStackTrace();
2033 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2034 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2037 void saveAccount() {
2041 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2042 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2043 return resolveSignalServiceAddress(canonicalizedNumber
);
2046 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2047 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2049 return resolveSignalServiceAddress(address
);
2052 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2053 if (address
.matches(account
.getSelfAddress())) {
2054 return account
.getSelfAddress();
2057 return account
.getRecipientStore().resolveServiceAddress(address
);
2061 public void close() throws IOException
{
2062 if (messagePipe
!= null) {
2063 messagePipe
.shutdown();
2067 if (unidentifiedMessagePipe
!= null) {
2068 unidentifiedMessagePipe
.shutdown();
2069 unidentifiedMessagePipe
= null;
2075 public interface ReceiveMessageHandler
{
2077 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);