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 org
.asamk
.Signal
;
20 import org
.asamk
.signal
.AttachmentInvalidException
;
21 import org
.asamk
.signal
.GroupNotFoundException
;
22 import org
.asamk
.signal
.NotAGroupMemberException
;
23 import org
.asamk
.signal
.TrustLevel
;
24 import org
.asamk
.signal
.UserAlreadyExists
;
25 import org
.asamk
.signal
.storage
.SignalAccount
;
26 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
27 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
28 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
29 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
30 import org
.asamk
.signal
.storage
.threads
.ThreadInfo
;
31 import org
.asamk
.signal
.util
.IOUtils
;
32 import org
.asamk
.signal
.util
.Util
;
33 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
34 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
43 import org
.signal
.libsignal
.metadata
.SelfSendException
;
44 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
45 import org
.signal
.zkgroup
.InvalidInputException
;
46 import org
.signal
.zkgroup
.VerificationFailedException
;
47 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
48 import org
.whispersystems
.libsignal
.IdentityKey
;
49 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
50 import org
.whispersystems
.libsignal
.InvalidKeyException
;
51 import org
.whispersystems
.libsignal
.InvalidMessageException
;
52 import org
.whispersystems
.libsignal
.InvalidVersionException
;
53 import org
.whispersystems
.libsignal
.ecc
.Curve
;
54 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
55 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
56 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
57 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
58 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
59 import org
.whispersystems
.libsignal
.util
.Medium
;
60 import org
.whispersystems
.libsignal
.util
.Pair
;
61 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
63 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
64 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
93 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
94 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
95 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
105 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
106 import org
.whispersystems
.util
.Base64
;
109 import java
.io
.FileInputStream
;
110 import java
.io
.FileNotFoundException
;
111 import java
.io
.FileOutputStream
;
112 import java
.io
.IOException
;
113 import java
.io
.InputStream
;
114 import java
.io
.OutputStream
;
116 import java
.nio
.file
.Files
;
117 import java
.nio
.file
.Paths
;
118 import java
.nio
.file
.StandardCopyOption
;
119 import java
.util
.ArrayList
;
120 import java
.util
.Arrays
;
121 import java
.util
.Collection
;
122 import java
.util
.Collections
;
123 import java
.util
.Date
;
124 import java
.util
.HashSet
;
125 import java
.util
.LinkedList
;
126 import java
.util
.List
;
127 import java
.util
.Locale
;
128 import java
.util
.Map
;
129 import java
.util
.Objects
;
130 import java
.util
.Set
;
131 import java
.util
.concurrent
.TimeUnit
;
132 import java
.util
.concurrent
.TimeoutException
;
134 public class Manager
implements Signal
{
136 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
138 private final String settingsPath
;
139 private final String dataPath
;
140 private final String attachmentsPath
;
141 private final String avatarsPath
;
142 private final SleepTimer timer
= new UptimeSleepTimer();
144 private SignalAccount account
;
145 private String username
;
146 private SignalServiceAccountManager accountManager
;
147 private SignalServiceMessagePipe messagePipe
= null;
148 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
150 public Manager(String username
, String settingsPath
) {
151 this.username
= username
;
152 this.settingsPath
= settingsPath
;
153 this.dataPath
= this.settingsPath
+ "/data";
154 this.attachmentsPath
= this.settingsPath
+ "/attachments";
155 this.avatarsPath
= this.settingsPath
+ "/avatars";
159 public String
getUsername() {
163 private SignalServiceAddress
getSelfAddress() {
164 return new SignalServiceAddress(null, username
);
167 private SignalServiceAccountManager
getSignalServiceAccountManager() {
168 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, null, account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
171 private IdentityKey
getIdentity() {
172 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
175 public int getDeviceId() {
176 return account
.getDeviceId();
179 private String
getMessageCachePath() {
180 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
183 private String
getMessageCachePath(String sender
) {
184 return getMessageCachePath() + "/" + sender
.replace("/", "_");
187 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
188 String cachePath
= getMessageCachePath(sender
);
189 IOUtils
.createPrivateDirectories(cachePath
);
190 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
193 public boolean userHasKeys() {
194 return account
!= null && account
.getSignalProtocolStore() != null;
197 public void init() throws IOException
{
198 if (!SignalAccount
.userExists(dataPath
, username
)) {
201 account
= SignalAccount
.load(dataPath
, username
);
203 migrateLegacyConfigs();
205 accountManager
= getSignalServiceAccountManager();
207 if (account
.isRegistered() && accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
211 } catch (AuthorizationFailedException e
) {
212 System
.err
.println("Authorization failed, was the number registered elsewhere?");
217 private void migrateLegacyConfigs() {
218 // Copy group avatars that were previously stored in the attachments folder
219 // to the new avatar folder
220 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
221 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
222 File avatarFile
= getGroupAvatarFile(g
.groupId
);
223 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
224 if (!avatarFile
.exists() && attachmentFile
.exists()) {
226 IOUtils
.createPrivateDirectories(avatarsPath
);
227 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
228 } catch (Exception e
) {
233 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
236 if (account
.getProfileKey() == null) {
237 // Old config file, creating new profile key
238 account
.setProfileKey(KeyUtils
.createProfileKey());
243 private void createNewIdentity() throws IOException
{
244 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
245 int registrationId
= KeyHelper
.generateRegistrationId(false);
246 if (username
== null) {
247 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
249 ProfileKey profileKey
= KeyUtils
.createProfileKey();
250 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
255 public boolean isRegistered() {
256 return account
!= null && account
.isRegistered();
259 public void register(boolean voiceVerification
) throws IOException
{
260 if (account
== null) {
263 account
.setPassword(KeyUtils
.createPassword());
264 accountManager
= getSignalServiceAccountManager();
266 if (voiceVerification
) {
267 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
269 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
272 account
.setRegistered(false);
276 public void updateAccountAttributes() throws IOException
{
277 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
280 public void setProfileName(String name
) throws IOException
{
281 accountManager
.setProfileName(account
.getProfileKey(), name
);
284 public void setProfileAvatar(File avatar
) throws IOException
{
285 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
286 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
287 streamDetails
.getStream().close();
290 public void removeProfileAvatar() throws IOException
{
291 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
294 public void unregister() throws IOException
{
295 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
296 // If this is the master device, other users can't send messages to this number anymore.
297 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
298 accountManager
.setGcmId(Optional
.absent());
300 account
.setRegistered(false);
304 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
305 if (account
== null) {
308 account
.setPassword(KeyUtils
.createPassword());
309 accountManager
= getSignalServiceAccountManager();
310 String uuid
= accountManager
.getNewDeviceUuid();
312 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
315 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
316 account
.setSignalingKey(KeyUtils
.createSignalingKey());
317 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
319 username
= ret
.getNumber();
320 // TODO do this check before actually registering
321 if (SignalAccount
.userExists(dataPath
, username
)) {
322 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
325 // Create new account with the synced identity
326 byte[] profileKeyBytes
= ret
.getProfileKey();
327 ProfileKey profileKey
;
328 if (profileKeyBytes
== null) {
329 profileKey
= KeyUtils
.createProfileKey();
332 profileKey
= new ProfileKey(profileKeyBytes
);
333 } catch (InvalidInputException e
) {
334 throw new IOException("Received invalid profileKey", e
);
337 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
342 requestSyncContacts();
343 requestSyncBlocked();
344 requestSyncConfiguration();
349 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
350 List
<DeviceInfo
> devices
= accountManager
.getDevices();
351 account
.setMultiDevice(devices
.size() > 1);
356 public void removeLinkedDevices(int deviceId
) throws IOException
{
357 accountManager
.removeDevice(deviceId
);
358 List
<DeviceInfo
> devices
= accountManager
.getDevices();
359 account
.setMultiDevice(devices
.size() > 1);
363 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
364 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
366 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
369 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
370 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
371 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
373 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
374 account
.setMultiDevice(true);
378 private List
<PreKeyRecord
> generatePreKeys() {
379 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
381 final int offset
= account
.getPreKeyIdOffset();
382 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
383 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
384 ECKeyPair keyPair
= Curve
.generateKeyPair();
385 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
390 account
.addPreKeys(records
);
396 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
398 ECKeyPair keyPair
= Curve
.generateKeyPair();
399 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
400 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
402 account
.addSignedPreKey(record);
406 } catch (InvalidKeyException e
) {
407 throw new AssertionError(e
);
411 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
412 verificationCode
= verificationCode
.replace("-", "");
413 account
.setSignalingKey(KeyUtils
.createSignalingKey());
414 // TODO make unrestricted unidentified access configurable
415 accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
417 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
418 account
.setRegistered(true);
419 account
.setRegistrationLockPin(pin
);
425 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
426 if (pin
.isPresent()) {
427 account
.setRegistrationLockPin(pin
.get());
428 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
430 account
.setRegistrationLockPin(null);
431 accountManager
.removeV1Pin();
436 private void refreshPreKeys() throws IOException
{
437 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
438 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
439 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
441 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
444 private SignalServiceMessageReceiver
getMessageReceiver() {
445 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
448 private SignalServiceMessageSender
getMessageSender() {
449 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, null, username
, account
.getPassword(),
450 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
453 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
454 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
459 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
460 } catch (IOException ignored
) {
464 SignalServiceMessageReceiver receiver
= getMessageReceiver();
466 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
467 } catch (VerificationFailedException e
) {
468 throw new AssertionError(e
);
472 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
473 File file
= getGroupAvatarFile(groupId
);
474 if (!file
.exists()) {
475 return Optional
.absent();
478 return Optional
.of(Utils
.createAttachment(file
));
481 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
482 File file
= getContactAvatarFile(number
);
483 if (!file
.exists()) {
484 return Optional
.absent();
487 return Optional
.of(Utils
.createAttachment(file
));
490 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
491 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
493 throw new GroupNotFoundException(groupId
);
495 for (String member
: g
.members
) {
496 if (member
.equals(this.username
)) {
500 throw new NotAGroupMemberException(groupId
, g
.name
);
503 public List
<GroupInfo
> getGroups() {
504 return account
.getGroupStore().getGroups();
508 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
510 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
511 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
512 if (attachments
!= null) {
513 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
515 if (groupId
!= null) {
516 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
519 messageBuilder
.asGroupMessage(group
);
521 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
522 if (thread
!= null) {
523 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
526 final GroupInfo g
= getGroupForSending(groupId
);
528 // Don't send group message to ourself
529 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
530 membersSend
.remove(this.username
);
531 sendMessageLegacy(messageBuilder
, membersSend
);
534 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
535 long targetSentTimestamp
, byte[] groupId
)
536 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
537 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
538 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
539 .withReaction(reaction
)
540 .withProfileKey(account
.getProfileKey().serialize());
541 if (groupId
!= null) {
542 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
545 messageBuilder
.asGroupMessage(group
);
547 final GroupInfo g
= getGroupForSending(groupId
);
548 // Don't send group message to ourself
549 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
550 membersSend
.remove(this.username
);
551 sendMessageLegacy(messageBuilder
, membersSend
);
554 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
559 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
560 .asGroupMessage(group
);
562 final GroupInfo g
= getGroupForSending(groupId
);
563 g
.members
.remove(this.username
);
564 account
.getGroupStore().updateGroup(g
);
566 sendMessageLegacy(messageBuilder
, g
.members
);
569 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<String
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
571 if (groupId
== null) {
573 g
= new GroupInfo(KeyUtils
.createGroupId());
574 g
.members
.add(username
);
576 g
= getGroupForSending(groupId
);
583 if (members
!= null) {
584 Set
<String
> newMembers
= new HashSet
<>();
585 for (String member
: members
) {
587 member
= Utils
.canonicalizeNumber(member
, username
);
588 } catch (InvalidNumberException e
) {
589 System
.err
.println("Failed to add member \"" + member
+ "\" to group: " + e
.getMessage());
590 System
.err
.println("Aborting…");
593 if (g
.members
.contains(member
)) {
596 newMembers
.add(member
);
597 g
.members
.add(member
);
599 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newMembers
);
600 if (contacts
.size() != newMembers
.size()) {
601 // Some of the new members are not registered on Signal
602 for (ContactTokenDetails contact
: contacts
) {
603 newMembers
.remove(contact
.getNumber());
605 System
.err
.println("Failed to add members " + Util
.join(", ", newMembers
) + " to group: Not registered on Signal");
606 System
.err
.println("Aborting…");
611 if (avatarFile
!= null) {
612 IOUtils
.createPrivateDirectories(avatarsPath
);
613 File aFile
= getGroupAvatarFile(g
.groupId
);
614 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
617 account
.getGroupStore().updateGroup(g
);
619 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
621 // Don't send group message to ourself
622 final List
<String
> membersSend
= new ArrayList
<>(g
.members
);
623 membersSend
.remove(this.username
);
624 sendMessageLegacy(messageBuilder
, membersSend
);
628 private void sendUpdateGroupMessage(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
629 if (groupId
== null) {
632 GroupInfo g
= getGroupForSending(groupId
);
634 if (!g
.members
.contains(recipient
)) {
638 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
640 // Send group message only to the recipient who requested it
641 final List
<String
> membersSend
= new ArrayList
<>();
642 membersSend
.add(recipient
);
643 sendMessageLegacy(messageBuilder
, membersSend
);
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 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
662 .asGroupMessage(group
.build());
664 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(g
.groupId
));
665 if (thread
!= null) {
666 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
669 return messageBuilder
;
672 private void sendGroupInfoRequest(byte[] groupId
, String recipient
) throws IOException
, EncapsulatedExceptions
{
673 if (groupId
== null) {
677 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
680 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
681 .asGroupMessage(group
.build());
683 ThreadInfo thread
= account
.getThreadStore().getThread(Base64
.encodeBytes(groupId
));
684 if (thread
!= null) {
685 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
688 // Send group info request message to the recipient who sent us a message with this groupId
689 final List
<String
> membersSend
= new ArrayList
<>();
690 membersSend
.add(recipient
);
691 sendMessageLegacy(messageBuilder
, membersSend
);
695 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
696 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
{
697 List
<String
> recipients
= new ArrayList
<>(1);
698 recipients
.add(recipient
);
699 sendMessage(message
, attachments
, recipients
);
703 public void sendMessage(String messageText
, List
<String
> attachments
,
704 List
<String
> recipients
)
705 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
706 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
707 if (attachments
!= null) {
708 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
710 // Upload attachments here, so we only upload once even for multiple recipients
711 SignalServiceMessageSender messageSender
= getMessageSender();
712 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
713 for (SignalServiceAttachment attachment
: attachmentStreams
) {
714 if (attachment
.isStream()) {
715 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
716 } else if (attachment
.isPointer()) {
717 attachmentPointers
.add(attachment
.asPointer());
721 messageBuilder
.withAttachments(attachmentPointers
);
723 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
724 sendMessageLegacy(messageBuilder
, recipients
);
727 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
728 long targetSentTimestamp
, List
<String
> recipients
)
729 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
730 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
731 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
732 .withReaction(reaction
)
733 .withProfileKey(account
.getProfileKey().serialize());
734 sendMessageLegacy(messageBuilder
, recipients
);
738 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
{
739 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
740 .asEndSessionMessage();
742 sendMessageLegacy(messageBuilder
, recipients
);
746 public String
getContactName(String number
) throws InvalidNumberException
{
747 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
748 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
749 if (contact
== null) {
757 public void setContactName(String number
, String name
) throws InvalidNumberException
{
758 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
759 ContactInfo contact
= account
.getContactStore().getContact(canonicalizedNumber
);
760 if (contact
== null) {
761 contact
= new ContactInfo();
762 contact
.number
= canonicalizedNumber
;
763 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
765 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
768 account
.getContactStore().updateContact(contact
);
773 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
774 number
= Utils
.canonicalizeNumber(number
, username
);
775 ContactInfo contact
= account
.getContactStore().getContact(number
);
776 if (contact
== null) {
777 contact
= new ContactInfo();
778 contact
.number
= number
;
779 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
781 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
783 contact
.blocked
= blocked
;
784 account
.getContactStore().updateContact(contact
);
789 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
790 GroupInfo group
= getGroup(groupId
);
792 throw new GroupNotFoundException(groupId
);
794 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
795 group
.blocked
= blocked
;
796 account
.getGroupStore().updateGroup(group
);
802 public List
<byte[]> getGroupIds() {
803 List
<GroupInfo
> groups
= getGroups();
804 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
805 for (GroupInfo group
: groups
) {
806 ids
.add(group
.groupId
);
812 public String
getGroupName(byte[] groupId
) {
813 GroupInfo group
= getGroup(groupId
);
822 public List
<String
> getGroupMembers(byte[] groupId
) {
823 GroupInfo group
= getGroup(groupId
);
825 return new ArrayList
<>();
827 return new ArrayList
<>(group
.members
);
832 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
833 if (groupId
.length
== 0) {
836 if (name
.isEmpty()) {
839 if (members
.size() == 0) {
842 if (avatar
.isEmpty()) {
845 return sendUpdateGroupMessage(groupId
, name
, members
, avatar
);
849 * Change the expiration timer for a thread (number of groupId)
851 * @param numberOrGroupId
852 * @param messageExpirationTimer
854 public void setExpirationTimer(String numberOrGroupId
, int messageExpirationTimer
) {
855 ThreadInfo thread
= account
.getThreadStore().getThread(numberOrGroupId
);
856 thread
.messageExpirationTime
= messageExpirationTimer
;
857 account
.getThreadStore().updateThread(thread
);
860 private void requestSyncGroups() throws IOException
{
861 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
862 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
864 sendSyncMessage(message
);
865 } catch (UntrustedIdentityException e
) {
870 private void requestSyncContacts() throws IOException
{
871 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
872 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
874 sendSyncMessage(message
);
875 } catch (UntrustedIdentityException e
) {
880 private void requestSyncBlocked() throws IOException
{
881 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
882 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
884 sendSyncMessage(message
);
885 } catch (UntrustedIdentityException e
) {
890 private void requestSyncConfiguration() throws IOException
{
891 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
892 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
894 sendSyncMessage(message
);
895 } catch (UntrustedIdentityException e
) {
900 private byte[] getSenderCertificate() throws IOException
{
901 byte[] certificate
= accountManager
.getSenderCertificate();
902 // TODO cache for a day
906 private byte[] getSelfUnidentifiedAccessKey() {
907 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
910 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
911 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
913 return new SignalProfile(
914 encryptedProfile
.getIdentityKey(),
915 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
916 encryptedProfile
.getAvatar(),
917 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
918 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
920 } catch (InvalidCiphertextException e
) {
925 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
926 ContactInfo contact
= account
.getContactStore().getContact(recipient
.getNumber().get());
927 if (contact
== null || contact
.profileKey
== null) {
930 ProfileKey theirProfileKey
;
932 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
933 } catch (InvalidInputException e
) {
934 throw new AssertionError(e
);
936 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
938 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
942 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
943 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
946 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
949 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
950 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
951 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
953 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
954 return Optional
.absent();
958 return Optional
.of(new UnidentifiedAccessPair(
959 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
960 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
962 } catch (InvalidCertificateException e
) {
963 return Optional
.absent();
967 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
968 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
969 for (SignalServiceAddress recipient
: recipients
) {
970 result
.add(getAccessFor(recipient
));
975 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
976 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
977 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
978 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
980 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
981 return Optional
.absent();
985 return Optional
.of(new UnidentifiedAccessPair(
986 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
987 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
989 } catch (InvalidCertificateException e
) {
990 return Optional
.absent();
994 private void sendSyncMessage(SignalServiceSyncMessage message
)
995 throws IOException
, UntrustedIdentityException
{
996 SignalServiceMessageSender messageSender
= getMessageSender();
998 messageSender
.sendMessage(message
, getAccessForSync());
999 } catch (UntrustedIdentityException e
) {
1000 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1006 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1008 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1009 throws EncapsulatedExceptions
, IOException
{
1010 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1012 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1013 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1014 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1016 for (SendMessageResult result
: results
) {
1017 if (result
.isUnregisteredFailure()) {
1018 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1019 } else if (result
.isNetworkFailure()) {
1020 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1021 } else if (result
.getIdentityFailure() != null) {
1022 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1025 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1026 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1030 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<String
> recipients
)
1031 throws IOException
{
1032 Set
<SignalServiceAddress
> recipientsTS
= Utils
.getSignalServiceAddresses(recipients
, username
);
1033 if (recipientsTS
== null) {
1035 return Collections
.emptyList();
1038 if (messagePipe
== null) {
1039 messagePipe
= getMessageReceiver().createMessagePipe();
1041 if (unidentifiedMessagePipe
== null) {
1042 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1044 SignalServiceDataMessage message
= null;
1046 SignalServiceMessageSender messageSender
= getMessageSender();
1048 message
= messageBuilder
.build();
1049 if (message
.getGroupInfo().isPresent()) {
1051 final boolean isRecipientUpdate
= false;
1052 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipientsTS
), getAccessFor(recipientsTS
), isRecipientUpdate
, message
);
1053 for (SendMessageResult r
: result
) {
1054 if (r
.getIdentityFailure() != null) {
1055 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1059 } catch (UntrustedIdentityException e
) {
1060 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1061 return Collections
.emptyList();
1063 } else if (recipientsTS
.size() == 1 && recipientsTS
.contains(getSelfAddress())) {
1064 SignalServiceAddress recipient
= getSelfAddress();
1065 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1066 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1067 message
.getTimestamp(),
1069 message
.getExpiresInSeconds(),
1070 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1072 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1074 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1076 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1077 } catch (UntrustedIdentityException e
) {
1078 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1079 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1083 // Send to all individually, so sync messages are sent correctly
1084 List
<SendMessageResult
> results
= new ArrayList
<>(recipientsTS
.size());
1085 for (SignalServiceAddress address
: recipientsTS
) {
1086 ThreadInfo thread
= account
.getThreadStore().getThread(address
.getNumber().get());
1087 if (thread
!= null) {
1088 messageBuilder
.withExpiration(thread
.messageExpirationTime
);
1090 messageBuilder
.withExpiration(0);
1092 message
= messageBuilder
.build();
1094 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1095 results
.add(result
);
1096 } catch (UntrustedIdentityException e
) {
1097 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1098 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1104 if (message
!= null && message
.isEndSession()) {
1105 for (SignalServiceAddress recipient
: recipientsTS
) {
1106 handleEndSession(recipient
.getNumber().get());
1113 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1114 SignalServiceCipher cipher
= new SignalServiceCipher(getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1116 return cipher
.decrypt(envelope
);
1117 } catch (ProtocolUntrustedIdentityException e
) {
1118 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1119 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1124 private void handleEndSession(String source
) {
1125 account
.getSignalProtocolStore().deleteAllSessions(source
);
1128 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, String source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1130 if (message
.getGroupInfo().isPresent()) {
1131 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1132 threadId
= Base64
.encodeBytes(groupInfo
.getGroupId());
1133 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1134 switch (groupInfo
.getType()) {
1136 if (group
== null) {
1137 group
= new GroupInfo(groupInfo
.getGroupId());
1140 if (groupInfo
.getAvatar().isPresent()) {
1141 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1142 if (avatar
.isPointer()) {
1144 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1145 } catch (IOException
| InvalidMessageException e
) {
1146 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1151 if (groupInfo
.getName().isPresent()) {
1152 group
.name
= groupInfo
.getName().get();
1155 if (groupInfo
.getMembers().isPresent()) {
1156 group
.addMembers(groupInfo
.getMembers().get());
1159 account
.getGroupStore().updateGroup(group
);
1162 if (group
== null) {
1164 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1165 } catch (IOException
| EncapsulatedExceptions e
) {
1166 e
.printStackTrace();
1171 if (group
== null) {
1173 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1174 } catch (IOException
| EncapsulatedExceptions e
) {
1175 e
.printStackTrace();
1178 group
.members
.remove(source
);
1179 account
.getGroupStore().updateGroup(group
);
1183 if (group
!= null) {
1185 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1186 } catch (IOException
| EncapsulatedExceptions e
) {
1187 e
.printStackTrace();
1188 } catch (NotAGroupMemberException e
) {
1189 // We have left this group, so don't send a group update message
1196 threadId
= destination
.getNumber().get();
1201 if (message
.isEndSession()) {
1202 handleEndSession(isSync ? destination
.getNumber().get() : source
);
1204 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1205 ThreadInfo thread
= account
.getThreadStore().getThread(threadId
);
1206 if (thread
== null) {
1207 thread
= new ThreadInfo();
1208 thread
.id
= threadId
;
1210 if (thread
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1211 thread
.messageExpirationTime
= message
.getExpiresInSeconds();
1212 account
.getThreadStore().updateThread(thread
);
1215 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1216 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1217 if (attachment
.isPointer()) {
1219 retrieveAttachment(attachment
.asPointer());
1220 } catch (IOException
| InvalidMessageException e
) {
1221 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1226 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1227 if (source
.equals(username
)) {
1229 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1230 } catch (InvalidInputException ignored
) {
1233 ContactInfo contact
= account
.getContactStore().getContact(source
);
1234 if (contact
== null) {
1235 contact
= new ContactInfo();
1236 contact
.number
= source
;
1238 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1240 if (message
.getPreviews().isPresent()) {
1241 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1242 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1243 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1244 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1246 retrieveAttachment(attachment
);
1247 } catch (IOException
| InvalidMessageException e
) {
1248 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1255 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1256 final File cachePath
= new File(getMessageCachePath());
1257 if (!cachePath
.exists()) {
1260 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1261 if (!dir
.isDirectory()) {
1265 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1266 if (!fileEntry
.isFile()) {
1269 SignalServiceEnvelope envelope
;
1271 envelope
= Utils
.loadEnvelope(fileEntry
);
1272 if (envelope
== null) {
1275 } catch (IOException e
) {
1276 e
.printStackTrace();
1279 SignalServiceContent content
= null;
1280 if (!envelope
.isReceipt()) {
1282 content
= decryptMessage(envelope
);
1283 } catch (Exception e
) {
1286 handleMessage(envelope
, content
, ignoreAttachments
);
1289 handler
.handleMessage(envelope
, content
, null);
1291 Files
.delete(fileEntry
.toPath());
1292 } catch (IOException e
) {
1293 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1296 // Try to delete directory if empty
1301 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1302 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1303 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1306 if (messagePipe
== null) {
1307 messagePipe
= messageReceiver
.createMessagePipe();
1311 SignalServiceEnvelope envelope
;
1312 SignalServiceContent content
= null;
1313 Exception exception
= null;
1314 final long now
= new Date().getTime();
1316 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1317 // store message on disk, before acknowledging receipt to the server
1319 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1320 Utils
.storeEnvelope(envelope1
, cacheFile
);
1321 } catch (IOException e
) {
1322 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1325 } catch (TimeoutException e
) {
1326 if (returnOnTimeout
)
1329 } catch (InvalidVersionException e
) {
1330 System
.err
.println("Ignoring error: " + e
.getMessage());
1333 if (!envelope
.isReceipt()) {
1335 content
= decryptMessage(envelope
);
1336 } catch (Exception e
) {
1339 handleMessage(envelope
, content
, ignoreAttachments
);
1342 if (!isMessageBlocked(envelope
, content
)) {
1343 handler
.handleMessage(envelope
, content
, exception
);
1345 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1346 File cacheFile
= null;
1348 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1349 Files
.delete(cacheFile
.toPath());
1350 // Try to delete directory if empty
1351 new File(getMessageCachePath()).delete();
1352 } catch (IOException e
) {
1353 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1358 if (messagePipe
!= null) {
1359 messagePipe
.shutdown();
1365 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1366 SignalServiceAddress source
;
1367 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1368 source
= envelope
.getSourceAddress();
1369 } else if (content
!= null) {
1370 source
= content
.getSender();
1374 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1375 if (sourceContact
!= null && sourceContact
.blocked
) {
1379 if (content
!= null && content
.getDataMessage().isPresent()) {
1380 SignalServiceDataMessage message
= content
.getDataMessage().get();
1381 if (message
.getGroupInfo().isPresent()) {
1382 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1383 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1384 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1392 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1393 if (content
!= null) {
1394 SignalServiceAddress sender
;
1395 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1396 sender
= envelope
.getSourceAddress();
1398 sender
= content
.getSender();
1400 if (content
.getDataMessage().isPresent()) {
1401 SignalServiceDataMessage message
= content
.getDataMessage().get();
1402 handleSignalServiceDataMessage(message
, false, sender
.getNumber().get(), getSelfAddress(), ignoreAttachments
);
1404 if (content
.getSyncMessage().isPresent()) {
1405 account
.setMultiDevice(true);
1406 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1407 if (syncMessage
.getSent().isPresent()) {
1408 SentTranscriptMessage message
= syncMessage
.getSent().get();
1409 handleSignalServiceDataMessage(message
.getMessage(), true, sender
.getNumber().get(), message
.getDestination().orNull(), ignoreAttachments
);
1411 if (syncMessage
.getRequest().isPresent()) {
1412 RequestMessage rm
= syncMessage
.getRequest().get();
1413 if (rm
.isContactsRequest()) {
1416 } catch (UntrustedIdentityException
| IOException e
) {
1417 e
.printStackTrace();
1420 if (rm
.isGroupsRequest()) {
1423 } catch (UntrustedIdentityException
| IOException e
) {
1424 e
.printStackTrace();
1427 if (rm
.isBlockedListRequest()) {
1430 } catch (UntrustedIdentityException
| IOException e
) {
1431 e
.printStackTrace();
1434 // TODO Handle rm.isConfigurationRequest();
1436 if (syncMessage
.getGroups().isPresent()) {
1437 File tmpFile
= null;
1439 tmpFile
= IOUtils
.createTempFile();
1440 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1441 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1443 while ((g
= s
.read()) != null) {
1444 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1445 if (syncGroup
== null) {
1446 syncGroup
= new GroupInfo(g
.getId());
1448 if (g
.getName().isPresent()) {
1449 syncGroup
.name
= g
.getName().get();
1451 syncGroup
.addMembers(g
.getMembers());
1452 syncGroup
.active
= g
.isActive();
1453 syncGroup
.blocked
= g
.isBlocked();
1454 if (g
.getColor().isPresent()) {
1455 syncGroup
.color
= g
.getColor().get();
1458 if (g
.getAvatar().isPresent()) {
1459 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1461 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1462 syncGroup
.archived
= g
.isArchived();
1463 account
.getGroupStore().updateGroup(syncGroup
);
1466 } catch (Exception e
) {
1467 e
.printStackTrace();
1469 if (tmpFile
!= null) {
1471 Files
.delete(tmpFile
.toPath());
1472 } catch (IOException e
) {
1473 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1478 if (syncMessage
.getBlockedList().isPresent()) {
1479 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1480 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1481 if (address
.getNumber().isPresent()) {
1483 setContactBlocked(address
.getNumber().get(), true);
1484 } catch (InvalidNumberException e
) {
1485 e
.printStackTrace();
1489 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1491 setGroupBlocked(groupId
, true);
1492 } catch (GroupNotFoundException e
) {
1493 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1497 if (syncMessage
.getContacts().isPresent()) {
1498 File tmpFile
= null;
1500 tmpFile
= IOUtils
.createTempFile();
1501 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1502 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1503 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1504 if (contactsMessage
.isComplete()) {
1505 account
.getContactStore().clear();
1508 while ((c
= s
.read()) != null) {
1509 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1510 account
.setProfileKey(c
.getProfileKey().get());
1512 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress().getNumber().get());
1513 if (contact
== null) {
1514 contact
= new ContactInfo();
1515 contact
.number
= c
.getAddress().getNumber().get();
1517 if (c
.getName().isPresent()) {
1518 contact
.name
= c
.getName().get();
1520 if (c
.getColor().isPresent()) {
1521 contact
.color
= c
.getColor().get();
1523 if (c
.getProfileKey().isPresent()) {
1524 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1526 if (c
.getVerified().isPresent()) {
1527 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1528 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1530 if (c
.getExpirationTimer().isPresent()) {
1531 ThreadInfo thread
= account
.getThreadStore().getThread(c
.getAddress().getNumber().get());
1532 if (thread
== null) {
1533 thread
= new ThreadInfo();
1534 thread
.id
= c
.getAddress().getNumber().get();
1536 thread
.messageExpirationTime
= c
.getExpirationTimer().get();
1537 account
.getThreadStore().updateThread(thread
);
1539 contact
.blocked
= c
.isBlocked();
1540 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1541 contact
.archived
= c
.isArchived();
1542 account
.getContactStore().updateContact(contact
);
1544 if (c
.getAvatar().isPresent()) {
1545 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1549 } catch (Exception e
) {
1550 e
.printStackTrace();
1552 if (tmpFile
!= null) {
1554 Files
.delete(tmpFile
.toPath());
1555 } catch (IOException e
) {
1556 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1561 if (syncMessage
.getVerified().isPresent()) {
1562 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1563 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1565 if (syncMessage
.getConfiguration().isPresent()) {
1572 private File
getContactAvatarFile(String number
) {
1573 return new File(avatarsPath
, "contact-" + number
);
1576 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1577 IOUtils
.createPrivateDirectories(avatarsPath
);
1578 if (attachment
.isPointer()) {
1579 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1580 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1582 SignalServiceAttachmentStream stream
= attachment
.asStream();
1583 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1587 private File
getGroupAvatarFile(byte[] groupId
) {
1588 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1591 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1592 IOUtils
.createPrivateDirectories(avatarsPath
);
1593 if (attachment
.isPointer()) {
1594 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1595 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1597 SignalServiceAttachmentStream stream
= attachment
.asStream();
1598 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1602 public File
getAttachmentFile(long attachmentId
) {
1603 return new File(attachmentsPath
, attachmentId
+ "");
1606 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1607 IOUtils
.createPrivateDirectories(attachmentsPath
);
1608 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1611 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1612 if (storePreview
&& pointer
.getPreview().isPresent()) {
1613 File previewFile
= new File(outputFile
+ ".preview");
1614 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1615 byte[] preview
= pointer
.getPreview().get();
1616 output
.write(preview
, 0, preview
.length
);
1617 } catch (FileNotFoundException e
) {
1618 e
.printStackTrace();
1623 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1625 File tmpFile
= IOUtils
.createTempFile();
1626 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1627 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1628 byte[] buffer
= new byte[4096];
1631 while ((read
= input
.read(buffer
)) != -1) {
1632 output
.write(buffer
, 0, read
);
1634 } catch (FileNotFoundException e
) {
1635 e
.printStackTrace();
1640 Files
.delete(tmpFile
.toPath());
1641 } catch (IOException e
) {
1642 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1648 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1649 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1650 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1654 public boolean isRemote() {
1658 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1659 File groupsFile
= IOUtils
.createTempFile();
1662 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1663 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1664 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1665 ThreadInfo info
= account
.getThreadStore().getThread(Base64
.encodeBytes(record.groupId
));
1666 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1667 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1668 record.active
, Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1669 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1673 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1674 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1675 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1676 .withStream(groupsFileStream
)
1677 .withContentType("application/octet-stream")
1678 .withLength(groupsFile
.length())
1681 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1686 Files
.delete(groupsFile
.toPath());
1687 } catch (IOException e
) {
1688 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1693 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1694 File contactsFile
= IOUtils
.createTempFile();
1697 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1698 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1699 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1700 VerifiedMessage verifiedMessage
= null;
1701 ThreadInfo info
= account
.getThreadStore().getThread(record.number
);
1702 if (getIdentities().containsKey(record.number
)) {
1703 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1704 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1705 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1706 currentIdentity
= id
;
1709 if (currentIdentity
!= null) {
1710 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1714 ProfileKey profileKey
= null;
1716 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1717 } catch (InvalidInputException ignored
) {
1719 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1720 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1721 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1722 Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1723 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1726 if (account
.getProfileKey() != null) {
1727 // Send our own profile key as well
1728 out
.write(new DeviceContact(account
.getSelfAddress(),
1729 Optional
.absent(), Optional
.absent(),
1730 Optional
.absent(), Optional
.absent(),
1731 Optional
.of(account
.getProfileKey()),
1732 false, Optional
.absent(), Optional
.absent(), false));
1736 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1737 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1738 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1739 .withStream(contactsFileStream
)
1740 .withContentType("application/octet-stream")
1741 .withLength(contactsFile
.length())
1744 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1749 Files
.delete(contactsFile
.toPath());
1750 } catch (IOException e
) {
1751 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1756 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1757 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1758 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1759 if (record.blocked
) {
1760 addresses
.add(record.getAddress());
1763 List
<byte[]> groupIds
= new ArrayList
<>();
1764 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1765 if (record.blocked
) {
1766 groupIds
.add(record.groupId
);
1769 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1772 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1773 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1774 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1777 public List
<ContactInfo
> getContacts() {
1778 return account
.getContactStore().getContacts();
1781 public ContactInfo
getContact(String number
) {
1782 return account
.getContactStore().getContact(number
);
1785 public GroupInfo
getGroup(byte[] groupId
) {
1786 return account
.getGroupStore().getGroup(groupId
);
1789 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1790 return account
.getSignalProtocolStore().getIdentities();
1793 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1794 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1795 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1799 * Trust this the identity with this fingerprint
1801 * @param name username of the identity
1802 * @param fingerprint Fingerprint
1804 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1805 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1809 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1810 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1814 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1816 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1817 } catch (IOException
| UntrustedIdentityException e
) {
1818 e
.printStackTrace();
1827 * Trust this the identity with this safety number
1829 * @param name username of the identity
1830 * @param safetyNumber Safety number
1832 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1833 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1837 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1838 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1842 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1844 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1845 } catch (IOException
| UntrustedIdentityException e
) {
1846 e
.printStackTrace();
1855 * Trust all keys of this identity without verification
1857 * @param name username of the identity
1859 public boolean trustIdentityAllKeys(String name
) {
1860 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1864 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1865 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1866 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1868 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1869 } catch (IOException
| UntrustedIdentityException e
) {
1870 e
.printStackTrace();
1878 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1879 return Utils
.computeSafetyNumber(username
, getIdentity(), theirUsername
, theirIdentityKey
);
1882 public interface ReceiveMessageHandler
{
1884 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);