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
.NotAGroupMemberException
;
25 import org
.asamk
.signal
.StickerPackInvalidException
;
26 import org
.asamk
.signal
.TrustLevel
;
27 import org
.asamk
.signal
.UserAlreadyExists
;
28 import org
.asamk
.signal
.storage
.SignalAccount
;
29 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
30 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
31 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
32 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
33 import org
.asamk
.signal
.util
.IOUtils
;
34 import org
.asamk
.signal
.util
.Util
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
45 import org
.signal
.libsignal
.metadata
.SelfSendException
;
46 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
47 import org
.signal
.zkgroup
.InvalidInputException
;
48 import org
.signal
.zkgroup
.VerificationFailedException
;
49 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
50 import org
.whispersystems
.libsignal
.IdentityKey
;
51 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
52 import org
.whispersystems
.libsignal
.InvalidKeyException
;
53 import org
.whispersystems
.libsignal
.InvalidMessageException
;
54 import org
.whispersystems
.libsignal
.InvalidVersionException
;
55 import org
.whispersystems
.libsignal
.ecc
.Curve
;
56 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
57 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
58 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
59 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
60 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
61 import org
.whispersystems
.libsignal
.util
.Medium
;
62 import org
.whispersystems
.libsignal
.util
.Pair
;
63 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
64 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
98 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
99 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
100 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
101 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
104 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
105 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
106 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
107 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
108 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
112 import org
.whispersystems
.util
.Base64
;
115 import java
.io
.FileInputStream
;
116 import java
.io
.FileNotFoundException
;
117 import java
.io
.FileOutputStream
;
118 import java
.io
.IOException
;
119 import java
.io
.InputStream
;
120 import java
.io
.OutputStream
;
122 import java
.net
.URISyntaxException
;
123 import java
.net
.URLEncoder
;
124 import java
.nio
.file
.Files
;
125 import java
.nio
.file
.Paths
;
126 import java
.nio
.file
.StandardCopyOption
;
127 import java
.util
.ArrayList
;
128 import java
.util
.Arrays
;
129 import java
.util
.Collection
;
130 import java
.util
.Collections
;
131 import java
.util
.Date
;
132 import java
.util
.HashSet
;
133 import java
.util
.LinkedList
;
134 import java
.util
.List
;
135 import java
.util
.Locale
;
136 import java
.util
.Objects
;
137 import java
.util
.Set
;
138 import java
.util
.UUID
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.stream
.Collectors
;
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 long 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 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
548 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
549 long targetSentTimestamp
, byte[] groupId
)
550 throws IOException
, EncapsulatedExceptions
, 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 long 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 return sendMessage(message
, attachments
, recipients
);
698 public long 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 return 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 long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1137 throws EncapsulatedExceptions
, IOException
{
1138 final long timestamp
= System
.currentTimeMillis();
1139 messageBuilder
.withTimestamp(timestamp
);
1140 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1142 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1143 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1144 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1146 for (SendMessageResult result
: results
) {
1147 if (result
.isUnregisteredFailure()) {
1148 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1149 } else if (result
.isNetworkFailure()) {
1150 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1151 } else if (result
.getIdentityFailure() != null) {
1152 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1155 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1156 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1161 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1162 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1164 for (String number
: numbers
) {
1165 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1167 return signalServiceAddresses
;
1170 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1171 throws IOException
{
1172 if (messagePipe
== null) {
1173 messagePipe
= getMessageReceiver().createMessagePipe();
1175 if (unidentifiedMessagePipe
== null) {
1176 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1178 SignalServiceDataMessage message
= null;
1180 SignalServiceMessageSender messageSender
= getMessageSender();
1182 message
= messageBuilder
.build();
1183 if (message
.getGroupContext().isPresent()) {
1185 final boolean isRecipientUpdate
= false;
1186 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1187 for (SendMessageResult r
: result
) {
1188 if (r
.getIdentityFailure() != null) {
1189 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1193 } catch (UntrustedIdentityException e
) {
1194 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1195 return Collections
.emptyList();
1197 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1198 SignalServiceAddress recipient
= account
.getSelfAddress();
1199 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1200 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1201 message
.getTimestamp(),
1203 message
.getExpiresInSeconds(),
1204 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1206 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1208 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1210 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1211 } catch (UntrustedIdentityException e
) {
1212 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1213 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1217 // Send to all individually, so sync messages are sent correctly
1218 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1219 for (SignalServiceAddress address
: recipients
) {
1220 ContactInfo contact
= account
.getContactStore().getContact(address
);
1221 if (contact
!= null) {
1222 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1223 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1225 messageBuilder
.withExpiration(0);
1226 messageBuilder
.withProfileKey(null);
1228 message
= messageBuilder
.build();
1230 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1231 results
.add(result
);
1232 } catch (UntrustedIdentityException e
) {
1233 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1234 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1240 if (message
!= null && message
.isEndSession()) {
1241 for (SignalServiceAddress recipient
: recipients
) {
1242 handleEndSession(recipient
);
1249 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1250 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1252 return cipher
.decrypt(envelope
);
1253 } catch (ProtocolUntrustedIdentityException e
) {
1254 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1255 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1256 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1257 throw identityException
;
1259 throw new AssertionError(e
);
1263 private void handleEndSession(SignalServiceAddress source
) {
1264 account
.getSignalProtocolStore().deleteAllSessions(source
);
1267 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1268 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1269 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1270 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1271 switch (groupInfo
.getType()) {
1273 if (group
== null) {
1274 group
= new GroupInfo(groupInfo
.getGroupId());
1277 if (groupInfo
.getAvatar().isPresent()) {
1278 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1279 if (avatar
.isPointer()) {
1281 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1282 } catch (IOException
| InvalidMessageException e
) {
1283 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1288 if (groupInfo
.getName().isPresent()) {
1289 group
.name
= groupInfo
.getName().get();
1292 if (groupInfo
.getMembers().isPresent()) {
1293 group
.addMembers(groupInfo
.getMembers().get()
1295 .map(this::resolveSignalServiceAddress
)
1296 .collect(Collectors
.toSet()));
1299 account
.getGroupStore().updateGroup(group
);
1302 if (group
== null) {
1304 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1305 } catch (IOException
| EncapsulatedExceptions e
) {
1306 e
.printStackTrace();
1311 if (group
== null) {
1313 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1314 } catch (IOException
| EncapsulatedExceptions e
) {
1315 e
.printStackTrace();
1318 group
.removeMember(source
);
1319 account
.getGroupStore().updateGroup(group
);
1323 if (group
!= null) {
1325 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1326 } catch (IOException
| EncapsulatedExceptions e
) {
1327 e
.printStackTrace();
1328 } catch (NotAGroupMemberException e
) {
1329 // We have left this group, so don't send a group update message
1335 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1336 if (message
.isEndSession()) {
1337 handleEndSession(conversationPartnerAddress
);
1339 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1340 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1341 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1342 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1343 if (group
== null) {
1344 group
= new GroupInfo(groupInfo
.getGroupId());
1346 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1347 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1348 account
.getGroupStore().updateGroup(group
);
1351 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1352 if (contact
== null) {
1353 contact
= new ContactInfo(conversationPartnerAddress
);
1355 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1356 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1357 account
.getContactStore().updateContact(contact
);
1361 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1362 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1363 if (attachment
.isPointer()) {
1365 retrieveAttachment(attachment
.asPointer());
1366 } catch (IOException
| InvalidMessageException e
) {
1367 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1372 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1373 if (source
.matches(account
.getSelfAddress())) {
1375 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1376 } catch (InvalidInputException ignored
) {
1378 ContactInfo contact
= account
.getContactStore().getContact(source
);
1379 if (contact
!= null) {
1380 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1381 account
.getContactStore().updateContact(contact
);
1384 ContactInfo contact
= account
.getContactStore().getContact(source
);
1385 if (contact
== null) {
1386 contact
= new ContactInfo(source
);
1388 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1389 account
.getContactStore().updateContact(contact
);
1392 if (message
.getPreviews().isPresent()) {
1393 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1394 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1395 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1396 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1398 retrieveAttachment(attachment
);
1399 } catch (IOException
| InvalidMessageException e
) {
1400 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1407 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1408 final File cachePath
= new File(getMessageCachePath());
1409 if (!cachePath
.exists()) {
1412 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1413 if (!dir
.isDirectory()) {
1414 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1418 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1419 if (!fileEntry
.isFile()) {
1422 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1424 // Try to delete directory if empty
1429 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1430 SignalServiceEnvelope envelope
;
1432 envelope
= Utils
.loadEnvelope(fileEntry
);
1433 if (envelope
== null) {
1436 } catch (IOException e
) {
1437 e
.printStackTrace();
1440 SignalServiceContent content
= null;
1441 if (!envelope
.isReceipt()) {
1443 content
= decryptMessage(envelope
);
1444 } catch (Exception e
) {
1447 handleMessage(envelope
, content
, ignoreAttachments
);
1450 handler
.handleMessage(envelope
, content
, null);
1452 Files
.delete(fileEntry
.toPath());
1453 } catch (IOException e
) {
1454 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1458 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1459 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1460 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1463 if (messagePipe
== null) {
1464 messagePipe
= messageReceiver
.createMessagePipe();
1468 SignalServiceEnvelope envelope
;
1469 SignalServiceContent content
= null;
1470 Exception exception
= null;
1471 final long now
= new Date().getTime();
1473 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1474 // store message on disk, before acknowledging receipt to the server
1476 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1477 Utils
.storeEnvelope(envelope1
, cacheFile
);
1478 } catch (IOException e
) {
1479 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1482 } catch (TimeoutException e
) {
1483 if (returnOnTimeout
)
1486 } catch (InvalidVersionException e
) {
1487 System
.err
.println("Ignoring error: " + e
.getMessage());
1490 if (!envelope
.isReceipt()) {
1492 content
= decryptMessage(envelope
);
1493 } catch (Exception e
) {
1496 handleMessage(envelope
, content
, ignoreAttachments
);
1499 if (!isMessageBlocked(envelope
, content
)) {
1500 handler
.handleMessage(envelope
, content
, exception
);
1502 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1503 File cacheFile
= null;
1505 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1506 Files
.delete(cacheFile
.toPath());
1507 // Try to delete directory if empty
1508 new File(getMessageCachePath()).delete();
1509 } catch (IOException e
) {
1510 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1515 if (messagePipe
!= null) {
1516 messagePipe
.shutdown();
1522 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1523 SignalServiceAddress source
;
1524 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1525 source
= envelope
.getSourceAddress();
1526 } else if (content
!= null) {
1527 source
= content
.getSender();
1531 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1532 if (sourceContact
!= null && sourceContact
.blocked
) {
1536 if (content
!= null && content
.getDataMessage().isPresent()) {
1537 SignalServiceDataMessage message
= content
.getDataMessage().get();
1538 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1539 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1540 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1541 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1549 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1550 if (content
!= null) {
1551 SignalServiceAddress sender
;
1552 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1553 sender
= envelope
.getSourceAddress();
1555 sender
= content
.getSender();
1557 if (content
.getDataMessage().isPresent()) {
1558 SignalServiceDataMessage message
= content
.getDataMessage().get();
1560 if (content
.isNeedsReceipt()) {
1562 sendReceipt(sender
, message
.getTimestamp());
1563 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1564 e
.printStackTrace();
1568 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1570 if (content
.getSyncMessage().isPresent()) {
1571 account
.setMultiDevice(true);
1572 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1573 if (syncMessage
.getSent().isPresent()) {
1574 SentTranscriptMessage message
= syncMessage
.getSent().get();
1575 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1577 if (syncMessage
.getRequest().isPresent()) {
1578 RequestMessage rm
= syncMessage
.getRequest().get();
1579 if (rm
.isContactsRequest()) {
1582 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1583 e
.printStackTrace();
1586 if (rm
.isGroupsRequest()) {
1589 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1590 e
.printStackTrace();
1593 if (rm
.isBlockedListRequest()) {
1596 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1597 e
.printStackTrace();
1600 // TODO Handle rm.isConfigurationRequest();
1602 if (syncMessage
.getGroups().isPresent()) {
1603 File tmpFile
= null;
1605 tmpFile
= IOUtils
.createTempFile();
1606 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1607 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1609 while ((g
= s
.read()) != null) {
1610 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1611 if (syncGroup
== null) {
1612 syncGroup
= new GroupInfo(g
.getId());
1614 if (g
.getName().isPresent()) {
1615 syncGroup
.name
= g
.getName().get();
1617 syncGroup
.addMembers(g
.getMembers()
1619 .map(this::resolveSignalServiceAddress
)
1620 .collect(Collectors
.toSet()));
1621 if (!g
.isActive()) {
1622 syncGroup
.removeMember(account
.getSelfAddress());
1624 // Add ourself to the member set as it's marked as active
1625 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1627 syncGroup
.blocked
= g
.isBlocked();
1628 if (g
.getColor().isPresent()) {
1629 syncGroup
.color
= g
.getColor().get();
1632 if (g
.getAvatar().isPresent()) {
1633 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1635 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1636 syncGroup
.archived
= g
.isArchived();
1637 account
.getGroupStore().updateGroup(syncGroup
);
1640 } catch (Exception e
) {
1641 e
.printStackTrace();
1643 if (tmpFile
!= null) {
1645 Files
.delete(tmpFile
.toPath());
1646 } catch (IOException e
) {
1647 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1652 if (syncMessage
.getBlockedList().isPresent()) {
1653 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1654 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1655 setContactBlocked(resolveSignalServiceAddress(address
), true);
1657 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1659 setGroupBlocked(groupId
, true);
1660 } catch (GroupNotFoundException e
) {
1661 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1665 if (syncMessage
.getContacts().isPresent()) {
1666 File tmpFile
= null;
1668 tmpFile
= IOUtils
.createTempFile();
1669 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1670 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1671 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1672 if (contactsMessage
.isComplete()) {
1673 account
.getContactStore().clear();
1676 while ((c
= s
.read()) != null) {
1677 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1678 account
.setProfileKey(c
.getProfileKey().get());
1680 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1681 ContactInfo contact
= account
.getContactStore().getContact(address
);
1682 if (contact
== null) {
1683 contact
= new ContactInfo(address
);
1685 if (c
.getName().isPresent()) {
1686 contact
.name
= c
.getName().get();
1688 if (c
.getColor().isPresent()) {
1689 contact
.color
= c
.getColor().get();
1691 if (c
.getProfileKey().isPresent()) {
1692 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1694 if (c
.getVerified().isPresent()) {
1695 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1696 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1698 if (c
.getExpirationTimer().isPresent()) {
1699 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1701 contact
.blocked
= c
.isBlocked();
1702 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1703 contact
.archived
= c
.isArchived();
1704 account
.getContactStore().updateContact(contact
);
1706 if (c
.getAvatar().isPresent()) {
1707 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1711 } catch (Exception e
) {
1712 e
.printStackTrace();
1714 if (tmpFile
!= null) {
1716 Files
.delete(tmpFile
.toPath());
1717 } catch (IOException e
) {
1718 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1723 if (syncMessage
.getVerified().isPresent()) {
1724 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1725 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1727 if (syncMessage
.getConfiguration().isPresent()) {
1734 private File
getContactAvatarFile(String number
) {
1735 return new File(avatarsPath
, "contact-" + number
);
1738 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1739 IOUtils
.createPrivateDirectories(avatarsPath
);
1740 if (attachment
.isPointer()) {
1741 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1742 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1744 SignalServiceAttachmentStream stream
= attachment
.asStream();
1745 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1749 private File
getGroupAvatarFile(byte[] groupId
) {
1750 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1753 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1754 IOUtils
.createPrivateDirectories(avatarsPath
);
1755 if (attachment
.isPointer()) {
1756 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1757 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1759 SignalServiceAttachmentStream stream
= attachment
.asStream();
1760 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1764 public File
getAttachmentFile(long attachmentId
) {
1765 return new File(attachmentsPath
, attachmentId
+ "");
1768 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1769 IOUtils
.createPrivateDirectories(attachmentsPath
);
1770 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1773 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1774 if (storePreview
&& pointer
.getPreview().isPresent()) {
1775 File previewFile
= new File(outputFile
+ ".preview");
1776 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1777 byte[] preview
= pointer
.getPreview().get();
1778 output
.write(preview
, 0, preview
.length
);
1779 } catch (FileNotFoundException e
) {
1780 e
.printStackTrace();
1785 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1787 File tmpFile
= IOUtils
.createTempFile();
1788 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1789 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1790 byte[] buffer
= new byte[4096];
1793 while ((read
= input
.read(buffer
)) != -1) {
1794 output
.write(buffer
, 0, read
);
1796 } catch (FileNotFoundException e
) {
1797 e
.printStackTrace();
1802 Files
.delete(tmpFile
.toPath());
1803 } catch (IOException e
) {
1804 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1810 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1811 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1812 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1816 public boolean isRemote() {
1820 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1821 File groupsFile
= IOUtils
.createTempFile();
1824 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1825 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1826 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1827 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1828 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1829 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1830 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1834 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1835 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1836 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1837 .withStream(groupsFileStream
)
1838 .withContentType("application/octet-stream")
1839 .withLength(groupsFile
.length())
1842 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1847 Files
.delete(groupsFile
.toPath());
1848 } catch (IOException e
) {
1849 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1854 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1855 File contactsFile
= IOUtils
.createTempFile();
1858 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1859 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1860 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1861 VerifiedMessage verifiedMessage
= null;
1862 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1863 if (currentIdentity
!= null) {
1864 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1867 ProfileKey profileKey
= null;
1869 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1870 } catch (InvalidInputException ignored
) {
1872 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1873 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1874 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1875 Optional
.of(record.messageExpirationTime
),
1876 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1879 if (account
.getProfileKey() != null) {
1880 // Send our own profile key as well
1881 out
.write(new DeviceContact(account
.getSelfAddress(),
1882 Optional
.absent(), Optional
.absent(),
1883 Optional
.absent(), Optional
.absent(),
1884 Optional
.of(account
.getProfileKey()),
1885 false, Optional
.absent(), Optional
.absent(), false));
1889 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1890 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1891 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1892 .withStream(contactsFileStream
)
1893 .withContentType("application/octet-stream")
1894 .withLength(contactsFile
.length())
1897 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1902 Files
.delete(contactsFile
.toPath());
1903 } catch (IOException e
) {
1904 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1909 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1910 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1911 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1912 if (record.blocked
) {
1913 addresses
.add(record.getAddress());
1916 List
<byte[]> groupIds
= new ArrayList
<>();
1917 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1918 if (record.blocked
) {
1919 groupIds
.add(record.groupId
);
1922 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1925 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1926 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1927 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1930 public List
<ContactInfo
> getContacts() {
1931 return account
.getContactStore().getContacts();
1934 public ContactInfo
getContact(String number
) {
1935 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1938 public GroupInfo
getGroup(byte[] groupId
) {
1939 return account
.getGroupStore().getGroup(groupId
);
1942 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1943 return account
.getSignalProtocolStore().getIdentities();
1946 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1947 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1951 * Trust this the identity with this fingerprint
1953 * @param name username of the identity
1954 * @param fingerprint Fingerprint
1956 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1957 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1958 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1962 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1963 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1967 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1969 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 } catch (IOException
| UntrustedIdentityException e
) {
1971 e
.printStackTrace();
1980 * Trust this the identity with this safety number
1982 * @param name username of the identity
1983 * @param safetyNumber Safety number
1985 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1986 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1987 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1991 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1992 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1996 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1998 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1999 } catch (IOException
| UntrustedIdentityException e
) {
2000 e
.printStackTrace();
2009 * Trust all keys of this identity without verification
2011 * @param name username of the identity
2013 public boolean trustIdentityAllKeys(String name
) {
2014 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2015 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2019 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2020 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2021 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2023 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2024 } catch (IOException
| UntrustedIdentityException e
) {
2025 e
.printStackTrace();
2033 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2034 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2037 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2038 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2039 return resolveSignalServiceAddress(canonicalizedNumber
);
2042 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2043 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2045 return resolveSignalServiceAddress(address
);
2048 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2049 if (address
.matches(account
.getSelfAddress())) {
2050 return account
.getSelfAddress();
2053 return account
.getRecipientStore().resolveServiceAddress(address
);
2056 public interface ReceiveMessageHandler
{
2058 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);