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
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.JsonStickerPack
;
25 import org
.asamk
.signal
.NotAGroupMemberException
;
26 import org
.asamk
.signal
.StickerPackInvalidException
;
27 import org
.asamk
.signal
.TrustLevel
;
28 import org
.asamk
.signal
.UserAlreadyExists
;
29 import org
.asamk
.signal
.storage
.SignalAccount
;
30 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
31 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
32 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
33 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
34 import org
.asamk
.signal
.util
.IOUtils
;
35 import org
.asamk
.signal
.util
.Util
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
46 import org
.signal
.libsignal
.metadata
.SelfSendException
;
47 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
105 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
106 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
108 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
109 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
110 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
111 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
112 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
113 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.FileInputStream
;
117 import java
.io
.FileNotFoundException
;
118 import java
.io
.FileOutputStream
;
119 import java
.io
.IOException
;
120 import java
.io
.InputStream
;
121 import java
.io
.OutputStream
;
123 import java
.net
.URISyntaxException
;
124 import java
.net
.URLEncoder
;
125 import java
.nio
.file
.Files
;
126 import java
.nio
.file
.Paths
;
127 import java
.nio
.file
.StandardCopyOption
;
128 import java
.util
.ArrayList
;
129 import java
.util
.Arrays
;
130 import java
.util
.Collection
;
131 import java
.util
.Collections
;
132 import java
.util
.Date
;
133 import java
.util
.HashSet
;
134 import java
.util
.LinkedList
;
135 import java
.util
.List
;
136 import java
.util
.Locale
;
137 import java
.util
.Objects
;
138 import java
.util
.Set
;
139 import java
.util
.UUID
;
140 import java
.util
.concurrent
.TimeUnit
;
141 import java
.util
.concurrent
.TimeoutException
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
{
147 private final String settingsPath
;
148 private final String dataPath
;
149 private final String attachmentsPath
;
150 private final String avatarsPath
;
151 private final SleepTimer timer
= new UptimeSleepTimer();
153 private SignalAccount account
;
154 private String username
;
155 private SignalServiceAccountManager accountManager
;
156 private SignalServiceMessagePipe messagePipe
= null;
157 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
159 public Manager(String username
, String settingsPath
) {
160 this.username
= username
;
161 this.settingsPath
= settingsPath
;
162 this.dataPath
= this.settingsPath
+ "/data";
163 this.attachmentsPath
= this.settingsPath
+ "/attachments";
164 this.avatarsPath
= this.settingsPath
+ "/avatars";
168 public String
getUsername() {
172 public SignalServiceAddress
getSelfAddress() {
173 return account
.getSelfAddress();
176 private SignalServiceAccountManager
getSignalServiceAccountManager() {
177 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
180 private IdentityKey
getIdentity() {
181 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
184 public int getDeviceId() {
185 return account
.getDeviceId();
188 private String
getMessageCachePath() {
189 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
192 private String
getMessageCachePath(String sender
) {
193 if (sender
== null || sender
.isEmpty()) {
194 return getMessageCachePath();
197 return getMessageCachePath() + "/" + sender
.replace("/", "_");
200 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
201 String cachePath
= getMessageCachePath(sender
);
202 IOUtils
.createPrivateDirectories(cachePath
);
203 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
206 public boolean userHasKeys() {
207 return account
!= null && account
.getSignalProtocolStore() != null;
210 public void init() throws IOException
{
211 if (!SignalAccount
.userExists(dataPath
, username
)) {
214 account
= SignalAccount
.load(dataPath
, username
);
215 account
.setResolver(this::resolveSignalServiceAddress
);
217 migrateLegacyConfigs();
219 accountManager
= getSignalServiceAccountManager();
220 if (account
.isRegistered()) {
221 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
225 if (account
.getUuid() == null) {
226 account
.setUuid(accountManager
.getOwnUuid());
232 private void migrateLegacyConfigs() {
233 // Copy group avatars that were previously stored in the attachments folder
234 // to the new avatar folder
235 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
236 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
237 File avatarFile
= getGroupAvatarFile(g
.groupId
);
238 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
239 if (!avatarFile
.exists() && attachmentFile
.exists()) {
241 IOUtils
.createPrivateDirectories(avatarsPath
);
242 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
243 } catch (Exception e
) {
248 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
251 if (account
.getProfileKey() == null) {
252 // Old config file, creating new profile key
253 account
.setProfileKey(KeyUtils
.createProfileKey());
258 private void createNewIdentity() throws IOException
{
259 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
260 int registrationId
= KeyHelper
.generateRegistrationId(false);
261 if (username
== null) {
262 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
263 account
.setResolver(this::resolveSignalServiceAddress
);
265 ProfileKey profileKey
= KeyUtils
.createProfileKey();
266 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
267 account
.setResolver(this::resolveSignalServiceAddress
);
272 public boolean isRegistered() {
273 return account
!= null && account
.isRegistered();
276 public void register(boolean voiceVerification
) throws IOException
{
277 if (account
== null) {
280 account
.setPassword(KeyUtils
.createPassword());
281 account
.setUuid(null);
282 accountManager
= getSignalServiceAccountManager();
284 if (voiceVerification
) {
285 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
287 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
290 account
.setRegistered(false);
294 public void updateAccountAttributes() throws IOException
{
295 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
298 public void setProfileName(String name
) throws IOException
{
299 accountManager
.setProfileName(account
.getProfileKey(), name
);
302 public void setProfileAvatar(File avatar
) throws IOException
{
303 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
304 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
305 streamDetails
.getStream().close();
308 public void removeProfileAvatar() throws IOException
{
309 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
312 public void unregister() throws IOException
{
313 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
314 // If this is the master device, other users can't send messages to this number anymore.
315 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
316 accountManager
.setGcmId(Optional
.absent());
318 account
.setRegistered(false);
322 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
323 if (account
== null) {
326 account
.setPassword(KeyUtils
.createPassword());
327 accountManager
= getSignalServiceAccountManager();
328 String uuid
= accountManager
.getNewDeviceUuid();
330 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
333 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
334 account
.setSignalingKey(KeyUtils
.createSignalingKey());
335 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
337 username
= ret
.getNumber();
338 // TODO do this check before actually registering
339 if (SignalAccount
.userExists(dataPath
, username
)) {
340 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
343 // Create new account with the synced identity
344 byte[] profileKeyBytes
= ret
.getProfileKey();
345 ProfileKey profileKey
;
346 if (profileKeyBytes
== null) {
347 profileKey
= KeyUtils
.createProfileKey();
350 profileKey
= new ProfileKey(profileKeyBytes
);
351 } catch (InvalidInputException e
) {
352 throw new IOException("Received invalid profileKey", e
);
355 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
356 account
.setResolver(this::resolveSignalServiceAddress
);
361 requestSyncContacts();
362 requestSyncBlocked();
363 requestSyncConfiguration();
368 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
369 List
<DeviceInfo
> devices
= accountManager
.getDevices();
370 account
.setMultiDevice(devices
.size() > 1);
375 public void removeLinkedDevices(int deviceId
) throws IOException
{
376 accountManager
.removeDevice(deviceId
);
377 List
<DeviceInfo
> devices
= accountManager
.getDevices();
378 account
.setMultiDevice(devices
.size() > 1);
382 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
383 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
385 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
388 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
389 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
390 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
392 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
393 account
.setMultiDevice(true);
397 private List
<PreKeyRecord
> generatePreKeys() {
398 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
400 final int offset
= account
.getPreKeyIdOffset();
401 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
402 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
403 ECKeyPair keyPair
= Curve
.generateKeyPair();
404 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
409 account
.addPreKeys(records
);
415 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
417 ECKeyPair keyPair
= Curve
.generateKeyPair();
418 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
419 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
421 account
.addSignedPreKey(record);
425 } catch (InvalidKeyException e
) {
426 throw new AssertionError(e
);
430 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
431 verificationCode
= verificationCode
.replace("-", "");
432 account
.setSignalingKey(KeyUtils
.createSignalingKey());
433 // TODO make unrestricted unidentified access configurable
434 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
436 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
437 account
.setRegistered(true);
438 account
.setUuid(uuid
);
439 account
.setRegistrationLockPin(pin
);
440 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
446 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
447 if (pin
.isPresent()) {
448 account
.setRegistrationLockPin(pin
.get());
449 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
451 account
.setRegistrationLockPin(null);
452 accountManager
.removeV1Pin();
457 private void refreshPreKeys() throws IOException
{
458 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
459 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
460 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
462 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
465 private SignalServiceMessageReceiver
getMessageReceiver() {
466 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
469 private SignalServiceMessageSender
getMessageSender() {
470 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
471 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
474 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
475 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
480 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
481 } catch (IOException ignored
) {
485 SignalServiceMessageReceiver receiver
= getMessageReceiver();
487 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
488 } catch (VerificationFailedException e
) {
489 throw new AssertionError(e
);
493 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
494 File file
= getGroupAvatarFile(groupId
);
495 if (!file
.exists()) {
496 return Optional
.absent();
499 return Optional
.of(Utils
.createAttachment(file
));
502 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
503 File file
= getContactAvatarFile(number
);
504 if (!file
.exists()) {
505 return Optional
.absent();
508 return Optional
.of(Utils
.createAttachment(file
));
511 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
512 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
514 throw new GroupNotFoundException(groupId
);
516 if (!g
.isMember(account
.getSelfAddress())) {
517 throw new NotAGroupMemberException(groupId
, g
.name
);
522 public List
<GroupInfo
> getGroups() {
523 return account
.getGroupStore().getGroups();
527 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
529 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
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 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
, AttachmentInvalidException
, InvalidNumberException
{
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
{
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
{
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 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
609 System
.err
.println("Aborting…");
613 g
.addMembers(members
);
616 if (avatarFile
!= null) {
617 IOUtils
.createPrivateDirectories(avatarsPath
);
618 File aFile
= getGroupAvatarFile(g
.groupId
);
619 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
622 account
.getGroupStore().updateGroup(g
);
624 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
626 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
630 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
631 if (groupId
== null) {
634 GroupInfo g
= getGroupForSending(groupId
);
636 if (!g
.isMember(recipient
)) {
640 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
642 // Send group message only to the recipient who requested it
643 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
646 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
647 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
650 .withMembers(new ArrayList
<>(g
.getMembers()));
652 File aFile
= getGroupAvatarFile(g
.groupId
);
653 if (aFile
.exists()) {
655 group
.withAvatar(Utils
.createAttachment(aFile
));
656 } catch (IOException e
) {
657 throw new AttachmentInvalidException(aFile
.toString(), e
);
661 return SignalServiceDataMessage
.newBuilder()
662 .asGroupMessage(group
.build())
663 .withExpiration(g
.messageExpirationTime
);
666 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
667 if (groupId
== null) {
671 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
674 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
675 .asGroupMessage(group
.build());
677 // Send group info request message to the recipient who sent us a message with this groupId
678 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
681 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
682 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
683 Collections
.singletonList(messageId
),
684 System
.currentTimeMillis());
686 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
690 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
691 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
692 List
<String
> recipients
= new ArrayList
<>(1);
693 recipients
.add(recipient
);
694 sendMessage(message
, attachments
, recipients
);
698 public void sendMessage(String messageText
, List
<String
> attachments
,
699 List
<String
> recipients
)
700 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
701 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
702 if (attachments
!= null) {
703 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
705 // Upload attachments here, so we only upload once even for multiple recipients
706 SignalServiceMessageSender messageSender
= getMessageSender();
707 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
708 for (SignalServiceAttachment attachment
: attachmentStreams
) {
709 if (attachment
.isStream()) {
710 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
711 } else if (attachment
.isPointer()) {
712 attachmentPointers
.add(attachment
.asPointer());
716 messageBuilder
.withAttachments(attachmentPointers
);
718 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
721 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
722 long targetSentTimestamp
, List
<String
> recipients
)
723 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
724 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
725 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
726 .withReaction(reaction
);
727 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
731 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
732 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
733 .asEndSessionMessage();
735 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
739 public String
getContactName(String number
) throws InvalidNumberException
{
740 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
741 if (contact
== null) {
749 public void setContactName(String number
, String name
) throws InvalidNumberException
{
750 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
751 ContactInfo contact
= account
.getContactStore().getContact(address
);
752 if (contact
== null) {
753 contact
= new ContactInfo(address
);
754 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
756 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
759 account
.getContactStore().updateContact(contact
);
764 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
765 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
768 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
769 ContactInfo contact
= account
.getContactStore().getContact(address
);
770 if (contact
== null) {
771 contact
= new ContactInfo(address
);
772 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
774 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
776 contact
.blocked
= blocked
;
777 account
.getContactStore().updateContact(contact
);
782 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
783 GroupInfo group
= getGroup(groupId
);
785 throw new GroupNotFoundException(groupId
);
787 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
788 group
.blocked
= blocked
;
789 account
.getGroupStore().updateGroup(group
);
795 public List
<byte[]> getGroupIds() {
796 List
<GroupInfo
> groups
= getGroups();
797 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
798 for (GroupInfo group
: groups
) {
799 ids
.add(group
.groupId
);
805 public String
getGroupName(byte[] groupId
) {
806 GroupInfo group
= getGroup(groupId
);
815 public List
<String
> getGroupMembers(byte[] groupId
) {
816 GroupInfo group
= getGroup(groupId
);
818 return Collections
.emptyList();
820 return new ArrayList
<>(group
.getMembersE164());
825 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
826 if (groupId
.length
== 0) {
829 if (name
.isEmpty()) {
832 if (members
.size() == 0) {
835 if (avatar
.isEmpty()) {
838 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
842 * Change the expiration timer for a contact
844 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
845 ContactInfo c
= account
.getContactStore().getContact(address
);
846 c
.messageExpirationTime
= messageExpirationTimer
;
847 account
.getContactStore().updateContact(c
);
851 * Change the expiration timer for a group
853 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
854 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
855 g
.messageExpirationTime
= messageExpirationTimer
;
856 account
.getGroupStore().updateGroup(g
);
860 * Upload the sticker pack from path.
862 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
863 * @return if successful, returns the URL to install the sticker pack in the signal app
865 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
866 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
868 SignalServiceMessageSender messageSender
= getMessageSender();
870 byte[] packKey
= KeyUtils
.createStickerUploadKey();
871 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
874 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
876 } catch (URISyntaxException e
) {
877 throw new AssertionError(e
);
881 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
883 String rootPath
= null;
885 final File file
= new File(path
);
886 if (file
.getName().endsWith(".zip")) {
887 zip
= new ZipFile(file
);
888 } else if (file
.getName().equals("manifest.json")) {
889 rootPath
= file
.getParent();
891 throw new StickerPackInvalidException("Could not find manifest.json");
894 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
896 if (pack
.stickers
== null) {
897 throw new StickerPackInvalidException("Must set a 'stickers' field.");
900 if (pack
.stickers
.isEmpty()) {
901 throw new StickerPackInvalidException("Must include stickers.");
904 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
905 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
906 if (sticker
.file
== null) {
907 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
910 Pair
<InputStream
, Long
> data
;
912 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
913 } catch (IOException ignored
) {
914 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
917 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
918 stickers
.add(stickerInfo
);
921 StickerInfo cover
= null;
922 if (pack
.cover
!= null) {
923 if (pack
.cover
.file
== null) {
924 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
927 Pair
<InputStream
, Long
> data
;
929 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
930 } catch (IOException ignored
) {
931 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
934 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
937 return new SignalServiceStickerManifestUpload(
944 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
945 InputStream inputStream
;
947 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
949 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
951 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
954 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
956 final ZipEntry entry
= zip
.getEntry(subfile
);
957 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
959 final File file
= new File(rootPath
, subfile
);
960 return new Pair
<>(new FileInputStream(file
), file
.length());
964 private void requestSyncGroups() throws IOException
{
965 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
966 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
968 sendSyncMessage(message
);
969 } catch (UntrustedIdentityException e
) {
974 private void requestSyncContacts() throws IOException
{
975 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
976 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
978 sendSyncMessage(message
);
979 } catch (UntrustedIdentityException e
) {
984 private void requestSyncBlocked() throws IOException
{
985 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
986 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
988 sendSyncMessage(message
);
989 } catch (UntrustedIdentityException e
) {
994 private void requestSyncConfiguration() throws IOException
{
995 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
996 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
998 sendSyncMessage(message
);
999 } catch (UntrustedIdentityException e
) {
1000 e
.printStackTrace();
1004 private byte[] getSenderCertificate() {
1005 // TODO support UUID capable sender certificates
1006 // byte[] certificate = accountManager.getSenderCertificate();
1009 certificate
= accountManager
.getSenderCertificateLegacy();
1010 } catch (IOException e
) {
1011 System
.err
.println("Failed to get sender certificate: " + e
);
1014 // TODO cache for a day
1018 private byte[] getSelfUnidentifiedAccessKey() {
1019 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1022 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1023 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1025 return new SignalProfile(
1026 encryptedProfile
.getIdentityKey(),
1027 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1028 encryptedProfile
.getAvatar(),
1029 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1030 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1032 } catch (InvalidCiphertextException e
) {
1037 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1038 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1039 if (contact
== null || contact
.profileKey
== null) {
1042 ProfileKey theirProfileKey
;
1044 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1045 } catch (InvalidInputException
| IOException e
) {
1046 throw new AssertionError(e
);
1048 SignalProfile targetProfile
;
1050 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1051 } catch (IOException e
) {
1052 System
.err
.println("Failed to get recipient profile: " + e
);
1056 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1060 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1061 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1064 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1067 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1068 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1069 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1071 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1072 return Optional
.absent();
1076 return Optional
.of(new UnidentifiedAccessPair(
1077 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1078 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1080 } catch (InvalidCertificateException e
) {
1081 return Optional
.absent();
1085 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1086 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1087 for (SignalServiceAddress recipient
: recipients
) {
1088 result
.add(getAccessFor(recipient
));
1093 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1094 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1095 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1096 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1098 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1099 return Optional
.absent();
1103 return Optional
.of(new UnidentifiedAccessPair(
1104 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1105 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1107 } catch (InvalidCertificateException e
) {
1108 return Optional
.absent();
1112 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1113 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1115 if (unidentifiedAccess
.isPresent()) {
1116 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1119 return Optional
.absent();
1122 private void sendSyncMessage(SignalServiceSyncMessage message
)
1123 throws IOException
, UntrustedIdentityException
{
1124 SignalServiceMessageSender messageSender
= getMessageSender();
1126 messageSender
.sendMessage(message
, getAccessForSync());
1127 } catch (UntrustedIdentityException e
) {
1128 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1134 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1136 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1137 throws EncapsulatedExceptions
, IOException
{
1138 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1140 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1141 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1142 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1144 for (SendMessageResult result
: results
) {
1145 if (result
.isUnregisteredFailure()) {
1146 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1147 } else if (result
.isNetworkFailure()) {
1148 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1149 } else if (result
.getIdentityFailure() != null) {
1150 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1153 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1154 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1158 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1159 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1161 for (String number
: numbers
) {
1162 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1164 return signalServiceAddresses
;
1167 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1168 throws IOException
{
1169 if (messagePipe
== null) {
1170 messagePipe
= getMessageReceiver().createMessagePipe();
1172 if (unidentifiedMessagePipe
== null) {
1173 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1175 SignalServiceDataMessage message
= null;
1177 SignalServiceMessageSender messageSender
= getMessageSender();
1179 message
= messageBuilder
.build();
1180 if (message
.getGroupInfo().isPresent()) {
1182 final boolean isRecipientUpdate
= false;
1183 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1184 for (SendMessageResult r
: result
) {
1185 if (r
.getIdentityFailure() != null) {
1186 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1190 } catch (UntrustedIdentityException e
) {
1191 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1192 return Collections
.emptyList();
1194 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1195 SignalServiceAddress recipient
= account
.getSelfAddress();
1196 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1197 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1198 message
.getTimestamp(),
1200 message
.getExpiresInSeconds(),
1201 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1203 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1205 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1207 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1208 } catch (UntrustedIdentityException e
) {
1209 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1210 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1214 // Send to all individually, so sync messages are sent correctly
1215 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1216 for (SignalServiceAddress address
: recipients
) {
1217 ContactInfo contact
= account
.getContactStore().getContact(address
);
1218 if (contact
!= null) {
1219 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1220 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1222 messageBuilder
.withExpiration(0);
1223 messageBuilder
.withProfileKey(null);
1225 message
= messageBuilder
.build();
1227 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1228 results
.add(result
);
1229 } catch (UntrustedIdentityException e
) {
1230 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1231 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1237 if (message
!= null && message
.isEndSession()) {
1238 for (SignalServiceAddress recipient
: recipients
) {
1239 handleEndSession(recipient
);
1246 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1247 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1249 return cipher
.decrypt(envelope
);
1250 } catch (ProtocolUntrustedIdentityException e
) {
1251 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1252 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1257 private void handleEndSession(SignalServiceAddress source
) {
1258 account
.getSignalProtocolStore().deleteAllSessions(source
);
1261 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1262 if (message
.getGroupInfo().isPresent()) {
1263 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1264 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1265 switch (groupInfo
.getType()) {
1267 if (group
== null) {
1268 group
= new GroupInfo(groupInfo
.getGroupId());
1271 if (groupInfo
.getAvatar().isPresent()) {
1272 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1273 if (avatar
.isPointer()) {
1275 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1276 } catch (IOException
| InvalidMessageException e
) {
1277 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1282 if (groupInfo
.getName().isPresent()) {
1283 group
.name
= groupInfo
.getName().get();
1286 if (groupInfo
.getMembers().isPresent()) {
1287 group
.addMembers(groupInfo
.getMembers().get());
1290 account
.getGroupStore().updateGroup(group
);
1293 if (group
== null) {
1295 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1296 } catch (IOException
| EncapsulatedExceptions e
) {
1297 e
.printStackTrace();
1302 if (group
== null) {
1304 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1305 } catch (IOException
| EncapsulatedExceptions e
) {
1306 e
.printStackTrace();
1309 group
.removeMember(source
);
1310 account
.getGroupStore().updateGroup(group
);
1314 if (group
!= null) {
1316 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1317 } catch (IOException
| EncapsulatedExceptions e
) {
1318 e
.printStackTrace();
1319 } catch (NotAGroupMemberException e
) {
1320 // We have left this group, so don't send a group update message
1326 if (message
.isEndSession()) {
1327 handleEndSession(isSync ? destination
: source
);
1329 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1330 if (message
.getGroupInfo().isPresent()) {
1331 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1332 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1333 if (group
== null) {
1334 group
= new GroupInfo(groupInfo
.getGroupId());
1336 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1337 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1338 account
.getGroupStore().updateGroup(group
);
1341 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1342 if (contact
== null) {
1343 contact
= new ContactInfo(isSync ? destination
: source
);
1345 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1346 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1347 account
.getContactStore().updateContact(contact
);
1351 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1352 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1353 if (attachment
.isPointer()) {
1355 retrieveAttachment(attachment
.asPointer());
1356 } catch (IOException
| InvalidMessageException e
) {
1357 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1362 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1363 if (source
.matches(account
.getSelfAddress())) {
1365 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1366 } catch (InvalidInputException ignored
) {
1368 ContactInfo contact
= account
.getContactStore().getContact(source
);
1369 if (contact
!= null) {
1370 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1371 account
.getContactStore().updateContact(contact
);
1374 ContactInfo contact
= account
.getContactStore().getContact(source
);
1375 if (contact
== null) {
1376 contact
= new ContactInfo(source
);
1378 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1379 account
.getContactStore().updateContact(contact
);
1382 if (message
.getPreviews().isPresent()) {
1383 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1384 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1385 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1386 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1388 retrieveAttachment(attachment
);
1389 } catch (IOException
| InvalidMessageException e
) {
1390 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1397 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1398 final File cachePath
= new File(getMessageCachePath());
1399 if (!cachePath
.exists()) {
1402 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1403 if (!dir
.isDirectory()) {
1404 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1408 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1409 if (!fileEntry
.isFile()) {
1412 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1414 // Try to delete directory if empty
1419 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1420 SignalServiceEnvelope envelope
;
1422 envelope
= Utils
.loadEnvelope(fileEntry
);
1423 if (envelope
== null) {
1426 } catch (IOException e
) {
1427 e
.printStackTrace();
1430 SignalServiceContent content
= null;
1431 if (!envelope
.isReceipt()) {
1433 content
= decryptMessage(envelope
);
1434 } catch (Exception e
) {
1437 handleMessage(envelope
, content
, ignoreAttachments
);
1440 handler
.handleMessage(envelope
, content
, null);
1442 Files
.delete(fileEntry
.toPath());
1443 } catch (IOException e
) {
1444 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1448 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1449 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1450 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1453 if (messagePipe
== null) {
1454 messagePipe
= messageReceiver
.createMessagePipe();
1458 SignalServiceEnvelope envelope
;
1459 SignalServiceContent content
= null;
1460 Exception exception
= null;
1461 final long now
= new Date().getTime();
1463 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1464 // store message on disk, before acknowledging receipt to the server
1466 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1467 Utils
.storeEnvelope(envelope1
, cacheFile
);
1468 } catch (IOException e
) {
1469 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1472 } catch (TimeoutException e
) {
1473 if (returnOnTimeout
)
1476 } catch (InvalidVersionException e
) {
1477 System
.err
.println("Ignoring error: " + e
.getMessage());
1480 if (!envelope
.isReceipt()) {
1482 content
= decryptMessage(envelope
);
1483 } catch (Exception e
) {
1486 handleMessage(envelope
, content
, ignoreAttachments
);
1489 if (!isMessageBlocked(envelope
, content
)) {
1490 handler
.handleMessage(envelope
, content
, exception
);
1492 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1493 File cacheFile
= null;
1495 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1496 Files
.delete(cacheFile
.toPath());
1497 // Try to delete directory if empty
1498 new File(getMessageCachePath()).delete();
1499 } catch (IOException e
) {
1500 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1505 if (messagePipe
!= null) {
1506 messagePipe
.shutdown();
1512 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1513 SignalServiceAddress source
;
1514 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1515 source
= envelope
.getSourceAddress();
1516 } else if (content
!= null) {
1517 source
= content
.getSender();
1521 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1522 if (sourceContact
!= null && sourceContact
.blocked
) {
1526 if (content
!= null && content
.getDataMessage().isPresent()) {
1527 SignalServiceDataMessage message
= content
.getDataMessage().get();
1528 if (message
.getGroupInfo().isPresent()) {
1529 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1530 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1531 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1539 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1540 if (content
!= null) {
1541 SignalServiceAddress sender
;
1542 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1543 sender
= envelope
.getSourceAddress();
1545 sender
= content
.getSender();
1547 if (content
.getDataMessage().isPresent()) {
1548 SignalServiceDataMessage message
= content
.getDataMessage().get();
1550 if (content
.isNeedsReceipt()) {
1552 sendReceipt(sender
, message
.getTimestamp());
1553 } catch (IOException
| UntrustedIdentityException e
) {
1554 e
.printStackTrace();
1558 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1560 if (content
.getSyncMessage().isPresent()) {
1561 account
.setMultiDevice(true);
1562 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1563 if (syncMessage
.getSent().isPresent()) {
1564 SentTranscriptMessage message
= syncMessage
.getSent().get();
1565 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1567 if (syncMessage
.getRequest().isPresent()) {
1568 RequestMessage rm
= syncMessage
.getRequest().get();
1569 if (rm
.isContactsRequest()) {
1572 } catch (UntrustedIdentityException
| IOException e
) {
1573 e
.printStackTrace();
1576 if (rm
.isGroupsRequest()) {
1579 } catch (UntrustedIdentityException
| IOException e
) {
1580 e
.printStackTrace();
1583 if (rm
.isBlockedListRequest()) {
1586 } catch (UntrustedIdentityException
| IOException e
) {
1587 e
.printStackTrace();
1590 // TODO Handle rm.isConfigurationRequest();
1592 if (syncMessage
.getGroups().isPresent()) {
1593 File tmpFile
= null;
1595 tmpFile
= IOUtils
.createTempFile();
1596 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1597 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1599 while ((g
= s
.read()) != null) {
1600 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1601 if (syncGroup
== null) {
1602 syncGroup
= new GroupInfo(g
.getId());
1604 if (g
.getName().isPresent()) {
1605 syncGroup
.name
= g
.getName().get();
1607 syncGroup
.addMembers(g
.getMembers());
1608 if (!g
.isActive()) {
1609 syncGroup
.removeMember(account
.getSelfAddress());
1611 // Add ourself to the member set as it's marked as active
1612 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1614 syncGroup
.blocked
= g
.isBlocked();
1615 if (g
.getColor().isPresent()) {
1616 syncGroup
.color
= g
.getColor().get();
1619 if (g
.getAvatar().isPresent()) {
1620 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1622 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1623 syncGroup
.archived
= g
.isArchived();
1624 account
.getGroupStore().updateGroup(syncGroup
);
1627 } catch (Exception e
) {
1628 e
.printStackTrace();
1630 if (tmpFile
!= null) {
1632 Files
.delete(tmpFile
.toPath());
1633 } catch (IOException e
) {
1634 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1639 if (syncMessage
.getBlockedList().isPresent()) {
1640 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1641 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1642 setContactBlocked(address
, true);
1644 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1646 setGroupBlocked(groupId
, true);
1647 } catch (GroupNotFoundException e
) {
1648 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1652 if (syncMessage
.getContacts().isPresent()) {
1653 File tmpFile
= null;
1655 tmpFile
= IOUtils
.createTempFile();
1656 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1657 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1658 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1659 if (contactsMessage
.isComplete()) {
1660 account
.getContactStore().clear();
1663 while ((c
= s
.read()) != null) {
1664 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1665 account
.setProfileKey(c
.getProfileKey().get());
1667 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1668 if (contact
== null) {
1669 contact
= new ContactInfo(c
.getAddress());
1671 if (c
.getName().isPresent()) {
1672 contact
.name
= c
.getName().get();
1674 if (c
.getColor().isPresent()) {
1675 contact
.color
= c
.getColor().get();
1677 if (c
.getProfileKey().isPresent()) {
1678 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1680 if (c
.getVerified().isPresent()) {
1681 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1682 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1684 if (c
.getExpirationTimer().isPresent()) {
1685 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1687 contact
.blocked
= c
.isBlocked();
1688 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1689 contact
.archived
= c
.isArchived();
1690 account
.getContactStore().updateContact(contact
);
1692 if (c
.getAvatar().isPresent()) {
1693 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1697 } catch (Exception e
) {
1698 e
.printStackTrace();
1700 if (tmpFile
!= null) {
1702 Files
.delete(tmpFile
.toPath());
1703 } catch (IOException e
) {
1704 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1709 if (syncMessage
.getVerified().isPresent()) {
1710 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1711 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1713 if (syncMessage
.getConfiguration().isPresent()) {
1720 private File
getContactAvatarFile(String number
) {
1721 return new File(avatarsPath
, "contact-" + number
);
1724 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1725 IOUtils
.createPrivateDirectories(avatarsPath
);
1726 if (attachment
.isPointer()) {
1727 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1728 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1730 SignalServiceAttachmentStream stream
= attachment
.asStream();
1731 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1735 private File
getGroupAvatarFile(byte[] groupId
) {
1736 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1739 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1740 IOUtils
.createPrivateDirectories(avatarsPath
);
1741 if (attachment
.isPointer()) {
1742 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1743 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1745 SignalServiceAttachmentStream stream
= attachment
.asStream();
1746 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1750 public File
getAttachmentFile(long attachmentId
) {
1751 return new File(attachmentsPath
, attachmentId
+ "");
1754 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1755 IOUtils
.createPrivateDirectories(attachmentsPath
);
1756 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1759 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1760 if (storePreview
&& pointer
.getPreview().isPresent()) {
1761 File previewFile
= new File(outputFile
+ ".preview");
1762 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1763 byte[] preview
= pointer
.getPreview().get();
1764 output
.write(preview
, 0, preview
.length
);
1765 } catch (FileNotFoundException e
) {
1766 e
.printStackTrace();
1771 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1773 File tmpFile
= IOUtils
.createTempFile();
1774 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1775 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1776 byte[] buffer
= new byte[4096];
1779 while ((read
= input
.read(buffer
)) != -1) {
1780 output
.write(buffer
, 0, read
);
1782 } catch (FileNotFoundException e
) {
1783 e
.printStackTrace();
1788 Files
.delete(tmpFile
.toPath());
1789 } catch (IOException e
) {
1790 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1796 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1797 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1798 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1802 public boolean isRemote() {
1806 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1807 File groupsFile
= IOUtils
.createTempFile();
1810 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1811 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1812 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1813 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1814 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1815 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1816 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1820 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1821 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1822 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1823 .withStream(groupsFileStream
)
1824 .withContentType("application/octet-stream")
1825 .withLength(groupsFile
.length())
1828 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1833 Files
.delete(groupsFile
.toPath());
1834 } catch (IOException e
) {
1835 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1840 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1841 File contactsFile
= IOUtils
.createTempFile();
1844 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1845 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1846 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1847 VerifiedMessage verifiedMessage
= null;
1848 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1849 if (currentIdentity
!= null) {
1850 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1853 ProfileKey profileKey
= null;
1855 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1856 } catch (InvalidInputException ignored
) {
1858 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1859 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1860 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1861 Optional
.of(record.messageExpirationTime
),
1862 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1865 if (account
.getProfileKey() != null) {
1866 // Send our own profile key as well
1867 out
.write(new DeviceContact(account
.getSelfAddress(),
1868 Optional
.absent(), Optional
.absent(),
1869 Optional
.absent(), Optional
.absent(),
1870 Optional
.of(account
.getProfileKey()),
1871 false, Optional
.absent(), Optional
.absent(), false));
1875 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1876 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1877 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1878 .withStream(contactsFileStream
)
1879 .withContentType("application/octet-stream")
1880 .withLength(contactsFile
.length())
1883 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1888 Files
.delete(contactsFile
.toPath());
1889 } catch (IOException e
) {
1890 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1895 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1896 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1897 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1898 if (record.blocked
) {
1899 addresses
.add(record.getAddress());
1902 List
<byte[]> groupIds
= new ArrayList
<>();
1903 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1904 if (record.blocked
) {
1905 groupIds
.add(record.groupId
);
1908 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1911 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1912 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1913 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1916 public List
<ContactInfo
> getContacts() {
1917 return account
.getContactStore().getContacts();
1920 public ContactInfo
getContact(String number
) {
1921 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1924 public GroupInfo
getGroup(byte[] groupId
) {
1925 return account
.getGroupStore().getGroup(groupId
);
1928 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1929 return account
.getSignalProtocolStore().getIdentities();
1932 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1933 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1937 * Trust this the identity with this fingerprint
1939 * @param name username of the identity
1940 * @param fingerprint Fingerprint
1942 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1943 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1944 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1948 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1949 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1953 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1955 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1956 } catch (IOException
| UntrustedIdentityException e
) {
1957 e
.printStackTrace();
1966 * Trust this the identity with this safety number
1968 * @param name username of the identity
1969 * @param safetyNumber Safety number
1971 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1972 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1973 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1977 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1978 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1982 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1984 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1985 } catch (IOException
| UntrustedIdentityException e
) {
1986 e
.printStackTrace();
1995 * Trust all keys of this identity without verification
1997 * @param name username of the identity
1999 public boolean trustIdentityAllKeys(String name
) {
2000 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2001 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2005 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2006 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2007 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2009 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2010 } catch (IOException
| UntrustedIdentityException e
) {
2011 e
.printStackTrace();
2019 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2020 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2023 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2024 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2025 return resolveSignalServiceAddress(canonicalizedNumber
);
2028 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2029 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2030 if (address
.matches(account
.getSelfAddress())) {
2031 return account
.getSelfAddress();
2034 ContactInfo contactInfo
= account
.getContactStore().getContact(address
);
2035 if (contactInfo
== null) {
2038 return contactInfo
.getAddress();
2041 public interface ReceiveMessageHandler
{
2043 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);