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());
1239 account
.getContactStore().updateContact(contact
);
1241 if (message
.getPreviews().isPresent()) {
1242 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1243 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1244 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1245 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1247 retrieveAttachment(attachment
);
1248 } catch (IOException
| InvalidMessageException e
) {
1249 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1256 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1257 final File cachePath
= new File(getMessageCachePath());
1258 if (!cachePath
.exists()) {
1261 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1262 if (!dir
.isDirectory()) {
1266 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1267 if (!fileEntry
.isFile()) {
1270 SignalServiceEnvelope envelope
;
1272 envelope
= Utils
.loadEnvelope(fileEntry
);
1273 if (envelope
== null) {
1276 } catch (IOException e
) {
1277 e
.printStackTrace();
1280 SignalServiceContent content
= null;
1281 if (!envelope
.isReceipt()) {
1283 content
= decryptMessage(envelope
);
1284 } catch (Exception e
) {
1287 handleMessage(envelope
, content
, ignoreAttachments
);
1290 handler
.handleMessage(envelope
, content
, null);
1292 Files
.delete(fileEntry
.toPath());
1293 } catch (IOException e
) {
1294 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1297 // Try to delete directory if empty
1302 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1303 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1304 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1307 if (messagePipe
== null) {
1308 messagePipe
= messageReceiver
.createMessagePipe();
1312 SignalServiceEnvelope envelope
;
1313 SignalServiceContent content
= null;
1314 Exception exception
= null;
1315 final long now
= new Date().getTime();
1317 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1318 // store message on disk, before acknowledging receipt to the server
1320 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1321 Utils
.storeEnvelope(envelope1
, cacheFile
);
1322 } catch (IOException e
) {
1323 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1326 } catch (TimeoutException e
) {
1327 if (returnOnTimeout
)
1330 } catch (InvalidVersionException e
) {
1331 System
.err
.println("Ignoring error: " + e
.getMessage());
1334 if (!envelope
.isReceipt()) {
1336 content
= decryptMessage(envelope
);
1337 } catch (Exception e
) {
1340 handleMessage(envelope
, content
, ignoreAttachments
);
1343 if (!isMessageBlocked(envelope
, content
)) {
1344 handler
.handleMessage(envelope
, content
, exception
);
1346 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1347 File cacheFile
= null;
1349 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1350 Files
.delete(cacheFile
.toPath());
1351 // Try to delete directory if empty
1352 new File(getMessageCachePath()).delete();
1353 } catch (IOException e
) {
1354 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1359 if (messagePipe
!= null) {
1360 messagePipe
.shutdown();
1366 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1367 SignalServiceAddress source
;
1368 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1369 source
= envelope
.getSourceAddress();
1370 } else if (content
!= null) {
1371 source
= content
.getSender();
1375 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1376 if (sourceContact
!= null && sourceContact
.blocked
) {
1380 if (content
!= null && content
.getDataMessage().isPresent()) {
1381 SignalServiceDataMessage message
= content
.getDataMessage().get();
1382 if (message
.getGroupInfo().isPresent()) {
1383 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1384 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1385 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1393 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1394 if (content
!= null) {
1395 SignalServiceAddress sender
;
1396 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1397 sender
= envelope
.getSourceAddress();
1399 sender
= content
.getSender();
1401 if (content
.getDataMessage().isPresent()) {
1402 SignalServiceDataMessage message
= content
.getDataMessage().get();
1403 handleSignalServiceDataMessage(message
, false, sender
.getNumber().get(), getSelfAddress(), ignoreAttachments
);
1405 if (content
.getSyncMessage().isPresent()) {
1406 account
.setMultiDevice(true);
1407 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1408 if (syncMessage
.getSent().isPresent()) {
1409 SentTranscriptMessage message
= syncMessage
.getSent().get();
1410 handleSignalServiceDataMessage(message
.getMessage(), true, sender
.getNumber().get(), message
.getDestination().orNull(), ignoreAttachments
);
1412 if (syncMessage
.getRequest().isPresent()) {
1413 RequestMessage rm
= syncMessage
.getRequest().get();
1414 if (rm
.isContactsRequest()) {
1417 } catch (UntrustedIdentityException
| IOException e
) {
1418 e
.printStackTrace();
1421 if (rm
.isGroupsRequest()) {
1424 } catch (UntrustedIdentityException
| IOException e
) {
1425 e
.printStackTrace();
1428 if (rm
.isBlockedListRequest()) {
1431 } catch (UntrustedIdentityException
| IOException e
) {
1432 e
.printStackTrace();
1435 // TODO Handle rm.isConfigurationRequest();
1437 if (syncMessage
.getGroups().isPresent()) {
1438 File tmpFile
= null;
1440 tmpFile
= IOUtils
.createTempFile();
1441 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1442 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1444 while ((g
= s
.read()) != null) {
1445 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1446 if (syncGroup
== null) {
1447 syncGroup
= new GroupInfo(g
.getId());
1449 if (g
.getName().isPresent()) {
1450 syncGroup
.name
= g
.getName().get();
1452 syncGroup
.addMembers(g
.getMembers());
1453 syncGroup
.active
= g
.isActive();
1454 syncGroup
.blocked
= g
.isBlocked();
1455 if (g
.getColor().isPresent()) {
1456 syncGroup
.color
= g
.getColor().get();
1459 if (g
.getAvatar().isPresent()) {
1460 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1462 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1463 syncGroup
.archived
= g
.isArchived();
1464 account
.getGroupStore().updateGroup(syncGroup
);
1467 } catch (Exception e
) {
1468 e
.printStackTrace();
1470 if (tmpFile
!= null) {
1472 Files
.delete(tmpFile
.toPath());
1473 } catch (IOException e
) {
1474 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1479 if (syncMessage
.getBlockedList().isPresent()) {
1480 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1481 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1482 if (address
.getNumber().isPresent()) {
1484 setContactBlocked(address
.getNumber().get(), true);
1485 } catch (InvalidNumberException e
) {
1486 e
.printStackTrace();
1490 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1492 setGroupBlocked(groupId
, true);
1493 } catch (GroupNotFoundException e
) {
1494 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1498 if (syncMessage
.getContacts().isPresent()) {
1499 File tmpFile
= null;
1501 tmpFile
= IOUtils
.createTempFile();
1502 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1503 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1504 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1505 if (contactsMessage
.isComplete()) {
1506 account
.getContactStore().clear();
1509 while ((c
= s
.read()) != null) {
1510 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1511 account
.setProfileKey(c
.getProfileKey().get());
1513 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress().getNumber().get());
1514 if (contact
== null) {
1515 contact
= new ContactInfo();
1516 contact
.number
= c
.getAddress().getNumber().get();
1518 if (c
.getName().isPresent()) {
1519 contact
.name
= c
.getName().get();
1521 if (c
.getColor().isPresent()) {
1522 contact
.color
= c
.getColor().get();
1524 if (c
.getProfileKey().isPresent()) {
1525 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1527 if (c
.getVerified().isPresent()) {
1528 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1529 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1531 if (c
.getExpirationTimer().isPresent()) {
1532 ThreadInfo thread
= account
.getThreadStore().getThread(c
.getAddress().getNumber().get());
1533 if (thread
== null) {
1534 thread
= new ThreadInfo();
1535 thread
.id
= c
.getAddress().getNumber().get();
1537 thread
.messageExpirationTime
= c
.getExpirationTimer().get();
1538 account
.getThreadStore().updateThread(thread
);
1540 contact
.blocked
= c
.isBlocked();
1541 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1542 contact
.archived
= c
.isArchived();
1543 account
.getContactStore().updateContact(contact
);
1545 if (c
.getAvatar().isPresent()) {
1546 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1550 } catch (Exception e
) {
1551 e
.printStackTrace();
1553 if (tmpFile
!= null) {
1555 Files
.delete(tmpFile
.toPath());
1556 } catch (IOException e
) {
1557 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1562 if (syncMessage
.getVerified().isPresent()) {
1563 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1564 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1566 if (syncMessage
.getConfiguration().isPresent()) {
1573 private File
getContactAvatarFile(String number
) {
1574 return new File(avatarsPath
, "contact-" + number
);
1577 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1578 IOUtils
.createPrivateDirectories(avatarsPath
);
1579 if (attachment
.isPointer()) {
1580 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1581 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1583 SignalServiceAttachmentStream stream
= attachment
.asStream();
1584 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1588 private File
getGroupAvatarFile(byte[] groupId
) {
1589 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1592 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1593 IOUtils
.createPrivateDirectories(avatarsPath
);
1594 if (attachment
.isPointer()) {
1595 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1596 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1598 SignalServiceAttachmentStream stream
= attachment
.asStream();
1599 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1603 public File
getAttachmentFile(long attachmentId
) {
1604 return new File(attachmentsPath
, attachmentId
+ "");
1607 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1608 IOUtils
.createPrivateDirectories(attachmentsPath
);
1609 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1612 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1613 if (storePreview
&& pointer
.getPreview().isPresent()) {
1614 File previewFile
= new File(outputFile
+ ".preview");
1615 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1616 byte[] preview
= pointer
.getPreview().get();
1617 output
.write(preview
, 0, preview
.length
);
1618 } catch (FileNotFoundException e
) {
1619 e
.printStackTrace();
1624 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1626 File tmpFile
= IOUtils
.createTempFile();
1627 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1628 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1629 byte[] buffer
= new byte[4096];
1632 while ((read
= input
.read(buffer
)) != -1) {
1633 output
.write(buffer
, 0, read
);
1635 } catch (FileNotFoundException e
) {
1636 e
.printStackTrace();
1641 Files
.delete(tmpFile
.toPath());
1642 } catch (IOException e
) {
1643 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1649 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1650 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1651 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1655 public boolean isRemote() {
1659 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1660 File groupsFile
= IOUtils
.createTempFile();
1663 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1664 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1665 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1666 ThreadInfo info
= account
.getThreadStore().getThread(Base64
.encodeBytes(record.groupId
));
1667 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1668 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1669 record.active
, Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1670 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1674 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1675 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1676 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1677 .withStream(groupsFileStream
)
1678 .withContentType("application/octet-stream")
1679 .withLength(groupsFile
.length())
1682 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1687 Files
.delete(groupsFile
.toPath());
1688 } catch (IOException e
) {
1689 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1694 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1695 File contactsFile
= IOUtils
.createTempFile();
1698 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1699 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1700 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1701 VerifiedMessage verifiedMessage
= null;
1702 ThreadInfo info
= account
.getThreadStore().getThread(record.number
);
1703 if (getIdentities().containsKey(record.number
)) {
1704 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1705 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1706 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1707 currentIdentity
= id
;
1710 if (currentIdentity
!= null) {
1711 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1715 ProfileKey profileKey
= null;
1717 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1718 } catch (InvalidInputException ignored
) {
1720 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1721 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1722 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1723 Optional
.fromNullable(info
!= null ? info
.messageExpirationTime
: null),
1724 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1727 if (account
.getProfileKey() != null) {
1728 // Send our own profile key as well
1729 out
.write(new DeviceContact(account
.getSelfAddress(),
1730 Optional
.absent(), Optional
.absent(),
1731 Optional
.absent(), Optional
.absent(),
1732 Optional
.of(account
.getProfileKey()),
1733 false, Optional
.absent(), Optional
.absent(), false));
1737 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1738 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1739 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1740 .withStream(contactsFileStream
)
1741 .withContentType("application/octet-stream")
1742 .withLength(contactsFile
.length())
1745 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1750 Files
.delete(contactsFile
.toPath());
1751 } catch (IOException e
) {
1752 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1757 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1758 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1759 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1760 if (record.blocked
) {
1761 addresses
.add(record.getAddress());
1764 List
<byte[]> groupIds
= new ArrayList
<>();
1765 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1766 if (record.blocked
) {
1767 groupIds
.add(record.groupId
);
1770 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1773 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1774 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1775 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1778 public List
<ContactInfo
> getContacts() {
1779 return account
.getContactStore().getContacts();
1782 public ContactInfo
getContact(String number
) {
1783 return account
.getContactStore().getContact(number
);
1786 public GroupInfo
getGroup(byte[] groupId
) {
1787 return account
.getGroupStore().getGroup(groupId
);
1790 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1791 return account
.getSignalProtocolStore().getIdentities();
1794 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1795 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1796 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1800 * Trust this the identity with this fingerprint
1802 * @param name username of the identity
1803 * @param fingerprint Fingerprint
1805 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1806 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1810 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1811 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1815 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1817 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1818 } catch (IOException
| UntrustedIdentityException e
) {
1819 e
.printStackTrace();
1828 * Trust this the identity with this safety number
1830 * @param name username of the identity
1831 * @param safetyNumber Safety number
1833 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1834 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1838 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1839 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1843 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1845 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1846 } catch (IOException
| UntrustedIdentityException e
) {
1847 e
.printStackTrace();
1856 * Trust all keys of this identity without verification
1858 * @param name username of the identity
1860 public boolean trustIdentityAllKeys(String name
) {
1861 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1865 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1866 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1867 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1869 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1870 } catch (IOException
| UntrustedIdentityException e
) {
1871 e
.printStackTrace();
1879 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1880 return Utils
.computeSafetyNumber(username
, getIdentity(), theirUsername
, theirIdentityKey
);
1883 public interface ReceiveMessageHandler
{
1885 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);