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 void 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 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 void 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 sendMessage(message
, attachments
, recipients
);
699 public void 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 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 void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1138 throws EncapsulatedExceptions
, IOException
{
1139 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1141 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1142 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1143 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1145 for (SendMessageResult result
: results
) {
1146 if (result
.isUnregisteredFailure()) {
1147 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1148 } else if (result
.isNetworkFailure()) {
1149 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1150 } else if (result
.getIdentityFailure() != null) {
1151 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1154 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1155 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1159 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1160 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1162 for (String number
: numbers
) {
1163 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1165 return signalServiceAddresses
;
1168 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1169 throws IOException
{
1170 if (messagePipe
== null) {
1171 messagePipe
= getMessageReceiver().createMessagePipe();
1173 if (unidentifiedMessagePipe
== null) {
1174 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1176 SignalServiceDataMessage message
= null;
1178 SignalServiceMessageSender messageSender
= getMessageSender();
1180 message
= messageBuilder
.build();
1181 if (message
.getGroupContext().isPresent()) {
1183 final boolean isRecipientUpdate
= false;
1184 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1185 for (SendMessageResult r
: result
) {
1186 if (r
.getIdentityFailure() != null) {
1187 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1191 } catch (UntrustedIdentityException e
) {
1192 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1193 return Collections
.emptyList();
1195 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1196 SignalServiceAddress recipient
= account
.getSelfAddress();
1197 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1198 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1199 message
.getTimestamp(),
1201 message
.getExpiresInSeconds(),
1202 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1204 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1206 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1208 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1209 } catch (UntrustedIdentityException e
) {
1210 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1211 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1215 // Send to all individually, so sync messages are sent correctly
1216 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1217 for (SignalServiceAddress address
: recipients
) {
1218 ContactInfo contact
= account
.getContactStore().getContact(address
);
1219 if (contact
!= null) {
1220 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1221 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1223 messageBuilder
.withExpiration(0);
1224 messageBuilder
.withProfileKey(null);
1226 message
= messageBuilder
.build();
1228 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1229 results
.add(result
);
1230 } catch (UntrustedIdentityException e
) {
1231 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1232 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1238 if (message
!= null && message
.isEndSession()) {
1239 for (SignalServiceAddress recipient
: recipients
) {
1240 handleEndSession(recipient
);
1247 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1248 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1250 return cipher
.decrypt(envelope
);
1251 } catch (ProtocolUntrustedIdentityException e
) {
1252 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1253 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1254 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1255 throw identityException
;
1257 throw new AssertionError(e
);
1261 private void handleEndSession(SignalServiceAddress source
) {
1262 account
.getSignalProtocolStore().deleteAllSessions(source
);
1265 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1266 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1267 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1268 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1269 switch (groupInfo
.getType()) {
1271 if (group
== null) {
1272 group
= new GroupInfo(groupInfo
.getGroupId());
1275 if (groupInfo
.getAvatar().isPresent()) {
1276 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1277 if (avatar
.isPointer()) {
1279 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1280 } catch (IOException
| InvalidMessageException e
) {
1281 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1286 if (groupInfo
.getName().isPresent()) {
1287 group
.name
= groupInfo
.getName().get();
1290 if (groupInfo
.getMembers().isPresent()) {
1291 group
.addMembers(groupInfo
.getMembers().get()
1293 .map(this::resolveSignalServiceAddress
)
1294 .collect(Collectors
.toSet()));
1297 account
.getGroupStore().updateGroup(group
);
1300 if (group
== null) {
1302 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1303 } catch (IOException
| EncapsulatedExceptions e
) {
1304 e
.printStackTrace();
1309 if (group
== null) {
1311 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1312 } catch (IOException
| EncapsulatedExceptions e
) {
1313 e
.printStackTrace();
1316 group
.removeMember(source
);
1317 account
.getGroupStore().updateGroup(group
);
1321 if (group
!= null) {
1323 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1324 } catch (IOException
| EncapsulatedExceptions e
) {
1325 e
.printStackTrace();
1326 } catch (NotAGroupMemberException e
) {
1327 // We have left this group, so don't send a group update message
1333 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1334 if (message
.isEndSession()) {
1335 handleEndSession(conversationPartnerAddress
);
1337 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1338 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1339 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1340 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1341 if (group
== null) {
1342 group
= new GroupInfo(groupInfo
.getGroupId());
1344 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1345 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1346 account
.getGroupStore().updateGroup(group
);
1349 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1350 if (contact
== null) {
1351 contact
= new ContactInfo(conversationPartnerAddress
);
1353 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1354 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1355 account
.getContactStore().updateContact(contact
);
1359 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1360 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1361 if (attachment
.isPointer()) {
1363 retrieveAttachment(attachment
.asPointer());
1364 } catch (IOException
| InvalidMessageException e
) {
1365 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1370 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1371 if (source
.matches(account
.getSelfAddress())) {
1373 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1374 } catch (InvalidInputException ignored
) {
1376 ContactInfo contact
= account
.getContactStore().getContact(source
);
1377 if (contact
!= null) {
1378 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1379 account
.getContactStore().updateContact(contact
);
1382 ContactInfo contact
= account
.getContactStore().getContact(source
);
1383 if (contact
== null) {
1384 contact
= new ContactInfo(source
);
1386 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1387 account
.getContactStore().updateContact(contact
);
1390 if (message
.getPreviews().isPresent()) {
1391 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1392 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1393 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1394 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1396 retrieveAttachment(attachment
);
1397 } catch (IOException
| InvalidMessageException e
) {
1398 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1405 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1406 final File cachePath
= new File(getMessageCachePath());
1407 if (!cachePath
.exists()) {
1410 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1411 if (!dir
.isDirectory()) {
1412 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1416 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1417 if (!fileEntry
.isFile()) {
1420 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1422 // Try to delete directory if empty
1427 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1428 SignalServiceEnvelope envelope
;
1430 envelope
= Utils
.loadEnvelope(fileEntry
);
1431 if (envelope
== null) {
1434 } catch (IOException e
) {
1435 e
.printStackTrace();
1438 SignalServiceContent content
= null;
1439 if (!envelope
.isReceipt()) {
1441 content
= decryptMessage(envelope
);
1442 } catch (Exception e
) {
1445 handleMessage(envelope
, content
, ignoreAttachments
);
1448 handler
.handleMessage(envelope
, content
, null);
1450 Files
.delete(fileEntry
.toPath());
1451 } catch (IOException e
) {
1452 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1456 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1457 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1458 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1461 if (messagePipe
== null) {
1462 messagePipe
= messageReceiver
.createMessagePipe();
1466 SignalServiceEnvelope envelope
;
1467 SignalServiceContent content
= null;
1468 Exception exception
= null;
1469 final long now
= new Date().getTime();
1471 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1472 // store message on disk, before acknowledging receipt to the server
1474 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1475 Utils
.storeEnvelope(envelope1
, cacheFile
);
1476 } catch (IOException e
) {
1477 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1480 } catch (TimeoutException e
) {
1481 if (returnOnTimeout
)
1484 } catch (InvalidVersionException e
) {
1485 System
.err
.println("Ignoring error: " + e
.getMessage());
1488 if (!envelope
.isReceipt()) {
1490 content
= decryptMessage(envelope
);
1491 } catch (Exception e
) {
1494 handleMessage(envelope
, content
, ignoreAttachments
);
1497 if (!isMessageBlocked(envelope
, content
)) {
1498 handler
.handleMessage(envelope
, content
, exception
);
1500 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1501 File cacheFile
= null;
1503 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1504 Files
.delete(cacheFile
.toPath());
1505 // Try to delete directory if empty
1506 new File(getMessageCachePath()).delete();
1507 } catch (IOException e
) {
1508 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1513 if (messagePipe
!= null) {
1514 messagePipe
.shutdown();
1520 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1521 SignalServiceAddress source
;
1522 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1523 source
= envelope
.getSourceAddress();
1524 } else if (content
!= null) {
1525 source
= content
.getSender();
1529 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1530 if (sourceContact
!= null && sourceContact
.blocked
) {
1534 if (content
!= null && content
.getDataMessage().isPresent()) {
1535 SignalServiceDataMessage message
= content
.getDataMessage().get();
1536 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1537 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1538 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1539 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1547 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1548 if (content
!= null) {
1549 SignalServiceAddress sender
;
1550 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1551 sender
= envelope
.getSourceAddress();
1553 sender
= content
.getSender();
1555 if (content
.getDataMessage().isPresent()) {
1556 SignalServiceDataMessage message
= content
.getDataMessage().get();
1558 if (content
.isNeedsReceipt()) {
1560 sendReceipt(sender
, message
.getTimestamp());
1561 } catch (IOException
| UntrustedIdentityException e
) {
1562 e
.printStackTrace();
1566 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1568 if (content
.getSyncMessage().isPresent()) {
1569 account
.setMultiDevice(true);
1570 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1571 if (syncMessage
.getSent().isPresent()) {
1572 SentTranscriptMessage message
= syncMessage
.getSent().get();
1573 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1575 if (syncMessage
.getRequest().isPresent()) {
1576 RequestMessage rm
= syncMessage
.getRequest().get();
1577 if (rm
.isContactsRequest()) {
1580 } catch (UntrustedIdentityException
| IOException e
) {
1581 e
.printStackTrace();
1584 if (rm
.isGroupsRequest()) {
1587 } catch (UntrustedIdentityException
| IOException e
) {
1588 e
.printStackTrace();
1591 if (rm
.isBlockedListRequest()) {
1594 } catch (UntrustedIdentityException
| IOException e
) {
1595 e
.printStackTrace();
1598 // TODO Handle rm.isConfigurationRequest();
1600 if (syncMessage
.getGroups().isPresent()) {
1601 File tmpFile
= null;
1603 tmpFile
= IOUtils
.createTempFile();
1604 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1605 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1607 while ((g
= s
.read()) != null) {
1608 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1609 if (syncGroup
== null) {
1610 syncGroup
= new GroupInfo(g
.getId());
1612 if (g
.getName().isPresent()) {
1613 syncGroup
.name
= g
.getName().get();
1615 syncGroup
.addMembers(g
.getMembers()
1617 .map(this::resolveSignalServiceAddress
)
1618 .collect(Collectors
.toSet()));
1619 if (!g
.isActive()) {
1620 syncGroup
.removeMember(account
.getSelfAddress());
1622 // Add ourself to the member set as it's marked as active
1623 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1625 syncGroup
.blocked
= g
.isBlocked();
1626 if (g
.getColor().isPresent()) {
1627 syncGroup
.color
= g
.getColor().get();
1630 if (g
.getAvatar().isPresent()) {
1631 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1633 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1634 syncGroup
.archived
= g
.isArchived();
1635 account
.getGroupStore().updateGroup(syncGroup
);
1638 } catch (Exception e
) {
1639 e
.printStackTrace();
1641 if (tmpFile
!= null) {
1643 Files
.delete(tmpFile
.toPath());
1644 } catch (IOException e
) {
1645 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1650 if (syncMessage
.getBlockedList().isPresent()) {
1651 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1652 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1653 setContactBlocked(resolveSignalServiceAddress(address
), true);
1655 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1657 setGroupBlocked(groupId
, true);
1658 } catch (GroupNotFoundException e
) {
1659 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1663 if (syncMessage
.getContacts().isPresent()) {
1664 File tmpFile
= null;
1666 tmpFile
= IOUtils
.createTempFile();
1667 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1668 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1669 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1670 if (contactsMessage
.isComplete()) {
1671 account
.getContactStore().clear();
1674 while ((c
= s
.read()) != null) {
1675 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1676 account
.setProfileKey(c
.getProfileKey().get());
1678 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1679 ContactInfo contact
= account
.getContactStore().getContact(address
);
1680 if (contact
== null) {
1681 contact
= new ContactInfo(address
);
1683 if (c
.getName().isPresent()) {
1684 contact
.name
= c
.getName().get();
1686 if (c
.getColor().isPresent()) {
1687 contact
.color
= c
.getColor().get();
1689 if (c
.getProfileKey().isPresent()) {
1690 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1692 if (c
.getVerified().isPresent()) {
1693 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1694 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1696 if (c
.getExpirationTimer().isPresent()) {
1697 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1699 contact
.blocked
= c
.isBlocked();
1700 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1701 contact
.archived
= c
.isArchived();
1702 account
.getContactStore().updateContact(contact
);
1704 if (c
.getAvatar().isPresent()) {
1705 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1709 } catch (Exception e
) {
1710 e
.printStackTrace();
1712 if (tmpFile
!= null) {
1714 Files
.delete(tmpFile
.toPath());
1715 } catch (IOException e
) {
1716 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1721 if (syncMessage
.getVerified().isPresent()) {
1722 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1723 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1725 if (syncMessage
.getConfiguration().isPresent()) {
1732 private File
getContactAvatarFile(String number
) {
1733 return new File(avatarsPath
, "contact-" + number
);
1736 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1737 IOUtils
.createPrivateDirectories(avatarsPath
);
1738 if (attachment
.isPointer()) {
1739 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1740 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1742 SignalServiceAttachmentStream stream
= attachment
.asStream();
1743 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1747 private File
getGroupAvatarFile(byte[] groupId
) {
1748 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1751 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1752 IOUtils
.createPrivateDirectories(avatarsPath
);
1753 if (attachment
.isPointer()) {
1754 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1755 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1757 SignalServiceAttachmentStream stream
= attachment
.asStream();
1758 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1762 public File
getAttachmentFile(long attachmentId
) {
1763 return new File(attachmentsPath
, attachmentId
+ "");
1766 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1767 IOUtils
.createPrivateDirectories(attachmentsPath
);
1768 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1771 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1772 if (storePreview
&& pointer
.getPreview().isPresent()) {
1773 File previewFile
= new File(outputFile
+ ".preview");
1774 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1775 byte[] preview
= pointer
.getPreview().get();
1776 output
.write(preview
, 0, preview
.length
);
1777 } catch (FileNotFoundException e
) {
1778 e
.printStackTrace();
1783 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1785 File tmpFile
= IOUtils
.createTempFile();
1786 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1787 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1788 byte[] buffer
= new byte[4096];
1791 while ((read
= input
.read(buffer
)) != -1) {
1792 output
.write(buffer
, 0, read
);
1794 } catch (FileNotFoundException e
) {
1795 e
.printStackTrace();
1800 Files
.delete(tmpFile
.toPath());
1801 } catch (IOException e
) {
1802 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1808 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1809 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1810 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1814 public boolean isRemote() {
1818 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1819 File groupsFile
= IOUtils
.createTempFile();
1822 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1823 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1824 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1825 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1826 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1827 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1828 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1832 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1833 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1834 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1835 .withStream(groupsFileStream
)
1836 .withContentType("application/octet-stream")
1837 .withLength(groupsFile
.length())
1840 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1845 Files
.delete(groupsFile
.toPath());
1846 } catch (IOException e
) {
1847 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1852 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1853 File contactsFile
= IOUtils
.createTempFile();
1856 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1857 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1858 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1859 VerifiedMessage verifiedMessage
= null;
1860 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1861 if (currentIdentity
!= null) {
1862 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1865 ProfileKey profileKey
= null;
1867 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1868 } catch (InvalidInputException ignored
) {
1870 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1871 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1872 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1873 Optional
.of(record.messageExpirationTime
),
1874 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1877 if (account
.getProfileKey() != null) {
1878 // Send our own profile key as well
1879 out
.write(new DeviceContact(account
.getSelfAddress(),
1880 Optional
.absent(), Optional
.absent(),
1881 Optional
.absent(), Optional
.absent(),
1882 Optional
.of(account
.getProfileKey()),
1883 false, Optional
.absent(), Optional
.absent(), false));
1887 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1888 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1889 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1890 .withStream(contactsFileStream
)
1891 .withContentType("application/octet-stream")
1892 .withLength(contactsFile
.length())
1895 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1900 Files
.delete(contactsFile
.toPath());
1901 } catch (IOException e
) {
1902 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1907 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1908 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1909 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1910 if (record.blocked
) {
1911 addresses
.add(record.getAddress());
1914 List
<byte[]> groupIds
= new ArrayList
<>();
1915 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1916 if (record.blocked
) {
1917 groupIds
.add(record.groupId
);
1920 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1923 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1924 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1925 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1928 public List
<ContactInfo
> getContacts() {
1929 return account
.getContactStore().getContacts();
1932 public ContactInfo
getContact(String number
) {
1933 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1936 public GroupInfo
getGroup(byte[] groupId
) {
1937 return account
.getGroupStore().getGroup(groupId
);
1940 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1941 return account
.getSignalProtocolStore().getIdentities();
1944 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1945 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1949 * Trust this the identity with this fingerprint
1951 * @param name username of the identity
1952 * @param fingerprint Fingerprint
1954 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1955 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1956 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1960 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1961 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1965 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1967 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1968 } catch (IOException
| UntrustedIdentityException e
) {
1969 e
.printStackTrace();
1978 * Trust this the identity with this safety number
1980 * @param name username of the identity
1981 * @param safetyNumber Safety number
1983 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1984 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1985 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1989 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1990 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1994 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1996 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1997 } catch (IOException
| UntrustedIdentityException e
) {
1998 e
.printStackTrace();
2007 * Trust all keys of this identity without verification
2009 * @param name username of the identity
2011 public boolean trustIdentityAllKeys(String name
) {
2012 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2013 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2017 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2018 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2019 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2021 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2022 } catch (IOException
| UntrustedIdentityException e
) {
2023 e
.printStackTrace();
2031 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2032 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2035 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2036 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2037 return resolveSignalServiceAddress(canonicalizedNumber
);
2040 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2041 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2043 return resolveSignalServiceAddress(address
);
2046 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2047 if (address
.matches(account
.getSelfAddress())) {
2048 return account
.getSelfAddress();
2051 return account
.getRecipientStore().resolveServiceAddress(address
);
2054 public interface ReceiveMessageHandler
{
2056 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);