2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.Signal
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.JsonStickerPack
;
25 import org
.asamk
.signal
.NotAGroupMemberException
;
26 import org
.asamk
.signal
.StickerPackInvalidException
;
27 import org
.asamk
.signal
.TrustLevel
;
28 import org
.asamk
.signal
.UserAlreadyExists
;
29 import org
.asamk
.signal
.storage
.SignalAccount
;
30 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
31 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
32 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
33 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
34 import org
.asamk
.signal
.util
.IOUtils
;
35 import org
.asamk
.signal
.util
.Util
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
46 import org
.signal
.libsignal
.metadata
.SelfSendException
;
47 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
105 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
106 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
108 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
112 import org
.whispersystems
.util
.Base64
;
115 import java
.io
.FileInputStream
;
116 import java
.io
.FileNotFoundException
;
117 import java
.io
.FileOutputStream
;
118 import java
.io
.IOException
;
119 import java
.io
.InputStream
;
120 import java
.io
.OutputStream
;
122 import java
.net
.URISyntaxException
;
123 import java
.net
.URLEncoder
;
124 import java
.nio
.file
.Files
;
125 import java
.nio
.file
.Paths
;
126 import java
.nio
.file
.StandardCopyOption
;
127 import java
.util
.ArrayList
;
128 import java
.util
.Arrays
;
129 import java
.util
.Collection
;
130 import java
.util
.Collections
;
131 import java
.util
.Date
;
132 import java
.util
.HashSet
;
133 import java
.util
.LinkedList
;
134 import java
.util
.List
;
135 import java
.util
.Locale
;
136 import java
.util
.Map
;
137 import java
.util
.Objects
;
138 import java
.util
.Set
;
139 import java
.util
.UUID
;
140 import java
.util
.concurrent
.TimeUnit
;
141 import java
.util
.concurrent
.TimeoutException
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
{
147 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
149 private final String settingsPath
;
150 private final String dataPath
;
151 private final String attachmentsPath
;
152 private final String avatarsPath
;
153 private final SleepTimer timer
= new UptimeSleepTimer();
155 private SignalAccount account
;
156 private String username
;
157 private SignalServiceAccountManager accountManager
;
158 private SignalServiceMessagePipe messagePipe
= null;
159 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
161 public Manager(String username
, String settingsPath
) {
162 this.username
= username
;
163 this.settingsPath
= settingsPath
;
164 this.dataPath
= this.settingsPath
+ "/data";
165 this.attachmentsPath
= this.settingsPath
+ "/attachments";
166 this.avatarsPath
= this.settingsPath
+ "/avatars";
170 public String
getUsername() {
174 public SignalServiceAddress
getSelfAddress() {
175 return account
.getSelfAddress();
178 private SignalServiceAccountManager
getSignalServiceAccountManager() {
179 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
182 private IdentityKey
getIdentity() {
183 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
186 public int getDeviceId() {
187 return account
.getDeviceId();
190 private String
getMessageCachePath() {
191 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
194 private String
getMessageCachePath(String sender
) {
195 return getMessageCachePath() + "/" + sender
.replace("/", "_");
198 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
199 String cachePath
= getMessageCachePath(sender
);
200 IOUtils
.createPrivateDirectories(cachePath
);
201 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
204 public boolean userHasKeys() {
205 return account
!= null && account
.getSignalProtocolStore() != null;
208 public void init() throws IOException
{
209 if (!SignalAccount
.userExists(dataPath
, username
)) {
212 account
= SignalAccount
.load(dataPath
, username
);
214 migrateLegacyConfigs();
216 accountManager
= getSignalServiceAccountManager();
217 if (account
.isRegistered()) {
218 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
222 if (account
.getUuid() == null) {
223 account
.setUuid(accountManager
.getOwnUuid());
229 private void migrateLegacyConfigs() {
230 // Copy group avatars that were previously stored in the attachments folder
231 // to the new avatar folder
232 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
233 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
234 File avatarFile
= getGroupAvatarFile(g
.groupId
);
235 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
236 if (!avatarFile
.exists() && attachmentFile
.exists()) {
238 IOUtils
.createPrivateDirectories(avatarsPath
);
239 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
240 } catch (Exception e
) {
245 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
248 if (account
.getProfileKey() == null) {
249 // Old config file, creating new profile key
250 account
.setProfileKey(KeyUtils
.createProfileKey());
255 private void createNewIdentity() throws IOException
{
256 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
257 int registrationId
= KeyHelper
.generateRegistrationId(false);
258 if (username
== null) {
259 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
261 account
.getSignalProtocolStore().saveIdentity(username
, identityKey
.getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
263 ProfileKey profileKey
= KeyUtils
.createProfileKey();
264 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
269 public boolean isRegistered() {
270 return account
!= null && account
.isRegistered();
273 public void register(boolean voiceVerification
) throws IOException
{
274 if (account
== null) {
277 account
.setPassword(KeyUtils
.createPassword());
278 account
.setUuid(null);
279 accountManager
= getSignalServiceAccountManager();
281 if (voiceVerification
) {
282 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
284 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
287 account
.setRegistered(false);
291 public void updateAccountAttributes() throws IOException
{
292 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
295 public void setProfileName(String name
) throws IOException
{
296 accountManager
.setProfileName(account
.getProfileKey(), name
);
299 public void setProfileAvatar(File avatar
) throws IOException
{
300 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
301 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
302 streamDetails
.getStream().close();
305 public void removeProfileAvatar() throws IOException
{
306 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
309 public void unregister() throws IOException
{
310 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
311 // If this is the master device, other users can't send messages to this number anymore.
312 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
313 accountManager
.setGcmId(Optional
.absent());
315 account
.setRegistered(false);
319 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
320 if (account
== null) {
323 account
.setPassword(KeyUtils
.createPassword());
324 accountManager
= getSignalServiceAccountManager();
325 String uuid
= accountManager
.getNewDeviceUuid();
327 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
330 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
331 account
.setSignalingKey(KeyUtils
.createSignalingKey());
332 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
334 username
= ret
.getNumber();
335 // TODO do this check before actually registering
336 if (SignalAccount
.userExists(dataPath
, username
)) {
337 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
340 // Create new account with the synced identity
341 byte[] profileKeyBytes
= ret
.getProfileKey();
342 ProfileKey profileKey
;
343 if (profileKeyBytes
== null) {
344 profileKey
= KeyUtils
.createProfileKey();
347 profileKey
= new ProfileKey(profileKeyBytes
);
348 } catch (InvalidInputException e
) {
349 throw new IOException("Received invalid profileKey", e
);
352 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
357 requestSyncContacts();
358 requestSyncBlocked();
359 requestSyncConfiguration();
364 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
365 List
<DeviceInfo
> devices
= accountManager
.getDevices();
366 account
.setMultiDevice(devices
.size() > 1);
371 public void removeLinkedDevices(int deviceId
) throws IOException
{
372 accountManager
.removeDevice(deviceId
);
373 List
<DeviceInfo
> devices
= accountManager
.getDevices();
374 account
.setMultiDevice(devices
.size() > 1);
378 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
379 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
381 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
384 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
385 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
386 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
388 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
389 account
.setMultiDevice(true);
393 private List
<PreKeyRecord
> generatePreKeys() {
394 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
396 final int offset
= account
.getPreKeyIdOffset();
397 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
398 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
399 ECKeyPair keyPair
= Curve
.generateKeyPair();
400 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
405 account
.addPreKeys(records
);
411 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
413 ECKeyPair keyPair
= Curve
.generateKeyPair();
414 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
415 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
417 account
.addSignedPreKey(record);
421 } catch (InvalidKeyException e
) {
422 throw new AssertionError(e
);
426 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
427 verificationCode
= verificationCode
.replace("-", "");
428 account
.setSignalingKey(KeyUtils
.createSignalingKey());
429 // TODO make unrestricted unidentified access configurable
430 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
432 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
433 account
.setRegistered(true);
434 account
.setUuid(uuid
);
435 account
.setRegistrationLockPin(pin
);
441 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
442 if (pin
.isPresent()) {
443 account
.setRegistrationLockPin(pin
.get());
444 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
446 account
.setRegistrationLockPin(null);
447 accountManager
.removeV1Pin();
452 private void refreshPreKeys() throws IOException
{
453 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
454 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
455 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
457 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
460 private SignalServiceMessageReceiver
getMessageReceiver() {
461 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
464 private SignalServiceMessageSender
getMessageSender() {
465 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
466 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
469 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
470 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
475 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
476 } catch (IOException ignored
) {
480 SignalServiceMessageReceiver receiver
= getMessageReceiver();
482 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
483 } catch (VerificationFailedException e
) {
484 throw new AssertionError(e
);
488 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
489 File file
= getGroupAvatarFile(groupId
);
490 if (!file
.exists()) {
491 return Optional
.absent();
494 return Optional
.of(Utils
.createAttachment(file
));
497 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
498 File file
= getContactAvatarFile(number
);
499 if (!file
.exists()) {
500 return Optional
.absent();
503 return Optional
.of(Utils
.createAttachment(file
));
506 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
507 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
509 throw new GroupNotFoundException(groupId
);
511 if (!g
.isMember(account
.getSelfAddress())) {
512 throw new NotAGroupMemberException(groupId
, g
.name
);
517 public List
<GroupInfo
> getGroups() {
518 return account
.getGroupStore().getGroups();
522 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
524 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
525 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
526 if (attachments
!= null) {
527 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
529 if (groupId
!= null) {
530 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
533 messageBuilder
.asGroupMessage(group
);
536 final GroupInfo g
= getGroupForSending(groupId
);
538 messageBuilder
.withExpiration(g
.messageExpirationTime
);
540 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
543 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
544 long targetSentTimestamp
, byte[] groupId
)
545 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
546 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
547 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
548 .withReaction(reaction
);
549 if (groupId
!= null) {
550 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
553 messageBuilder
.asGroupMessage(group
);
555 final GroupInfo g
= getGroupForSending(groupId
);
556 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
559 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
560 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
564 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
565 .asGroupMessage(group
);
567 final GroupInfo g
= getGroupForSending(groupId
);
568 g
.removeMember(account
.getSelfAddress());
569 account
.getGroupStore().updateGroup(g
);
571 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
574 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
576 if (groupId
== null) {
578 g
= new GroupInfo(KeyUtils
.createGroupId());
579 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
581 g
= getGroupForSending(groupId
);
588 if (members
!= null) {
589 final Set
<String
> newE164Members
= new HashSet
<>();
590 for (SignalServiceAddress member
: members
) {
591 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
594 newE164Members
.add(member
.getNumber().get());
597 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
598 if (contacts
.size() != newE164Members
.size()) {
599 // Some of the new members are not registered on Signal
600 for (ContactTokenDetails contact
: contacts
) {
601 newE164Members
.remove(contact
.getNumber());
603 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
604 System
.err
.println("Aborting…");
608 g
.addMembers(members
);
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 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
625 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
626 if (groupId
== null) {
629 GroupInfo g
= getGroupForSending(groupId
);
631 if (!g
.isMember(recipient
)) {
635 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
637 // Send group message only to the recipient who requested it
638 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
641 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
642 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
645 .withMembers(new ArrayList
<>(g
.getMembers()));
647 File aFile
= getGroupAvatarFile(g
.groupId
);
648 if (aFile
.exists()) {
650 group
.withAvatar(Utils
.createAttachment(aFile
));
651 } catch (IOException e
) {
652 throw new AttachmentInvalidException(aFile
.toString(), e
);
656 return SignalServiceDataMessage
.newBuilder()
657 .asGroupMessage(group
.build())
658 .withExpiration(g
.messageExpirationTime
);
661 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
662 if (groupId
== null) {
666 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
669 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
670 .asGroupMessage(group
.build());
672 // Send group info request message to the recipient who sent us a message with this groupId
673 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
676 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
677 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
678 Collections
.singletonList(messageId
),
679 System
.currentTimeMillis());
681 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
685 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
686 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
687 List
<String
> recipients
= new ArrayList
<>(1);
688 recipients
.add(recipient
);
689 sendMessage(message
, attachments
, recipients
);
693 public void sendMessage(String messageText
, List
<String
> attachments
,
694 List
<String
> recipients
)
695 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
696 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
697 if (attachments
!= null) {
698 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
700 // Upload attachments here, so we only upload once even for multiple recipients
701 SignalServiceMessageSender messageSender
= getMessageSender();
702 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
703 for (SignalServiceAttachment attachment
: attachmentStreams
) {
704 if (attachment
.isStream()) {
705 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
706 } else if (attachment
.isPointer()) {
707 attachmentPointers
.add(attachment
.asPointer());
711 messageBuilder
.withAttachments(attachmentPointers
);
713 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
716 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
717 long targetSentTimestamp
, List
<String
> recipients
)
718 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
719 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
720 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
721 .withReaction(reaction
);
722 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
726 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
727 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
728 .asEndSessionMessage();
730 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
734 public String
getContactName(String number
) throws InvalidNumberException
{
735 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
736 ContactInfo contact
= account
.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber
));
737 if (contact
== null) {
745 public void setContactName(String number
, String name
) throws InvalidNumberException
{
746 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
747 final SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
748 ContactInfo contact
= account
.getContactStore().getContact(address
);
749 if (contact
== null) {
750 contact
= new ContactInfo(address
);
751 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
753 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
756 account
.getContactStore().updateContact(contact
);
761 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
762 number
= Utils
.canonicalizeNumber(number
, account
.getUsername());
763 final SignalServiceAddress address
= new SignalServiceAddress(null, number
);
764 ContactInfo contact
= account
.getContactStore().getContact(address
);
765 if (contact
== null) {
766 contact
= new ContactInfo(address
);
767 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
769 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
771 contact
.blocked
= blocked
;
772 account
.getContactStore().updateContact(contact
);
777 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
778 GroupInfo group
= getGroup(groupId
);
780 throw new GroupNotFoundException(groupId
);
782 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
783 group
.blocked
= blocked
;
784 account
.getGroupStore().updateGroup(group
);
790 public List
<byte[]> getGroupIds() {
791 List
<GroupInfo
> groups
= getGroups();
792 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
793 for (GroupInfo group
: groups
) {
794 ids
.add(group
.groupId
);
800 public String
getGroupName(byte[] groupId
) {
801 GroupInfo group
= getGroup(groupId
);
810 public List
<String
> getGroupMembers(byte[] groupId
) {
811 GroupInfo group
= getGroup(groupId
);
813 return Collections
.emptyList();
815 return new ArrayList
<>(group
.getMembersE164());
820 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
821 if (groupId
.length
== 0) {
824 if (name
.isEmpty()) {
827 if (members
.size() == 0) {
830 if (avatar
.isEmpty()) {
833 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
837 * Change the expiration timer for a contact
839 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
840 ContactInfo c
= account
.getContactStore().getContact(address
);
841 c
.messageExpirationTime
= messageExpirationTimer
;
842 account
.getContactStore().updateContact(c
);
846 * Change the expiration timer for a group
848 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
849 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
850 g
.messageExpirationTime
= messageExpirationTimer
;
851 account
.getGroupStore().updateGroup(g
);
855 * Upload the sticker pack from path.
857 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
858 * @return if successful, returns the URL to install the sticker pack in the signal app
860 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
861 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
863 SignalServiceMessageSender messageSender
= getMessageSender();
865 byte[] packKey
= KeyUtils
.createStickerUploadKey();
866 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
869 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
871 } catch (URISyntaxException e
) {
872 throw new AssertionError(e
);
876 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
878 String rootPath
= null;
880 final File file
= new File(path
);
881 if (file
.getName().endsWith(".zip")) {
882 zip
= new ZipFile(file
);
883 } else if (file
.getName().equals("manifest.json")) {
884 rootPath
= file
.getParent();
886 throw new StickerPackInvalidException("Could not find manifest.json");
889 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
891 if (pack
.stickers
== null) {
892 throw new StickerPackInvalidException("Must set a 'stickers' field.");
895 if (pack
.stickers
.isEmpty()) {
896 throw new StickerPackInvalidException("Must include stickers.");
899 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
900 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
901 if (sticker
.file
== null) {
902 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
905 Pair
<InputStream
, Long
> data
;
907 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
908 } catch (IOException ignored
) {
909 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
912 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
913 stickers
.add(stickerInfo
);
916 StickerInfo cover
= null;
917 if (pack
.cover
!= null) {
918 if (pack
.cover
.file
== null) {
919 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
922 Pair
<InputStream
, Long
> data
;
924 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
925 } catch (IOException ignored
) {
926 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
929 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
932 return new SignalServiceStickerManifestUpload(
939 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
940 InputStream inputStream
;
942 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
944 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
946 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
949 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
951 final ZipEntry entry
= zip
.getEntry(subfile
);
952 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
954 final File file
= new File(rootPath
, subfile
);
955 return new Pair
<>(new FileInputStream(file
), file
.length());
959 private void requestSyncGroups() throws IOException
{
960 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
961 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
963 sendSyncMessage(message
);
964 } catch (UntrustedIdentityException e
) {
969 private void requestSyncContacts() throws IOException
{
970 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
971 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
973 sendSyncMessage(message
);
974 } catch (UntrustedIdentityException e
) {
979 private void requestSyncBlocked() throws IOException
{
980 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
981 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
983 sendSyncMessage(message
);
984 } catch (UntrustedIdentityException e
) {
989 private void requestSyncConfiguration() throws IOException
{
990 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
991 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
993 sendSyncMessage(message
);
994 } catch (UntrustedIdentityException e
) {
999 private byte[] getSenderCertificate() throws IOException
{
1000 // TODO support UUID capable sender certificates
1001 // byte[] certificate = accountManager.getSenderCertificate();
1002 byte[] certificate
= accountManager
.getSenderCertificateLegacy();
1003 // TODO cache for a day
1007 private byte[] getSelfUnidentifiedAccessKey() {
1008 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1011 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1012 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1014 return new SignalProfile(
1015 encryptedProfile
.getIdentityKey(),
1016 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1017 encryptedProfile
.getAvatar(),
1018 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1019 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1021 } catch (InvalidCiphertextException e
) {
1026 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1027 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1028 if (contact
== null || contact
.profileKey
== null) {
1031 ProfileKey theirProfileKey
;
1033 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1034 } catch (InvalidInputException e
) {
1035 throw new AssertionError(e
);
1037 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1039 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1043 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1044 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1047 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1050 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1051 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1052 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1054 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1055 return Optional
.absent();
1059 return Optional
.of(new UnidentifiedAccessPair(
1060 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1061 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1063 } catch (InvalidCertificateException e
) {
1064 return Optional
.absent();
1068 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1069 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1070 for (SignalServiceAddress recipient
: recipients
) {
1071 result
.add(getAccessFor(recipient
));
1076 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1077 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1078 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1079 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1081 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1082 return Optional
.absent();
1086 return Optional
.of(new UnidentifiedAccessPair(
1087 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1088 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1090 } catch (InvalidCertificateException e
) {
1091 return Optional
.absent();
1095 private void sendSyncMessage(SignalServiceSyncMessage message
)
1096 throws IOException
, UntrustedIdentityException
{
1097 SignalServiceMessageSender messageSender
= getMessageSender();
1099 messageSender
.sendMessage(message
, getAccessForSync());
1100 } catch (UntrustedIdentityException e
) {
1101 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1107 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1109 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1110 throws EncapsulatedExceptions
, IOException
{
1111 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1113 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1114 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1115 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1117 for (SendMessageResult result
: results
) {
1118 if (result
.isUnregisteredFailure()) {
1119 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1120 } else if (result
.isNetworkFailure()) {
1121 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1122 } else if (result
.getIdentityFailure() != null) {
1123 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1126 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1127 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1131 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1132 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1133 final String username
= account
.getUsername();
1135 for (String number
: numbers
) {
1136 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1137 if (canonicalizedNumber
.equals(username
)) {
1138 signalServiceAddresses
.add(account
.getSelfAddress());
1140 SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
1141 ContactInfo contact
= account
.getContactStore().getContact(address
);
1142 signalServiceAddresses
.add(contact
== null
1144 : contact
.getAddress());
1147 return signalServiceAddresses
;
1150 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1151 throws IOException
{
1152 if (messagePipe
== null) {
1153 messagePipe
= getMessageReceiver().createMessagePipe();
1155 if (unidentifiedMessagePipe
== null) {
1156 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1158 SignalServiceDataMessage message
= null;
1160 SignalServiceMessageSender messageSender
= getMessageSender();
1162 message
= messageBuilder
.build();
1163 if (message
.getGroupInfo().isPresent()) {
1165 final boolean isRecipientUpdate
= false;
1166 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1167 for (SendMessageResult r
: result
) {
1168 if (r
.getIdentityFailure() != null) {
1169 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1173 } catch (UntrustedIdentityException e
) {
1174 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1175 return Collections
.emptyList();
1177 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1178 SignalServiceAddress recipient
= account
.getSelfAddress();
1179 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1180 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1181 message
.getTimestamp(),
1183 message
.getExpiresInSeconds(),
1184 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1186 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1188 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1190 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1191 } catch (UntrustedIdentityException e
) {
1192 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1193 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1197 // Send to all individually, so sync messages are sent correctly
1198 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1199 for (SignalServiceAddress address
: recipients
) {
1200 ContactInfo contact
= account
.getContactStore().getContact(address
);
1201 if (contact
!= null) {
1202 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1203 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1205 messageBuilder
.withExpiration(0);
1206 messageBuilder
.withProfileKey(null);
1208 message
= messageBuilder
.build();
1210 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1211 results
.add(result
);
1212 } catch (UntrustedIdentityException e
) {
1213 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1214 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1220 if (message
!= null && message
.isEndSession()) {
1221 for (SignalServiceAddress recipient
: recipients
) {
1222 handleEndSession(recipient
.getNumber().get());
1229 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1230 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1232 return cipher
.decrypt(envelope
);
1233 } catch (ProtocolUntrustedIdentityException e
) {
1234 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1235 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1240 private void handleEndSession(String source
) {
1241 account
.getSignalProtocolStore().deleteAllSessions(source
);
1244 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1245 if (message
.getGroupInfo().isPresent()) {
1246 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1247 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1248 switch (groupInfo
.getType()) {
1250 if (group
== null) {
1251 group
= new GroupInfo(groupInfo
.getGroupId());
1254 if (groupInfo
.getAvatar().isPresent()) {
1255 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1256 if (avatar
.isPointer()) {
1258 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1259 } catch (IOException
| InvalidMessageException e
) {
1260 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1265 if (groupInfo
.getName().isPresent()) {
1266 group
.name
= groupInfo
.getName().get();
1269 if (groupInfo
.getMembers().isPresent()) {
1270 group
.addMembers(groupInfo
.getMembers().get());
1273 account
.getGroupStore().updateGroup(group
);
1276 if (group
== null) {
1278 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1279 } catch (IOException
| EncapsulatedExceptions e
) {
1280 e
.printStackTrace();
1285 if (group
== null) {
1287 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1288 } catch (IOException
| EncapsulatedExceptions e
) {
1289 e
.printStackTrace();
1292 group
.removeMember(source
);
1293 account
.getGroupStore().updateGroup(group
);
1297 if (group
!= null) {
1299 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1300 } catch (IOException
| EncapsulatedExceptions e
) {
1301 e
.printStackTrace();
1302 } catch (NotAGroupMemberException e
) {
1303 // We have left this group, so don't send a group update message
1309 if (message
.isEndSession()) {
1310 handleEndSession(isSync ? destination
.getNumber().get() : source
.getNumber().get());
1312 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1313 if (message
.getGroupInfo().isPresent()) {
1314 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1315 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1316 if (group
== null) {
1317 group
= new GroupInfo(groupInfo
.getGroupId());
1319 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1320 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1321 account
.getGroupStore().updateGroup(group
);
1324 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1325 if (contact
== null) {
1326 contact
= new ContactInfo(isSync ? destination
: source
);
1328 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1329 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1330 account
.getContactStore().updateContact(contact
);
1334 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1335 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1336 if (attachment
.isPointer()) {
1338 retrieveAttachment(attachment
.asPointer());
1339 } catch (IOException
| InvalidMessageException e
) {
1340 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1345 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1346 if (source
.matches(account
.getSelfAddress())) {
1348 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1349 } catch (InvalidInputException ignored
) {
1351 ContactInfo contact
= account
.getContactStore().getContact(source
);
1352 if (contact
!= null) {
1353 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1354 account
.getContactStore().updateContact(contact
);
1357 ContactInfo contact
= account
.getContactStore().getContact(source
);
1358 if (contact
== null) {
1359 contact
= new ContactInfo(source
);
1361 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1362 account
.getContactStore().updateContact(contact
);
1365 if (message
.getPreviews().isPresent()) {
1366 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1367 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1368 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1369 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1371 retrieveAttachment(attachment
);
1372 } catch (IOException
| InvalidMessageException e
) {
1373 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1380 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1381 final File cachePath
= new File(getMessageCachePath());
1382 if (!cachePath
.exists()) {
1385 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1386 if (!dir
.isDirectory()) {
1390 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1391 if (!fileEntry
.isFile()) {
1394 SignalServiceEnvelope envelope
;
1396 envelope
= Utils
.loadEnvelope(fileEntry
);
1397 if (envelope
== null) {
1400 } catch (IOException e
) {
1401 e
.printStackTrace();
1404 SignalServiceContent content
= null;
1405 if (!envelope
.isReceipt()) {
1407 content
= decryptMessage(envelope
);
1408 } catch (Exception e
) {
1411 handleMessage(envelope
, content
, ignoreAttachments
);
1414 handler
.handleMessage(envelope
, content
, null);
1416 Files
.delete(fileEntry
.toPath());
1417 } catch (IOException e
) {
1418 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1421 // Try to delete directory if empty
1426 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1427 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1428 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1431 if (messagePipe
== null) {
1432 messagePipe
= messageReceiver
.createMessagePipe();
1436 SignalServiceEnvelope envelope
;
1437 SignalServiceContent content
= null;
1438 Exception exception
= null;
1439 final long now
= new Date().getTime();
1441 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1442 // store message on disk, before acknowledging receipt to the server
1444 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1445 Utils
.storeEnvelope(envelope1
, cacheFile
);
1446 } catch (IOException e
) {
1447 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1450 } catch (TimeoutException e
) {
1451 if (returnOnTimeout
)
1454 } catch (InvalidVersionException e
) {
1455 System
.err
.println("Ignoring error: " + e
.getMessage());
1458 if (!envelope
.isReceipt()) {
1460 content
= decryptMessage(envelope
);
1461 } catch (Exception e
) {
1464 handleMessage(envelope
, content
, ignoreAttachments
);
1467 if (!isMessageBlocked(envelope
, content
)) {
1468 handler
.handleMessage(envelope
, content
, exception
);
1470 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1471 File cacheFile
= null;
1473 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1474 Files
.delete(cacheFile
.toPath());
1475 // Try to delete directory if empty
1476 new File(getMessageCachePath()).delete();
1477 } catch (IOException e
) {
1478 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1483 if (messagePipe
!= null) {
1484 messagePipe
.shutdown();
1490 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1491 SignalServiceAddress source
;
1492 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1493 source
= envelope
.getSourceAddress();
1494 } else if (content
!= null) {
1495 source
= content
.getSender();
1499 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1500 if (sourceContact
!= null && sourceContact
.blocked
) {
1504 if (content
!= null && content
.getDataMessage().isPresent()) {
1505 SignalServiceDataMessage message
= content
.getDataMessage().get();
1506 if (message
.getGroupInfo().isPresent()) {
1507 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1508 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1509 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1517 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1518 if (content
!= null) {
1519 SignalServiceAddress sender
;
1520 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1521 sender
= envelope
.getSourceAddress();
1523 sender
= content
.getSender();
1525 if (content
.getDataMessage().isPresent()) {
1526 SignalServiceDataMessage message
= content
.getDataMessage().get();
1528 if (content
.isNeedsReceipt()) {
1530 sendReceipt(sender
, message
.getTimestamp());
1531 } catch (IOException
| UntrustedIdentityException e
) {
1532 e
.printStackTrace();
1536 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1538 if (content
.getSyncMessage().isPresent()) {
1539 account
.setMultiDevice(true);
1540 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1541 if (syncMessage
.getSent().isPresent()) {
1542 SentTranscriptMessage message
= syncMessage
.getSent().get();
1543 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1545 if (syncMessage
.getRequest().isPresent()) {
1546 RequestMessage rm
= syncMessage
.getRequest().get();
1547 if (rm
.isContactsRequest()) {
1550 } catch (UntrustedIdentityException
| IOException e
) {
1551 e
.printStackTrace();
1554 if (rm
.isGroupsRequest()) {
1557 } catch (UntrustedIdentityException
| IOException e
) {
1558 e
.printStackTrace();
1561 if (rm
.isBlockedListRequest()) {
1564 } catch (UntrustedIdentityException
| IOException e
) {
1565 e
.printStackTrace();
1568 // TODO Handle rm.isConfigurationRequest();
1570 if (syncMessage
.getGroups().isPresent()) {
1571 File tmpFile
= null;
1573 tmpFile
= IOUtils
.createTempFile();
1574 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1575 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1577 while ((g
= s
.read()) != null) {
1578 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1579 if (syncGroup
== null) {
1580 syncGroup
= new GroupInfo(g
.getId());
1582 if (g
.getName().isPresent()) {
1583 syncGroup
.name
= g
.getName().get();
1585 syncGroup
.addMembers(g
.getMembers());
1586 if (!g
.isActive()) {
1587 syncGroup
.removeMember(account
.getSelfAddress());
1589 // Add ourself to the member set as it's marked as active
1590 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1592 syncGroup
.blocked
= g
.isBlocked();
1593 if (g
.getColor().isPresent()) {
1594 syncGroup
.color
= g
.getColor().get();
1597 if (g
.getAvatar().isPresent()) {
1598 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1600 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1601 syncGroup
.archived
= g
.isArchived();
1602 account
.getGroupStore().updateGroup(syncGroup
);
1605 } catch (Exception e
) {
1606 e
.printStackTrace();
1608 if (tmpFile
!= null) {
1610 Files
.delete(tmpFile
.toPath());
1611 } catch (IOException e
) {
1612 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1617 if (syncMessage
.getBlockedList().isPresent()) {
1618 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1619 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1620 if (address
.getNumber().isPresent()) {
1622 setContactBlocked(address
.getNumber().get(), true);
1623 } catch (InvalidNumberException e
) {
1624 e
.printStackTrace();
1628 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1630 setGroupBlocked(groupId
, true);
1631 } catch (GroupNotFoundException e
) {
1632 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1636 if (syncMessage
.getContacts().isPresent()) {
1637 File tmpFile
= null;
1639 tmpFile
= IOUtils
.createTempFile();
1640 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1641 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1642 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1643 if (contactsMessage
.isComplete()) {
1644 account
.getContactStore().clear();
1647 while ((c
= s
.read()) != null) {
1648 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1649 account
.setProfileKey(c
.getProfileKey().get());
1651 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1652 if (contact
== null) {
1653 contact
= new ContactInfo(c
.getAddress());
1655 if (c
.getName().isPresent()) {
1656 contact
.name
= c
.getName().get();
1658 if (c
.getColor().isPresent()) {
1659 contact
.color
= c
.getColor().get();
1661 if (c
.getProfileKey().isPresent()) {
1662 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1664 if (c
.getVerified().isPresent()) {
1665 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1666 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1668 if (c
.getExpirationTimer().isPresent()) {
1669 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1671 contact
.blocked
= c
.isBlocked();
1672 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1673 contact
.archived
= c
.isArchived();
1674 account
.getContactStore().updateContact(contact
);
1676 if (c
.getAvatar().isPresent()) {
1677 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1681 } catch (Exception e
) {
1682 e
.printStackTrace();
1684 if (tmpFile
!= null) {
1686 Files
.delete(tmpFile
.toPath());
1687 } catch (IOException e
) {
1688 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1693 if (syncMessage
.getVerified().isPresent()) {
1694 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1695 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1697 if (syncMessage
.getConfiguration().isPresent()) {
1704 private File
getContactAvatarFile(String number
) {
1705 return new File(avatarsPath
, "contact-" + number
);
1708 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1709 IOUtils
.createPrivateDirectories(avatarsPath
);
1710 if (attachment
.isPointer()) {
1711 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1712 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1714 SignalServiceAttachmentStream stream
= attachment
.asStream();
1715 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1719 private File
getGroupAvatarFile(byte[] groupId
) {
1720 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1723 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1724 IOUtils
.createPrivateDirectories(avatarsPath
);
1725 if (attachment
.isPointer()) {
1726 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1727 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1729 SignalServiceAttachmentStream stream
= attachment
.asStream();
1730 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1734 public File
getAttachmentFile(long attachmentId
) {
1735 return new File(attachmentsPath
, attachmentId
+ "");
1738 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1739 IOUtils
.createPrivateDirectories(attachmentsPath
);
1740 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1743 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1744 if (storePreview
&& pointer
.getPreview().isPresent()) {
1745 File previewFile
= new File(outputFile
+ ".preview");
1746 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1747 byte[] preview
= pointer
.getPreview().get();
1748 output
.write(preview
, 0, preview
.length
);
1749 } catch (FileNotFoundException e
) {
1750 e
.printStackTrace();
1755 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1757 File tmpFile
= IOUtils
.createTempFile();
1758 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1759 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1760 byte[] buffer
= new byte[4096];
1763 while ((read
= input
.read(buffer
)) != -1) {
1764 output
.write(buffer
, 0, read
);
1766 } catch (FileNotFoundException e
) {
1767 e
.printStackTrace();
1772 Files
.delete(tmpFile
.toPath());
1773 } catch (IOException e
) {
1774 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1780 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1781 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1782 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1786 public boolean isRemote() {
1790 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1791 File groupsFile
= IOUtils
.createTempFile();
1794 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1795 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1796 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1797 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1798 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1799 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1800 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1804 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1805 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1806 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1807 .withStream(groupsFileStream
)
1808 .withContentType("application/octet-stream")
1809 .withLength(groupsFile
.length())
1812 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1817 Files
.delete(groupsFile
.toPath());
1818 } catch (IOException e
) {
1819 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1824 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1825 File contactsFile
= IOUtils
.createTempFile();
1828 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1829 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1830 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1831 VerifiedMessage verifiedMessage
= null;
1832 if (getIdentities().containsKey(record.number
)) {
1833 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1834 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1835 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1836 currentIdentity
= id
;
1839 if (currentIdentity
!= null) {
1840 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1844 ProfileKey profileKey
= null;
1846 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1847 } catch (InvalidInputException ignored
) {
1849 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1850 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1851 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1852 Optional
.of(record.messageExpirationTime
),
1853 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1856 if (account
.getProfileKey() != null) {
1857 // Send our own profile key as well
1858 out
.write(new DeviceContact(account
.getSelfAddress(),
1859 Optional
.absent(), Optional
.absent(),
1860 Optional
.absent(), Optional
.absent(),
1861 Optional
.of(account
.getProfileKey()),
1862 false, Optional
.absent(), Optional
.absent(), false));
1866 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1867 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1868 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1869 .withStream(contactsFileStream
)
1870 .withContentType("application/octet-stream")
1871 .withLength(contactsFile
.length())
1874 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1879 Files
.delete(contactsFile
.toPath());
1880 } catch (IOException e
) {
1881 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1886 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1887 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1888 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1889 if (record.blocked
) {
1890 addresses
.add(record.getAddress());
1893 List
<byte[]> groupIds
= new ArrayList
<>();
1894 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1895 if (record.blocked
) {
1896 groupIds
.add(record.groupId
);
1899 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1902 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1903 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1904 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1907 public List
<ContactInfo
> getContacts() {
1908 return account
.getContactStore().getContacts();
1911 public ContactInfo
getContact(String number
) {
1912 return account
.getContactStore().getContact(new SignalServiceAddress(null, number
));
1915 public GroupInfo
getGroup(byte[] groupId
) {
1916 return account
.getGroupStore().getGroup(groupId
);
1919 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1920 return account
.getSignalProtocolStore().getIdentities();
1923 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1924 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
1925 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1929 * Trust this the identity with this fingerprint
1931 * @param name username of the identity
1932 * @param fingerprint Fingerprint
1934 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1935 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1939 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1940 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1944 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1946 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1947 } catch (IOException
| UntrustedIdentityException e
) {
1948 e
.printStackTrace();
1957 * Trust this the identity with this safety number
1959 * @param name username of the identity
1960 * @param safetyNumber Safety number
1962 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1963 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1967 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1968 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1972 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1974 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1975 } catch (IOException
| UntrustedIdentityException e
) {
1976 e
.printStackTrace();
1985 * Trust all keys of this identity without verification
1987 * @param name username of the identity
1989 public boolean trustIdentityAllKeys(String name
) {
1990 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1994 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1995 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1996 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1998 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1999 } catch (IOException
| UntrustedIdentityException e
) {
2000 e
.printStackTrace();
2008 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
2009 return Utils
.computeSafetyNumber(account
.getUsername(), getIdentity(), theirUsername
, theirIdentityKey
);
2012 public interface ReceiveMessageHandler
{
2014 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);