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
.stream
.Collectors
;
143 import java
.util
.zip
.ZipEntry
;
144 import java
.util
.zip
.ZipFile
;
146 public class Manager
implements Signal
{
148 private final String settingsPath
;
149 private final String dataPath
;
150 private final String attachmentsPath
;
151 private final String avatarsPath
;
152 private final SleepTimer timer
= new UptimeSleepTimer();
154 private SignalAccount account
;
155 private String username
;
156 private SignalServiceAccountManager accountManager
;
157 private SignalServiceMessagePipe messagePipe
= null;
158 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
160 public Manager(String username
, String settingsPath
) {
161 this.username
= username
;
162 this.settingsPath
= settingsPath
;
163 this.dataPath
= this.settingsPath
+ "/data";
164 this.attachmentsPath
= this.settingsPath
+ "/attachments";
165 this.avatarsPath
= this.settingsPath
+ "/avatars";
169 public String
getUsername() {
173 public SignalServiceAddress
getSelfAddress() {
174 return account
.getSelfAddress();
177 private SignalServiceAccountManager
getSignalServiceAccountManager() {
178 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
181 private IdentityKey
getIdentity() {
182 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
185 public int getDeviceId() {
186 return account
.getDeviceId();
189 private String
getMessageCachePath() {
190 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
193 private String
getMessageCachePath(String sender
) {
194 if (sender
== null || sender
.isEmpty()) {
195 return getMessageCachePath();
198 return getMessageCachePath() + "/" + sender
.replace("/", "_");
201 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
202 String cachePath
= getMessageCachePath(sender
);
203 IOUtils
.createPrivateDirectories(cachePath
);
204 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
207 public boolean userHasKeys() {
208 return account
!= null && account
.getSignalProtocolStore() != null;
211 public void init() throws IOException
{
212 if (!SignalAccount
.userExists(dataPath
, username
)) {
215 account
= SignalAccount
.load(dataPath
, username
);
216 account
.setResolver(this::resolveSignalServiceAddress
);
218 migrateLegacyConfigs();
220 accountManager
= getSignalServiceAccountManager();
221 if (account
.isRegistered()) {
222 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
226 if (account
.getUuid() == null) {
227 account
.setUuid(accountManager
.getOwnUuid());
233 private void migrateLegacyConfigs() {
234 // Copy group avatars that were previously stored in the attachments folder
235 // to the new avatar folder
236 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
237 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
238 File avatarFile
= getGroupAvatarFile(g
.groupId
);
239 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
240 if (!avatarFile
.exists() && attachmentFile
.exists()) {
242 IOUtils
.createPrivateDirectories(avatarsPath
);
243 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
244 } catch (Exception e
) {
249 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
252 if (account
.getProfileKey() == null) {
253 // Old config file, creating new profile key
254 account
.setProfileKey(KeyUtils
.createProfileKey());
259 private void createNewIdentity() throws IOException
{
260 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
261 int registrationId
= KeyHelper
.generateRegistrationId(false);
262 if (username
== null) {
263 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
264 account
.setResolver(this::resolveSignalServiceAddress
);
266 ProfileKey profileKey
= KeyUtils
.createProfileKey();
267 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
268 account
.setResolver(this::resolveSignalServiceAddress
);
273 public boolean isRegistered() {
274 return account
!= null && account
.isRegistered();
277 public void register(boolean voiceVerification
) throws IOException
{
278 if (account
== null) {
281 account
.setPassword(KeyUtils
.createPassword());
282 account
.setUuid(null);
283 accountManager
= getSignalServiceAccountManager();
285 if (voiceVerification
) {
286 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
288 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
291 account
.setRegistered(false);
295 public void updateAccountAttributes() throws IOException
{
296 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
299 public void setProfileName(String name
) throws IOException
{
300 accountManager
.setProfileName(account
.getProfileKey(), name
);
303 public void setProfileAvatar(File avatar
) throws IOException
{
304 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
305 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
306 streamDetails
.getStream().close();
309 public void removeProfileAvatar() throws IOException
{
310 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
313 public void unregister() throws IOException
{
314 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
315 // If this is the master device, other users can't send messages to this number anymore.
316 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
317 accountManager
.setGcmId(Optional
.absent());
319 account
.setRegistered(false);
323 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
324 if (account
== null) {
327 account
.setPassword(KeyUtils
.createPassword());
328 accountManager
= getSignalServiceAccountManager();
329 String uuid
= accountManager
.getNewDeviceUuid();
331 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
334 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
335 account
.setSignalingKey(KeyUtils
.createSignalingKey());
336 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
338 username
= ret
.getNumber();
339 // TODO do this check before actually registering
340 if (SignalAccount
.userExists(dataPath
, username
)) {
341 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
344 // Create new account with the synced identity
345 byte[] profileKeyBytes
= ret
.getProfileKey();
346 ProfileKey profileKey
;
347 if (profileKeyBytes
== null) {
348 profileKey
= KeyUtils
.createProfileKey();
351 profileKey
= new ProfileKey(profileKeyBytes
);
352 } catch (InvalidInputException e
) {
353 throw new IOException("Received invalid profileKey", e
);
356 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
357 account
.setResolver(this::resolveSignalServiceAddress
);
362 requestSyncContacts();
363 requestSyncBlocked();
364 requestSyncConfiguration();
369 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
370 List
<DeviceInfo
> devices
= accountManager
.getDevices();
371 account
.setMultiDevice(devices
.size() > 1);
376 public void removeLinkedDevices(int deviceId
) throws IOException
{
377 accountManager
.removeDevice(deviceId
);
378 List
<DeviceInfo
> devices
= accountManager
.getDevices();
379 account
.setMultiDevice(devices
.size() > 1);
383 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
384 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
386 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
389 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
390 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
391 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
393 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
394 account
.setMultiDevice(true);
398 private List
<PreKeyRecord
> generatePreKeys() {
399 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
401 final int offset
= account
.getPreKeyIdOffset();
402 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
403 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
404 ECKeyPair keyPair
= Curve
.generateKeyPair();
405 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
410 account
.addPreKeys(records
);
416 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
418 ECKeyPair keyPair
= Curve
.generateKeyPair();
419 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
420 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
422 account
.addSignedPreKey(record);
426 } catch (InvalidKeyException e
) {
427 throw new AssertionError(e
);
431 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
432 verificationCode
= verificationCode
.replace("-", "");
433 account
.setSignalingKey(KeyUtils
.createSignalingKey());
434 // TODO make unrestricted unidentified access configurable
435 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
437 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
438 account
.setRegistered(true);
439 account
.setUuid(uuid
);
440 account
.setRegistrationLockPin(pin
);
441 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
447 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
448 if (pin
.isPresent()) {
449 account
.setRegistrationLockPin(pin
.get());
450 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
452 account
.setRegistrationLockPin(null);
453 accountManager
.removeV1Pin();
458 private void refreshPreKeys() throws IOException
{
459 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
460 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
461 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
463 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
466 private SignalServiceMessageReceiver
getMessageReceiver() {
467 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
470 private SignalServiceMessageSender
getMessageSender() {
471 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
472 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
475 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
476 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
481 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
482 } catch (IOException ignored
) {
486 SignalServiceMessageReceiver receiver
= getMessageReceiver();
488 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
489 } catch (VerificationFailedException e
) {
490 throw new AssertionError(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();
528 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
530 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
531 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
532 if (attachments
!= null) {
533 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
535 if (groupId
!= null) {
536 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
539 messageBuilder
.asGroupMessage(group
);
542 final GroupInfo g
= getGroupForSending(groupId
);
544 messageBuilder
.withExpiration(g
.messageExpirationTime
);
546 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
549 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
550 long targetSentTimestamp
, byte[] groupId
)
551 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
552 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
553 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
554 .withReaction(reaction
);
555 if (groupId
!= null) {
556 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
559 messageBuilder
.asGroupMessage(group
);
561 final GroupInfo g
= getGroupForSending(groupId
);
562 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
565 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
566 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
570 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
571 .asGroupMessage(group
);
573 final GroupInfo g
= getGroupForSending(groupId
);
574 g
.removeMember(account
.getSelfAddress());
575 account
.getGroupStore().updateGroup(g
);
577 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
580 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
582 if (groupId
== null) {
584 g
= new GroupInfo(KeyUtils
.createGroupId());
585 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
587 g
= getGroupForSending(groupId
);
594 if (members
!= null) {
595 final Set
<String
> newE164Members
= new HashSet
<>();
596 for (SignalServiceAddress member
: members
) {
597 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
600 newE164Members
.add(member
.getNumber().get());
603 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
604 if (contacts
.size() != newE164Members
.size()) {
605 // Some of the new members are not registered on Signal
606 for (ContactTokenDetails contact
: contacts
) {
607 newE164Members
.remove(contact
.getNumber());
609 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
610 System
.err
.println("Aborting…");
614 g
.addMembers(members
);
617 if (avatarFile
!= null) {
618 IOUtils
.createPrivateDirectories(avatarsPath
);
619 File aFile
= getGroupAvatarFile(g
.groupId
);
620 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
623 account
.getGroupStore().updateGroup(g
);
625 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
627 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
631 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
632 if (groupId
== null) {
635 GroupInfo g
= getGroupForSending(groupId
);
637 if (!g
.isMember(recipient
)) {
641 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
643 // Send group message only to the recipient who requested it
644 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
647 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
648 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
651 .withMembers(new ArrayList
<>(g
.getMembers()));
653 File aFile
= getGroupAvatarFile(g
.groupId
);
654 if (aFile
.exists()) {
656 group
.withAvatar(Utils
.createAttachment(aFile
));
657 } catch (IOException e
) {
658 throw new AttachmentInvalidException(aFile
.toString(), e
);
662 return SignalServiceDataMessage
.newBuilder()
663 .asGroupMessage(group
.build())
664 .withExpiration(g
.messageExpirationTime
);
667 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
668 if (groupId
== null) {
672 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
675 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
676 .asGroupMessage(group
.build());
678 // Send group info request message to the recipient who sent us a message with this groupId
679 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
682 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
683 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
684 Collections
.singletonList(messageId
),
685 System
.currentTimeMillis());
687 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
691 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
692 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
693 List
<String
> recipients
= new ArrayList
<>(1);
694 recipients
.add(recipient
);
695 return sendMessage(message
, attachments
, recipients
);
699 public long sendMessage(String messageText
, List
<String
> attachments
,
700 List
<String
> recipients
)
701 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
702 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
703 if (attachments
!= null) {
704 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
706 // Upload attachments here, so we only upload once even for multiple recipients
707 SignalServiceMessageSender messageSender
= getMessageSender();
708 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
709 for (SignalServiceAttachment attachment
: attachmentStreams
) {
710 if (attachment
.isStream()) {
711 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
712 } else if (attachment
.isPointer()) {
713 attachmentPointers
.add(attachment
.asPointer());
717 messageBuilder
.withAttachments(attachmentPointers
);
719 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
722 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
723 long targetSentTimestamp
, List
<String
> recipients
)
724 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
725 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
726 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
727 .withReaction(reaction
);
728 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
732 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
733 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
734 .asEndSessionMessage();
736 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
740 public String
getContactName(String number
) throws InvalidNumberException
{
741 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
742 if (contact
== null) {
750 public void setContactName(String number
, String name
) throws InvalidNumberException
{
751 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
752 ContactInfo contact
= account
.getContactStore().getContact(address
);
753 if (contact
== null) {
754 contact
= new ContactInfo(address
);
755 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
757 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
760 account
.getContactStore().updateContact(contact
);
765 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
766 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
769 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
770 ContactInfo contact
= account
.getContactStore().getContact(address
);
771 if (contact
== null) {
772 contact
= new ContactInfo(address
);
773 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
775 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
777 contact
.blocked
= blocked
;
778 account
.getContactStore().updateContact(contact
);
783 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
784 GroupInfo group
= getGroup(groupId
);
786 throw new GroupNotFoundException(groupId
);
788 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
789 group
.blocked
= blocked
;
790 account
.getGroupStore().updateGroup(group
);
796 public List
<byte[]> getGroupIds() {
797 List
<GroupInfo
> groups
= getGroups();
798 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
799 for (GroupInfo group
: groups
) {
800 ids
.add(group
.groupId
);
806 public String
getGroupName(byte[] groupId
) {
807 GroupInfo group
= getGroup(groupId
);
816 public List
<String
> getGroupMembers(byte[] groupId
) {
817 GroupInfo group
= getGroup(groupId
);
819 return Collections
.emptyList();
821 return new ArrayList
<>(group
.getMembersE164());
826 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
827 if (groupId
.length
== 0) {
830 if (name
.isEmpty()) {
833 if (members
.size() == 0) {
836 if (avatar
.isEmpty()) {
839 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
843 * Change the expiration timer for a contact
845 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
846 ContactInfo c
= account
.getContactStore().getContact(address
);
847 c
.messageExpirationTime
= messageExpirationTimer
;
848 account
.getContactStore().updateContact(c
);
852 * Change the expiration timer for a group
854 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
855 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
856 g
.messageExpirationTime
= messageExpirationTimer
;
857 account
.getGroupStore().updateGroup(g
);
861 * Upload the sticker pack from path.
863 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
864 * @return if successful, returns the URL to install the sticker pack in the signal app
866 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
867 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
869 SignalServiceMessageSender messageSender
= getMessageSender();
871 byte[] packKey
= KeyUtils
.createStickerUploadKey();
872 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
875 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
877 } catch (URISyntaxException e
) {
878 throw new AssertionError(e
);
882 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
884 String rootPath
= null;
886 final File file
= new File(path
);
887 if (file
.getName().endsWith(".zip")) {
888 zip
= new ZipFile(file
);
889 } else if (file
.getName().equals("manifest.json")) {
890 rootPath
= file
.getParent();
892 throw new StickerPackInvalidException("Could not find manifest.json");
895 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
897 if (pack
.stickers
== null) {
898 throw new StickerPackInvalidException("Must set a 'stickers' field.");
901 if (pack
.stickers
.isEmpty()) {
902 throw new StickerPackInvalidException("Must include stickers.");
905 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
906 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
907 if (sticker
.file
== null) {
908 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
911 Pair
<InputStream
, Long
> data
;
913 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
914 } catch (IOException ignored
) {
915 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
918 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
919 stickers
.add(stickerInfo
);
922 StickerInfo cover
= null;
923 if (pack
.cover
!= null) {
924 if (pack
.cover
.file
== null) {
925 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
928 Pair
<InputStream
, Long
> data
;
930 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
931 } catch (IOException ignored
) {
932 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
935 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
938 return new SignalServiceStickerManifestUpload(
945 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
946 InputStream inputStream
;
948 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
950 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
952 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
955 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
957 final ZipEntry entry
= zip
.getEntry(subfile
);
958 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
960 final File file
= new File(rootPath
, subfile
);
961 return new Pair
<>(new FileInputStream(file
), file
.length());
965 private void requestSyncGroups() throws IOException
{
966 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
967 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
969 sendSyncMessage(message
);
970 } catch (UntrustedIdentityException e
) {
975 private void requestSyncContacts() throws IOException
{
976 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
977 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
979 sendSyncMessage(message
);
980 } catch (UntrustedIdentityException e
) {
985 private void requestSyncBlocked() throws IOException
{
986 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
987 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
989 sendSyncMessage(message
);
990 } catch (UntrustedIdentityException e
) {
995 private void requestSyncConfiguration() throws IOException
{
996 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
997 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
999 sendSyncMessage(message
);
1000 } catch (UntrustedIdentityException e
) {
1001 e
.printStackTrace();
1005 private byte[] getSenderCertificate() {
1006 // TODO support UUID capable sender certificates
1007 // byte[] certificate = accountManager.getSenderCertificate();
1010 certificate
= accountManager
.getSenderCertificateLegacy();
1011 } catch (IOException e
) {
1012 System
.err
.println("Failed to get sender certificate: " + e
);
1015 // TODO cache for a day
1019 private byte[] getSelfUnidentifiedAccessKey() {
1020 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1023 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1024 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1026 return new SignalProfile(
1027 encryptedProfile
.getIdentityKey(),
1028 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1029 encryptedProfile
.getAvatar(),
1030 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1031 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1033 } catch (InvalidCiphertextException e
) {
1038 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1039 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1040 if (contact
== null || contact
.profileKey
== null) {
1043 ProfileKey theirProfileKey
;
1045 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1046 } catch (InvalidInputException
| IOException e
) {
1047 throw new AssertionError(e
);
1049 SignalProfile targetProfile
;
1051 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1052 } catch (IOException e
) {
1053 System
.err
.println("Failed to get recipient profile: " + e
);
1057 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1061 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1062 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1065 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1068 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1069 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1070 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1072 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1073 return Optional
.absent();
1077 return Optional
.of(new UnidentifiedAccessPair(
1078 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1079 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1081 } catch (InvalidCertificateException e
) {
1082 return Optional
.absent();
1086 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1087 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1088 for (SignalServiceAddress recipient
: recipients
) {
1089 result
.add(getAccessFor(recipient
));
1094 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1095 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1096 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1097 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1099 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1100 return Optional
.absent();
1104 return Optional
.of(new UnidentifiedAccessPair(
1105 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1106 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1108 } catch (InvalidCertificateException e
) {
1109 return Optional
.absent();
1113 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1114 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1116 if (unidentifiedAccess
.isPresent()) {
1117 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1120 return Optional
.absent();
1123 private void sendSyncMessage(SignalServiceSyncMessage message
)
1124 throws IOException
, UntrustedIdentityException
{
1125 SignalServiceMessageSender messageSender
= getMessageSender();
1127 messageSender
.sendMessage(message
, getAccessForSync());
1128 } catch (UntrustedIdentityException e
) {
1129 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1135 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1137 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1138 throws EncapsulatedExceptions
, IOException
{
1139 final long timestamp
= System
.currentTimeMillis();
1140 messageBuilder
.withTimestamp(timestamp
);
1141 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1143 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1144 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1145 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1147 for (SendMessageResult result
: results
) {
1148 if (result
.isUnregisteredFailure()) {
1149 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1150 } else if (result
.isNetworkFailure()) {
1151 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1152 } else if (result
.getIdentityFailure() != null) {
1153 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1156 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1157 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1162 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1163 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1165 for (String number
: numbers
) {
1166 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1168 return signalServiceAddresses
;
1171 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1172 throws IOException
{
1173 if (messagePipe
== null) {
1174 messagePipe
= getMessageReceiver().createMessagePipe();
1176 if (unidentifiedMessagePipe
== null) {
1177 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1179 SignalServiceDataMessage message
= null;
1181 SignalServiceMessageSender messageSender
= getMessageSender();
1183 message
= messageBuilder
.build();
1184 if (message
.getGroupContext().isPresent()) {
1186 final boolean isRecipientUpdate
= false;
1187 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1188 for (SendMessageResult r
: result
) {
1189 if (r
.getIdentityFailure() != null) {
1190 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1194 } catch (UntrustedIdentityException e
) {
1195 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1196 return Collections
.emptyList();
1198 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1199 SignalServiceAddress recipient
= account
.getSelfAddress();
1200 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1201 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1202 message
.getTimestamp(),
1204 message
.getExpiresInSeconds(),
1205 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1207 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1209 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1211 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1212 } catch (UntrustedIdentityException e
) {
1213 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1214 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1218 // Send to all individually, so sync messages are sent correctly
1219 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1220 for (SignalServiceAddress address
: recipients
) {
1221 ContactInfo contact
= account
.getContactStore().getContact(address
);
1222 if (contact
!= null) {
1223 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1224 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1226 messageBuilder
.withExpiration(0);
1227 messageBuilder
.withProfileKey(null);
1229 message
= messageBuilder
.build();
1231 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1232 results
.add(result
);
1233 } catch (UntrustedIdentityException e
) {
1234 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1235 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1241 if (message
!= null && message
.isEndSession()) {
1242 for (SignalServiceAddress recipient
: recipients
) {
1243 handleEndSession(recipient
);
1250 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1251 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1253 return cipher
.decrypt(envelope
);
1254 } catch (ProtocolUntrustedIdentityException e
) {
1255 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1256 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1257 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1258 throw identityException
;
1260 throw new AssertionError(e
);
1264 private void handleEndSession(SignalServiceAddress source
) {
1265 account
.getSignalProtocolStore().deleteAllSessions(source
);
1268 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1269 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1270 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1271 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1272 switch (groupInfo
.getType()) {
1274 if (group
== null) {
1275 group
= new GroupInfo(groupInfo
.getGroupId());
1278 if (groupInfo
.getAvatar().isPresent()) {
1279 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1280 if (avatar
.isPointer()) {
1282 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1283 } catch (IOException
| InvalidMessageException e
) {
1284 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1289 if (groupInfo
.getName().isPresent()) {
1290 group
.name
= groupInfo
.getName().get();
1293 if (groupInfo
.getMembers().isPresent()) {
1294 group
.addMembers(groupInfo
.getMembers().get()
1296 .map(this::resolveSignalServiceAddress
)
1297 .collect(Collectors
.toSet()));
1300 account
.getGroupStore().updateGroup(group
);
1303 if (group
== null) {
1305 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1306 } catch (IOException
| EncapsulatedExceptions e
) {
1307 e
.printStackTrace();
1312 if (group
== null) {
1314 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1315 } catch (IOException
| EncapsulatedExceptions e
) {
1316 e
.printStackTrace();
1319 group
.removeMember(source
);
1320 account
.getGroupStore().updateGroup(group
);
1324 if (group
!= null) {
1326 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1327 } catch (IOException
| EncapsulatedExceptions e
) {
1328 e
.printStackTrace();
1329 } catch (NotAGroupMemberException e
) {
1330 // We have left this group, so don't send a group update message
1336 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1337 if (message
.isEndSession()) {
1338 handleEndSession(conversationPartnerAddress
);
1340 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1341 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1342 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1343 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1344 if (group
== null) {
1345 group
= new GroupInfo(groupInfo
.getGroupId());
1347 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1348 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1349 account
.getGroupStore().updateGroup(group
);
1352 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1353 if (contact
== null) {
1354 contact
= new ContactInfo(conversationPartnerAddress
);
1356 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1357 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1358 account
.getContactStore().updateContact(contact
);
1362 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1363 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1364 if (attachment
.isPointer()) {
1366 retrieveAttachment(attachment
.asPointer());
1367 } catch (IOException
| InvalidMessageException e
) {
1368 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1373 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1374 if (source
.matches(account
.getSelfAddress())) {
1376 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1377 } catch (InvalidInputException ignored
) {
1379 ContactInfo contact
= account
.getContactStore().getContact(source
);
1380 if (contact
!= null) {
1381 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1382 account
.getContactStore().updateContact(contact
);
1385 ContactInfo contact
= account
.getContactStore().getContact(source
);
1386 if (contact
== null) {
1387 contact
= new ContactInfo(source
);
1389 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1390 account
.getContactStore().updateContact(contact
);
1393 if (message
.getPreviews().isPresent()) {
1394 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1395 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1396 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1397 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1399 retrieveAttachment(attachment
);
1400 } catch (IOException
| InvalidMessageException e
) {
1401 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1408 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1409 final File cachePath
= new File(getMessageCachePath());
1410 if (!cachePath
.exists()) {
1413 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1414 if (!dir
.isDirectory()) {
1415 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1419 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1420 if (!fileEntry
.isFile()) {
1423 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1425 // Try to delete directory if empty
1430 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1431 SignalServiceEnvelope envelope
;
1433 envelope
= Utils
.loadEnvelope(fileEntry
);
1434 if (envelope
== null) {
1437 } catch (IOException e
) {
1438 e
.printStackTrace();
1441 SignalServiceContent content
= null;
1442 if (!envelope
.isReceipt()) {
1444 content
= decryptMessage(envelope
);
1445 } catch (Exception e
) {
1448 handleMessage(envelope
, content
, ignoreAttachments
);
1451 handler
.handleMessage(envelope
, content
, null);
1453 Files
.delete(fileEntry
.toPath());
1454 } catch (IOException e
) {
1455 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1459 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1460 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1461 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1464 if (messagePipe
== null) {
1465 messagePipe
= messageReceiver
.createMessagePipe();
1469 SignalServiceEnvelope envelope
;
1470 SignalServiceContent content
= null;
1471 Exception exception
= null;
1472 final long now
= new Date().getTime();
1474 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1475 // store message on disk, before acknowledging receipt to the server
1477 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1478 Utils
.storeEnvelope(envelope1
, cacheFile
);
1479 } catch (IOException e
) {
1480 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1483 } catch (TimeoutException e
) {
1484 if (returnOnTimeout
)
1487 } catch (InvalidVersionException e
) {
1488 System
.err
.println("Ignoring error: " + e
.getMessage());
1491 if (!envelope
.isReceipt()) {
1493 content
= decryptMessage(envelope
);
1494 } catch (Exception e
) {
1497 handleMessage(envelope
, content
, ignoreAttachments
);
1500 if (!isMessageBlocked(envelope
, content
)) {
1501 handler
.handleMessage(envelope
, content
, exception
);
1503 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1504 File cacheFile
= null;
1506 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1507 Files
.delete(cacheFile
.toPath());
1508 // Try to delete directory if empty
1509 new File(getMessageCachePath()).delete();
1510 } catch (IOException e
) {
1511 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1516 if (messagePipe
!= null) {
1517 messagePipe
.shutdown();
1523 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1524 SignalServiceAddress source
;
1525 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1526 source
= envelope
.getSourceAddress();
1527 } else if (content
!= null) {
1528 source
= content
.getSender();
1532 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1533 if (sourceContact
!= null && sourceContact
.blocked
) {
1537 if (content
!= null && content
.getDataMessage().isPresent()) {
1538 SignalServiceDataMessage message
= content
.getDataMessage().get();
1539 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1540 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1541 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1542 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1550 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1551 if (content
!= null) {
1552 SignalServiceAddress sender
;
1553 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1554 sender
= envelope
.getSourceAddress();
1556 sender
= content
.getSender();
1558 if (content
.getDataMessage().isPresent()) {
1559 SignalServiceDataMessage message
= content
.getDataMessage().get();
1561 if (content
.isNeedsReceipt()) {
1563 sendReceipt(sender
, message
.getTimestamp());
1564 } catch (IOException
| UntrustedIdentityException e
) {
1565 e
.printStackTrace();
1569 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1571 if (content
.getSyncMessage().isPresent()) {
1572 account
.setMultiDevice(true);
1573 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1574 if (syncMessage
.getSent().isPresent()) {
1575 SentTranscriptMessage message
= syncMessage
.getSent().get();
1576 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1578 if (syncMessage
.getRequest().isPresent()) {
1579 RequestMessage rm
= syncMessage
.getRequest().get();
1580 if (rm
.isContactsRequest()) {
1583 } catch (UntrustedIdentityException
| IOException e
) {
1584 e
.printStackTrace();
1587 if (rm
.isGroupsRequest()) {
1590 } catch (UntrustedIdentityException
| IOException e
) {
1591 e
.printStackTrace();
1594 if (rm
.isBlockedListRequest()) {
1597 } catch (UntrustedIdentityException
| IOException e
) {
1598 e
.printStackTrace();
1601 // TODO Handle rm.isConfigurationRequest();
1603 if (syncMessage
.getGroups().isPresent()) {
1604 File tmpFile
= null;
1606 tmpFile
= IOUtils
.createTempFile();
1607 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1608 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1610 while ((g
= s
.read()) != null) {
1611 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1612 if (syncGroup
== null) {
1613 syncGroup
= new GroupInfo(g
.getId());
1615 if (g
.getName().isPresent()) {
1616 syncGroup
.name
= g
.getName().get();
1618 syncGroup
.addMembers(g
.getMembers()
1620 .map(this::resolveSignalServiceAddress
)
1621 .collect(Collectors
.toSet()));
1622 if (!g
.isActive()) {
1623 syncGroup
.removeMember(account
.getSelfAddress());
1625 // Add ourself to the member set as it's marked as active
1626 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1628 syncGroup
.blocked
= g
.isBlocked();
1629 if (g
.getColor().isPresent()) {
1630 syncGroup
.color
= g
.getColor().get();
1633 if (g
.getAvatar().isPresent()) {
1634 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1636 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1637 syncGroup
.archived
= g
.isArchived();
1638 account
.getGroupStore().updateGroup(syncGroup
);
1641 } catch (Exception e
) {
1642 e
.printStackTrace();
1644 if (tmpFile
!= null) {
1646 Files
.delete(tmpFile
.toPath());
1647 } catch (IOException e
) {
1648 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1653 if (syncMessage
.getBlockedList().isPresent()) {
1654 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1655 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1656 setContactBlocked(resolveSignalServiceAddress(address
), true);
1658 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1660 setGroupBlocked(groupId
, true);
1661 } catch (GroupNotFoundException e
) {
1662 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1666 if (syncMessage
.getContacts().isPresent()) {
1667 File tmpFile
= null;
1669 tmpFile
= IOUtils
.createTempFile();
1670 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1671 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1672 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1673 if (contactsMessage
.isComplete()) {
1674 account
.getContactStore().clear();
1677 while ((c
= s
.read()) != null) {
1678 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1679 account
.setProfileKey(c
.getProfileKey().get());
1681 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1682 ContactInfo contact
= account
.getContactStore().getContact(address
);
1683 if (contact
== null) {
1684 contact
= new ContactInfo(address
);
1686 if (c
.getName().isPresent()) {
1687 contact
.name
= c
.getName().get();
1689 if (c
.getColor().isPresent()) {
1690 contact
.color
= c
.getColor().get();
1692 if (c
.getProfileKey().isPresent()) {
1693 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1695 if (c
.getVerified().isPresent()) {
1696 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1697 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1699 if (c
.getExpirationTimer().isPresent()) {
1700 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1702 contact
.blocked
= c
.isBlocked();
1703 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1704 contact
.archived
= c
.isArchived();
1705 account
.getContactStore().updateContact(contact
);
1707 if (c
.getAvatar().isPresent()) {
1708 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1712 } catch (Exception e
) {
1713 e
.printStackTrace();
1715 if (tmpFile
!= null) {
1717 Files
.delete(tmpFile
.toPath());
1718 } catch (IOException e
) {
1719 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1724 if (syncMessage
.getVerified().isPresent()) {
1725 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1726 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1728 if (syncMessage
.getConfiguration().isPresent()) {
1735 private File
getContactAvatarFile(String number
) {
1736 return new File(avatarsPath
, "contact-" + number
);
1739 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1740 IOUtils
.createPrivateDirectories(avatarsPath
);
1741 if (attachment
.isPointer()) {
1742 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1743 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1745 SignalServiceAttachmentStream stream
= attachment
.asStream();
1746 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1750 private File
getGroupAvatarFile(byte[] groupId
) {
1751 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1754 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1755 IOUtils
.createPrivateDirectories(avatarsPath
);
1756 if (attachment
.isPointer()) {
1757 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1758 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1760 SignalServiceAttachmentStream stream
= attachment
.asStream();
1761 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1765 public File
getAttachmentFile(long attachmentId
) {
1766 return new File(attachmentsPath
, attachmentId
+ "");
1769 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1770 IOUtils
.createPrivateDirectories(attachmentsPath
);
1771 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1774 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1775 if (storePreview
&& pointer
.getPreview().isPresent()) {
1776 File previewFile
= new File(outputFile
+ ".preview");
1777 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1778 byte[] preview
= pointer
.getPreview().get();
1779 output
.write(preview
, 0, preview
.length
);
1780 } catch (FileNotFoundException e
) {
1781 e
.printStackTrace();
1786 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1788 File tmpFile
= IOUtils
.createTempFile();
1789 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1790 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1791 byte[] buffer
= new byte[4096];
1794 while ((read
= input
.read(buffer
)) != -1) {
1795 output
.write(buffer
, 0, read
);
1797 } catch (FileNotFoundException e
) {
1798 e
.printStackTrace();
1803 Files
.delete(tmpFile
.toPath());
1804 } catch (IOException e
) {
1805 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1811 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1812 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1813 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1817 public boolean isRemote() {
1821 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1822 File groupsFile
= IOUtils
.createTempFile();
1825 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1826 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1827 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1828 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1829 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1830 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1831 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1835 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1836 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1837 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1838 .withStream(groupsFileStream
)
1839 .withContentType("application/octet-stream")
1840 .withLength(groupsFile
.length())
1843 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1848 Files
.delete(groupsFile
.toPath());
1849 } catch (IOException e
) {
1850 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1855 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1856 File contactsFile
= IOUtils
.createTempFile();
1859 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1860 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1861 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1862 VerifiedMessage verifiedMessage
= null;
1863 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1864 if (currentIdentity
!= null) {
1865 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1868 ProfileKey profileKey
= null;
1870 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1871 } catch (InvalidInputException ignored
) {
1873 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1874 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1875 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1876 Optional
.of(record.messageExpirationTime
),
1877 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1880 if (account
.getProfileKey() != null) {
1881 // Send our own profile key as well
1882 out
.write(new DeviceContact(account
.getSelfAddress(),
1883 Optional
.absent(), Optional
.absent(),
1884 Optional
.absent(), Optional
.absent(),
1885 Optional
.of(account
.getProfileKey()),
1886 false, Optional
.absent(), Optional
.absent(), false));
1890 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1891 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1892 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1893 .withStream(contactsFileStream
)
1894 .withContentType("application/octet-stream")
1895 .withLength(contactsFile
.length())
1898 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1903 Files
.delete(contactsFile
.toPath());
1904 } catch (IOException e
) {
1905 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1910 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1911 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1912 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1913 if (record.blocked
) {
1914 addresses
.add(record.getAddress());
1917 List
<byte[]> groupIds
= new ArrayList
<>();
1918 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1919 if (record.blocked
) {
1920 groupIds
.add(record.groupId
);
1923 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1926 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1927 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1928 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1931 public List
<ContactInfo
> getContacts() {
1932 return account
.getContactStore().getContacts();
1935 public ContactInfo
getContact(String number
) {
1936 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1939 public GroupInfo
getGroup(byte[] groupId
) {
1940 return account
.getGroupStore().getGroup(groupId
);
1943 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1944 return account
.getSignalProtocolStore().getIdentities();
1947 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1948 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1952 * Trust this the identity with this fingerprint
1954 * @param name username of the identity
1955 * @param fingerprint Fingerprint
1957 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1958 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1959 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1963 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1964 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1968 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1971 } catch (IOException
| UntrustedIdentityException e
) {
1972 e
.printStackTrace();
1981 * Trust this the identity with this safety number
1983 * @param name username of the identity
1984 * @param safetyNumber Safety number
1986 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1987 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1988 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1992 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1993 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1997 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1999 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2000 } catch (IOException
| UntrustedIdentityException e
) {
2001 e
.printStackTrace();
2010 * Trust all keys of this identity without verification
2012 * @param name username of the identity
2014 public boolean trustIdentityAllKeys(String name
) {
2015 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2016 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2020 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2021 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2022 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2024 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2025 } catch (IOException
| UntrustedIdentityException e
) {
2026 e
.printStackTrace();
2034 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2035 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2038 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2039 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2040 return resolveSignalServiceAddress(canonicalizedNumber
);
2043 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2044 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2046 return resolveSignalServiceAddress(address
);
2049 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2050 if (address
.matches(account
.getSelfAddress())) {
2051 return account
.getSelfAddress();
2054 return account
.getRecipientStore().resolveServiceAddress(address
);
2057 public interface ReceiveMessageHandler
{
2059 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);