2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.Signal
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.JsonStickerPack
;
25 import org
.asamk
.signal
.NotAGroupMemberException
;
26 import org
.asamk
.signal
.StickerPackInvalidException
;
27 import org
.asamk
.signal
.TrustLevel
;
28 import org
.asamk
.signal
.UserAlreadyExists
;
29 import org
.asamk
.signal
.storage
.SignalAccount
;
30 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
31 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
32 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
33 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
34 import org
.asamk
.signal
.util
.IOUtils
;
35 import org
.asamk
.signal
.util
.Util
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
46 import org
.signal
.libsignal
.metadata
.SelfSendException
;
47 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
105 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
106 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
108 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
109 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
110 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
111 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
112 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
113 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.FileInputStream
;
117 import java
.io
.FileNotFoundException
;
118 import java
.io
.FileOutputStream
;
119 import java
.io
.IOException
;
120 import java
.io
.InputStream
;
121 import java
.io
.OutputStream
;
123 import java
.net
.URISyntaxException
;
124 import java
.net
.URLEncoder
;
125 import java
.nio
.file
.Files
;
126 import java
.nio
.file
.Paths
;
127 import java
.nio
.file
.StandardCopyOption
;
128 import java
.util
.ArrayList
;
129 import java
.util
.Arrays
;
130 import java
.util
.Collection
;
131 import java
.util
.Collections
;
132 import java
.util
.Date
;
133 import java
.util
.HashSet
;
134 import java
.util
.LinkedList
;
135 import java
.util
.List
;
136 import java
.util
.Locale
;
137 import java
.util
.Objects
;
138 import java
.util
.Set
;
139 import java
.util
.UUID
;
140 import java
.util
.concurrent
.TimeUnit
;
141 import java
.util
.concurrent
.TimeoutException
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
{
147 private final String settingsPath
;
148 private final String dataPath
;
149 private final String attachmentsPath
;
150 private final String avatarsPath
;
151 private final SleepTimer timer
= new UptimeSleepTimer();
153 private SignalAccount account
;
154 private String username
;
155 private SignalServiceAccountManager accountManager
;
156 private SignalServiceMessagePipe messagePipe
= null;
157 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
159 public Manager(String username
, String settingsPath
) {
160 this.username
= username
;
161 this.settingsPath
= settingsPath
;
162 this.dataPath
= this.settingsPath
+ "/data";
163 this.attachmentsPath
= this.settingsPath
+ "/attachments";
164 this.avatarsPath
= this.settingsPath
+ "/avatars";
168 public String
getUsername() {
172 public SignalServiceAddress
getSelfAddress() {
173 return account
.getSelfAddress();
176 private SignalServiceAccountManager
getSignalServiceAccountManager() {
177 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
180 private IdentityKey
getIdentity() {
181 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
184 public int getDeviceId() {
185 return account
.getDeviceId();
188 private String
getMessageCachePath() {
189 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
192 private String
getMessageCachePath(String sender
) {
193 if (sender
== null || sender
.isEmpty()) {
194 return getMessageCachePath();
197 return getMessageCachePath() + "/" + sender
.replace("/", "_");
200 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
201 String cachePath
= getMessageCachePath(sender
);
202 IOUtils
.createPrivateDirectories(cachePath
);
203 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
206 public boolean userHasKeys() {
207 return account
!= null && account
.getSignalProtocolStore() != null;
210 public void init() throws IOException
{
211 if (!SignalAccount
.userExists(dataPath
, username
)) {
214 account
= SignalAccount
.load(dataPath
, username
);
215 account
.setResolver(this::resolveSignalServiceAddress
);
217 migrateLegacyConfigs();
219 accountManager
= getSignalServiceAccountManager();
220 if (account
.isRegistered()) {
221 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
225 if (account
.getUuid() == null) {
226 account
.setUuid(accountManager
.getOwnUuid());
232 private void migrateLegacyConfigs() {
233 // Copy group avatars that were previously stored in the attachments folder
234 // to the new avatar folder
235 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
236 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
237 File avatarFile
= getGroupAvatarFile(g
.groupId
);
238 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
239 if (!avatarFile
.exists() && attachmentFile
.exists()) {
241 IOUtils
.createPrivateDirectories(avatarsPath
);
242 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
243 } catch (Exception e
) {
248 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
251 if (account
.getProfileKey() == null) {
252 // Old config file, creating new profile key
253 account
.setProfileKey(KeyUtils
.createProfileKey());
258 private void createNewIdentity() throws IOException
{
259 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
260 int registrationId
= KeyHelper
.generateRegistrationId(false);
261 if (username
== null) {
262 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
263 account
.setResolver(this::resolveSignalServiceAddress
);
265 ProfileKey profileKey
= KeyUtils
.createProfileKey();
266 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
267 account
.setResolver(this::resolveSignalServiceAddress
);
272 public boolean isRegistered() {
273 return account
!= null && account
.isRegistered();
276 public void register(boolean voiceVerification
) throws IOException
{
277 if (account
== null) {
280 account
.setPassword(KeyUtils
.createPassword());
281 account
.setUuid(null);
282 accountManager
= getSignalServiceAccountManager();
284 if (voiceVerification
) {
285 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
287 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
290 account
.setRegistered(false);
294 public void updateAccountAttributes() throws IOException
{
295 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
298 public void setProfileName(String name
) throws IOException
{
299 accountManager
.setProfileName(account
.getProfileKey(), name
);
302 public void setProfileAvatar(File avatar
) throws IOException
{
303 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
304 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
305 streamDetails
.getStream().close();
308 public void removeProfileAvatar() throws IOException
{
309 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
312 public void unregister() throws IOException
{
313 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
314 // If this is the master device, other users can't send messages to this number anymore.
315 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
316 accountManager
.setGcmId(Optional
.absent());
318 account
.setRegistered(false);
322 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
323 if (account
== null) {
326 account
.setPassword(KeyUtils
.createPassword());
327 accountManager
= getSignalServiceAccountManager();
328 String uuid
= accountManager
.getNewDeviceUuid();
330 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
333 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
334 account
.setSignalingKey(KeyUtils
.createSignalingKey());
335 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
337 username
= ret
.getNumber();
338 // TODO do this check before actually registering
339 if (SignalAccount
.userExists(dataPath
, username
)) {
340 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
343 // Create new account with the synced identity
344 byte[] profileKeyBytes
= ret
.getProfileKey();
345 ProfileKey profileKey
;
346 if (profileKeyBytes
== null) {
347 profileKey
= KeyUtils
.createProfileKey();
350 profileKey
= new ProfileKey(profileKeyBytes
);
351 } catch (InvalidInputException e
) {
352 throw new IOException("Received invalid profileKey", e
);
355 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
356 account
.setResolver(this::resolveSignalServiceAddress
);
361 requestSyncContacts();
362 requestSyncBlocked();
363 requestSyncConfiguration();
368 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
369 List
<DeviceInfo
> devices
= accountManager
.getDevices();
370 account
.setMultiDevice(devices
.size() > 1);
375 public void removeLinkedDevices(int deviceId
) throws IOException
{
376 accountManager
.removeDevice(deviceId
);
377 List
<DeviceInfo
> devices
= accountManager
.getDevices();
378 account
.setMultiDevice(devices
.size() > 1);
382 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
383 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
385 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
388 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
389 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
390 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
392 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
393 account
.setMultiDevice(true);
397 private List
<PreKeyRecord
> generatePreKeys() {
398 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
400 final int offset
= account
.getPreKeyIdOffset();
401 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
402 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
403 ECKeyPair keyPair
= Curve
.generateKeyPair();
404 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
409 account
.addPreKeys(records
);
415 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
417 ECKeyPair keyPair
= Curve
.generateKeyPair();
418 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
419 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
421 account
.addSignedPreKey(record);
425 } catch (InvalidKeyException e
) {
426 throw new AssertionError(e
);
430 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
431 verificationCode
= verificationCode
.replace("-", "");
432 account
.setSignalingKey(KeyUtils
.createSignalingKey());
433 // TODO make unrestricted unidentified access configurable
434 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
436 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
437 account
.setRegistered(true);
438 account
.setUuid(uuid
);
439 account
.setRegistrationLockPin(pin
);
440 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
446 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
447 if (pin
.isPresent()) {
448 account
.setRegistrationLockPin(pin
.get());
449 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
451 account
.setRegistrationLockPin(null);
452 accountManager
.removeV1Pin();
457 private void refreshPreKeys() throws IOException
{
458 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
459 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
460 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
462 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
465 private SignalServiceMessageReceiver
getMessageReceiver() {
466 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
469 private SignalServiceMessageSender
getMessageSender() {
470 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
471 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
474 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
475 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
480 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
481 } catch (IOException ignored
) {
485 SignalServiceMessageReceiver receiver
= getMessageReceiver();
487 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
488 } catch (VerificationFailedException e
) {
489 throw new AssertionError(e
);
493 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
494 File file
= getGroupAvatarFile(groupId
);
495 if (!file
.exists()) {
496 return Optional
.absent();
499 return Optional
.of(Utils
.createAttachment(file
));
502 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
503 File file
= getContactAvatarFile(number
);
504 if (!file
.exists()) {
505 return Optional
.absent();
508 return Optional
.of(Utils
.createAttachment(file
));
511 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
512 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
514 throw new GroupNotFoundException(groupId
);
516 if (!g
.isMember(account
.getSelfAddress())) {
517 throw new NotAGroupMemberException(groupId
, g
.name
);
522 public List
<GroupInfo
> getGroups() {
523 return account
.getGroupStore().getGroups();
527 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
529 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
530 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
531 if (attachments
!= null) {
532 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
534 if (groupId
!= null) {
535 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
538 messageBuilder
.asGroupMessage(group
);
541 final GroupInfo g
= getGroupForSending(groupId
);
543 messageBuilder
.withExpiration(g
.messageExpirationTime
);
545 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
548 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
549 long targetSentTimestamp
, byte[] groupId
)
550 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
551 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
552 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
553 .withReaction(reaction
);
554 if (groupId
!= null) {
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
558 messageBuilder
.asGroupMessage(group
);
560 final GroupInfo g
= getGroupForSending(groupId
);
561 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
564 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
565 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
569 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
570 .asGroupMessage(group
);
572 final GroupInfo g
= getGroupForSending(groupId
);
573 g
.removeMember(account
.getSelfAddress());
574 account
.getGroupStore().updateGroup(g
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
581 if (groupId
== null) {
583 g
= new GroupInfo(KeyUtils
.createGroupId());
584 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
586 g
= getGroupForSending(groupId
);
593 if (members
!= null) {
594 final Set
<String
> newE164Members
= new HashSet
<>();
595 for (SignalServiceAddress member
: members
) {
596 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
599 newE164Members
.add(member
.getNumber().get());
602 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
603 if (contacts
.size() != newE164Members
.size()) {
604 // Some of the new members are not registered on Signal
605 for (ContactTokenDetails contact
: contacts
) {
606 newE164Members
.remove(contact
.getNumber());
608 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
609 System
.err
.println("Aborting…");
613 g
.addMembers(members
);
616 if (avatarFile
!= null) {
617 IOUtils
.createPrivateDirectories(avatarsPath
);
618 File aFile
= getGroupAvatarFile(g
.groupId
);
619 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
622 account
.getGroupStore().updateGroup(g
);
624 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
626 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
630 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
631 if (groupId
== null) {
634 GroupInfo g
= getGroupForSending(groupId
);
636 if (!g
.isMember(recipient
)) {
640 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
642 // Send group message only to the recipient who requested it
643 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
646 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
647 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
650 .withMembers(new ArrayList
<>(g
.getMembers()));
652 File aFile
= getGroupAvatarFile(g
.groupId
);
653 if (aFile
.exists()) {
655 group
.withAvatar(Utils
.createAttachment(aFile
));
656 } catch (IOException e
) {
657 throw new AttachmentInvalidException(aFile
.toString(), e
);
661 return SignalServiceDataMessage
.newBuilder()
662 .asGroupMessage(group
.build())
663 .withExpiration(g
.messageExpirationTime
);
666 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
667 if (groupId
== null) {
671 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
674 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
675 .asGroupMessage(group
.build());
677 // Send group info request message to the recipient who sent us a message with this groupId
678 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
681 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
682 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
683 Collections
.singletonList(messageId
),
684 System
.currentTimeMillis());
686 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
690 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
691 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
692 List
<String
> recipients
= new ArrayList
<>(1);
693 recipients
.add(recipient
);
694 sendMessage(message
, attachments
, recipients
);
698 public void sendMessage(String messageText
, List
<String
> attachments
,
699 List
<String
> recipients
)
700 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
701 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
702 if (attachments
!= null) {
703 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
705 // Upload attachments here, so we only upload once even for multiple recipients
706 SignalServiceMessageSender messageSender
= getMessageSender();
707 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
708 for (SignalServiceAttachment attachment
: attachmentStreams
) {
709 if (attachment
.isStream()) {
710 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
711 } else if (attachment
.isPointer()) {
712 attachmentPointers
.add(attachment
.asPointer());
716 messageBuilder
.withAttachments(attachmentPointers
);
718 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
721 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
722 long targetSentTimestamp
, List
<String
> recipients
)
723 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
724 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
725 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
726 .withReaction(reaction
);
727 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
731 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
732 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
733 .asEndSessionMessage();
735 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
739 public String
getContactName(String number
) throws InvalidNumberException
{
740 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
741 if (contact
== null) {
749 public void setContactName(String number
, String name
) throws InvalidNumberException
{
750 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
751 ContactInfo contact
= account
.getContactStore().getContact(address
);
752 if (contact
== null) {
753 contact
= new ContactInfo(address
);
754 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
756 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
759 account
.getContactStore().updateContact(contact
);
764 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
765 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
768 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
769 ContactInfo contact
= account
.getContactStore().getContact(address
);
770 if (contact
== null) {
771 contact
= new ContactInfo(address
);
772 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
774 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
776 contact
.blocked
= blocked
;
777 account
.getContactStore().updateContact(contact
);
782 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
783 GroupInfo group
= getGroup(groupId
);
785 throw new GroupNotFoundException(groupId
);
787 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
788 group
.blocked
= blocked
;
789 account
.getGroupStore().updateGroup(group
);
795 public List
<byte[]> getGroupIds() {
796 List
<GroupInfo
> groups
= getGroups();
797 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
798 for (GroupInfo group
: groups
) {
799 ids
.add(group
.groupId
);
805 public String
getGroupName(byte[] groupId
) {
806 GroupInfo group
= getGroup(groupId
);
815 public List
<String
> getGroupMembers(byte[] groupId
) {
816 GroupInfo group
= getGroup(groupId
);
818 return Collections
.emptyList();
820 return new ArrayList
<>(group
.getMembersE164());
825 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
826 if (groupId
.length
== 0) {
829 if (name
.isEmpty()) {
832 if (members
.size() == 0) {
835 if (avatar
.isEmpty()) {
838 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
842 * Change the expiration timer for a contact
844 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
845 ContactInfo c
= account
.getContactStore().getContact(address
);
846 c
.messageExpirationTime
= messageExpirationTimer
;
847 account
.getContactStore().updateContact(c
);
851 * Change the expiration timer for a group
853 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
854 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
855 g
.messageExpirationTime
= messageExpirationTimer
;
856 account
.getGroupStore().updateGroup(g
);
860 * Upload the sticker pack from path.
862 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
863 * @return if successful, returns the URL to install the sticker pack in the signal app
865 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
866 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
868 SignalServiceMessageSender messageSender
= getMessageSender();
870 byte[] packKey
= KeyUtils
.createStickerUploadKey();
871 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
874 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
876 } catch (URISyntaxException e
) {
877 throw new AssertionError(e
);
881 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
883 String rootPath
= null;
885 final File file
= new File(path
);
886 if (file
.getName().endsWith(".zip")) {
887 zip
= new ZipFile(file
);
888 } else if (file
.getName().equals("manifest.json")) {
889 rootPath
= file
.getParent();
891 throw new StickerPackInvalidException("Could not find manifest.json");
894 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
896 if (pack
.stickers
== null) {
897 throw new StickerPackInvalidException("Must set a 'stickers' field.");
900 if (pack
.stickers
.isEmpty()) {
901 throw new StickerPackInvalidException("Must include stickers.");
904 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
905 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
906 if (sticker
.file
== null) {
907 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
910 Pair
<InputStream
, Long
> data
;
912 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
913 } catch (IOException ignored
) {
914 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
917 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
918 stickers
.add(stickerInfo
);
921 StickerInfo cover
= null;
922 if (pack
.cover
!= null) {
923 if (pack
.cover
.file
== null) {
924 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
927 Pair
<InputStream
, Long
> data
;
929 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
930 } catch (IOException ignored
) {
931 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
934 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
937 return new SignalServiceStickerManifestUpload(
944 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
945 InputStream inputStream
;
947 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
949 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
951 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
954 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
956 final ZipEntry entry
= zip
.getEntry(subfile
);
957 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
959 final File file
= new File(rootPath
, subfile
);
960 return new Pair
<>(new FileInputStream(file
), file
.length());
964 private void requestSyncGroups() throws IOException
{
965 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
966 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
968 sendSyncMessage(message
);
969 } catch (UntrustedIdentityException e
) {
974 private void requestSyncContacts() throws IOException
{
975 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
976 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
978 sendSyncMessage(message
);
979 } catch (UntrustedIdentityException e
) {
984 private void requestSyncBlocked() throws IOException
{
985 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
986 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
988 sendSyncMessage(message
);
989 } catch (UntrustedIdentityException e
) {
994 private void requestSyncConfiguration() throws IOException
{
995 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
996 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
998 sendSyncMessage(message
);
999 } catch (UntrustedIdentityException e
) {
1000 e
.printStackTrace();
1004 private byte[] getSenderCertificate() {
1005 // TODO support UUID capable sender certificates
1006 // byte[] certificate = accountManager.getSenderCertificate();
1009 certificate
= accountManager
.getSenderCertificateLegacy();
1010 } catch (IOException e
) {
1011 System
.err
.println("Failed to get sender certificate: " + e
);
1014 // TODO cache for a day
1018 private byte[] getSelfUnidentifiedAccessKey() {
1019 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1022 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1023 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1025 return new SignalProfile(
1026 encryptedProfile
.getIdentityKey(),
1027 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1028 encryptedProfile
.getAvatar(),
1029 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1030 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1032 } catch (InvalidCiphertextException e
) {
1037 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1038 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1039 if (contact
== null || contact
.profileKey
== null) {
1042 ProfileKey theirProfileKey
;
1044 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1045 } catch (InvalidInputException
| IOException e
) {
1046 throw new AssertionError(e
);
1048 SignalProfile targetProfile
;
1050 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1051 } catch (IOException e
) {
1052 System
.err
.println("Failed to get recipient profile: " + e
);
1056 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1060 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1061 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1064 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1067 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1068 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1069 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1071 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1072 return Optional
.absent();
1076 return Optional
.of(new UnidentifiedAccessPair(
1077 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1078 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1080 } catch (InvalidCertificateException e
) {
1081 return Optional
.absent();
1085 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1086 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1087 for (SignalServiceAddress recipient
: recipients
) {
1088 result
.add(getAccessFor(recipient
));
1093 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1094 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1095 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1096 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1098 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1099 return Optional
.absent();
1103 return Optional
.of(new UnidentifiedAccessPair(
1104 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1105 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1107 } catch (InvalidCertificateException e
) {
1108 return Optional
.absent();
1112 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1113 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1115 if (unidentifiedAccess
.isPresent()) {
1116 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1119 return Optional
.absent();
1122 private void sendSyncMessage(SignalServiceSyncMessage message
)
1123 throws IOException
, UntrustedIdentityException
{
1124 SignalServiceMessageSender messageSender
= getMessageSender();
1126 messageSender
.sendMessage(message
, getAccessForSync());
1127 } catch (UntrustedIdentityException e
) {
1128 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1134 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1136 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1137 throws EncapsulatedExceptions
, IOException
{
1138 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1140 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1141 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1142 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1144 for (SendMessageResult result
: results
) {
1145 if (result
.isUnregisteredFailure()) {
1146 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1147 } else if (result
.isNetworkFailure()) {
1148 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1149 } else if (result
.getIdentityFailure() != null) {
1150 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1153 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1154 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1158 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1159 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1161 for (String number
: numbers
) {
1162 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1164 return signalServiceAddresses
;
1167 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1168 throws IOException
{
1169 if (messagePipe
== null) {
1170 messagePipe
= getMessageReceiver().createMessagePipe();
1172 if (unidentifiedMessagePipe
== null) {
1173 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1175 SignalServiceDataMessage message
= null;
1177 SignalServiceMessageSender messageSender
= getMessageSender();
1179 message
= messageBuilder
.build();
1180 if (message
.getGroupContext().isPresent()) {
1182 final boolean isRecipientUpdate
= false;
1183 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1184 for (SendMessageResult r
: result
) {
1185 if (r
.getIdentityFailure() != null) {
1186 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1190 } catch (UntrustedIdentityException e
) {
1191 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1192 return Collections
.emptyList();
1194 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1195 SignalServiceAddress recipient
= account
.getSelfAddress();
1196 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1197 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1198 message
.getTimestamp(),
1200 message
.getExpiresInSeconds(),
1201 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1203 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1205 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1207 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1208 } catch (UntrustedIdentityException e
) {
1209 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1210 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1214 // Send to all individually, so sync messages are sent correctly
1215 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1216 for (SignalServiceAddress address
: recipients
) {
1217 ContactInfo contact
= account
.getContactStore().getContact(address
);
1218 if (contact
!= null) {
1219 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1220 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1222 messageBuilder
.withExpiration(0);
1223 messageBuilder
.withProfileKey(null);
1225 message
= messageBuilder
.build();
1227 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1228 results
.add(result
);
1229 } catch (UntrustedIdentityException e
) {
1230 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1231 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1237 if (message
!= null && message
.isEndSession()) {
1238 for (SignalServiceAddress recipient
: recipients
) {
1239 handleEndSession(recipient
);
1246 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1247 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1249 return cipher
.decrypt(envelope
);
1250 } catch (ProtocolUntrustedIdentityException e
) {
1251 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1252 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1253 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1254 throw identityException
;
1256 throw new AssertionError(e
);
1260 private void handleEndSession(SignalServiceAddress source
) {
1261 account
.getSignalProtocolStore().deleteAllSessions(source
);
1264 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1265 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1266 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1267 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1268 switch (groupInfo
.getType()) {
1270 if (group
== null) {
1271 group
= new GroupInfo(groupInfo
.getGroupId());
1274 if (groupInfo
.getAvatar().isPresent()) {
1275 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1276 if (avatar
.isPointer()) {
1278 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1279 } catch (IOException
| InvalidMessageException e
) {
1280 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1285 if (groupInfo
.getName().isPresent()) {
1286 group
.name
= groupInfo
.getName().get();
1289 if (groupInfo
.getMembers().isPresent()) {
1290 group
.addMembers(groupInfo
.getMembers().get());
1293 account
.getGroupStore().updateGroup(group
);
1296 if (group
== null) {
1298 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1299 } catch (IOException
| EncapsulatedExceptions e
) {
1300 e
.printStackTrace();
1305 if (group
== null) {
1307 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1308 } catch (IOException
| EncapsulatedExceptions e
) {
1309 e
.printStackTrace();
1312 group
.removeMember(source
);
1313 account
.getGroupStore().updateGroup(group
);
1317 if (group
!= null) {
1319 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1320 } catch (IOException
| EncapsulatedExceptions e
) {
1321 e
.printStackTrace();
1322 } catch (NotAGroupMemberException e
) {
1323 // We have left this group, so don't send a group update message
1329 if (message
.isEndSession()) {
1330 handleEndSession(isSync ? destination
: source
);
1332 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1333 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1334 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1335 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1336 if (group
== null) {
1337 group
= new GroupInfo(groupInfo
.getGroupId());
1339 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1340 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1341 account
.getGroupStore().updateGroup(group
);
1344 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1345 if (contact
== null) {
1346 contact
= new ContactInfo(isSync ? destination
: source
);
1348 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1349 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1350 account
.getContactStore().updateContact(contact
);
1354 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1355 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1356 if (attachment
.isPointer()) {
1358 retrieveAttachment(attachment
.asPointer());
1359 } catch (IOException
| InvalidMessageException e
) {
1360 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1365 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1366 if (source
.matches(account
.getSelfAddress())) {
1368 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1369 } catch (InvalidInputException ignored
) {
1371 ContactInfo contact
= account
.getContactStore().getContact(source
);
1372 if (contact
!= null) {
1373 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1374 account
.getContactStore().updateContact(contact
);
1377 ContactInfo contact
= account
.getContactStore().getContact(source
);
1378 if (contact
== null) {
1379 contact
= new ContactInfo(source
);
1381 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1382 account
.getContactStore().updateContact(contact
);
1385 if (message
.getPreviews().isPresent()) {
1386 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1387 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1388 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1389 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1391 retrieveAttachment(attachment
);
1392 } catch (IOException
| InvalidMessageException e
) {
1393 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1400 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1401 final File cachePath
= new File(getMessageCachePath());
1402 if (!cachePath
.exists()) {
1405 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1406 if (!dir
.isDirectory()) {
1407 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1411 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1412 if (!fileEntry
.isFile()) {
1415 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1417 // Try to delete directory if empty
1422 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1423 SignalServiceEnvelope envelope
;
1425 envelope
= Utils
.loadEnvelope(fileEntry
);
1426 if (envelope
== null) {
1429 } catch (IOException e
) {
1430 e
.printStackTrace();
1433 SignalServiceContent content
= null;
1434 if (!envelope
.isReceipt()) {
1436 content
= decryptMessage(envelope
);
1437 } catch (Exception e
) {
1440 handleMessage(envelope
, content
, ignoreAttachments
);
1443 handler
.handleMessage(envelope
, content
, null);
1445 Files
.delete(fileEntry
.toPath());
1446 } catch (IOException e
) {
1447 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1451 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1452 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1453 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1456 if (messagePipe
== null) {
1457 messagePipe
= messageReceiver
.createMessagePipe();
1461 SignalServiceEnvelope envelope
;
1462 SignalServiceContent content
= null;
1463 Exception exception
= null;
1464 final long now
= new Date().getTime();
1466 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1467 // store message on disk, before acknowledging receipt to the server
1469 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1470 Utils
.storeEnvelope(envelope1
, cacheFile
);
1471 } catch (IOException e
) {
1472 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1475 } catch (TimeoutException e
) {
1476 if (returnOnTimeout
)
1479 } catch (InvalidVersionException e
) {
1480 System
.err
.println("Ignoring error: " + e
.getMessage());
1483 if (!envelope
.isReceipt()) {
1485 content
= decryptMessage(envelope
);
1486 } catch (Exception e
) {
1489 handleMessage(envelope
, content
, ignoreAttachments
);
1492 if (!isMessageBlocked(envelope
, content
)) {
1493 handler
.handleMessage(envelope
, content
, exception
);
1495 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1496 File cacheFile
= null;
1498 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1499 Files
.delete(cacheFile
.toPath());
1500 // Try to delete directory if empty
1501 new File(getMessageCachePath()).delete();
1502 } catch (IOException e
) {
1503 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1508 if (messagePipe
!= null) {
1509 messagePipe
.shutdown();
1515 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1516 SignalServiceAddress source
;
1517 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1518 source
= envelope
.getSourceAddress();
1519 } else if (content
!= null) {
1520 source
= content
.getSender();
1524 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1525 if (sourceContact
!= null && sourceContact
.blocked
) {
1529 if (content
!= null && content
.getDataMessage().isPresent()) {
1530 SignalServiceDataMessage message
= content
.getDataMessage().get();
1531 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1532 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1533 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1534 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1542 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1543 if (content
!= null) {
1544 SignalServiceAddress sender
;
1545 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1546 sender
= envelope
.getSourceAddress();
1548 sender
= content
.getSender();
1550 if (content
.getDataMessage().isPresent()) {
1551 SignalServiceDataMessage message
= content
.getDataMessage().get();
1553 if (content
.isNeedsReceipt()) {
1555 sendReceipt(sender
, message
.getTimestamp());
1556 } catch (IOException
| UntrustedIdentityException e
) {
1557 e
.printStackTrace();
1561 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1563 if (content
.getSyncMessage().isPresent()) {
1564 account
.setMultiDevice(true);
1565 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1566 if (syncMessage
.getSent().isPresent()) {
1567 SentTranscriptMessage message
= syncMessage
.getSent().get();
1568 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1570 if (syncMessage
.getRequest().isPresent()) {
1571 RequestMessage rm
= syncMessage
.getRequest().get();
1572 if (rm
.isContactsRequest()) {
1575 } catch (UntrustedIdentityException
| IOException e
) {
1576 e
.printStackTrace();
1579 if (rm
.isGroupsRequest()) {
1582 } catch (UntrustedIdentityException
| IOException e
) {
1583 e
.printStackTrace();
1586 if (rm
.isBlockedListRequest()) {
1589 } catch (UntrustedIdentityException
| IOException e
) {
1590 e
.printStackTrace();
1593 // TODO Handle rm.isConfigurationRequest();
1595 if (syncMessage
.getGroups().isPresent()) {
1596 File tmpFile
= null;
1598 tmpFile
= IOUtils
.createTempFile();
1599 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1600 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1602 while ((g
= s
.read()) != null) {
1603 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1604 if (syncGroup
== null) {
1605 syncGroup
= new GroupInfo(g
.getId());
1607 if (g
.getName().isPresent()) {
1608 syncGroup
.name
= g
.getName().get();
1610 syncGroup
.addMembers(g
.getMembers());
1611 if (!g
.isActive()) {
1612 syncGroup
.removeMember(account
.getSelfAddress());
1614 // Add ourself to the member set as it's marked as active
1615 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1617 syncGroup
.blocked
= g
.isBlocked();
1618 if (g
.getColor().isPresent()) {
1619 syncGroup
.color
= g
.getColor().get();
1622 if (g
.getAvatar().isPresent()) {
1623 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1625 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1626 syncGroup
.archived
= g
.isArchived();
1627 account
.getGroupStore().updateGroup(syncGroup
);
1630 } catch (Exception e
) {
1631 e
.printStackTrace();
1633 if (tmpFile
!= null) {
1635 Files
.delete(tmpFile
.toPath());
1636 } catch (IOException e
) {
1637 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1642 if (syncMessage
.getBlockedList().isPresent()) {
1643 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1644 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1645 setContactBlocked(address
, true);
1647 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1649 setGroupBlocked(groupId
, true);
1650 } catch (GroupNotFoundException e
) {
1651 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1655 if (syncMessage
.getContacts().isPresent()) {
1656 File tmpFile
= null;
1658 tmpFile
= IOUtils
.createTempFile();
1659 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1660 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1661 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1662 if (contactsMessage
.isComplete()) {
1663 account
.getContactStore().clear();
1666 while ((c
= s
.read()) != null) {
1667 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1668 account
.setProfileKey(c
.getProfileKey().get());
1670 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1671 if (contact
== null) {
1672 contact
= new ContactInfo(c
.getAddress());
1674 if (c
.getName().isPresent()) {
1675 contact
.name
= c
.getName().get();
1677 if (c
.getColor().isPresent()) {
1678 contact
.color
= c
.getColor().get();
1680 if (c
.getProfileKey().isPresent()) {
1681 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1683 if (c
.getVerified().isPresent()) {
1684 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1685 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1687 if (c
.getExpirationTimer().isPresent()) {
1688 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1690 contact
.blocked
= c
.isBlocked();
1691 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1692 contact
.archived
= c
.isArchived();
1693 account
.getContactStore().updateContact(contact
);
1695 if (c
.getAvatar().isPresent()) {
1696 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1700 } catch (Exception e
) {
1701 e
.printStackTrace();
1703 if (tmpFile
!= null) {
1705 Files
.delete(tmpFile
.toPath());
1706 } catch (IOException e
) {
1707 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1712 if (syncMessage
.getVerified().isPresent()) {
1713 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1714 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1716 if (syncMessage
.getConfiguration().isPresent()) {
1723 private File
getContactAvatarFile(String number
) {
1724 return new File(avatarsPath
, "contact-" + number
);
1727 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1728 IOUtils
.createPrivateDirectories(avatarsPath
);
1729 if (attachment
.isPointer()) {
1730 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1731 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1733 SignalServiceAttachmentStream stream
= attachment
.asStream();
1734 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1738 private File
getGroupAvatarFile(byte[] groupId
) {
1739 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1742 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1743 IOUtils
.createPrivateDirectories(avatarsPath
);
1744 if (attachment
.isPointer()) {
1745 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1746 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1748 SignalServiceAttachmentStream stream
= attachment
.asStream();
1749 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1753 public File
getAttachmentFile(long attachmentId
) {
1754 return new File(attachmentsPath
, attachmentId
+ "");
1757 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1758 IOUtils
.createPrivateDirectories(attachmentsPath
);
1759 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1762 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1763 if (storePreview
&& pointer
.getPreview().isPresent()) {
1764 File previewFile
= new File(outputFile
+ ".preview");
1765 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1766 byte[] preview
= pointer
.getPreview().get();
1767 output
.write(preview
, 0, preview
.length
);
1768 } catch (FileNotFoundException e
) {
1769 e
.printStackTrace();
1774 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1776 File tmpFile
= IOUtils
.createTempFile();
1777 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1778 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1779 byte[] buffer
= new byte[4096];
1782 while ((read
= input
.read(buffer
)) != -1) {
1783 output
.write(buffer
, 0, read
);
1785 } catch (FileNotFoundException e
) {
1786 e
.printStackTrace();
1791 Files
.delete(tmpFile
.toPath());
1792 } catch (IOException e
) {
1793 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1799 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1800 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1801 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1805 public boolean isRemote() {
1809 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1810 File groupsFile
= IOUtils
.createTempFile();
1813 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1814 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1815 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1816 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1817 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1818 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1819 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1823 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1824 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1825 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1826 .withStream(groupsFileStream
)
1827 .withContentType("application/octet-stream")
1828 .withLength(groupsFile
.length())
1831 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1836 Files
.delete(groupsFile
.toPath());
1837 } catch (IOException e
) {
1838 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1843 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1844 File contactsFile
= IOUtils
.createTempFile();
1847 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1848 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1849 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1850 VerifiedMessage verifiedMessage
= null;
1851 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1852 if (currentIdentity
!= null) {
1853 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1856 ProfileKey profileKey
= null;
1858 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1859 } catch (InvalidInputException ignored
) {
1861 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1862 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1863 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1864 Optional
.of(record.messageExpirationTime
),
1865 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1868 if (account
.getProfileKey() != null) {
1869 // Send our own profile key as well
1870 out
.write(new DeviceContact(account
.getSelfAddress(),
1871 Optional
.absent(), Optional
.absent(),
1872 Optional
.absent(), Optional
.absent(),
1873 Optional
.of(account
.getProfileKey()),
1874 false, Optional
.absent(), Optional
.absent(), false));
1878 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1879 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1880 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1881 .withStream(contactsFileStream
)
1882 .withContentType("application/octet-stream")
1883 .withLength(contactsFile
.length())
1886 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1891 Files
.delete(contactsFile
.toPath());
1892 } catch (IOException e
) {
1893 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1898 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1899 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1900 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1901 if (record.blocked
) {
1902 addresses
.add(record.getAddress());
1905 List
<byte[]> groupIds
= new ArrayList
<>();
1906 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1907 if (record.blocked
) {
1908 groupIds
.add(record.groupId
);
1911 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1914 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1915 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1916 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1919 public List
<ContactInfo
> getContacts() {
1920 return account
.getContactStore().getContacts();
1923 public ContactInfo
getContact(String number
) {
1924 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1927 public GroupInfo
getGroup(byte[] groupId
) {
1928 return account
.getGroupStore().getGroup(groupId
);
1931 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1932 return account
.getSignalProtocolStore().getIdentities();
1935 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1936 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1940 * Trust this the identity with this fingerprint
1942 * @param name username of the identity
1943 * @param fingerprint Fingerprint
1945 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1946 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1947 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1951 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1952 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1956 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1958 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1959 } catch (IOException
| UntrustedIdentityException e
) {
1960 e
.printStackTrace();
1969 * Trust this the identity with this safety number
1971 * @param name username of the identity
1972 * @param safetyNumber Safety number
1974 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1975 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1976 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1980 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1981 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1985 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1987 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1988 } catch (IOException
| UntrustedIdentityException e
) {
1989 e
.printStackTrace();
1998 * Trust all keys of this identity without verification
2000 * @param name username of the identity
2002 public boolean trustIdentityAllKeys(String name
) {
2003 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2004 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2008 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2009 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2010 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2012 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2013 } catch (IOException
| UntrustedIdentityException e
) {
2014 e
.printStackTrace();
2022 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2023 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2026 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2027 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2028 return resolveSignalServiceAddress(canonicalizedNumber
);
2031 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2032 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2033 if (address
.matches(account
.getSelfAddress())) {
2034 return account
.getSelfAddress();
2037 ContactInfo contactInfo
= account
.getContactStore().getContact(address
);
2038 if (contactInfo
== null) {
2041 return contactInfo
.getAddress();
2044 public interface ReceiveMessageHandler
{
2046 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);