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 byte[] certificate
= accountManager
.getSenderCertificate();
1001 // TODO cache for a day
1005 private byte[] getSelfUnidentifiedAccessKey() {
1006 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1009 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1010 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1012 return new SignalProfile(
1013 encryptedProfile
.getIdentityKey(),
1014 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1015 encryptedProfile
.getAvatar(),
1016 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1017 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1019 } catch (InvalidCiphertextException e
) {
1024 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1025 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1026 if (contact
== null || contact
.profileKey
== null) {
1029 ProfileKey theirProfileKey
;
1031 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1032 } catch (InvalidInputException e
) {
1033 throw new AssertionError(e
);
1035 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1037 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1041 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1042 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1045 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1048 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1049 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1050 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1052 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1053 return Optional
.absent();
1057 return Optional
.of(new UnidentifiedAccessPair(
1058 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1059 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1061 } catch (InvalidCertificateException e
) {
1062 return Optional
.absent();
1066 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1067 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1068 for (SignalServiceAddress recipient
: recipients
) {
1069 result
.add(getAccessFor(recipient
));
1074 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1075 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1076 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1077 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1079 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1080 return Optional
.absent();
1084 return Optional
.of(new UnidentifiedAccessPair(
1085 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1086 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1088 } catch (InvalidCertificateException e
) {
1089 return Optional
.absent();
1093 private void sendSyncMessage(SignalServiceSyncMessage message
)
1094 throws IOException
, UntrustedIdentityException
{
1095 SignalServiceMessageSender messageSender
= getMessageSender();
1097 messageSender
.sendMessage(message
, getAccessForSync());
1098 } catch (UntrustedIdentityException e
) {
1099 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1105 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1107 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1108 throws EncapsulatedExceptions
, IOException
{
1109 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1111 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1112 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1113 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1115 for (SendMessageResult result
: results
) {
1116 if (result
.isUnregisteredFailure()) {
1117 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1118 } else if (result
.isNetworkFailure()) {
1119 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1120 } else if (result
.getIdentityFailure() != null) {
1121 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1124 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1125 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1129 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1130 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1131 final String username
= account
.getUsername();
1133 for (String number
: numbers
) {
1134 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1135 if (canonicalizedNumber
.equals(username
)) {
1136 signalServiceAddresses
.add(account
.getSelfAddress());
1138 SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
1139 ContactInfo contact
= account
.getContactStore().getContact(address
);
1140 signalServiceAddresses
.add(contact
== null
1142 : contact
.getAddress());
1145 return signalServiceAddresses
;
1148 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1149 throws IOException
{
1150 if (messagePipe
== null) {
1151 messagePipe
= getMessageReceiver().createMessagePipe();
1153 if (unidentifiedMessagePipe
== null) {
1154 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1156 SignalServiceDataMessage message
= null;
1158 SignalServiceMessageSender messageSender
= getMessageSender();
1160 message
= messageBuilder
.build();
1161 if (message
.getGroupInfo().isPresent()) {
1163 final boolean isRecipientUpdate
= false;
1164 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1165 for (SendMessageResult r
: result
) {
1166 if (r
.getIdentityFailure() != null) {
1167 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1171 } catch (UntrustedIdentityException e
) {
1172 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1173 return Collections
.emptyList();
1175 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1176 SignalServiceAddress recipient
= account
.getSelfAddress();
1177 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1178 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1179 message
.getTimestamp(),
1181 message
.getExpiresInSeconds(),
1182 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1184 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1186 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1188 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1189 } catch (UntrustedIdentityException e
) {
1190 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1191 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1195 // Send to all individually, so sync messages are sent correctly
1196 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1197 for (SignalServiceAddress address
: recipients
) {
1198 ContactInfo contact
= account
.getContactStore().getContact(address
);
1199 if (contact
!= null) {
1200 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1201 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1203 messageBuilder
.withExpiration(0);
1204 messageBuilder
.withProfileKey(null);
1206 message
= messageBuilder
.build();
1208 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1209 results
.add(result
);
1210 } catch (UntrustedIdentityException e
) {
1211 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1212 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1218 if (message
!= null && message
.isEndSession()) {
1219 for (SignalServiceAddress recipient
: recipients
) {
1220 handleEndSession(recipient
.getNumber().get());
1227 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1228 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1230 return cipher
.decrypt(envelope
);
1231 } catch (ProtocolUntrustedIdentityException e
) {
1232 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1233 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1238 private void handleEndSession(String source
) {
1239 account
.getSignalProtocolStore().deleteAllSessions(source
);
1242 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1243 if (message
.getGroupInfo().isPresent()) {
1244 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1245 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1246 switch (groupInfo
.getType()) {
1248 if (group
== null) {
1249 group
= new GroupInfo(groupInfo
.getGroupId());
1252 if (groupInfo
.getAvatar().isPresent()) {
1253 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1254 if (avatar
.isPointer()) {
1256 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1257 } catch (IOException
| InvalidMessageException e
) {
1258 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1263 if (groupInfo
.getName().isPresent()) {
1264 group
.name
= groupInfo
.getName().get();
1267 if (groupInfo
.getMembers().isPresent()) {
1268 group
.addMembers(groupInfo
.getMembers().get());
1271 account
.getGroupStore().updateGroup(group
);
1274 if (group
== null) {
1276 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1277 } catch (IOException
| EncapsulatedExceptions e
) {
1278 e
.printStackTrace();
1283 if (group
== null) {
1285 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1286 } catch (IOException
| EncapsulatedExceptions e
) {
1287 e
.printStackTrace();
1290 group
.removeMember(source
);
1291 account
.getGroupStore().updateGroup(group
);
1295 if (group
!= null) {
1297 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1298 } catch (IOException
| EncapsulatedExceptions e
) {
1299 e
.printStackTrace();
1300 } catch (NotAGroupMemberException e
) {
1301 // We have left this group, so don't send a group update message
1307 if (message
.isEndSession()) {
1308 handleEndSession(isSync ? destination
.getNumber().get() : source
.getNumber().get());
1310 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1311 if (message
.getGroupInfo().isPresent()) {
1312 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1313 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1314 if (group
== null) {
1315 group
= new GroupInfo(groupInfo
.getGroupId());
1317 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1318 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1319 account
.getGroupStore().updateGroup(group
);
1322 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1323 if (contact
== null) {
1324 contact
= new ContactInfo(isSync ? destination
: source
);
1326 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1327 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1328 account
.getContactStore().updateContact(contact
);
1332 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1333 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1334 if (attachment
.isPointer()) {
1336 retrieveAttachment(attachment
.asPointer());
1337 } catch (IOException
| InvalidMessageException e
) {
1338 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1343 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1344 if (source
.matches(account
.getSelfAddress())) {
1346 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1347 } catch (InvalidInputException ignored
) {
1349 ContactInfo contact
= account
.getContactStore().getContact(source
);
1350 if (contact
!= null) {
1351 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1352 account
.getContactStore().updateContact(contact
);
1355 ContactInfo contact
= account
.getContactStore().getContact(source
);
1356 if (contact
== null) {
1357 contact
= new ContactInfo(source
);
1359 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1360 account
.getContactStore().updateContact(contact
);
1363 if (message
.getPreviews().isPresent()) {
1364 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1365 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1366 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1367 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1369 retrieveAttachment(attachment
);
1370 } catch (IOException
| InvalidMessageException e
) {
1371 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1378 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1379 final File cachePath
= new File(getMessageCachePath());
1380 if (!cachePath
.exists()) {
1383 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1384 if (!dir
.isDirectory()) {
1388 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1389 if (!fileEntry
.isFile()) {
1392 SignalServiceEnvelope envelope
;
1394 envelope
= Utils
.loadEnvelope(fileEntry
);
1395 if (envelope
== null) {
1398 } catch (IOException e
) {
1399 e
.printStackTrace();
1402 SignalServiceContent content
= null;
1403 if (!envelope
.isReceipt()) {
1405 content
= decryptMessage(envelope
);
1406 } catch (Exception e
) {
1409 handleMessage(envelope
, content
, ignoreAttachments
);
1412 handler
.handleMessage(envelope
, content
, null);
1414 Files
.delete(fileEntry
.toPath());
1415 } catch (IOException e
) {
1416 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1419 // Try to delete directory if empty
1424 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1425 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1426 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1429 if (messagePipe
== null) {
1430 messagePipe
= messageReceiver
.createMessagePipe();
1434 SignalServiceEnvelope envelope
;
1435 SignalServiceContent content
= null;
1436 Exception exception
= null;
1437 final long now
= new Date().getTime();
1439 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1440 // store message on disk, before acknowledging receipt to the server
1442 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1443 Utils
.storeEnvelope(envelope1
, cacheFile
);
1444 } catch (IOException e
) {
1445 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1448 } catch (TimeoutException e
) {
1449 if (returnOnTimeout
)
1452 } catch (InvalidVersionException e
) {
1453 System
.err
.println("Ignoring error: " + e
.getMessage());
1456 if (!envelope
.isReceipt()) {
1458 content
= decryptMessage(envelope
);
1459 } catch (Exception e
) {
1462 handleMessage(envelope
, content
, ignoreAttachments
);
1465 if (!isMessageBlocked(envelope
, content
)) {
1466 handler
.handleMessage(envelope
, content
, exception
);
1468 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1469 File cacheFile
= null;
1471 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1472 Files
.delete(cacheFile
.toPath());
1473 // Try to delete directory if empty
1474 new File(getMessageCachePath()).delete();
1475 } catch (IOException e
) {
1476 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1481 if (messagePipe
!= null) {
1482 messagePipe
.shutdown();
1488 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1489 SignalServiceAddress source
;
1490 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1491 source
= envelope
.getSourceAddress();
1492 } else if (content
!= null) {
1493 source
= content
.getSender();
1497 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1498 if (sourceContact
!= null && sourceContact
.blocked
) {
1502 if (content
!= null && content
.getDataMessage().isPresent()) {
1503 SignalServiceDataMessage message
= content
.getDataMessage().get();
1504 if (message
.getGroupInfo().isPresent()) {
1505 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1506 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1507 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1515 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1516 if (content
!= null) {
1517 SignalServiceAddress sender
;
1518 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1519 sender
= envelope
.getSourceAddress();
1521 sender
= content
.getSender();
1523 if (content
.getDataMessage().isPresent()) {
1524 SignalServiceDataMessage message
= content
.getDataMessage().get();
1526 if (content
.isNeedsReceipt()) {
1528 sendReceipt(sender
, message
.getTimestamp());
1529 } catch (IOException
| UntrustedIdentityException e
) {
1530 e
.printStackTrace();
1534 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1536 if (content
.getSyncMessage().isPresent()) {
1537 account
.setMultiDevice(true);
1538 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1539 if (syncMessage
.getSent().isPresent()) {
1540 SentTranscriptMessage message
= syncMessage
.getSent().get();
1541 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1543 if (syncMessage
.getRequest().isPresent()) {
1544 RequestMessage rm
= syncMessage
.getRequest().get();
1545 if (rm
.isContactsRequest()) {
1548 } catch (UntrustedIdentityException
| IOException e
) {
1549 e
.printStackTrace();
1552 if (rm
.isGroupsRequest()) {
1555 } catch (UntrustedIdentityException
| IOException e
) {
1556 e
.printStackTrace();
1559 if (rm
.isBlockedListRequest()) {
1562 } catch (UntrustedIdentityException
| IOException e
) {
1563 e
.printStackTrace();
1566 // TODO Handle rm.isConfigurationRequest();
1568 if (syncMessage
.getGroups().isPresent()) {
1569 File tmpFile
= null;
1571 tmpFile
= IOUtils
.createTempFile();
1572 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1573 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1575 while ((g
= s
.read()) != null) {
1576 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1577 if (syncGroup
== null) {
1578 syncGroup
= new GroupInfo(g
.getId());
1580 if (g
.getName().isPresent()) {
1581 syncGroup
.name
= g
.getName().get();
1583 syncGroup
.addMembers(g
.getMembers());
1584 if (!g
.isActive()) {
1585 syncGroup
.removeMember(account
.getSelfAddress());
1587 // Add ourself to the member set as it's marked as active
1588 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1590 syncGroup
.blocked
= g
.isBlocked();
1591 if (g
.getColor().isPresent()) {
1592 syncGroup
.color
= g
.getColor().get();
1595 if (g
.getAvatar().isPresent()) {
1596 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1598 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1599 syncGroup
.archived
= g
.isArchived();
1600 account
.getGroupStore().updateGroup(syncGroup
);
1603 } catch (Exception e
) {
1604 e
.printStackTrace();
1606 if (tmpFile
!= null) {
1608 Files
.delete(tmpFile
.toPath());
1609 } catch (IOException e
) {
1610 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1615 if (syncMessage
.getBlockedList().isPresent()) {
1616 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1617 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1618 if (address
.getNumber().isPresent()) {
1620 setContactBlocked(address
.getNumber().get(), true);
1621 } catch (InvalidNumberException e
) {
1622 e
.printStackTrace();
1626 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1628 setGroupBlocked(groupId
, true);
1629 } catch (GroupNotFoundException e
) {
1630 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1634 if (syncMessage
.getContacts().isPresent()) {
1635 File tmpFile
= null;
1637 tmpFile
= IOUtils
.createTempFile();
1638 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1639 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1640 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1641 if (contactsMessage
.isComplete()) {
1642 account
.getContactStore().clear();
1645 while ((c
= s
.read()) != null) {
1646 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1647 account
.setProfileKey(c
.getProfileKey().get());
1649 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1650 if (contact
== null) {
1651 contact
= new ContactInfo(c
.getAddress());
1653 if (c
.getName().isPresent()) {
1654 contact
.name
= c
.getName().get();
1656 if (c
.getColor().isPresent()) {
1657 contact
.color
= c
.getColor().get();
1659 if (c
.getProfileKey().isPresent()) {
1660 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1662 if (c
.getVerified().isPresent()) {
1663 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1664 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1666 if (c
.getExpirationTimer().isPresent()) {
1667 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1669 contact
.blocked
= c
.isBlocked();
1670 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1671 contact
.archived
= c
.isArchived();
1672 account
.getContactStore().updateContact(contact
);
1674 if (c
.getAvatar().isPresent()) {
1675 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1679 } catch (Exception e
) {
1680 e
.printStackTrace();
1682 if (tmpFile
!= null) {
1684 Files
.delete(tmpFile
.toPath());
1685 } catch (IOException e
) {
1686 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1691 if (syncMessage
.getVerified().isPresent()) {
1692 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1693 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1695 if (syncMessage
.getConfiguration().isPresent()) {
1702 private File
getContactAvatarFile(String number
) {
1703 return new File(avatarsPath
, "contact-" + number
);
1706 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1707 IOUtils
.createPrivateDirectories(avatarsPath
);
1708 if (attachment
.isPointer()) {
1709 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1710 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1712 SignalServiceAttachmentStream stream
= attachment
.asStream();
1713 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1717 private File
getGroupAvatarFile(byte[] groupId
) {
1718 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1721 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1722 IOUtils
.createPrivateDirectories(avatarsPath
);
1723 if (attachment
.isPointer()) {
1724 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1725 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1727 SignalServiceAttachmentStream stream
= attachment
.asStream();
1728 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1732 public File
getAttachmentFile(long attachmentId
) {
1733 return new File(attachmentsPath
, attachmentId
+ "");
1736 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1737 IOUtils
.createPrivateDirectories(attachmentsPath
);
1738 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1741 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1742 if (storePreview
&& pointer
.getPreview().isPresent()) {
1743 File previewFile
= new File(outputFile
+ ".preview");
1744 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1745 byte[] preview
= pointer
.getPreview().get();
1746 output
.write(preview
, 0, preview
.length
);
1747 } catch (FileNotFoundException e
) {
1748 e
.printStackTrace();
1753 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1755 File tmpFile
= IOUtils
.createTempFile();
1756 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1757 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1758 byte[] buffer
= new byte[4096];
1761 while ((read
= input
.read(buffer
)) != -1) {
1762 output
.write(buffer
, 0, read
);
1764 } catch (FileNotFoundException e
) {
1765 e
.printStackTrace();
1770 Files
.delete(tmpFile
.toPath());
1771 } catch (IOException e
) {
1772 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1778 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1779 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1780 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1784 public boolean isRemote() {
1788 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1789 File groupsFile
= IOUtils
.createTempFile();
1792 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1793 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1794 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1795 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1796 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1797 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1798 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1802 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1803 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1804 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1805 .withStream(groupsFileStream
)
1806 .withContentType("application/octet-stream")
1807 .withLength(groupsFile
.length())
1810 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1815 Files
.delete(groupsFile
.toPath());
1816 } catch (IOException e
) {
1817 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1822 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1823 File contactsFile
= IOUtils
.createTempFile();
1826 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1827 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1828 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1829 VerifiedMessage verifiedMessage
= null;
1830 if (getIdentities().containsKey(record.number
)) {
1831 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1832 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1833 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1834 currentIdentity
= id
;
1837 if (currentIdentity
!= null) {
1838 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1842 ProfileKey profileKey
= null;
1844 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1845 } catch (InvalidInputException ignored
) {
1847 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1848 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1849 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1850 Optional
.of(record.messageExpirationTime
),
1851 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1854 if (account
.getProfileKey() != null) {
1855 // Send our own profile key as well
1856 out
.write(new DeviceContact(account
.getSelfAddress(),
1857 Optional
.absent(), Optional
.absent(),
1858 Optional
.absent(), Optional
.absent(),
1859 Optional
.of(account
.getProfileKey()),
1860 false, Optional
.absent(), Optional
.absent(), false));
1864 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1865 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1866 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1867 .withStream(contactsFileStream
)
1868 .withContentType("application/octet-stream")
1869 .withLength(contactsFile
.length())
1872 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1877 Files
.delete(contactsFile
.toPath());
1878 } catch (IOException e
) {
1879 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1884 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1885 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1886 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1887 if (record.blocked
) {
1888 addresses
.add(record.getAddress());
1891 List
<byte[]> groupIds
= new ArrayList
<>();
1892 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1893 if (record.blocked
) {
1894 groupIds
.add(record.groupId
);
1897 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1900 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1901 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1902 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1905 public List
<ContactInfo
> getContacts() {
1906 return account
.getContactStore().getContacts();
1909 public ContactInfo
getContact(String number
) {
1910 return account
.getContactStore().getContact(new SignalServiceAddress(null, number
));
1913 public GroupInfo
getGroup(byte[] groupId
) {
1914 return account
.getGroupStore().getGroup(groupId
);
1917 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1918 return account
.getSignalProtocolStore().getIdentities();
1921 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1922 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
1923 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1927 * Trust this the identity with this fingerprint
1929 * @param name username of the identity
1930 * @param fingerprint Fingerprint
1932 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1933 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1937 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1938 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1942 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1944 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1945 } catch (IOException
| UntrustedIdentityException e
) {
1946 e
.printStackTrace();
1955 * Trust this the identity with this safety number
1957 * @param name username of the identity
1958 * @param safetyNumber Safety number
1960 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1961 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1965 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1966 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1970 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1972 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1973 } catch (IOException
| UntrustedIdentityException e
) {
1974 e
.printStackTrace();
1983 * Trust all keys of this identity without verification
1985 * @param name username of the identity
1987 public boolean trustIdentityAllKeys(String name
) {
1988 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1992 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1993 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1994 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1996 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1997 } catch (IOException
| UntrustedIdentityException e
) {
1998 e
.printStackTrace();
2006 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
2007 return Utils
.computeSafetyNumber(account
.getUsername(), getIdentity(), theirUsername
, theirIdentityKey
);
2010 public interface ReceiveMessageHandler
{
2012 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);