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
.DbusConfig
;
24 import org
.asamk
.signal
.GroupNotFoundException
;
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
.api
.util
.UuidUtil
;
110 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
111 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
112 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
113 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.FileInputStream
;
117 import java
.io
.FileNotFoundException
;
118 import java
.io
.FileOutputStream
;
119 import java
.io
.IOException
;
120 import java
.io
.InputStream
;
121 import java
.io
.OutputStream
;
123 import java
.net
.URISyntaxException
;
124 import java
.net
.URLEncoder
;
125 import java
.nio
.file
.Files
;
126 import java
.nio
.file
.Paths
;
127 import java
.nio
.file
.StandardCopyOption
;
128 import java
.util
.ArrayList
;
129 import java
.util
.Arrays
;
130 import java
.util
.Collection
;
131 import java
.util
.Collections
;
132 import java
.util
.Date
;
133 import java
.util
.HashSet
;
134 import java
.util
.LinkedList
;
135 import java
.util
.List
;
136 import java
.util
.Locale
;
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
.stream
.Collectors
;
143 import java
.util
.zip
.ZipEntry
;
144 import java
.util
.zip
.ZipFile
;
146 public class Manager
implements Signal
{
148 private final String settingsPath
;
149 private final String dataPath
;
150 private final String attachmentsPath
;
151 private final String avatarsPath
;
152 private final SleepTimer timer
= new UptimeSleepTimer();
154 private SignalAccount account
;
155 private String username
;
156 private SignalServiceAccountManager accountManager
;
157 private SignalServiceMessagePipe messagePipe
= null;
158 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
160 public Manager(String username
, String settingsPath
) {
161 this.username
= username
;
162 this.settingsPath
= settingsPath
;
163 this.dataPath
= this.settingsPath
+ "/data";
164 this.attachmentsPath
= this.settingsPath
+ "/attachments";
165 this.avatarsPath
= this.settingsPath
+ "/avatars";
169 public String
getUsername() {
173 public SignalServiceAddress
getSelfAddress() {
174 return account
.getSelfAddress();
177 private SignalServiceAccountManager
getSignalServiceAccountManager() {
178 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
181 private IdentityKey
getIdentity() {
182 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
185 public int getDeviceId() {
186 return account
.getDeviceId();
189 private String
getMessageCachePath() {
190 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
193 private String
getMessageCachePath(String sender
) {
194 if (sender
== null || sender
.isEmpty()) {
195 return getMessageCachePath();
198 return getMessageCachePath() + "/" + sender
.replace("/", "_");
201 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
202 String cachePath
= getMessageCachePath(sender
);
203 IOUtils
.createPrivateDirectories(cachePath
);
204 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
207 public boolean userHasKeys() {
208 return account
!= null && account
.getSignalProtocolStore() != null;
211 public void init() throws IOException
{
212 if (!SignalAccount
.userExists(dataPath
, username
)) {
215 account
= SignalAccount
.load(dataPath
, username
);
216 account
.setResolver(this::resolveSignalServiceAddress
);
218 migrateLegacyConfigs();
220 accountManager
= getSignalServiceAccountManager();
221 if (account
.isRegistered()) {
222 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
226 if (account
.getUuid() == null) {
227 account
.setUuid(accountManager
.getOwnUuid());
233 private void migrateLegacyConfigs() {
234 // Copy group avatars that were previously stored in the attachments folder
235 // to the new avatar folder
236 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
237 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
238 File avatarFile
= getGroupAvatarFile(g
.groupId
);
239 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
240 if (!avatarFile
.exists() && attachmentFile
.exists()) {
242 IOUtils
.createPrivateDirectories(avatarsPath
);
243 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
244 } catch (Exception e
) {
249 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
252 if (account
.getProfileKey() == null) {
253 // Old config file, creating new profile key
254 account
.setProfileKey(KeyUtils
.createProfileKey());
259 private void createNewIdentity() throws IOException
{
260 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
261 int registrationId
= KeyHelper
.generateRegistrationId(false);
262 if (username
== null) {
263 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
264 account
.setResolver(this::resolveSignalServiceAddress
);
266 ProfileKey profileKey
= KeyUtils
.createProfileKey();
267 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
268 account
.setResolver(this::resolveSignalServiceAddress
);
273 public boolean isRegistered() {
274 return account
!= null && account
.isRegistered();
277 public void register(boolean voiceVerification
) throws IOException
{
278 if (account
== null) {
281 account
.setPassword(KeyUtils
.createPassword());
282 account
.setUuid(null);
283 accountManager
= getSignalServiceAccountManager();
285 if (voiceVerification
) {
286 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
288 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
291 account
.setRegistered(false);
295 public void updateAccountAttributes() throws IOException
{
296 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
299 public void setProfileName(String name
) throws IOException
{
300 accountManager
.setProfileName(account
.getProfileKey(), name
);
303 public void setProfileAvatar(File avatar
) throws IOException
{
304 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
305 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
306 streamDetails
.getStream().close();
309 public void removeProfileAvatar() throws IOException
{
310 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
313 public void unregister() throws IOException
{
314 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
315 // If this is the master device, other users can't send messages to this number anymore.
316 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
317 accountManager
.setGcmId(Optional
.absent());
319 account
.setRegistered(false);
323 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
324 if (account
== null) {
327 account
.setPassword(KeyUtils
.createPassword());
328 accountManager
= getSignalServiceAccountManager();
329 String uuid
= accountManager
.getNewDeviceUuid();
331 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
334 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
335 account
.setSignalingKey(KeyUtils
.createSignalingKey());
336 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
338 username
= ret
.getNumber();
339 // TODO do this check before actually registering
340 if (SignalAccount
.userExists(dataPath
, username
)) {
341 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
344 // Create new account with the synced identity
345 byte[] profileKeyBytes
= ret
.getProfileKey();
346 ProfileKey profileKey
;
347 if (profileKeyBytes
== null) {
348 profileKey
= KeyUtils
.createProfileKey();
351 profileKey
= new ProfileKey(profileKeyBytes
);
352 } catch (InvalidInputException e
) {
353 throw new IOException("Received invalid profileKey", e
);
356 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
357 account
.setResolver(this::resolveSignalServiceAddress
);
362 requestSyncContacts();
363 requestSyncBlocked();
364 requestSyncConfiguration();
369 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
370 List
<DeviceInfo
> devices
= accountManager
.getDevices();
371 account
.setMultiDevice(devices
.size() > 1);
376 public void removeLinkedDevices(int deviceId
) throws IOException
{
377 accountManager
.removeDevice(deviceId
);
378 List
<DeviceInfo
> devices
= accountManager
.getDevices();
379 account
.setMultiDevice(devices
.size() > 1);
383 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
384 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
386 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
389 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
390 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
391 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
393 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
394 account
.setMultiDevice(true);
398 private List
<PreKeyRecord
> generatePreKeys() {
399 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
401 final int offset
= account
.getPreKeyIdOffset();
402 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
403 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
404 ECKeyPair keyPair
= Curve
.generateKeyPair();
405 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
410 account
.addPreKeys(records
);
416 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
418 ECKeyPair keyPair
= Curve
.generateKeyPair();
419 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
420 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
422 account
.addSignedPreKey(record);
426 } catch (InvalidKeyException e
) {
427 throw new AssertionError(e
);
431 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
432 verificationCode
= verificationCode
.replace("-", "");
433 account
.setSignalingKey(KeyUtils
.createSignalingKey());
434 // TODO make unrestricted unidentified access configurable
435 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
437 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
438 account
.setRegistered(true);
439 account
.setUuid(uuid
);
440 account
.setRegistrationLockPin(pin
);
441 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
447 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
448 if (pin
.isPresent()) {
449 account
.setRegistrationLockPin(pin
.get());
450 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
452 account
.setRegistrationLockPin(null);
453 accountManager
.removeV1Pin();
458 private void refreshPreKeys() throws IOException
{
459 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
460 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
461 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
463 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
466 private SignalServiceMessageReceiver
getMessageReceiver() {
467 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
470 private SignalServiceMessageSender
getMessageSender() {
471 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
472 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
475 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
476 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
481 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
482 } catch (IOException ignored
) {
486 SignalServiceMessageReceiver receiver
= getMessageReceiver();
488 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
489 } catch (VerificationFailedException e
) {
490 throw new AssertionError(e
);
494 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
495 File file
= getGroupAvatarFile(groupId
);
496 if (!file
.exists()) {
497 return Optional
.absent();
500 return Optional
.of(Utils
.createAttachment(file
));
503 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
504 File file
= getContactAvatarFile(number
);
505 if (!file
.exists()) {
506 return Optional
.absent();
509 return Optional
.of(Utils
.createAttachment(file
));
512 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
513 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
515 throw new GroupNotFoundException(groupId
);
517 if (!g
.isMember(account
.getSelfAddress())) {
518 throw new NotAGroupMemberException(groupId
, g
.name
);
523 public List
<GroupInfo
> getGroups() {
524 return account
.getGroupStore().getGroups();
528 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
530 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
531 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
532 if (attachments
!= null) {
533 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
535 if (groupId
!= null) {
536 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
539 messageBuilder
.asGroupMessage(group
);
542 final GroupInfo g
= getGroupForSending(groupId
);
544 messageBuilder
.withExpiration(g
.messageExpirationTime
);
546 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
549 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
550 long targetSentTimestamp
, byte[] groupId
)
551 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
552 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
553 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
554 .withReaction(reaction
);
555 if (groupId
!= null) {
556 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
559 messageBuilder
.asGroupMessage(group
);
561 final GroupInfo g
= getGroupForSending(groupId
);
562 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
565 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
566 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
570 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
571 .asGroupMessage(group
);
573 final GroupInfo g
= getGroupForSending(groupId
);
574 g
.removeMember(account
.getSelfAddress());
575 account
.getGroupStore().updateGroup(g
);
577 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
580 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
582 if (groupId
== null) {
584 g
= new GroupInfo(KeyUtils
.createGroupId());
585 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
587 g
= getGroupForSending(groupId
);
594 if (members
!= null) {
595 final Set
<String
> newE164Members
= new HashSet
<>();
596 for (SignalServiceAddress member
: members
) {
597 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
600 newE164Members
.add(member
.getNumber().get());
603 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
604 if (contacts
.size() != newE164Members
.size()) {
605 // Some of the new members are not registered on Signal
606 for (ContactTokenDetails contact
: contacts
) {
607 newE164Members
.remove(contact
.getNumber());
609 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
610 System
.err
.println("Aborting…");
614 g
.addMembers(members
);
617 if (avatarFile
!= null) {
618 IOUtils
.createPrivateDirectories(avatarsPath
);
619 File aFile
= getGroupAvatarFile(g
.groupId
);
620 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
623 account
.getGroupStore().updateGroup(g
);
625 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
627 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
631 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
632 if (groupId
== null) {
635 GroupInfo g
= getGroupForSending(groupId
);
637 if (!g
.isMember(recipient
)) {
641 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
643 // Send group message only to the recipient who requested it
644 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
647 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
648 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
651 .withMembers(new ArrayList
<>(g
.getMembers()));
653 File aFile
= getGroupAvatarFile(g
.groupId
);
654 if (aFile
.exists()) {
656 group
.withAvatar(Utils
.createAttachment(aFile
));
657 } catch (IOException e
) {
658 throw new AttachmentInvalidException(aFile
.toString(), e
);
662 return SignalServiceDataMessage
.newBuilder()
663 .asGroupMessage(group
.build())
664 .withExpiration(g
.messageExpirationTime
);
667 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
668 if (groupId
== null) {
672 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
675 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
676 .asGroupMessage(group
.build());
678 // Send group info request message to the recipient who sent us a message with this groupId
679 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
682 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
683 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
684 Collections
.singletonList(messageId
),
685 System
.currentTimeMillis());
687 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
691 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
692 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
693 List
<String
> recipients
= new ArrayList
<>(1);
694 recipients
.add(recipient
);
695 return sendMessage(message
, attachments
, recipients
);
699 public long sendMessage(String messageText
, List
<String
> attachments
,
700 List
<String
> recipients
)
701 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
702 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
703 if (attachments
!= null) {
704 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
706 // Upload attachments here, so we only upload once even for multiple recipients
707 SignalServiceMessageSender messageSender
= getMessageSender();
708 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
709 for (SignalServiceAttachment attachment
: attachmentStreams
) {
710 if (attachment
.isStream()) {
711 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
712 } else if (attachment
.isPointer()) {
713 attachmentPointers
.add(attachment
.asPointer());
717 messageBuilder
.withAttachments(attachmentPointers
);
719 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
722 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
723 long targetSentTimestamp
, List
<String
> recipients
)
724 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
725 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
726 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
727 .withReaction(reaction
);
728 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
732 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
733 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
734 .asEndSessionMessage();
736 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
738 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
739 } catch (Exception e
) {
740 for (SignalServiceAddress address
: signalServiceAddresses
) {
741 handleEndSession(address
);
748 public String
getContactName(String number
) throws InvalidNumberException
{
749 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
750 if (contact
== null) {
758 public void setContactName(String number
, String name
) throws InvalidNumberException
{
759 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
760 ContactInfo contact
= account
.getContactStore().getContact(address
);
761 if (contact
== null) {
762 contact
= new ContactInfo(address
);
763 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
765 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
768 account
.getContactStore().updateContact(contact
);
773 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
774 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
777 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
778 ContactInfo contact
= account
.getContactStore().getContact(address
);
779 if (contact
== null) {
780 contact
= new ContactInfo(address
);
781 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
783 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
785 contact
.blocked
= blocked
;
786 account
.getContactStore().updateContact(contact
);
791 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
792 GroupInfo group
= getGroup(groupId
);
794 throw new GroupNotFoundException(groupId
);
796 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
797 group
.blocked
= blocked
;
798 account
.getGroupStore().updateGroup(group
);
804 public List
<byte[]> getGroupIds() {
805 List
<GroupInfo
> groups
= getGroups();
806 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
807 for (GroupInfo group
: groups
) {
808 ids
.add(group
.groupId
);
814 public String
getGroupName(byte[] groupId
) {
815 GroupInfo group
= getGroup(groupId
);
824 public List
<String
> getGroupMembers(byte[] groupId
) {
825 GroupInfo group
= getGroup(groupId
);
827 return Collections
.emptyList();
829 return new ArrayList
<>(group
.getMembersE164());
834 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
835 if (groupId
.length
== 0) {
838 if (name
.isEmpty()) {
841 if (members
.size() == 0) {
844 if (avatar
.isEmpty()) {
847 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
851 * Change the expiration timer for a contact
853 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
854 ContactInfo c
= account
.getContactStore().getContact(address
);
855 c
.messageExpirationTime
= messageExpirationTimer
;
856 account
.getContactStore().updateContact(c
);
860 * Change the expiration timer for a group
862 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
863 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
864 g
.messageExpirationTime
= messageExpirationTimer
;
865 account
.getGroupStore().updateGroup(g
);
869 * Upload the sticker pack from path.
871 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
872 * @return if successful, returns the URL to install the sticker pack in the signal app
874 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
875 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
877 SignalServiceMessageSender messageSender
= getMessageSender();
879 byte[] packKey
= KeyUtils
.createStickerUploadKey();
880 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
883 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
885 } catch (URISyntaxException e
) {
886 throw new AssertionError(e
);
890 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
892 String rootPath
= null;
894 final File file
= new File(path
);
895 if (file
.getName().endsWith(".zip")) {
896 zip
= new ZipFile(file
);
897 } else if (file
.getName().equals("manifest.json")) {
898 rootPath
= file
.getParent();
900 throw new StickerPackInvalidException("Could not find manifest.json");
903 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
905 if (pack
.stickers
== null) {
906 throw new StickerPackInvalidException("Must set a 'stickers' field.");
909 if (pack
.stickers
.isEmpty()) {
910 throw new StickerPackInvalidException("Must include stickers.");
913 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
914 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
915 if (sticker
.file
== null) {
916 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
919 Pair
<InputStream
, Long
> data
;
921 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
922 } catch (IOException ignored
) {
923 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
926 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
927 stickers
.add(stickerInfo
);
930 StickerInfo cover
= null;
931 if (pack
.cover
!= null) {
932 if (pack
.cover
.file
== null) {
933 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
936 Pair
<InputStream
, Long
> data
;
938 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
939 } catch (IOException ignored
) {
940 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
943 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
946 return new SignalServiceStickerManifestUpload(
953 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
954 InputStream inputStream
;
956 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
958 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
960 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
963 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
965 final ZipEntry entry
= zip
.getEntry(subfile
);
966 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
968 final File file
= new File(rootPath
, subfile
);
969 return new Pair
<>(new FileInputStream(file
), file
.length());
973 private void requestSyncGroups() throws IOException
{
974 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
975 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
977 sendSyncMessage(message
);
978 } catch (UntrustedIdentityException e
) {
983 private void requestSyncContacts() throws IOException
{
984 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
985 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
987 sendSyncMessage(message
);
988 } catch (UntrustedIdentityException e
) {
993 private void requestSyncBlocked() throws IOException
{
994 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
995 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
997 sendSyncMessage(message
);
998 } catch (UntrustedIdentityException e
) {
1003 private void requestSyncConfiguration() throws IOException
{
1004 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1005 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1007 sendSyncMessage(message
);
1008 } catch (UntrustedIdentityException e
) {
1009 e
.printStackTrace();
1013 private byte[] getSenderCertificate() {
1014 // TODO support UUID capable sender certificates
1015 // byte[] certificate = accountManager.getSenderCertificate();
1018 certificate
= accountManager
.getSenderCertificateLegacy();
1019 } catch (IOException e
) {
1020 System
.err
.println("Failed to get sender certificate: " + e
);
1023 // TODO cache for a day
1027 private byte[] getSelfUnidentifiedAccessKey() {
1028 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1031 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1032 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1034 return new SignalProfile(
1035 encryptedProfile
.getIdentityKey(),
1036 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1037 encryptedProfile
.getAvatar(),
1038 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1039 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1041 } catch (InvalidCiphertextException e
) {
1046 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1047 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1048 if (contact
== null || contact
.profileKey
== null) {
1051 ProfileKey theirProfileKey
;
1053 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1054 } catch (InvalidInputException
| IOException e
) {
1055 throw new AssertionError(e
);
1057 SignalProfile targetProfile
;
1059 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1060 } catch (IOException e
) {
1061 System
.err
.println("Failed to get recipient profile: " + e
);
1065 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1069 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1070 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1073 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1076 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1077 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1078 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1080 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1081 return Optional
.absent();
1085 return Optional
.of(new UnidentifiedAccessPair(
1086 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1087 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1089 } catch (InvalidCertificateException e
) {
1090 return Optional
.absent();
1094 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1095 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1096 for (SignalServiceAddress recipient
: recipients
) {
1097 result
.add(getAccessFor(recipient
));
1102 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1103 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1104 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1105 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1107 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1108 return Optional
.absent();
1112 return Optional
.of(new UnidentifiedAccessPair(
1113 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1114 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1116 } catch (InvalidCertificateException e
) {
1117 return Optional
.absent();
1121 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1122 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1124 if (unidentifiedAccess
.isPresent()) {
1125 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1128 return Optional
.absent();
1131 private void sendSyncMessage(SignalServiceSyncMessage message
)
1132 throws IOException
, UntrustedIdentityException
{
1133 SignalServiceMessageSender messageSender
= getMessageSender();
1135 messageSender
.sendMessage(message
, getAccessForSync());
1136 } catch (UntrustedIdentityException e
) {
1137 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1143 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1145 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1146 throws EncapsulatedExceptions
, IOException
{
1147 final long timestamp
= System
.currentTimeMillis();
1148 messageBuilder
.withTimestamp(timestamp
);
1149 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1151 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1152 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1153 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1155 for (SendMessageResult result
: results
) {
1156 if (result
.isUnregisteredFailure()) {
1157 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1158 } else if (result
.isNetworkFailure()) {
1159 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1160 } else if (result
.getIdentityFailure() != null) {
1161 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1164 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1165 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1170 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1171 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1173 for (String number
: numbers
) {
1174 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1176 return signalServiceAddresses
;
1179 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1180 throws IOException
{
1181 if (messagePipe
== null) {
1182 messagePipe
= getMessageReceiver().createMessagePipe();
1184 if (unidentifiedMessagePipe
== null) {
1185 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1187 SignalServiceDataMessage message
= null;
1189 SignalServiceMessageSender messageSender
= getMessageSender();
1191 message
= messageBuilder
.build();
1192 if (message
.getGroupContext().isPresent()) {
1194 final boolean isRecipientUpdate
= false;
1195 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1196 for (SendMessageResult r
: result
) {
1197 if (r
.getIdentityFailure() != null) {
1198 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1202 } catch (UntrustedIdentityException e
) {
1203 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1204 return Collections
.emptyList();
1206 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1207 SignalServiceAddress recipient
= account
.getSelfAddress();
1208 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1209 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1210 message
.getTimestamp(),
1212 message
.getExpiresInSeconds(),
1213 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1215 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1217 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1219 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1220 } catch (UntrustedIdentityException e
) {
1221 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1222 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1226 // Send to all individually, so sync messages are sent correctly
1227 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1228 for (SignalServiceAddress address
: recipients
) {
1229 ContactInfo contact
= account
.getContactStore().getContact(address
);
1230 if (contact
!= null) {
1231 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1232 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1234 messageBuilder
.withExpiration(0);
1235 messageBuilder
.withProfileKey(null);
1237 message
= messageBuilder
.build();
1239 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1240 results
.add(result
);
1241 } catch (UntrustedIdentityException e
) {
1242 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1243 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1249 if (message
!= null && message
.isEndSession()) {
1250 for (SignalServiceAddress recipient
: recipients
) {
1251 handleEndSession(recipient
);
1258 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1259 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1261 return cipher
.decrypt(envelope
);
1262 } catch (ProtocolUntrustedIdentityException e
) {
1263 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1264 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1265 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1266 throw identityException
;
1268 throw new AssertionError(e
);
1272 private void handleEndSession(SignalServiceAddress source
) {
1273 account
.getSignalProtocolStore().deleteAllSessions(source
);
1276 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1277 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1278 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1279 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1280 switch (groupInfo
.getType()) {
1282 if (group
== null) {
1283 group
= new GroupInfo(groupInfo
.getGroupId());
1286 if (groupInfo
.getAvatar().isPresent()) {
1287 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1288 if (avatar
.isPointer()) {
1290 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1291 } catch (IOException
| InvalidMessageException e
) {
1292 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1297 if (groupInfo
.getName().isPresent()) {
1298 group
.name
= groupInfo
.getName().get();
1301 if (groupInfo
.getMembers().isPresent()) {
1302 group
.addMembers(groupInfo
.getMembers().get()
1304 .map(this::resolveSignalServiceAddress
)
1305 .collect(Collectors
.toSet()));
1308 account
.getGroupStore().updateGroup(group
);
1311 if (group
== null) {
1313 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1314 } catch (IOException
| EncapsulatedExceptions e
) {
1315 e
.printStackTrace();
1320 if (group
== null) {
1322 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1323 } catch (IOException
| EncapsulatedExceptions e
) {
1324 e
.printStackTrace();
1327 group
.removeMember(source
);
1328 account
.getGroupStore().updateGroup(group
);
1332 if (group
!= null) {
1334 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1335 } catch (IOException
| EncapsulatedExceptions e
) {
1336 e
.printStackTrace();
1337 } catch (NotAGroupMemberException e
) {
1338 // We have left this group, so don't send a group update message
1344 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1345 if (message
.isEndSession()) {
1346 handleEndSession(conversationPartnerAddress
);
1348 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1349 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1350 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1351 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1352 if (group
== null) {
1353 group
= new GroupInfo(groupInfo
.getGroupId());
1355 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1356 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1357 account
.getGroupStore().updateGroup(group
);
1360 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1361 if (contact
== null) {
1362 contact
= new ContactInfo(conversationPartnerAddress
);
1364 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1365 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1366 account
.getContactStore().updateContact(contact
);
1370 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1371 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1372 if (attachment
.isPointer()) {
1374 retrieveAttachment(attachment
.asPointer());
1375 } catch (IOException
| InvalidMessageException e
) {
1376 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1381 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1382 if (source
.matches(account
.getSelfAddress())) {
1384 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1385 } catch (InvalidInputException ignored
) {
1387 ContactInfo contact
= account
.getContactStore().getContact(source
);
1388 if (contact
!= null) {
1389 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1390 account
.getContactStore().updateContact(contact
);
1393 ContactInfo contact
= account
.getContactStore().getContact(source
);
1394 if (contact
== null) {
1395 contact
= new ContactInfo(source
);
1397 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1398 account
.getContactStore().updateContact(contact
);
1401 if (message
.getPreviews().isPresent()) {
1402 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1403 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1404 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1405 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1407 retrieveAttachment(attachment
);
1408 } catch (IOException
| InvalidMessageException e
) {
1409 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1416 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1417 final File cachePath
= new File(getMessageCachePath());
1418 if (!cachePath
.exists()) {
1421 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1422 if (!dir
.isDirectory()) {
1423 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1427 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1428 if (!fileEntry
.isFile()) {
1431 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1433 // Try to delete directory if empty
1438 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1439 SignalServiceEnvelope envelope
;
1441 envelope
= Utils
.loadEnvelope(fileEntry
);
1442 if (envelope
== null) {
1445 } catch (IOException e
) {
1446 e
.printStackTrace();
1449 SignalServiceContent content
= null;
1450 if (!envelope
.isReceipt()) {
1452 content
= decryptMessage(envelope
);
1453 } catch (Exception e
) {
1456 handleMessage(envelope
, content
, ignoreAttachments
);
1459 handler
.handleMessage(envelope
, content
, null);
1461 Files
.delete(fileEntry
.toPath());
1462 } catch (IOException e
) {
1463 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1467 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1468 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1469 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1472 if (messagePipe
== null) {
1473 messagePipe
= messageReceiver
.createMessagePipe();
1477 SignalServiceEnvelope envelope
;
1478 SignalServiceContent content
= null;
1479 Exception exception
= null;
1480 final long now
= new Date().getTime();
1482 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1483 // store message on disk, before acknowledging receipt to the server
1485 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1486 Utils
.storeEnvelope(envelope1
, cacheFile
);
1487 } catch (IOException e
) {
1488 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1491 } catch (TimeoutException e
) {
1492 if (returnOnTimeout
)
1495 } catch (InvalidVersionException e
) {
1496 System
.err
.println("Ignoring error: " + e
.getMessage());
1499 if (!envelope
.isReceipt()) {
1501 content
= decryptMessage(envelope
);
1502 } catch (Exception e
) {
1505 handleMessage(envelope
, content
, ignoreAttachments
);
1508 if (!isMessageBlocked(envelope
, content
)) {
1509 handler
.handleMessage(envelope
, content
, exception
);
1511 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1512 File cacheFile
= null;
1514 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1515 Files
.delete(cacheFile
.toPath());
1516 // Try to delete directory if empty
1517 new File(getMessageCachePath()).delete();
1518 } catch (IOException e
) {
1519 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1524 if (messagePipe
!= null) {
1525 messagePipe
.shutdown();
1531 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1532 SignalServiceAddress source
;
1533 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1534 source
= envelope
.getSourceAddress();
1535 } else if (content
!= null) {
1536 source
= content
.getSender();
1540 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1541 if (sourceContact
!= null && sourceContact
.blocked
) {
1545 if (content
!= null && content
.getDataMessage().isPresent()) {
1546 SignalServiceDataMessage message
= content
.getDataMessage().get();
1547 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1548 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1549 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1550 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1558 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1559 if (content
!= null) {
1560 SignalServiceAddress sender
;
1561 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1562 sender
= envelope
.getSourceAddress();
1564 sender
= content
.getSender();
1566 if (content
.getDataMessage().isPresent()) {
1567 SignalServiceDataMessage message
= content
.getDataMessage().get();
1569 if (content
.isNeedsReceipt()) {
1571 sendReceipt(sender
, message
.getTimestamp());
1572 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1573 e
.printStackTrace();
1577 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1579 if (content
.getSyncMessage().isPresent()) {
1580 account
.setMultiDevice(true);
1581 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1582 if (syncMessage
.getSent().isPresent()) {
1583 SentTranscriptMessage message
= syncMessage
.getSent().get();
1584 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1586 if (syncMessage
.getRequest().isPresent()) {
1587 RequestMessage rm
= syncMessage
.getRequest().get();
1588 if (rm
.isContactsRequest()) {
1591 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1592 e
.printStackTrace();
1595 if (rm
.isGroupsRequest()) {
1598 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1599 e
.printStackTrace();
1602 if (rm
.isBlockedListRequest()) {
1605 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1606 e
.printStackTrace();
1609 // TODO Handle rm.isConfigurationRequest();
1611 if (syncMessage
.getGroups().isPresent()) {
1612 File tmpFile
= null;
1614 tmpFile
= IOUtils
.createTempFile();
1615 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1616 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1618 while ((g
= s
.read()) != null) {
1619 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1620 if (syncGroup
== null) {
1621 syncGroup
= new GroupInfo(g
.getId());
1623 if (g
.getName().isPresent()) {
1624 syncGroup
.name
= g
.getName().get();
1626 syncGroup
.addMembers(g
.getMembers()
1628 .map(this::resolveSignalServiceAddress
)
1629 .collect(Collectors
.toSet()));
1630 if (!g
.isActive()) {
1631 syncGroup
.removeMember(account
.getSelfAddress());
1633 // Add ourself to the member set as it's marked as active
1634 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1636 syncGroup
.blocked
= g
.isBlocked();
1637 if (g
.getColor().isPresent()) {
1638 syncGroup
.color
= g
.getColor().get();
1641 if (g
.getAvatar().isPresent()) {
1642 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1644 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1645 syncGroup
.archived
= g
.isArchived();
1646 account
.getGroupStore().updateGroup(syncGroup
);
1649 } catch (Exception e
) {
1650 e
.printStackTrace();
1652 if (tmpFile
!= null) {
1654 Files
.delete(tmpFile
.toPath());
1655 } catch (IOException e
) {
1656 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1661 if (syncMessage
.getBlockedList().isPresent()) {
1662 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1663 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1664 setContactBlocked(resolveSignalServiceAddress(address
), true);
1666 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1668 setGroupBlocked(groupId
, true);
1669 } catch (GroupNotFoundException e
) {
1670 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1674 if (syncMessage
.getContacts().isPresent()) {
1675 File tmpFile
= null;
1677 tmpFile
= IOUtils
.createTempFile();
1678 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1679 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1680 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1681 if (contactsMessage
.isComplete()) {
1682 account
.getContactStore().clear();
1685 while ((c
= s
.read()) != null) {
1686 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1687 account
.setProfileKey(c
.getProfileKey().get());
1689 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1690 ContactInfo contact
= account
.getContactStore().getContact(address
);
1691 if (contact
== null) {
1692 contact
= new ContactInfo(address
);
1694 if (c
.getName().isPresent()) {
1695 contact
.name
= c
.getName().get();
1697 if (c
.getColor().isPresent()) {
1698 contact
.color
= c
.getColor().get();
1700 if (c
.getProfileKey().isPresent()) {
1701 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1703 if (c
.getVerified().isPresent()) {
1704 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1705 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1707 if (c
.getExpirationTimer().isPresent()) {
1708 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1710 contact
.blocked
= c
.isBlocked();
1711 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1712 contact
.archived
= c
.isArchived();
1713 account
.getContactStore().updateContact(contact
);
1715 if (c
.getAvatar().isPresent()) {
1716 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1720 } catch (Exception e
) {
1721 e
.printStackTrace();
1723 if (tmpFile
!= null) {
1725 Files
.delete(tmpFile
.toPath());
1726 } catch (IOException e
) {
1727 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1732 if (syncMessage
.getVerified().isPresent()) {
1733 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1734 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1736 if (syncMessage
.getConfiguration().isPresent()) {
1743 private File
getContactAvatarFile(String number
) {
1744 return new File(avatarsPath
, "contact-" + number
);
1747 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1748 IOUtils
.createPrivateDirectories(avatarsPath
);
1749 if (attachment
.isPointer()) {
1750 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1751 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1753 SignalServiceAttachmentStream stream
= attachment
.asStream();
1754 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1758 private File
getGroupAvatarFile(byte[] groupId
) {
1759 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1762 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1763 IOUtils
.createPrivateDirectories(avatarsPath
);
1764 if (attachment
.isPointer()) {
1765 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1766 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1768 SignalServiceAttachmentStream stream
= attachment
.asStream();
1769 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1773 public File
getAttachmentFile(long attachmentId
) {
1774 return new File(attachmentsPath
, attachmentId
+ "");
1777 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1778 IOUtils
.createPrivateDirectories(attachmentsPath
);
1779 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1782 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1783 if (storePreview
&& pointer
.getPreview().isPresent()) {
1784 File previewFile
= new File(outputFile
+ ".preview");
1785 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1786 byte[] preview
= pointer
.getPreview().get();
1787 output
.write(preview
, 0, preview
.length
);
1788 } catch (FileNotFoundException e
) {
1789 e
.printStackTrace();
1794 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1796 File tmpFile
= IOUtils
.createTempFile();
1797 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1798 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1799 byte[] buffer
= new byte[4096];
1802 while ((read
= input
.read(buffer
)) != -1) {
1803 output
.write(buffer
, 0, read
);
1805 } catch (FileNotFoundException e
) {
1806 e
.printStackTrace();
1811 Files
.delete(tmpFile
.toPath());
1812 } catch (IOException e
) {
1813 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1819 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1820 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1821 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1825 public boolean isRemote() {
1830 public String
getObjectPath() {
1834 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1835 File groupsFile
= IOUtils
.createTempFile();
1838 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1839 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1840 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1841 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1842 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1843 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1844 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1848 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1849 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1850 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1851 .withStream(groupsFileStream
)
1852 .withContentType("application/octet-stream")
1853 .withLength(groupsFile
.length())
1856 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1861 Files
.delete(groupsFile
.toPath());
1862 } catch (IOException e
) {
1863 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1868 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1869 File contactsFile
= IOUtils
.createTempFile();
1872 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1873 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1874 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1875 VerifiedMessage verifiedMessage
= null;
1876 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1877 if (currentIdentity
!= null) {
1878 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1881 ProfileKey profileKey
= null;
1883 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1884 } catch (InvalidInputException ignored
) {
1886 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1887 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1888 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1889 Optional
.of(record.messageExpirationTime
),
1890 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1893 if (account
.getProfileKey() != null) {
1894 // Send our own profile key as well
1895 out
.write(new DeviceContact(account
.getSelfAddress(),
1896 Optional
.absent(), Optional
.absent(),
1897 Optional
.absent(), Optional
.absent(),
1898 Optional
.of(account
.getProfileKey()),
1899 false, Optional
.absent(), Optional
.absent(), false));
1903 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1904 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1905 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1906 .withStream(contactsFileStream
)
1907 .withContentType("application/octet-stream")
1908 .withLength(contactsFile
.length())
1911 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1916 Files
.delete(contactsFile
.toPath());
1917 } catch (IOException e
) {
1918 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1923 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1924 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1925 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1926 if (record.blocked
) {
1927 addresses
.add(record.getAddress());
1930 List
<byte[]> groupIds
= new ArrayList
<>();
1931 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1932 if (record.blocked
) {
1933 groupIds
.add(record.groupId
);
1936 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1939 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1940 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1941 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1944 public List
<ContactInfo
> getContacts() {
1945 return account
.getContactStore().getContacts();
1948 public ContactInfo
getContact(String number
) {
1949 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1952 public GroupInfo
getGroup(byte[] groupId
) {
1953 return account
.getGroupStore().getGroup(groupId
);
1956 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1957 return account
.getSignalProtocolStore().getIdentities();
1960 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1961 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1965 * Trust this the identity with this fingerprint
1967 * @param name username of the identity
1968 * @param fingerprint Fingerprint
1970 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1971 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1972 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1976 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1977 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1981 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1983 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1984 } catch (IOException
| UntrustedIdentityException e
) {
1985 e
.printStackTrace();
1994 * Trust this the identity with this safety number
1996 * @param name username of the identity
1997 * @param safetyNumber Safety number
1999 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2000 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2001 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2005 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2006 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2010 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2012 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2013 } catch (IOException
| UntrustedIdentityException e
) {
2014 e
.printStackTrace();
2023 * Trust all keys of this identity without verification
2025 * @param name username of the identity
2027 public boolean trustIdentityAllKeys(String name
) {
2028 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2029 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2033 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2034 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2035 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2037 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2038 } catch (IOException
| UntrustedIdentityException e
) {
2039 e
.printStackTrace();
2047 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2048 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2051 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2052 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2053 return resolveSignalServiceAddress(canonicalizedNumber
);
2056 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2057 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2059 return resolveSignalServiceAddress(address
);
2062 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2063 if (address
.matches(account
.getSelfAddress())) {
2064 return account
.getSelfAddress();
2067 return account
.getRecipientStore().resolveServiceAddress(address
);
2070 public interface ReceiveMessageHandler
{
2072 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);