2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.Signal
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.JsonStickerPack
;
25 import org
.asamk
.signal
.NotAGroupMemberException
;
26 import org
.asamk
.signal
.StickerPackInvalidException
;
27 import org
.asamk
.signal
.TrustLevel
;
28 import org
.asamk
.signal
.UserAlreadyExists
;
29 import org
.asamk
.signal
.storage
.SignalAccount
;
30 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
31 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
32 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
33 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
34 import org
.asamk
.signal
.util
.IOUtils
;
35 import org
.asamk
.signal
.util
.Util
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
37 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
45 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
46 import org
.signal
.libsignal
.metadata
.SelfSendException
;
47 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
48 import org
.signal
.zkgroup
.InvalidInputException
;
49 import org
.signal
.zkgroup
.VerificationFailedException
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
105 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
106 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
107 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
108 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
109 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
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
.Map
;
138 import java
.util
.Objects
;
139 import java
.util
.Set
;
140 import java
.util
.UUID
;
141 import java
.util
.concurrent
.TimeUnit
;
142 import java
.util
.concurrent
.TimeoutException
;
143 import java
.util
.zip
.ZipEntry
;
144 import java
.util
.zip
.ZipFile
;
146 public class Manager
implements Signal
{
148 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
150 private final String settingsPath
;
151 private final String dataPath
;
152 private final String attachmentsPath
;
153 private final String avatarsPath
;
154 private final SleepTimer timer
= new UptimeSleepTimer();
156 private SignalAccount account
;
157 private String username
;
158 private SignalServiceAccountManager accountManager
;
159 private SignalServiceMessagePipe messagePipe
= null;
160 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
162 public Manager(String username
, String settingsPath
) {
163 this.username
= username
;
164 this.settingsPath
= settingsPath
;
165 this.dataPath
= this.settingsPath
+ "/data";
166 this.attachmentsPath
= this.settingsPath
+ "/attachments";
167 this.avatarsPath
= this.settingsPath
+ "/avatars";
171 public String
getUsername() {
175 public SignalServiceAddress
getSelfAddress() {
176 return account
.getSelfAddress();
179 private SignalServiceAccountManager
getSignalServiceAccountManager() {
180 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
183 private IdentityKey
getIdentity() {
184 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
187 public int getDeviceId() {
188 return account
.getDeviceId();
191 private String
getMessageCachePath() {
192 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
195 private String
getMessageCachePath(String sender
) {
196 return getMessageCachePath() + "/" + sender
.replace("/", "_");
199 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
200 String cachePath
= getMessageCachePath(sender
);
201 IOUtils
.createPrivateDirectories(cachePath
);
202 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
205 public boolean userHasKeys() {
206 return account
!= null && account
.getSignalProtocolStore() != null;
209 public void init() throws IOException
{
210 if (!SignalAccount
.userExists(dataPath
, username
)) {
213 account
= SignalAccount
.load(dataPath
, username
);
215 migrateLegacyConfigs();
217 accountManager
= getSignalServiceAccountManager();
219 if (account
.isRegistered()) {
220 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
224 if (account
.getUuid() == null) {
225 account
.setUuid(accountManager
.getOwnUuid());
229 } catch (AuthorizationFailedException e
) {
230 System
.err
.println("Authorization failed, was the number registered elsewhere?");
235 private void migrateLegacyConfigs() {
236 // Copy group avatars that were previously stored in the attachments folder
237 // to the new avatar folder
238 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
239 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
240 File avatarFile
= getGroupAvatarFile(g
.groupId
);
241 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
242 if (!avatarFile
.exists() && attachmentFile
.exists()) {
244 IOUtils
.createPrivateDirectories(avatarsPath
);
245 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
246 } catch (Exception e
) {
251 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
254 if (account
.getProfileKey() == null) {
255 // Old config file, creating new profile key
256 account
.setProfileKey(KeyUtils
.createProfileKey());
261 private void createNewIdentity() throws IOException
{
262 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
263 int registrationId
= KeyHelper
.generateRegistrationId(false);
264 if (username
== null) {
265 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
267 ProfileKey profileKey
= KeyUtils
.createProfileKey();
268 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
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 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, 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
);
360 requestSyncContacts();
361 requestSyncBlocked();
362 requestSyncConfiguration();
367 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
368 List
<DeviceInfo
> devices
= accountManager
.getDevices();
369 account
.setMultiDevice(devices
.size() > 1);
374 public void removeLinkedDevices(int deviceId
) throws IOException
{
375 accountManager
.removeDevice(deviceId
);
376 List
<DeviceInfo
> devices
= accountManager
.getDevices();
377 account
.setMultiDevice(devices
.size() > 1);
381 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
382 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
384 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
387 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
388 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
389 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
391 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
392 account
.setMultiDevice(true);
396 private List
<PreKeyRecord
> generatePreKeys() {
397 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
399 final int offset
= account
.getPreKeyIdOffset();
400 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
401 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
402 ECKeyPair keyPair
= Curve
.generateKeyPair();
403 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
408 account
.addPreKeys(records
);
414 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
416 ECKeyPair keyPair
= Curve
.generateKeyPair();
417 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
418 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
420 account
.addSignedPreKey(record);
424 } catch (InvalidKeyException e
) {
425 throw new AssertionError(e
);
429 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
430 verificationCode
= verificationCode
.replace("-", "");
431 account
.setSignalingKey(KeyUtils
.createSignalingKey());
432 // TODO make unrestricted unidentified access configurable
433 UUID uuid
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
435 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
436 account
.setRegistered(true);
437 account
.setUuid(uuid
);
438 account
.setRegistrationLockPin(pin
);
444 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
445 if (pin
.isPresent()) {
446 account
.setRegistrationLockPin(pin
.get());
447 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
449 account
.setRegistrationLockPin(null);
450 accountManager
.removeV1Pin();
455 private void refreshPreKeys() throws IOException
{
456 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
457 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
458 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
460 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
463 private SignalServiceMessageReceiver
getMessageReceiver() {
464 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
467 private SignalServiceMessageSender
getMessageSender() {
468 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
469 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
472 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
473 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
478 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
479 } catch (IOException ignored
) {
483 SignalServiceMessageReceiver receiver
= getMessageReceiver();
485 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
486 } catch (VerificationFailedException e
) {
487 throw new AssertionError(e
);
491 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
492 File file
= getGroupAvatarFile(groupId
);
493 if (!file
.exists()) {
494 return Optional
.absent();
497 return Optional
.of(Utils
.createAttachment(file
));
500 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
501 File file
= getContactAvatarFile(number
);
502 if (!file
.exists()) {
503 return Optional
.absent();
506 return Optional
.of(Utils
.createAttachment(file
));
509 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
510 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
512 throw new GroupNotFoundException(groupId
);
514 if (!g
.isMember(account
.getSelfAddress())) {
515 throw new NotAGroupMemberException(groupId
, g
.name
);
520 public List
<GroupInfo
> getGroups() {
521 return account
.getGroupStore().getGroups();
525 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
527 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
528 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
529 if (attachments
!= null) {
530 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
532 if (groupId
!= null) {
533 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
536 messageBuilder
.asGroupMessage(group
);
539 final GroupInfo g
= getGroupForSending(groupId
);
541 messageBuilder
.withExpiration(g
.messageExpirationTime
);
543 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
546 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
547 long targetSentTimestamp
, byte[] groupId
)
548 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
549 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
550 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
551 .withReaction(reaction
);
552 if (groupId
!= null) {
553 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
556 messageBuilder
.asGroupMessage(group
);
558 final GroupInfo g
= getGroupForSending(groupId
);
559 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
562 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
563 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
567 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
568 .asGroupMessage(group
);
570 final GroupInfo g
= getGroupForSending(groupId
);
571 g
.removeMember(account
.getSelfAddress());
572 account
.getGroupStore().updateGroup(g
);
574 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
577 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
579 if (groupId
== null) {
581 g
= new GroupInfo(KeyUtils
.createGroupId());
582 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
584 g
= getGroupForSending(groupId
);
591 if (members
!= null) {
592 final Set
<String
> newE164Members
= new HashSet
<>();
593 for (SignalServiceAddress member
: members
) {
594 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
597 newE164Members
.add(member
.getNumber().get());
600 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
601 if (contacts
.size() != newE164Members
.size()) {
602 // Some of the new members are not registered on Signal
603 for (ContactTokenDetails contact
: contacts
) {
604 newE164Members
.remove(contact
.getNumber());
606 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
607 System
.err
.println("Aborting…");
611 g
.addMembers(members
);
614 if (avatarFile
!= null) {
615 IOUtils
.createPrivateDirectories(avatarsPath
);
616 File aFile
= getGroupAvatarFile(g
.groupId
);
617 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
620 account
.getGroupStore().updateGroup(g
);
622 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
624 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
628 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
629 if (groupId
== null) {
632 GroupInfo g
= getGroupForSending(groupId
);
634 if (!g
.isMember(recipient
)) {
638 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
640 // Send group message only to the recipient who requested it
641 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
644 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
645 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
648 .withMembers(new ArrayList
<>(g
.getMembers()));
650 File aFile
= getGroupAvatarFile(g
.groupId
);
651 if (aFile
.exists()) {
653 group
.withAvatar(Utils
.createAttachment(aFile
));
654 } catch (IOException e
) {
655 throw new AttachmentInvalidException(aFile
.toString(), e
);
659 return SignalServiceDataMessage
.newBuilder()
660 .asGroupMessage(group
.build())
661 .withExpiration(g
.messageExpirationTime
);
664 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
665 if (groupId
== null) {
669 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
672 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
673 .asGroupMessage(group
.build());
675 // Send group info request message to the recipient who sent us a message with this groupId
676 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
679 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
680 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
681 Collections
.singletonList(messageId
),
682 System
.currentTimeMillis());
684 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
688 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
689 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
690 List
<String
> recipients
= new ArrayList
<>(1);
691 recipients
.add(recipient
);
692 sendMessage(message
, attachments
, recipients
);
696 public void sendMessage(String messageText
, List
<String
> attachments
,
697 List
<String
> recipients
)
698 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
699 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
700 if (attachments
!= null) {
701 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
703 // Upload attachments here, so we only upload once even for multiple recipients
704 SignalServiceMessageSender messageSender
= getMessageSender();
705 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
706 for (SignalServiceAttachment attachment
: attachmentStreams
) {
707 if (attachment
.isStream()) {
708 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
709 } else if (attachment
.isPointer()) {
710 attachmentPointers
.add(attachment
.asPointer());
714 messageBuilder
.withAttachments(attachmentPointers
);
716 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
719 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
720 long targetSentTimestamp
, List
<String
> recipients
)
721 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
722 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
723 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
724 .withReaction(reaction
);
725 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
729 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
730 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
731 .asEndSessionMessage();
733 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
737 public String
getContactName(String number
) throws InvalidNumberException
{
738 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
739 ContactInfo contact
= account
.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber
));
740 if (contact
== null) {
748 public void setContactName(String number
, String name
) throws InvalidNumberException
{
749 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
750 final SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
751 ContactInfo contact
= account
.getContactStore().getContact(address
);
752 if (contact
== null) {
753 contact
= new ContactInfo(address
);
754 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
756 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
759 account
.getContactStore().updateContact(contact
);
764 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
765 number
= Utils
.canonicalizeNumber(number
, account
.getUsername());
766 final SignalServiceAddress address
= new SignalServiceAddress(null, number
);
767 ContactInfo contact
= account
.getContactStore().getContact(address
);
768 if (contact
== null) {
769 contact
= new ContactInfo(address
);
770 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
772 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
774 contact
.blocked
= blocked
;
775 account
.getContactStore().updateContact(contact
);
780 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
781 GroupInfo group
= getGroup(groupId
);
783 throw new GroupNotFoundException(groupId
);
785 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
786 group
.blocked
= blocked
;
787 account
.getGroupStore().updateGroup(group
);
793 public List
<byte[]> getGroupIds() {
794 List
<GroupInfo
> groups
= getGroups();
795 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
796 for (GroupInfo group
: groups
) {
797 ids
.add(group
.groupId
);
803 public String
getGroupName(byte[] groupId
) {
804 GroupInfo group
= getGroup(groupId
);
813 public List
<String
> getGroupMembers(byte[] groupId
) {
814 GroupInfo group
= getGroup(groupId
);
816 return Collections
.emptyList();
818 return new ArrayList
<>(group
.getMembersE164());
823 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
824 if (groupId
.length
== 0) {
827 if (name
.isEmpty()) {
830 if (members
.size() == 0) {
833 if (avatar
.isEmpty()) {
836 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
840 * Change the expiration timer for a contact
842 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
843 ContactInfo c
= account
.getContactStore().getContact(address
);
844 c
.messageExpirationTime
= messageExpirationTimer
;
845 account
.getContactStore().updateContact(c
);
849 * Change the expiration timer for a group
851 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
852 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
853 g
.messageExpirationTime
= messageExpirationTimer
;
854 account
.getGroupStore().updateGroup(g
);
858 * Upload the sticker pack from path.
860 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
861 * @return if successful, returns the URL to install the sticker pack in the signal app
863 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
864 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
866 SignalServiceMessageSender messageSender
= getMessageSender();
868 byte[] packKey
= KeyUtils
.createStickerUploadKey();
869 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
872 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
874 } catch (URISyntaxException e
) {
875 throw new AssertionError(e
);
879 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
881 String rootPath
= null;
883 final File file
= new File(path
);
884 if (file
.getName().endsWith(".zip")) {
885 zip
= new ZipFile(file
);
886 } else if (file
.getName().equals("manifest.json")) {
887 rootPath
= file
.getParent();
889 throw new StickerPackInvalidException("Could not find manifest.json");
892 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
894 if (pack
.stickers
== null) {
895 throw new StickerPackInvalidException("Must set a 'stickers' field.");
898 if (pack
.stickers
.isEmpty()) {
899 throw new StickerPackInvalidException("Must include stickers.");
902 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
903 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
904 if (sticker
.file
== null) {
905 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
908 Pair
<InputStream
, Long
> data
;
910 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
911 } catch (IOException ignored
) {
912 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
915 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
916 stickers
.add(stickerInfo
);
919 StickerInfo cover
= null;
920 if (pack
.cover
!= null) {
921 if (pack
.cover
.file
== null) {
922 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
925 Pair
<InputStream
, Long
> data
;
927 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
928 } catch (IOException ignored
) {
929 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
932 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
935 return new SignalServiceStickerManifestUpload(
942 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
943 InputStream inputStream
;
945 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
947 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
949 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
952 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
954 final ZipEntry entry
= zip
.getEntry(subfile
);
955 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
957 final File file
= new File(rootPath
, subfile
);
958 return new Pair
<>(new FileInputStream(file
), file
.length());
962 private void requestSyncGroups() throws IOException
{
963 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
964 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
966 sendSyncMessage(message
);
967 } catch (UntrustedIdentityException e
) {
972 private void requestSyncContacts() throws IOException
{
973 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
974 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
976 sendSyncMessage(message
);
977 } catch (UntrustedIdentityException e
) {
982 private void requestSyncBlocked() throws IOException
{
983 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
984 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
986 sendSyncMessage(message
);
987 } catch (UntrustedIdentityException e
) {
992 private void requestSyncConfiguration() throws IOException
{
993 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
994 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
996 sendSyncMessage(message
);
997 } catch (UntrustedIdentityException e
) {
1002 private byte[] getSenderCertificate() throws IOException
{
1003 byte[] certificate
= accountManager
.getSenderCertificate();
1004 // TODO cache for a day
1008 private byte[] getSelfUnidentifiedAccessKey() {
1009 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1012 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1013 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1015 return new SignalProfile(
1016 encryptedProfile
.getIdentityKey(),
1017 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1018 encryptedProfile
.getAvatar(),
1019 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1020 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1022 } catch (InvalidCiphertextException e
) {
1027 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1028 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1029 if (contact
== null || contact
.profileKey
== null) {
1032 ProfileKey theirProfileKey
;
1034 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1035 } catch (InvalidInputException e
) {
1036 throw new AssertionError(e
);
1038 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1040 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1044 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1045 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1048 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1051 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1052 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1053 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1055 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1056 return Optional
.absent();
1060 return Optional
.of(new UnidentifiedAccessPair(
1061 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1062 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1064 } catch (InvalidCertificateException e
) {
1065 return Optional
.absent();
1069 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1070 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1071 for (SignalServiceAddress recipient
: recipients
) {
1072 result
.add(getAccessFor(recipient
));
1077 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1078 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1079 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1080 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1082 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1083 return Optional
.absent();
1087 return Optional
.of(new UnidentifiedAccessPair(
1088 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1089 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1091 } catch (InvalidCertificateException e
) {
1092 return Optional
.absent();
1096 private void sendSyncMessage(SignalServiceSyncMessage message
)
1097 throws IOException
, UntrustedIdentityException
{
1098 SignalServiceMessageSender messageSender
= getMessageSender();
1100 messageSender
.sendMessage(message
, getAccessForSync());
1101 } catch (UntrustedIdentityException e
) {
1102 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1108 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1110 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1111 throws EncapsulatedExceptions
, IOException
{
1112 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1114 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1115 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1116 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1118 for (SendMessageResult result
: results
) {
1119 if (result
.isUnregisteredFailure()) {
1120 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1121 } else if (result
.isNetworkFailure()) {
1122 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1123 } else if (result
.getIdentityFailure() != null) {
1124 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1127 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1128 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1132 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1133 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1134 final String username
= account
.getUsername();
1136 for (String number
: numbers
) {
1137 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1138 if (canonicalizedNumber
.equals(username
)) {
1139 signalServiceAddresses
.add(account
.getSelfAddress());
1141 SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
1142 ContactInfo contact
= account
.getContactStore().getContact(address
);
1143 signalServiceAddresses
.add(contact
== null
1145 : contact
.getAddress());
1148 return signalServiceAddresses
;
1151 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1152 throws IOException
{
1153 if (messagePipe
== null) {
1154 messagePipe
= getMessageReceiver().createMessagePipe();
1156 if (unidentifiedMessagePipe
== null) {
1157 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1159 SignalServiceDataMessage message
= null;
1161 SignalServiceMessageSender messageSender
= getMessageSender();
1163 message
= messageBuilder
.build();
1164 if (message
.getGroupInfo().isPresent()) {
1166 final boolean isRecipientUpdate
= false;
1167 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1168 for (SendMessageResult r
: result
) {
1169 if (r
.getIdentityFailure() != null) {
1170 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1174 } catch (UntrustedIdentityException e
) {
1175 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1176 return Collections
.emptyList();
1178 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1179 SignalServiceAddress recipient
= account
.getSelfAddress();
1180 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1181 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1182 message
.getTimestamp(),
1184 message
.getExpiresInSeconds(),
1185 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1187 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1189 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1191 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1192 } catch (UntrustedIdentityException e
) {
1193 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1194 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1198 // Send to all individually, so sync messages are sent correctly
1199 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1200 for (SignalServiceAddress address
: recipients
) {
1201 ContactInfo contact
= account
.getContactStore().getContact(address
);
1202 if (contact
!= null) {
1203 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1204 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1206 messageBuilder
.withExpiration(0);
1207 messageBuilder
.withProfileKey(null);
1209 message
= messageBuilder
.build();
1211 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1212 results
.add(result
);
1213 } catch (UntrustedIdentityException e
) {
1214 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1215 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1221 if (message
!= null && message
.isEndSession()) {
1222 for (SignalServiceAddress recipient
: recipients
) {
1223 handleEndSession(recipient
.getNumber().get());
1230 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1231 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1233 return cipher
.decrypt(envelope
);
1234 } catch (ProtocolUntrustedIdentityException e
) {
1235 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1236 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1241 private void handleEndSession(String source
) {
1242 account
.getSignalProtocolStore().deleteAllSessions(source
);
1245 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1246 if (message
.getGroupInfo().isPresent()) {
1247 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1248 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1249 switch (groupInfo
.getType()) {
1251 if (group
== null) {
1252 group
= new GroupInfo(groupInfo
.getGroupId());
1255 if (groupInfo
.getAvatar().isPresent()) {
1256 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1257 if (avatar
.isPointer()) {
1259 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1260 } catch (IOException
| InvalidMessageException e
) {
1261 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1266 if (groupInfo
.getName().isPresent()) {
1267 group
.name
= groupInfo
.getName().get();
1270 if (groupInfo
.getMembers().isPresent()) {
1271 group
.addMembers(groupInfo
.getMembers().get());
1274 account
.getGroupStore().updateGroup(group
);
1277 if (group
== null) {
1279 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1280 } catch (IOException
| EncapsulatedExceptions e
) {
1281 e
.printStackTrace();
1286 if (group
== null) {
1288 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1289 } catch (IOException
| EncapsulatedExceptions e
) {
1290 e
.printStackTrace();
1293 group
.removeMember(source
);
1294 account
.getGroupStore().updateGroup(group
);
1298 if (group
!= null) {
1300 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1301 } catch (IOException
| EncapsulatedExceptions e
) {
1302 e
.printStackTrace();
1303 } catch (NotAGroupMemberException e
) {
1304 // We have left this group, so don't send a group update message
1310 if (message
.isEndSession()) {
1311 handleEndSession(isSync ? destination
.getNumber().get() : source
.getNumber().get());
1313 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1314 if (message
.getGroupInfo().isPresent()) {
1315 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1316 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1317 if (group
== null) {
1318 group
= new GroupInfo(groupInfo
.getGroupId());
1320 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1321 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1322 account
.getGroupStore().updateGroup(group
);
1325 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1326 if (contact
== null) {
1327 contact
= new ContactInfo(isSync ? destination
: source
);
1329 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1330 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1331 account
.getContactStore().updateContact(contact
);
1335 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1336 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1337 if (attachment
.isPointer()) {
1339 retrieveAttachment(attachment
.asPointer());
1340 } catch (IOException
| InvalidMessageException e
) {
1341 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1346 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1347 if (source
.matches(account
.getSelfAddress())) {
1349 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1350 } catch (InvalidInputException ignored
) {
1352 ContactInfo contact
= account
.getContactStore().getContact(source
);
1353 if (contact
!= null) {
1354 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1355 account
.getContactStore().updateContact(contact
);
1358 ContactInfo contact
= account
.getContactStore().getContact(source
);
1359 if (contact
== null) {
1360 contact
= new ContactInfo(source
);
1362 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1363 account
.getContactStore().updateContact(contact
);
1366 if (message
.getPreviews().isPresent()) {
1367 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1368 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1369 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1370 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1372 retrieveAttachment(attachment
);
1373 } catch (IOException
| InvalidMessageException e
) {
1374 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1381 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1382 final File cachePath
= new File(getMessageCachePath());
1383 if (!cachePath
.exists()) {
1386 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1387 if (!dir
.isDirectory()) {
1391 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1392 if (!fileEntry
.isFile()) {
1395 SignalServiceEnvelope envelope
;
1397 envelope
= Utils
.loadEnvelope(fileEntry
);
1398 if (envelope
== null) {
1401 } catch (IOException e
) {
1402 e
.printStackTrace();
1405 SignalServiceContent content
= null;
1406 if (!envelope
.isReceipt()) {
1408 content
= decryptMessage(envelope
);
1409 } catch (Exception e
) {
1412 handleMessage(envelope
, content
, ignoreAttachments
);
1415 handler
.handleMessage(envelope
, content
, null);
1417 Files
.delete(fileEntry
.toPath());
1418 } catch (IOException e
) {
1419 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1422 // Try to delete directory if empty
1427 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1428 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1429 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1432 if (messagePipe
== null) {
1433 messagePipe
= messageReceiver
.createMessagePipe();
1437 SignalServiceEnvelope envelope
;
1438 SignalServiceContent content
= null;
1439 Exception exception
= null;
1440 final long now
= new Date().getTime();
1442 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1443 // store message on disk, before acknowledging receipt to the server
1445 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1446 Utils
.storeEnvelope(envelope1
, cacheFile
);
1447 } catch (IOException e
) {
1448 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1451 } catch (TimeoutException e
) {
1452 if (returnOnTimeout
)
1455 } catch (InvalidVersionException e
) {
1456 System
.err
.println("Ignoring error: " + e
.getMessage());
1459 if (!envelope
.isReceipt()) {
1461 content
= decryptMessage(envelope
);
1462 } catch (Exception e
) {
1465 handleMessage(envelope
, content
, ignoreAttachments
);
1468 if (!isMessageBlocked(envelope
, content
)) {
1469 handler
.handleMessage(envelope
, content
, exception
);
1471 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1472 File cacheFile
= null;
1474 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1475 Files
.delete(cacheFile
.toPath());
1476 // Try to delete directory if empty
1477 new File(getMessageCachePath()).delete();
1478 } catch (IOException e
) {
1479 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1484 if (messagePipe
!= null) {
1485 messagePipe
.shutdown();
1491 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1492 SignalServiceAddress source
;
1493 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1494 source
= envelope
.getSourceAddress();
1495 } else if (content
!= null) {
1496 source
= content
.getSender();
1500 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1501 if (sourceContact
!= null && sourceContact
.blocked
) {
1505 if (content
!= null && content
.getDataMessage().isPresent()) {
1506 SignalServiceDataMessage message
= content
.getDataMessage().get();
1507 if (message
.getGroupInfo().isPresent()) {
1508 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1509 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1510 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1518 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1519 if (content
!= null) {
1520 SignalServiceAddress sender
;
1521 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1522 sender
= envelope
.getSourceAddress();
1524 sender
= content
.getSender();
1526 if (content
.getDataMessage().isPresent()) {
1527 SignalServiceDataMessage message
= content
.getDataMessage().get();
1529 if (content
.isNeedsReceipt()) {
1531 sendReceipt(sender
, message
.getTimestamp());
1532 } catch (IOException
| UntrustedIdentityException e
) {
1533 e
.printStackTrace();
1537 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1539 if (content
.getSyncMessage().isPresent()) {
1540 account
.setMultiDevice(true);
1541 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1542 if (syncMessage
.getSent().isPresent()) {
1543 SentTranscriptMessage message
= syncMessage
.getSent().get();
1544 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1546 if (syncMessage
.getRequest().isPresent()) {
1547 RequestMessage rm
= syncMessage
.getRequest().get();
1548 if (rm
.isContactsRequest()) {
1551 } catch (UntrustedIdentityException
| IOException e
) {
1552 e
.printStackTrace();
1555 if (rm
.isGroupsRequest()) {
1558 } catch (UntrustedIdentityException
| IOException e
) {
1559 e
.printStackTrace();
1562 if (rm
.isBlockedListRequest()) {
1565 } catch (UntrustedIdentityException
| IOException e
) {
1566 e
.printStackTrace();
1569 // TODO Handle rm.isConfigurationRequest();
1571 if (syncMessage
.getGroups().isPresent()) {
1572 File tmpFile
= null;
1574 tmpFile
= IOUtils
.createTempFile();
1575 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1576 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1578 while ((g
= s
.read()) != null) {
1579 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1580 if (syncGroup
== null) {
1581 syncGroup
= new GroupInfo(g
.getId());
1583 if (g
.getName().isPresent()) {
1584 syncGroup
.name
= g
.getName().get();
1586 syncGroup
.addMembers(g
.getMembers());
1587 if (!g
.isActive()) {
1588 syncGroup
.removeMember(account
.getSelfAddress());
1590 // Add ourself to the member set as it's marked as active
1591 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1593 syncGroup
.blocked
= g
.isBlocked();
1594 if (g
.getColor().isPresent()) {
1595 syncGroup
.color
= g
.getColor().get();
1598 if (g
.getAvatar().isPresent()) {
1599 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1601 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1602 syncGroup
.archived
= g
.isArchived();
1603 account
.getGroupStore().updateGroup(syncGroup
);
1606 } catch (Exception e
) {
1607 e
.printStackTrace();
1609 if (tmpFile
!= null) {
1611 Files
.delete(tmpFile
.toPath());
1612 } catch (IOException e
) {
1613 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1618 if (syncMessage
.getBlockedList().isPresent()) {
1619 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1620 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1621 if (address
.getNumber().isPresent()) {
1623 setContactBlocked(address
.getNumber().get(), true);
1624 } catch (InvalidNumberException e
) {
1625 e
.printStackTrace();
1629 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1631 setGroupBlocked(groupId
, true);
1632 } catch (GroupNotFoundException e
) {
1633 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1637 if (syncMessage
.getContacts().isPresent()) {
1638 File tmpFile
= null;
1640 tmpFile
= IOUtils
.createTempFile();
1641 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1642 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1643 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1644 if (contactsMessage
.isComplete()) {
1645 account
.getContactStore().clear();
1648 while ((c
= s
.read()) != null) {
1649 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1650 account
.setProfileKey(c
.getProfileKey().get());
1652 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1653 if (contact
== null) {
1654 contact
= new ContactInfo(c
.getAddress());
1656 if (c
.getName().isPresent()) {
1657 contact
.name
= c
.getName().get();
1659 if (c
.getColor().isPresent()) {
1660 contact
.color
= c
.getColor().get();
1662 if (c
.getProfileKey().isPresent()) {
1663 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1665 if (c
.getVerified().isPresent()) {
1666 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1667 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1669 if (c
.getExpirationTimer().isPresent()) {
1670 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1672 contact
.blocked
= c
.isBlocked();
1673 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1674 contact
.archived
= c
.isArchived();
1675 account
.getContactStore().updateContact(contact
);
1677 if (c
.getAvatar().isPresent()) {
1678 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1682 } catch (Exception e
) {
1683 e
.printStackTrace();
1685 if (tmpFile
!= null) {
1687 Files
.delete(tmpFile
.toPath());
1688 } catch (IOException e
) {
1689 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1694 if (syncMessage
.getVerified().isPresent()) {
1695 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1696 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1698 if (syncMessage
.getConfiguration().isPresent()) {
1705 private File
getContactAvatarFile(String number
) {
1706 return new File(avatarsPath
, "contact-" + number
);
1709 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1710 IOUtils
.createPrivateDirectories(avatarsPath
);
1711 if (attachment
.isPointer()) {
1712 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1713 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1715 SignalServiceAttachmentStream stream
= attachment
.asStream();
1716 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1720 private File
getGroupAvatarFile(byte[] groupId
) {
1721 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1724 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1725 IOUtils
.createPrivateDirectories(avatarsPath
);
1726 if (attachment
.isPointer()) {
1727 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1728 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1730 SignalServiceAttachmentStream stream
= attachment
.asStream();
1731 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1735 public File
getAttachmentFile(long attachmentId
) {
1736 return new File(attachmentsPath
, attachmentId
+ "");
1739 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1740 IOUtils
.createPrivateDirectories(attachmentsPath
);
1741 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1744 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1745 if (storePreview
&& pointer
.getPreview().isPresent()) {
1746 File previewFile
= new File(outputFile
+ ".preview");
1747 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1748 byte[] preview
= pointer
.getPreview().get();
1749 output
.write(preview
, 0, preview
.length
);
1750 } catch (FileNotFoundException e
) {
1751 e
.printStackTrace();
1756 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1758 File tmpFile
= IOUtils
.createTempFile();
1759 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1760 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1761 byte[] buffer
= new byte[4096];
1764 while ((read
= input
.read(buffer
)) != -1) {
1765 output
.write(buffer
, 0, read
);
1767 } catch (FileNotFoundException e
) {
1768 e
.printStackTrace();
1773 Files
.delete(tmpFile
.toPath());
1774 } catch (IOException e
) {
1775 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1781 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1782 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1783 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1787 public boolean isRemote() {
1791 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1792 File groupsFile
= IOUtils
.createTempFile();
1795 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1796 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1797 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1798 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1799 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1800 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1801 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1805 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1806 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1807 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1808 .withStream(groupsFileStream
)
1809 .withContentType("application/octet-stream")
1810 .withLength(groupsFile
.length())
1813 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1818 Files
.delete(groupsFile
.toPath());
1819 } catch (IOException e
) {
1820 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1825 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1826 File contactsFile
= IOUtils
.createTempFile();
1829 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1830 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1831 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1832 VerifiedMessage verifiedMessage
= null;
1833 if (getIdentities().containsKey(record.number
)) {
1834 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1835 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1836 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1837 currentIdentity
= id
;
1840 if (currentIdentity
!= null) {
1841 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1845 ProfileKey profileKey
= null;
1847 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1848 } catch (InvalidInputException ignored
) {
1850 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1851 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1852 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1853 Optional
.of(record.messageExpirationTime
),
1854 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1857 if (account
.getProfileKey() != null) {
1858 // Send our own profile key as well
1859 out
.write(new DeviceContact(account
.getSelfAddress(),
1860 Optional
.absent(), Optional
.absent(),
1861 Optional
.absent(), Optional
.absent(),
1862 Optional
.of(account
.getProfileKey()),
1863 false, Optional
.absent(), Optional
.absent(), false));
1867 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1868 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1869 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1870 .withStream(contactsFileStream
)
1871 .withContentType("application/octet-stream")
1872 .withLength(contactsFile
.length())
1875 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1880 Files
.delete(contactsFile
.toPath());
1881 } catch (IOException e
) {
1882 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1887 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1888 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1889 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1890 if (record.blocked
) {
1891 addresses
.add(record.getAddress());
1894 List
<byte[]> groupIds
= new ArrayList
<>();
1895 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1896 if (record.blocked
) {
1897 groupIds
.add(record.groupId
);
1900 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1903 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1904 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1905 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1908 public List
<ContactInfo
> getContacts() {
1909 return account
.getContactStore().getContacts();
1912 public ContactInfo
getContact(String number
) {
1913 return account
.getContactStore().getContact(new SignalServiceAddress(null, number
));
1916 public GroupInfo
getGroup(byte[] groupId
) {
1917 return account
.getGroupStore().getGroup(groupId
);
1920 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1921 return account
.getSignalProtocolStore().getIdentities();
1924 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1925 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
1926 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1930 * Trust this the identity with this fingerprint
1932 * @param name username of the identity
1933 * @param fingerprint Fingerprint
1935 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1936 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1940 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1941 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1945 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1947 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1948 } catch (IOException
| UntrustedIdentityException e
) {
1949 e
.printStackTrace();
1958 * Trust this the identity with this safety number
1960 * @param name username of the identity
1961 * @param safetyNumber Safety number
1963 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1964 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1968 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1969 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1973 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1975 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1976 } catch (IOException
| UntrustedIdentityException e
) {
1977 e
.printStackTrace();
1986 * Trust all keys of this identity without verification
1988 * @param name username of the identity
1990 public boolean trustIdentityAllKeys(String name
) {
1991 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1995 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1996 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1997 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1999 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2000 } catch (IOException
| UntrustedIdentityException e
) {
2001 e
.printStackTrace();
2009 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
2010 return Utils
.computeSafetyNumber(account
.getUsername(), getIdentity(), theirUsername
, theirIdentityKey
);
2013 public interface ReceiveMessageHandler
{
2015 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);