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
.NotAGroupMemberException
;
25 import org
.asamk
.signal
.StickerPackInvalidException
;
26 import org
.asamk
.signal
.TrustLevel
;
27 import org
.asamk
.signal
.UserAlreadyExists
;
28 import org
.asamk
.signal
.storage
.SignalAccount
;
29 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
30 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
31 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
32 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
33 import org
.asamk
.signal
.util
.IOUtils
;
34 import org
.asamk
.signal
.util
.Util
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
45 import org
.signal
.libsignal
.metadata
.SelfSendException
;
46 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
47 import org
.signal
.zkgroup
.InvalidInputException
;
48 import org
.signal
.zkgroup
.VerificationFailedException
;
49 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
50 import org
.whispersystems
.libsignal
.IdentityKey
;
51 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
52 import org
.whispersystems
.libsignal
.InvalidKeyException
;
53 import org
.whispersystems
.libsignal
.InvalidMessageException
;
54 import org
.whispersystems
.libsignal
.InvalidVersionException
;
55 import org
.whispersystems
.libsignal
.ecc
.Curve
;
56 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
57 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
58 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
59 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
60 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
61 import org
.whispersystems
.libsignal
.util
.Medium
;
62 import org
.whispersystems
.libsignal
.util
.Pair
;
63 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
64 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
98 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
99 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
100 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
101 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
104 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
105 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
106 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
107 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
108 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
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
.Objects
;
137 import java
.util
.Set
;
138 import java
.util
.UUID
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.stream
.Collectors
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
{
147 private final String settingsPath
;
148 private final String dataPath
;
149 private final String attachmentsPath
;
150 private final String avatarsPath
;
151 private final SleepTimer timer
= new UptimeSleepTimer();
153 private SignalAccount account
;
154 private String username
;
155 private SignalServiceAccountManager accountManager
;
156 private SignalServiceMessagePipe messagePipe
= null;
157 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
159 public Manager(String username
, String settingsPath
) {
160 this.username
= username
;
161 this.settingsPath
= settingsPath
;
162 this.dataPath
= this.settingsPath
+ "/data";
163 this.attachmentsPath
= this.settingsPath
+ "/attachments";
164 this.avatarsPath
= this.settingsPath
+ "/avatars";
168 public String
getUsername() {
172 public SignalServiceAddress
getSelfAddress() {
173 return account
.getSelfAddress();
176 private SignalServiceAccountManager
getSignalServiceAccountManager() {
177 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
180 private IdentityKey
getIdentity() {
181 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
184 public int getDeviceId() {
185 return account
.getDeviceId();
188 private String
getMessageCachePath() {
189 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
192 private String
getMessageCachePath(String sender
) {
193 if (sender
== null || sender
.isEmpty()) {
194 return getMessageCachePath();
197 return getMessageCachePath() + "/" + sender
.replace("/", "_");
200 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
201 String cachePath
= getMessageCachePath(sender
);
202 IOUtils
.createPrivateDirectories(cachePath
);
203 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
206 public boolean userHasKeys() {
207 return account
!= null && account
.getSignalProtocolStore() != null;
210 public void init() throws IOException
{
211 if (!SignalAccount
.userExists(dataPath
, username
)) {
214 account
= SignalAccount
.load(dataPath
, username
);
215 account
.setResolver(this::resolveSignalServiceAddress
);
217 migrateLegacyConfigs();
219 accountManager
= getSignalServiceAccountManager();
220 if (account
.isRegistered()) {
221 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
225 if (account
.getUuid() == null) {
226 account
.setUuid(accountManager
.getOwnUuid());
232 private void migrateLegacyConfigs() {
233 // Copy group avatars that were previously stored in the attachments folder
234 // to the new avatar folder
235 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
236 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
237 File avatarFile
= getGroupAvatarFile(g
.groupId
);
238 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
239 if (!avatarFile
.exists() && attachmentFile
.exists()) {
241 IOUtils
.createPrivateDirectories(avatarsPath
);
242 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
243 } catch (Exception e
) {
248 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
251 if (account
.getProfileKey() == null) {
252 // Old config file, creating new profile key
253 account
.setProfileKey(KeyUtils
.createProfileKey());
258 private void createNewIdentity() throws IOException
{
259 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
260 int registrationId
= KeyHelper
.generateRegistrationId(false);
261 if (username
== null) {
262 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
263 account
.setResolver(this::resolveSignalServiceAddress
);
265 ProfileKey profileKey
= KeyUtils
.createProfileKey();
266 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
267 account
.setResolver(this::resolveSignalServiceAddress
);
272 public boolean isRegistered() {
273 return account
!= null && account
.isRegistered();
276 public void register(boolean voiceVerification
) throws IOException
{
277 if (account
== null) {
280 account
.setPassword(KeyUtils
.createPassword());
281 account
.setUuid(null);
282 accountManager
= getSignalServiceAccountManager();
284 if (voiceVerification
) {
285 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
287 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
290 account
.setRegistered(false);
294 public void updateAccountAttributes() throws IOException
{
295 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
298 public void setProfileName(String name
) throws IOException
{
299 accountManager
.setProfileName(account
.getProfileKey(), name
);
302 public void setProfileAvatar(File avatar
) throws IOException
{
303 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
304 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
305 streamDetails
.getStream().close();
308 public void removeProfileAvatar() throws IOException
{
309 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
312 public void unregister() throws IOException
{
313 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
314 // If this is the master device, other users can't send messages to this number anymore.
315 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
316 accountManager
.setGcmId(Optional
.absent());
318 account
.setRegistered(false);
322 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
323 if (account
== null) {
326 account
.setPassword(KeyUtils
.createPassword());
327 accountManager
= getSignalServiceAccountManager();
328 String uuid
= accountManager
.getNewDeviceUuid();
330 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
333 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
334 account
.setSignalingKey(KeyUtils
.createSignalingKey());
335 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
337 username
= ret
.getNumber();
338 // TODO do this check before actually registering
339 if (SignalAccount
.userExists(dataPath
, username
)) {
340 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
343 // Create new account with the synced identity
344 byte[] profileKeyBytes
= ret
.getProfileKey();
345 ProfileKey profileKey
;
346 if (profileKeyBytes
== null) {
347 profileKey
= KeyUtils
.createProfileKey();
350 profileKey
= new ProfileKey(profileKeyBytes
);
351 } catch (InvalidInputException e
) {
352 throw new IOException("Received invalid profileKey", e
);
355 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
356 account
.setResolver(this::resolveSignalServiceAddress
);
361 requestSyncContacts();
362 requestSyncBlocked();
363 requestSyncConfiguration();
368 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
369 List
<DeviceInfo
> devices
= accountManager
.getDevices();
370 account
.setMultiDevice(devices
.size() > 1);
375 public void removeLinkedDevices(int deviceId
) throws IOException
{
376 accountManager
.removeDevice(deviceId
);
377 List
<DeviceInfo
> devices
= accountManager
.getDevices();
378 account
.setMultiDevice(devices
.size() > 1);
382 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
383 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
385 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
388 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
389 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
390 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
392 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
393 account
.setMultiDevice(true);
397 private List
<PreKeyRecord
> generatePreKeys() {
398 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
400 final int offset
= account
.getPreKeyIdOffset();
401 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
402 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
403 ECKeyPair keyPair
= Curve
.generateKeyPair();
404 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
409 account
.addPreKeys(records
);
415 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
417 ECKeyPair keyPair
= Curve
.generateKeyPair();
418 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
419 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
421 account
.addSignedPreKey(record);
425 } catch (InvalidKeyException e
) {
426 throw new AssertionError(e
);
430 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
431 verificationCode
= verificationCode
.replace("-", "");
432 account
.setSignalingKey(KeyUtils
.createSignalingKey());
433 // TODO make unrestricted unidentified access configurable
434 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
436 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
437 account
.setRegistered(true);
438 account
.setUuid(uuid
);
439 account
.setRegistrationLockPin(pin
);
440 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
446 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
447 if (pin
.isPresent()) {
448 account
.setRegistrationLockPin(pin
.get());
449 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
451 account
.setRegistrationLockPin(null);
452 accountManager
.removeV1Pin();
457 private void refreshPreKeys() throws IOException
{
458 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
459 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
460 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
462 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
465 private SignalServiceMessageReceiver
getMessageReceiver() {
466 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
469 private SignalServiceMessageSender
getMessageSender() {
470 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
471 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
474 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
475 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
480 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
481 } catch (IOException ignored
) {
485 SignalServiceMessageReceiver receiver
= getMessageReceiver();
487 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
488 } catch (VerificationFailedException e
) {
489 throw new AssertionError(e
);
493 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
494 File file
= getGroupAvatarFile(groupId
);
495 if (!file
.exists()) {
496 return Optional
.absent();
499 return Optional
.of(Utils
.createAttachment(file
));
502 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
503 File file
= getContactAvatarFile(number
);
504 if (!file
.exists()) {
505 return Optional
.absent();
508 return Optional
.of(Utils
.createAttachment(file
));
511 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
512 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
514 throw new GroupNotFoundException(groupId
);
516 if (!g
.isMember(account
.getSelfAddress())) {
517 throw new NotAGroupMemberException(groupId
, g
.name
);
522 public List
<GroupInfo
> getGroups() {
523 return account
.getGroupStore().getGroups();
527 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
529 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
530 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
531 if (attachments
!= null) {
532 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
534 if (groupId
!= null) {
535 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
538 messageBuilder
.asGroupMessage(group
);
541 final GroupInfo g
= getGroupForSending(groupId
);
543 messageBuilder
.withExpiration(g
.messageExpirationTime
);
545 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
548 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
549 long targetSentTimestamp
, byte[] groupId
)
550 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
551 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
552 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
553 .withReaction(reaction
);
554 if (groupId
!= null) {
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
558 messageBuilder
.asGroupMessage(group
);
560 final GroupInfo g
= getGroupForSending(groupId
);
561 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
564 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
565 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
569 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
570 .asGroupMessage(group
);
572 final GroupInfo g
= getGroupForSending(groupId
);
573 g
.removeMember(account
.getSelfAddress());
574 account
.getGroupStore().updateGroup(g
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
581 if (groupId
== null) {
583 g
= new GroupInfo(KeyUtils
.createGroupId());
584 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
586 g
= getGroupForSending(groupId
);
593 if (members
!= null) {
594 final Set
<String
> newE164Members
= new HashSet
<>();
595 for (SignalServiceAddress member
: members
) {
596 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
599 newE164Members
.add(member
.getNumber().get());
602 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
603 if (contacts
.size() != newE164Members
.size()) {
604 // Some of the new members are not registered on Signal
605 for (ContactTokenDetails contact
: contacts
) {
606 newE164Members
.remove(contact
.getNumber());
608 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
609 System
.err
.println("Aborting…");
613 g
.addMembers(members
);
616 if (avatarFile
!= null) {
617 IOUtils
.createPrivateDirectories(avatarsPath
);
618 File aFile
= getGroupAvatarFile(g
.groupId
);
619 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
622 account
.getGroupStore().updateGroup(g
);
624 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
626 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
630 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
631 if (groupId
== null) {
634 GroupInfo g
= getGroupForSending(groupId
);
636 if (!g
.isMember(recipient
)) {
640 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
642 // Send group message only to the recipient who requested it
643 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
646 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
647 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
650 .withMembers(new ArrayList
<>(g
.getMembers()));
652 File aFile
= getGroupAvatarFile(g
.groupId
);
653 if (aFile
.exists()) {
655 group
.withAvatar(Utils
.createAttachment(aFile
));
656 } catch (IOException e
) {
657 throw new AttachmentInvalidException(aFile
.toString(), e
);
661 return SignalServiceDataMessage
.newBuilder()
662 .asGroupMessage(group
.build())
663 .withExpiration(g
.messageExpirationTime
);
666 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
667 if (groupId
== null) {
671 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
674 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
675 .asGroupMessage(group
.build());
677 // Send group info request message to the recipient who sent us a message with this groupId
678 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
681 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
682 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
683 Collections
.singletonList(messageId
),
684 System
.currentTimeMillis());
686 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
690 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
691 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
692 List
<String
> recipients
= new ArrayList
<>(1);
693 recipients
.add(recipient
);
694 return sendMessage(message
, attachments
, recipients
);
698 public long sendMessage(String messageText
, List
<String
> attachments
,
699 List
<String
> recipients
)
700 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
701 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
702 if (attachments
!= null) {
703 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
705 // Upload attachments here, so we only upload once even for multiple recipients
706 SignalServiceMessageSender messageSender
= getMessageSender();
707 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
708 for (SignalServiceAttachment attachment
: attachmentStreams
) {
709 if (attachment
.isStream()) {
710 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
711 } else if (attachment
.isPointer()) {
712 attachmentPointers
.add(attachment
.asPointer());
716 messageBuilder
.withAttachments(attachmentPointers
);
718 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
721 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
722 long targetSentTimestamp
, List
<String
> recipients
)
723 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
724 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
725 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
726 .withReaction(reaction
);
727 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
731 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
732 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
733 .asEndSessionMessage();
735 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
737 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
738 } catch (Exception e
) {
739 for (SignalServiceAddress address
: signalServiceAddresses
) {
740 handleEndSession(address
);
747 public String
getContactName(String number
) throws InvalidNumberException
{
748 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
749 if (contact
== null) {
757 public void setContactName(String number
, String name
) throws InvalidNumberException
{
758 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
759 ContactInfo contact
= account
.getContactStore().getContact(address
);
760 if (contact
== null) {
761 contact
= new ContactInfo(address
);
762 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
764 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
767 account
.getContactStore().updateContact(contact
);
772 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
773 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
776 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
777 ContactInfo contact
= account
.getContactStore().getContact(address
);
778 if (contact
== null) {
779 contact
= new ContactInfo(address
);
780 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
782 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
784 contact
.blocked
= blocked
;
785 account
.getContactStore().updateContact(contact
);
790 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
791 GroupInfo group
= getGroup(groupId
);
793 throw new GroupNotFoundException(groupId
);
795 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
796 group
.blocked
= blocked
;
797 account
.getGroupStore().updateGroup(group
);
803 public List
<byte[]> getGroupIds() {
804 List
<GroupInfo
> groups
= getGroups();
805 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
806 for (GroupInfo group
: groups
) {
807 ids
.add(group
.groupId
);
813 public String
getGroupName(byte[] groupId
) {
814 GroupInfo group
= getGroup(groupId
);
823 public List
<String
> getGroupMembers(byte[] groupId
) {
824 GroupInfo group
= getGroup(groupId
);
826 return Collections
.emptyList();
828 return new ArrayList
<>(group
.getMembersE164());
833 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
834 if (groupId
.length
== 0) {
837 if (name
.isEmpty()) {
840 if (members
.size() == 0) {
843 if (avatar
.isEmpty()) {
846 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
850 * Change the expiration timer for a contact
852 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
853 ContactInfo c
= account
.getContactStore().getContact(address
);
854 c
.messageExpirationTime
= messageExpirationTimer
;
855 account
.getContactStore().updateContact(c
);
859 * Change the expiration timer for a group
861 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
862 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
863 g
.messageExpirationTime
= messageExpirationTimer
;
864 account
.getGroupStore().updateGroup(g
);
868 * Upload the sticker pack from path.
870 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
871 * @return if successful, returns the URL to install the sticker pack in the signal app
873 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
874 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
876 SignalServiceMessageSender messageSender
= getMessageSender();
878 byte[] packKey
= KeyUtils
.createStickerUploadKey();
879 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
882 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
884 } catch (URISyntaxException e
) {
885 throw new AssertionError(e
);
889 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
891 String rootPath
= null;
893 final File file
= new File(path
);
894 if (file
.getName().endsWith(".zip")) {
895 zip
= new ZipFile(file
);
896 } else if (file
.getName().equals("manifest.json")) {
897 rootPath
= file
.getParent();
899 throw new StickerPackInvalidException("Could not find manifest.json");
902 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
904 if (pack
.stickers
== null) {
905 throw new StickerPackInvalidException("Must set a 'stickers' field.");
908 if (pack
.stickers
.isEmpty()) {
909 throw new StickerPackInvalidException("Must include stickers.");
912 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
913 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
914 if (sticker
.file
== null) {
915 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
918 Pair
<InputStream
, Long
> data
;
920 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
921 } catch (IOException ignored
) {
922 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
925 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
926 stickers
.add(stickerInfo
);
929 StickerInfo cover
= null;
930 if (pack
.cover
!= null) {
931 if (pack
.cover
.file
== null) {
932 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
935 Pair
<InputStream
, Long
> data
;
937 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
938 } catch (IOException ignored
) {
939 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
942 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
945 return new SignalServiceStickerManifestUpload(
952 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
953 InputStream inputStream
;
955 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
957 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
959 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
962 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
964 final ZipEntry entry
= zip
.getEntry(subfile
);
965 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
967 final File file
= new File(rootPath
, subfile
);
968 return new Pair
<>(new FileInputStream(file
), file
.length());
972 private void requestSyncGroups() throws IOException
{
973 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
974 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
976 sendSyncMessage(message
);
977 } catch (UntrustedIdentityException e
) {
982 private void requestSyncContacts() throws IOException
{
983 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
984 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
986 sendSyncMessage(message
);
987 } catch (UntrustedIdentityException e
) {
992 private void requestSyncBlocked() throws IOException
{
993 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
994 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
996 sendSyncMessage(message
);
997 } catch (UntrustedIdentityException e
) {
1002 private void requestSyncConfiguration() throws IOException
{
1003 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1004 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1006 sendSyncMessage(message
);
1007 } catch (UntrustedIdentityException e
) {
1008 e
.printStackTrace();
1012 private byte[] getSenderCertificate() {
1013 // TODO support UUID capable sender certificates
1014 // byte[] certificate = accountManager.getSenderCertificate();
1017 certificate
= accountManager
.getSenderCertificateLegacy();
1018 } catch (IOException e
) {
1019 System
.err
.println("Failed to get sender certificate: " + e
);
1022 // TODO cache for a day
1026 private byte[] getSelfUnidentifiedAccessKey() {
1027 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1030 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1031 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1033 return new SignalProfile(
1034 encryptedProfile
.getIdentityKey(),
1035 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1036 encryptedProfile
.getAvatar(),
1037 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1038 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1040 } catch (InvalidCiphertextException e
) {
1045 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1046 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1047 if (contact
== null || contact
.profileKey
== null) {
1050 ProfileKey theirProfileKey
;
1052 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1053 } catch (InvalidInputException
| IOException e
) {
1054 throw new AssertionError(e
);
1056 SignalProfile targetProfile
;
1058 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1059 } catch (IOException e
) {
1060 System
.err
.println("Failed to get recipient profile: " + e
);
1064 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1068 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1069 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1072 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1075 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1076 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1077 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1079 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1080 return Optional
.absent();
1084 return Optional
.of(new UnidentifiedAccessPair(
1085 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1086 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1088 } catch (InvalidCertificateException e
) {
1089 return Optional
.absent();
1093 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1094 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1095 for (SignalServiceAddress recipient
: recipients
) {
1096 result
.add(getAccessFor(recipient
));
1101 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1102 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1103 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1104 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1106 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1107 return Optional
.absent();
1111 return Optional
.of(new UnidentifiedAccessPair(
1112 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1113 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1115 } catch (InvalidCertificateException e
) {
1116 return Optional
.absent();
1120 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1121 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1123 if (unidentifiedAccess
.isPresent()) {
1124 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1127 return Optional
.absent();
1130 private void sendSyncMessage(SignalServiceSyncMessage message
)
1131 throws IOException
, UntrustedIdentityException
{
1132 SignalServiceMessageSender messageSender
= getMessageSender();
1134 messageSender
.sendMessage(message
, getAccessForSync());
1135 } catch (UntrustedIdentityException e
) {
1136 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1142 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1144 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1145 throws EncapsulatedExceptions
, IOException
{
1146 final long timestamp
= System
.currentTimeMillis();
1147 messageBuilder
.withTimestamp(timestamp
);
1148 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1150 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1151 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1152 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1154 for (SendMessageResult result
: results
) {
1155 if (result
.isUnregisteredFailure()) {
1156 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1157 } else if (result
.isNetworkFailure()) {
1158 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1159 } else if (result
.getIdentityFailure() != null) {
1160 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1163 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1164 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1169 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1170 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1172 for (String number
: numbers
) {
1173 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1175 return signalServiceAddresses
;
1178 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1179 throws IOException
{
1180 if (messagePipe
== null) {
1181 messagePipe
= getMessageReceiver().createMessagePipe();
1183 if (unidentifiedMessagePipe
== null) {
1184 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1186 SignalServiceDataMessage message
= null;
1188 SignalServiceMessageSender messageSender
= getMessageSender();
1190 message
= messageBuilder
.build();
1191 if (message
.getGroupContext().isPresent()) {
1193 final boolean isRecipientUpdate
= false;
1194 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1195 for (SendMessageResult r
: result
) {
1196 if (r
.getIdentityFailure() != null) {
1197 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1201 } catch (UntrustedIdentityException e
) {
1202 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1203 return Collections
.emptyList();
1205 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1206 SignalServiceAddress recipient
= account
.getSelfAddress();
1207 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1208 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1209 message
.getTimestamp(),
1211 message
.getExpiresInSeconds(),
1212 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1214 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1216 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1218 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1219 } catch (UntrustedIdentityException e
) {
1220 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1221 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1225 // Send to all individually, so sync messages are sent correctly
1226 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1227 for (SignalServiceAddress address
: recipients
) {
1228 ContactInfo contact
= account
.getContactStore().getContact(address
);
1229 if (contact
!= null) {
1230 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1231 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1233 messageBuilder
.withExpiration(0);
1234 messageBuilder
.withProfileKey(null);
1236 message
= messageBuilder
.build();
1238 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1239 results
.add(result
);
1240 } catch (UntrustedIdentityException e
) {
1241 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1242 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1248 if (message
!= null && message
.isEndSession()) {
1249 for (SignalServiceAddress recipient
: recipients
) {
1250 handleEndSession(recipient
);
1257 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1258 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1260 return cipher
.decrypt(envelope
);
1261 } catch (ProtocolUntrustedIdentityException e
) {
1262 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1263 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1264 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1265 throw identityException
;
1267 throw new AssertionError(e
);
1271 private void handleEndSession(SignalServiceAddress source
) {
1272 account
.getSignalProtocolStore().deleteAllSessions(source
);
1275 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1276 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1277 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1278 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1279 switch (groupInfo
.getType()) {
1281 if (group
== null) {
1282 group
= new GroupInfo(groupInfo
.getGroupId());
1285 if (groupInfo
.getAvatar().isPresent()) {
1286 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1287 if (avatar
.isPointer()) {
1289 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1290 } catch (IOException
| InvalidMessageException e
) {
1291 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1296 if (groupInfo
.getName().isPresent()) {
1297 group
.name
= groupInfo
.getName().get();
1300 if (groupInfo
.getMembers().isPresent()) {
1301 group
.addMembers(groupInfo
.getMembers().get()
1303 .map(this::resolveSignalServiceAddress
)
1304 .collect(Collectors
.toSet()));
1307 account
.getGroupStore().updateGroup(group
);
1310 if (group
== null) {
1312 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1313 } catch (IOException
| EncapsulatedExceptions e
) {
1314 e
.printStackTrace();
1319 if (group
== null) {
1321 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1322 } catch (IOException
| EncapsulatedExceptions e
) {
1323 e
.printStackTrace();
1326 group
.removeMember(source
);
1327 account
.getGroupStore().updateGroup(group
);
1331 if (group
!= null) {
1333 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1334 } catch (IOException
| EncapsulatedExceptions e
) {
1335 e
.printStackTrace();
1336 } catch (NotAGroupMemberException e
) {
1337 // We have left this group, so don't send a group update message
1343 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1344 if (message
.isEndSession()) {
1345 handleEndSession(conversationPartnerAddress
);
1347 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1348 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1349 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1350 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1351 if (group
== null) {
1352 group
= new GroupInfo(groupInfo
.getGroupId());
1354 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1355 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1356 account
.getGroupStore().updateGroup(group
);
1359 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1360 if (contact
== null) {
1361 contact
= new ContactInfo(conversationPartnerAddress
);
1363 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1364 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1365 account
.getContactStore().updateContact(contact
);
1369 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1370 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1371 if (attachment
.isPointer()) {
1373 retrieveAttachment(attachment
.asPointer());
1374 } catch (IOException
| InvalidMessageException e
) {
1375 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1380 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1381 if (source
.matches(account
.getSelfAddress())) {
1383 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1384 } catch (InvalidInputException ignored
) {
1386 ContactInfo contact
= account
.getContactStore().getContact(source
);
1387 if (contact
!= null) {
1388 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1389 account
.getContactStore().updateContact(contact
);
1392 ContactInfo contact
= account
.getContactStore().getContact(source
);
1393 if (contact
== null) {
1394 contact
= new ContactInfo(source
);
1396 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1397 account
.getContactStore().updateContact(contact
);
1400 if (message
.getPreviews().isPresent()) {
1401 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1402 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1403 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1404 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1406 retrieveAttachment(attachment
);
1407 } catch (IOException
| InvalidMessageException e
) {
1408 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1415 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1416 final File cachePath
= new File(getMessageCachePath());
1417 if (!cachePath
.exists()) {
1420 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1421 if (!dir
.isDirectory()) {
1422 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1426 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1427 if (!fileEntry
.isFile()) {
1430 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1432 // Try to delete directory if empty
1437 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1438 SignalServiceEnvelope envelope
;
1440 envelope
= Utils
.loadEnvelope(fileEntry
);
1441 if (envelope
== null) {
1444 } catch (IOException e
) {
1445 e
.printStackTrace();
1448 SignalServiceContent content
= null;
1449 if (!envelope
.isReceipt()) {
1451 content
= decryptMessage(envelope
);
1452 } catch (Exception e
) {
1455 handleMessage(envelope
, content
, ignoreAttachments
);
1458 handler
.handleMessage(envelope
, content
, null);
1460 Files
.delete(fileEntry
.toPath());
1461 } catch (IOException e
) {
1462 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1466 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1467 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1468 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1471 if (messagePipe
== null) {
1472 messagePipe
= messageReceiver
.createMessagePipe();
1476 SignalServiceEnvelope envelope
;
1477 SignalServiceContent content
= null;
1478 Exception exception
= null;
1479 final long now
= new Date().getTime();
1481 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1482 // store message on disk, before acknowledging receipt to the server
1484 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1485 Utils
.storeEnvelope(envelope1
, cacheFile
);
1486 } catch (IOException e
) {
1487 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1490 } catch (TimeoutException e
) {
1491 if (returnOnTimeout
)
1494 } catch (InvalidVersionException e
) {
1495 System
.err
.println("Ignoring error: " + e
.getMessage());
1498 if (!envelope
.isReceipt()) {
1500 content
= decryptMessage(envelope
);
1501 } catch (Exception e
) {
1504 handleMessage(envelope
, content
, ignoreAttachments
);
1507 if (!isMessageBlocked(envelope
, content
)) {
1508 handler
.handleMessage(envelope
, content
, exception
);
1510 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1511 File cacheFile
= null;
1513 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1514 Files
.delete(cacheFile
.toPath());
1515 // Try to delete directory if empty
1516 new File(getMessageCachePath()).delete();
1517 } catch (IOException e
) {
1518 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1523 if (messagePipe
!= null) {
1524 messagePipe
.shutdown();
1530 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1531 SignalServiceAddress source
;
1532 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1533 source
= envelope
.getSourceAddress();
1534 } else if (content
!= null) {
1535 source
= content
.getSender();
1539 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1540 if (sourceContact
!= null && sourceContact
.blocked
) {
1544 if (content
!= null && content
.getDataMessage().isPresent()) {
1545 SignalServiceDataMessage message
= content
.getDataMessage().get();
1546 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1547 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1548 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1549 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1557 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1558 if (content
!= null) {
1559 SignalServiceAddress sender
;
1560 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1561 sender
= envelope
.getSourceAddress();
1563 sender
= content
.getSender();
1565 if (content
.getDataMessage().isPresent()) {
1566 SignalServiceDataMessage message
= content
.getDataMessage().get();
1568 if (content
.isNeedsReceipt()) {
1570 sendReceipt(sender
, message
.getTimestamp());
1571 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1572 e
.printStackTrace();
1576 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1578 if (content
.getSyncMessage().isPresent()) {
1579 account
.setMultiDevice(true);
1580 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1581 if (syncMessage
.getSent().isPresent()) {
1582 SentTranscriptMessage message
= syncMessage
.getSent().get();
1583 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1585 if (syncMessage
.getRequest().isPresent()) {
1586 RequestMessage rm
= syncMessage
.getRequest().get();
1587 if (rm
.isContactsRequest()) {
1590 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1591 e
.printStackTrace();
1594 if (rm
.isGroupsRequest()) {
1597 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1598 e
.printStackTrace();
1601 if (rm
.isBlockedListRequest()) {
1604 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1605 e
.printStackTrace();
1608 // TODO Handle rm.isConfigurationRequest();
1610 if (syncMessage
.getGroups().isPresent()) {
1611 File tmpFile
= null;
1613 tmpFile
= IOUtils
.createTempFile();
1614 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1615 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1617 while ((g
= s
.read()) != null) {
1618 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1619 if (syncGroup
== null) {
1620 syncGroup
= new GroupInfo(g
.getId());
1622 if (g
.getName().isPresent()) {
1623 syncGroup
.name
= g
.getName().get();
1625 syncGroup
.addMembers(g
.getMembers()
1627 .map(this::resolveSignalServiceAddress
)
1628 .collect(Collectors
.toSet()));
1629 if (!g
.isActive()) {
1630 syncGroup
.removeMember(account
.getSelfAddress());
1632 // Add ourself to the member set as it's marked as active
1633 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1635 syncGroup
.blocked
= g
.isBlocked();
1636 if (g
.getColor().isPresent()) {
1637 syncGroup
.color
= g
.getColor().get();
1640 if (g
.getAvatar().isPresent()) {
1641 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1643 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1644 syncGroup
.archived
= g
.isArchived();
1645 account
.getGroupStore().updateGroup(syncGroup
);
1648 } catch (Exception e
) {
1649 e
.printStackTrace();
1651 if (tmpFile
!= null) {
1653 Files
.delete(tmpFile
.toPath());
1654 } catch (IOException e
) {
1655 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1660 if (syncMessage
.getBlockedList().isPresent()) {
1661 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1662 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1663 setContactBlocked(resolveSignalServiceAddress(address
), true);
1665 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1667 setGroupBlocked(groupId
, true);
1668 } catch (GroupNotFoundException e
) {
1669 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1673 if (syncMessage
.getContacts().isPresent()) {
1674 File tmpFile
= null;
1676 tmpFile
= IOUtils
.createTempFile();
1677 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1678 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1679 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1680 if (contactsMessage
.isComplete()) {
1681 account
.getContactStore().clear();
1684 while ((c
= s
.read()) != null) {
1685 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1686 account
.setProfileKey(c
.getProfileKey().get());
1688 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1689 ContactInfo contact
= account
.getContactStore().getContact(address
);
1690 if (contact
== null) {
1691 contact
= new ContactInfo(address
);
1693 if (c
.getName().isPresent()) {
1694 contact
.name
= c
.getName().get();
1696 if (c
.getColor().isPresent()) {
1697 contact
.color
= c
.getColor().get();
1699 if (c
.getProfileKey().isPresent()) {
1700 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1702 if (c
.getVerified().isPresent()) {
1703 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1704 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1706 if (c
.getExpirationTimer().isPresent()) {
1707 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1709 contact
.blocked
= c
.isBlocked();
1710 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1711 contact
.archived
= c
.isArchived();
1712 account
.getContactStore().updateContact(contact
);
1714 if (c
.getAvatar().isPresent()) {
1715 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1719 } catch (Exception e
) {
1720 e
.printStackTrace();
1722 if (tmpFile
!= null) {
1724 Files
.delete(tmpFile
.toPath());
1725 } catch (IOException e
) {
1726 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1731 if (syncMessage
.getVerified().isPresent()) {
1732 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1733 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1735 if (syncMessage
.getConfiguration().isPresent()) {
1742 private File
getContactAvatarFile(String number
) {
1743 return new File(avatarsPath
, "contact-" + number
);
1746 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1747 IOUtils
.createPrivateDirectories(avatarsPath
);
1748 if (attachment
.isPointer()) {
1749 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1750 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1752 SignalServiceAttachmentStream stream
= attachment
.asStream();
1753 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1757 private File
getGroupAvatarFile(byte[] groupId
) {
1758 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1761 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1762 IOUtils
.createPrivateDirectories(avatarsPath
);
1763 if (attachment
.isPointer()) {
1764 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1765 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1767 SignalServiceAttachmentStream stream
= attachment
.asStream();
1768 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1772 public File
getAttachmentFile(long attachmentId
) {
1773 return new File(attachmentsPath
, attachmentId
+ "");
1776 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1777 IOUtils
.createPrivateDirectories(attachmentsPath
);
1778 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1781 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1782 if (storePreview
&& pointer
.getPreview().isPresent()) {
1783 File previewFile
= new File(outputFile
+ ".preview");
1784 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1785 byte[] preview
= pointer
.getPreview().get();
1786 output
.write(preview
, 0, preview
.length
);
1787 } catch (FileNotFoundException e
) {
1788 e
.printStackTrace();
1793 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1795 File tmpFile
= IOUtils
.createTempFile();
1796 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1797 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1798 byte[] buffer
= new byte[4096];
1801 while ((read
= input
.read(buffer
)) != -1) {
1802 output
.write(buffer
, 0, read
);
1804 } catch (FileNotFoundException e
) {
1805 e
.printStackTrace();
1810 Files
.delete(tmpFile
.toPath());
1811 } catch (IOException e
) {
1812 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1818 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1819 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1820 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1824 public boolean isRemote() {
1828 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1829 File groupsFile
= IOUtils
.createTempFile();
1832 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1833 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1834 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1835 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1836 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1837 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1838 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1842 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1843 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1844 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1845 .withStream(groupsFileStream
)
1846 .withContentType("application/octet-stream")
1847 .withLength(groupsFile
.length())
1850 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1855 Files
.delete(groupsFile
.toPath());
1856 } catch (IOException e
) {
1857 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1862 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1863 File contactsFile
= IOUtils
.createTempFile();
1866 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1867 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1868 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1869 VerifiedMessage verifiedMessage
= null;
1870 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1871 if (currentIdentity
!= null) {
1872 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1875 ProfileKey profileKey
= null;
1877 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1878 } catch (InvalidInputException ignored
) {
1880 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1881 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1882 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1883 Optional
.of(record.messageExpirationTime
),
1884 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1887 if (account
.getProfileKey() != null) {
1888 // Send our own profile key as well
1889 out
.write(new DeviceContact(account
.getSelfAddress(),
1890 Optional
.absent(), Optional
.absent(),
1891 Optional
.absent(), Optional
.absent(),
1892 Optional
.of(account
.getProfileKey()),
1893 false, Optional
.absent(), Optional
.absent(), false));
1897 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1898 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1899 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1900 .withStream(contactsFileStream
)
1901 .withContentType("application/octet-stream")
1902 .withLength(contactsFile
.length())
1905 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1910 Files
.delete(contactsFile
.toPath());
1911 } catch (IOException e
) {
1912 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1917 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1918 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1919 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1920 if (record.blocked
) {
1921 addresses
.add(record.getAddress());
1924 List
<byte[]> groupIds
= new ArrayList
<>();
1925 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1926 if (record.blocked
) {
1927 groupIds
.add(record.groupId
);
1930 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1933 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1934 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1935 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1938 public List
<ContactInfo
> getContacts() {
1939 return account
.getContactStore().getContacts();
1942 public ContactInfo
getContact(String number
) {
1943 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1946 public GroupInfo
getGroup(byte[] groupId
) {
1947 return account
.getGroupStore().getGroup(groupId
);
1950 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1951 return account
.getSignalProtocolStore().getIdentities();
1954 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1955 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1959 * Trust this the identity with this fingerprint
1961 * @param name username of the identity
1962 * @param fingerprint Fingerprint
1964 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1965 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1966 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1970 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1971 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1975 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1977 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1978 } catch (IOException
| UntrustedIdentityException e
) {
1979 e
.printStackTrace();
1988 * Trust this the identity with this safety number
1990 * @param name username of the identity
1991 * @param safetyNumber Safety number
1993 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1994 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1995 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1999 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2000 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2004 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2006 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2007 } catch (IOException
| UntrustedIdentityException e
) {
2008 e
.printStackTrace();
2017 * Trust all keys of this identity without verification
2019 * @param name username of the identity
2021 public boolean trustIdentityAllKeys(String name
) {
2022 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2023 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2027 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2028 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2029 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2031 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2032 } catch (IOException
| UntrustedIdentityException e
) {
2033 e
.printStackTrace();
2041 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2042 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2045 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2046 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2047 return resolveSignalServiceAddress(canonicalizedNumber
);
2050 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2051 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2053 return resolveSignalServiceAddress(address
);
2056 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2057 if (address
.matches(account
.getSelfAddress())) {
2058 return account
.getSelfAddress();
2061 return account
.getRecipientStore().resolveServiceAddress(address
);
2064 public interface ReceiveMessageHandler
{
2066 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);