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
.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
.AuthorizationFailedException
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
105 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
106 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
108 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
112 import org
.whispersystems
.util
.Base64
;
115 import java
.io
.FileInputStream
;
116 import java
.io
.FileNotFoundException
;
117 import java
.io
.FileOutputStream
;
118 import java
.io
.IOException
;
119 import java
.io
.InputStream
;
120 import java
.io
.OutputStream
;
122 import java
.net
.URISyntaxException
;
123 import java
.net
.URLEncoder
;
124 import java
.nio
.file
.Files
;
125 import java
.nio
.file
.Paths
;
126 import java
.nio
.file
.StandardCopyOption
;
127 import java
.util
.ArrayList
;
128 import java
.util
.Arrays
;
129 import java
.util
.Collection
;
130 import java
.util
.Collections
;
131 import java
.util
.Date
;
132 import java
.util
.HashSet
;
133 import java
.util
.LinkedList
;
134 import java
.util
.List
;
135 import java
.util
.Locale
;
136 import java
.util
.Map
;
137 import java
.util
.Objects
;
138 import java
.util
.Set
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.zip
.ZipEntry
;
142 import java
.util
.zip
.ZipFile
;
144 public class Manager
implements Signal
{
146 private static final SignalServiceProfile
.Capabilities capabilities
= new SignalServiceProfile
.Capabilities(false, false);
148 private final String settingsPath
;
149 private final String dataPath
;
150 private final String attachmentsPath
;
151 private final String avatarsPath
;
152 private final SleepTimer timer
= new UptimeSleepTimer();
154 private SignalAccount account
;
155 private String username
;
156 private SignalServiceAccountManager accountManager
;
157 private SignalServiceMessagePipe messagePipe
= null;
158 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
160 public Manager(String username
, String settingsPath
) {
161 this.username
= username
;
162 this.settingsPath
= settingsPath
;
163 this.dataPath
= this.settingsPath
+ "/data";
164 this.attachmentsPath
= this.settingsPath
+ "/attachments";
165 this.avatarsPath
= this.settingsPath
+ "/avatars";
169 public String
getUsername() {
173 public SignalServiceAddress
getSelfAddress() {
174 return account
.getSelfAddress();
177 private SignalServiceAccountManager
getSignalServiceAccountManager() {
178 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, null, account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
181 private IdentityKey
getIdentity() {
182 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
185 public int getDeviceId() {
186 return account
.getDeviceId();
189 private String
getMessageCachePath() {
190 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
193 private String
getMessageCachePath(String sender
) {
194 return getMessageCachePath() + "/" + sender
.replace("/", "_");
197 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
198 String cachePath
= getMessageCachePath(sender
);
199 IOUtils
.createPrivateDirectories(cachePath
);
200 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
203 public boolean userHasKeys() {
204 return account
!= null && account
.getSignalProtocolStore() != null;
207 public void init() throws IOException
{
208 if (!SignalAccount
.userExists(dataPath
, username
)) {
211 account
= SignalAccount
.load(dataPath
, username
);
213 migrateLegacyConfigs();
215 accountManager
= getSignalServiceAccountManager();
217 if (account
.isRegistered() && accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
221 } catch (AuthorizationFailedException e
) {
222 System
.err
.println("Authorization failed, was the number registered elsewhere?");
227 private void migrateLegacyConfigs() {
228 // Copy group avatars that were previously stored in the attachments folder
229 // to the new avatar folder
230 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
231 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
232 File avatarFile
= getGroupAvatarFile(g
.groupId
);
233 File attachmentFile
= getAttachmentFile(g
.getAvatarId());
234 if (!avatarFile
.exists() && attachmentFile
.exists()) {
236 IOUtils
.createPrivateDirectories(avatarsPath
);
237 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
238 } catch (Exception e
) {
243 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
246 if (account
.getProfileKey() == null) {
247 // Old config file, creating new profile key
248 account
.setProfileKey(KeyUtils
.createProfileKey());
253 private void createNewIdentity() throws IOException
{
254 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
255 int registrationId
= KeyHelper
.generateRegistrationId(false);
256 if (username
== null) {
257 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
259 ProfileKey profileKey
= KeyUtils
.createProfileKey();
260 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
265 public boolean isRegistered() {
266 return account
!= null && account
.isRegistered();
269 public void register(boolean voiceVerification
) throws IOException
{
270 if (account
== null) {
273 account
.setPassword(KeyUtils
.createPassword());
274 accountManager
= getSignalServiceAccountManager();
276 if (voiceVerification
) {
277 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
279 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
282 account
.setRegistered(false);
286 public void updateAccountAttributes() throws IOException
{
287 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
);
290 public void setProfileName(String name
) throws IOException
{
291 accountManager
.setProfileName(account
.getProfileKey(), name
);
294 public void setProfileAvatar(File avatar
) throws IOException
{
295 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
296 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
297 streamDetails
.getStream().close();
300 public void removeProfileAvatar() throws IOException
{
301 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
304 public void unregister() throws IOException
{
305 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
306 // If this is the master device, other users can't send messages to this number anymore.
307 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
308 accountManager
.setGcmId(Optional
.absent());
310 account
.setRegistered(false);
314 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
315 if (account
== null) {
318 account
.setPassword(KeyUtils
.createPassword());
319 accountManager
= getSignalServiceAccountManager();
320 String uuid
= accountManager
.getNewDeviceUuid();
322 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
325 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
326 account
.setSignalingKey(KeyUtils
.createSignalingKey());
327 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
329 username
= ret
.getNumber();
330 // TODO do this check before actually registering
331 if (SignalAccount
.userExists(dataPath
, username
)) {
332 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
335 // Create new account with the synced identity
336 byte[] profileKeyBytes
= ret
.getProfileKey();
337 ProfileKey profileKey
;
338 if (profileKeyBytes
== null) {
339 profileKey
= KeyUtils
.createProfileKey();
342 profileKey
= new ProfileKey(profileKeyBytes
);
343 } catch (InvalidInputException e
) {
344 throw new IOException("Received invalid profileKey", e
);
347 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
352 requestSyncContacts();
353 requestSyncBlocked();
354 requestSyncConfiguration();
359 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
360 List
<DeviceInfo
> devices
= accountManager
.getDevices();
361 account
.setMultiDevice(devices
.size() > 1);
366 public void removeLinkedDevices(int deviceId
) throws IOException
{
367 accountManager
.removeDevice(deviceId
);
368 List
<DeviceInfo
> devices
= accountManager
.getDevices();
369 account
.setMultiDevice(devices
.size() > 1);
373 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
374 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
376 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
379 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
380 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
381 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
383 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
384 account
.setMultiDevice(true);
388 private List
<PreKeyRecord
> generatePreKeys() {
389 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
391 final int offset
= account
.getPreKeyIdOffset();
392 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
393 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
394 ECKeyPair keyPair
= Curve
.generateKeyPair();
395 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
400 account
.addPreKeys(records
);
406 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
408 ECKeyPair keyPair
= Curve
.generateKeyPair();
409 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
410 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
412 account
.addSignedPreKey(record);
416 } catch (InvalidKeyException e
) {
417 throw new AssertionError(e
);
421 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
422 verificationCode
= verificationCode
.replace("-", "");
423 account
.setSignalingKey(KeyUtils
.createSignalingKey());
424 // TODO make unrestricted unidentified access configurable
425 accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
);
427 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
428 account
.setRegistered(true);
429 account
.setRegistrationLockPin(pin
);
435 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
436 if (pin
.isPresent()) {
437 account
.setRegistrationLockPin(pin
.get());
438 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
440 account
.setRegistrationLockPin(null);
441 accountManager
.removeV1Pin();
446 private void refreshPreKeys() throws IOException
{
447 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
448 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
449 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
451 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
454 private SignalServiceMessageReceiver
getMessageReceiver() {
455 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
);
458 private SignalServiceMessageSender
getMessageSender() {
459 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
460 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent());
463 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
464 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
469 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
470 } catch (IOException ignored
) {
474 SignalServiceMessageReceiver receiver
= getMessageReceiver();
476 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
477 } catch (VerificationFailedException e
) {
478 throw new AssertionError(e
);
482 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
483 File file
= getGroupAvatarFile(groupId
);
484 if (!file
.exists()) {
485 return Optional
.absent();
488 return Optional
.of(Utils
.createAttachment(file
));
491 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
492 File file
= getContactAvatarFile(number
);
493 if (!file
.exists()) {
494 return Optional
.absent();
497 return Optional
.of(Utils
.createAttachment(file
));
500 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
501 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
503 throw new GroupNotFoundException(groupId
);
505 if (!g
.isMember(account
.getSelfAddress())) {
506 throw new NotAGroupMemberException(groupId
, g
.name
);
511 public List
<GroupInfo
> getGroups() {
512 return account
.getGroupStore().getGroups();
516 public void sendGroupMessage(String messageText
, List
<String
> attachments
,
518 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
519 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
520 if (attachments
!= null) {
521 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
523 if (groupId
!= null) {
524 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
527 messageBuilder
.asGroupMessage(group
);
530 final GroupInfo g
= getGroupForSending(groupId
);
532 messageBuilder
.withExpiration(g
.messageExpirationTime
);
534 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
537 public void sendGroupMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
538 long targetSentTimestamp
, byte[] groupId
)
539 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
{
540 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
541 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
542 .withReaction(reaction
)
543 .withProfileKey(account
.getProfileKey().serialize());
544 if (groupId
!= null) {
545 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
548 messageBuilder
.asGroupMessage(group
);
550 final GroupInfo g
= getGroupForSending(groupId
);
551 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
554 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
559 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
560 .asGroupMessage(group
);
562 final GroupInfo g
= getGroupForSending(groupId
);
563 g
.removeMember(account
.getSelfAddress());
564 account
.getGroupStore().updateGroup(g
);
566 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
569 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
571 if (groupId
== null) {
573 g
= new GroupInfo(KeyUtils
.createGroupId());
574 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
576 g
= getGroupForSending(groupId
);
583 if (members
!= null) {
584 final Set
<String
> newE164Members
= new HashSet
<>();
585 for (SignalServiceAddress member
: members
) {
586 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
589 newE164Members
.add(member
.getNumber().get());
592 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
593 if (contacts
.size() != newE164Members
.size()) {
594 // Some of the new members are not registered on Signal
595 for (ContactTokenDetails contact
: contacts
) {
596 newE164Members
.remove(contact
.getNumber());
598 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
599 System
.err
.println("Aborting…");
603 g
.addMembers(members
);
606 if (avatarFile
!= null) {
607 IOUtils
.createPrivateDirectories(avatarsPath
);
608 File aFile
= getGroupAvatarFile(g
.groupId
);
609 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
612 account
.getGroupStore().updateGroup(g
);
614 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
616 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
620 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
621 if (groupId
== null) {
624 GroupInfo g
= getGroupForSending(groupId
);
626 if (!g
.isMember(recipient
)) {
630 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
632 // Send group message only to the recipient who requested it
633 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
636 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
637 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
640 .withMembers(new ArrayList
<>(g
.getMembers()));
642 File aFile
= getGroupAvatarFile(g
.groupId
);
643 if (aFile
.exists()) {
645 group
.withAvatar(Utils
.createAttachment(aFile
));
646 } catch (IOException e
) {
647 throw new AttachmentInvalidException(aFile
.toString(), e
);
651 return SignalServiceDataMessage
.newBuilder()
652 .asGroupMessage(group
.build())
653 .withExpiration(g
.messageExpirationTime
);
656 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
657 if (groupId
== null) {
661 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
664 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
665 .asGroupMessage(group
.build());
667 // Send group info request message to the recipient who sent us a message with this groupId
668 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
672 public void sendMessage(String message
, List
<String
> attachments
, String recipient
)
673 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
674 List
<String
> recipients
= new ArrayList
<>(1);
675 recipients
.add(recipient
);
676 sendMessage(message
, attachments
, recipients
);
680 public void sendMessage(String messageText
, List
<String
> attachments
,
681 List
<String
> recipients
)
682 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
683 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
684 if (attachments
!= null) {
685 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
687 // Upload attachments here, so we only upload once even for multiple recipients
688 SignalServiceMessageSender messageSender
= getMessageSender();
689 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
690 for (SignalServiceAttachment attachment
: attachmentStreams
) {
691 if (attachment
.isStream()) {
692 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
693 } else if (attachment
.isPointer()) {
694 attachmentPointers
.add(attachment
.asPointer());
698 messageBuilder
.withAttachments(attachmentPointers
);
700 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
701 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
704 public void sendMessageReaction(String emoji
, boolean remove
, SignalServiceAddress targetAuthor
,
705 long targetSentTimestamp
, List
<String
> recipients
)
706 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
707 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
);
708 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
709 .withReaction(reaction
)
710 .withProfileKey(account
.getProfileKey().serialize());
711 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
715 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
716 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
717 .asEndSessionMessage();
719 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
723 public String
getContactName(String number
) throws InvalidNumberException
{
724 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
725 ContactInfo contact
= account
.getContactStore().getContact(new SignalServiceAddress(null, canonicalizedNumber
));
726 if (contact
== null) {
734 public void setContactName(String number
, String name
) throws InvalidNumberException
{
735 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
736 final SignalServiceAddress address
= new SignalServiceAddress(null, canonicalizedNumber
);
737 ContactInfo contact
= account
.getContactStore().getContact(address
);
738 if (contact
== null) {
739 contact
= new ContactInfo(address
);
740 System
.err
.println("Add contact " + canonicalizedNumber
+ " named " + name
);
742 System
.err
.println("Updating contact " + canonicalizedNumber
+ " name " + contact
.name
+ " -> " + name
);
745 account
.getContactStore().updateContact(contact
);
750 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
751 number
= Utils
.canonicalizeNumber(number
, account
.getUsername());
752 final SignalServiceAddress address
= new SignalServiceAddress(null, number
);
753 ContactInfo contact
= account
.getContactStore().getContact(address
);
754 if (contact
== null) {
755 contact
= new ContactInfo(address
);
756 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + number
);
758 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + number
);
760 contact
.blocked
= blocked
;
761 account
.getContactStore().updateContact(contact
);
766 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
767 GroupInfo group
= getGroup(groupId
);
769 throw new GroupNotFoundException(groupId
);
771 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
772 group
.blocked
= blocked
;
773 account
.getGroupStore().updateGroup(group
);
779 public List
<byte[]> getGroupIds() {
780 List
<GroupInfo
> groups
= getGroups();
781 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
782 for (GroupInfo group
: groups
) {
783 ids
.add(group
.groupId
);
789 public String
getGroupName(byte[] groupId
) {
790 GroupInfo group
= getGroup(groupId
);
799 public List
<String
> getGroupMembers(byte[] groupId
) {
800 GroupInfo group
= getGroup(groupId
);
802 return Collections
.emptyList();
804 return new ArrayList
<>(group
.getMembersE164());
809 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
810 if (groupId
.length
== 0) {
813 if (name
.isEmpty()) {
816 if (members
.size() == 0) {
819 if (avatar
.isEmpty()) {
822 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
826 * Change the expiration timer for a contact
828 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
829 ContactInfo c
= account
.getContactStore().getContact(address
);
830 c
.messageExpirationTime
= messageExpirationTimer
;
831 account
.getContactStore().updateContact(c
);
835 * Change the expiration timer for a group
837 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
838 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
839 g
.messageExpirationTime
= messageExpirationTimer
;
840 account
.getGroupStore().updateGroup(g
);
844 * Upload the sticker pack from path.
846 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
847 * @return if successful, returns the URL to install the sticker pack in the signal app
849 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
850 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
852 SignalServiceMessageSender messageSender
= getMessageSender();
854 byte[] packKey
= KeyUtils
.createStickerUploadKey();
855 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
858 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
860 } catch (URISyntaxException e
) {
861 throw new AssertionError(e
);
865 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
867 String rootPath
= null;
869 final File file
= new File(path
);
870 if (file
.getName().endsWith(".zip")) {
871 zip
= new ZipFile(file
);
872 } else if (file
.getName().equals("manifest.json")) {
873 rootPath
= file
.getParent();
875 throw new StickerPackInvalidException("Could not find manifest.json");
878 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
880 if (pack
.stickers
== null) {
881 throw new StickerPackInvalidException("Must set a 'stickers' field.");
884 if (pack
.stickers
.isEmpty()) {
885 throw new StickerPackInvalidException("Must include stickers.");
888 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
889 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
890 if (sticker
.file
== null) {
891 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
894 Pair
<InputStream
, Long
> data
;
896 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
897 } catch (IOException ignored
) {
898 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
901 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
902 stickers
.add(stickerInfo
);
905 StickerInfo cover
= null;
906 if (pack
.cover
!= null) {
907 if (pack
.cover
.file
== null) {
908 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
911 Pair
<InputStream
, Long
> data
;
913 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
914 } catch (IOException ignored
) {
915 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
918 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
921 return new SignalServiceStickerManifestUpload(
928 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
929 InputStream inputStream
;
931 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
933 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
935 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
938 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
940 final ZipEntry entry
= zip
.getEntry(subfile
);
941 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
943 final File file
= new File(rootPath
, subfile
);
944 return new Pair
<>(new FileInputStream(file
), file
.length());
948 private void requestSyncGroups() throws IOException
{
949 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
950 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
952 sendSyncMessage(message
);
953 } catch (UntrustedIdentityException e
) {
958 private void requestSyncContacts() throws IOException
{
959 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
960 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
962 sendSyncMessage(message
);
963 } catch (UntrustedIdentityException e
) {
968 private void requestSyncBlocked() throws IOException
{
969 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
970 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
972 sendSyncMessage(message
);
973 } catch (UntrustedIdentityException e
) {
978 private void requestSyncConfiguration() throws IOException
{
979 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
980 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
982 sendSyncMessage(message
);
983 } catch (UntrustedIdentityException e
) {
988 private byte[] getSenderCertificate() throws IOException
{
989 byte[] certificate
= accountManager
.getSenderCertificate();
990 // TODO cache for a day
994 private byte[] getSelfUnidentifiedAccessKey() {
995 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
998 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
999 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1001 return new SignalProfile(
1002 encryptedProfile
.getIdentityKey(),
1003 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1004 encryptedProfile
.getAvatar(),
1005 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1006 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1008 } catch (InvalidCiphertextException e
) {
1013 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) throws IOException
{
1014 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1015 if (contact
== null || contact
.profileKey
== null) {
1018 ProfileKey theirProfileKey
;
1020 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1021 } catch (InvalidInputException e
) {
1022 throw new AssertionError(e
);
1024 SignalProfile targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1026 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1030 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1031 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1034 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1037 private Optional
<UnidentifiedAccessPair
> getAccessForSync() throws IOException
{
1038 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1039 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1041 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1042 return Optional
.absent();
1046 return Optional
.of(new UnidentifiedAccessPair(
1047 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1048 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1050 } catch (InvalidCertificateException e
) {
1051 return Optional
.absent();
1055 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) throws IOException
{
1056 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1057 for (SignalServiceAddress recipient
: recipients
) {
1058 result
.add(getAccessFor(recipient
));
1063 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) throws IOException
{
1064 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1065 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1066 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1068 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1069 return Optional
.absent();
1073 return Optional
.of(new UnidentifiedAccessPair(
1074 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1075 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1077 } catch (InvalidCertificateException e
) {
1078 return Optional
.absent();
1082 private void sendSyncMessage(SignalServiceSyncMessage message
)
1083 throws IOException
, UntrustedIdentityException
{
1084 SignalServiceMessageSender messageSender
= getMessageSender();
1086 messageSender
.sendMessage(message
, getAccessForSync());
1087 } catch (UntrustedIdentityException e
) {
1088 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1094 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1096 private void sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1097 throws EncapsulatedExceptions
, IOException
{
1098 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1100 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1101 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1102 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1104 for (SendMessageResult result
: results
) {
1105 if (result
.isUnregisteredFailure()) {
1106 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getNumber().get(), null));
1107 } else if (result
.isNetworkFailure()) {
1108 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getNumber().get(), null));
1109 } else if (result
.getIdentityFailure() != null) {
1110 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getNumber().get(), result
.getIdentityFailure().getIdentityKey()));
1113 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1114 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1118 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1119 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1120 final String username
= account
.getUsername();
1122 for (String number
: numbers
) {
1123 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, username
);
1124 if (canonicalizedNumber
.equals(username
)) {
1125 signalServiceAddresses
.add(account
.getSelfAddress());
1127 // TODO get corresponding uuid
1128 signalServiceAddresses
.add(new SignalServiceAddress(null, canonicalizedNumber
));
1131 return signalServiceAddresses
;
1134 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1135 throws IOException
{
1136 if (messagePipe
== null) {
1137 messagePipe
= getMessageReceiver().createMessagePipe();
1139 if (unidentifiedMessagePipe
== null) {
1140 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1142 SignalServiceDataMessage message
= null;
1144 SignalServiceMessageSender messageSender
= getMessageSender();
1146 message
= messageBuilder
.build();
1147 if (message
.getGroupInfo().isPresent()) {
1149 final boolean isRecipientUpdate
= false;
1150 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1151 for (SendMessageResult r
: result
) {
1152 if (r
.getIdentityFailure() != null) {
1153 account
.getSignalProtocolStore().saveIdentity(r
.getAddress().getNumber().get(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1157 } catch (UntrustedIdentityException e
) {
1158 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1159 return Collections
.emptyList();
1161 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1162 SignalServiceAddress recipient
= account
.getSelfAddress();
1163 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1164 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1165 message
.getTimestamp(),
1167 message
.getExpiresInSeconds(),
1168 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1170 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1172 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1174 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1175 } catch (UntrustedIdentityException e
) {
1176 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1177 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1181 // Send to all individually, so sync messages are sent correctly
1182 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1183 for (SignalServiceAddress address
: recipients
) {
1184 ContactInfo contact
= account
.getContactStore().getContact(address
);
1185 if (contact
!= null) {
1186 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1188 messageBuilder
.withExpiration(0);
1190 message
= messageBuilder
.build();
1192 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1193 results
.add(result
);
1194 } catch (UntrustedIdentityException e
) {
1195 account
.getSignalProtocolStore().saveIdentity(e
.getIdentifier(), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1196 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1202 if (message
!= null && message
.isEndSession()) {
1203 for (SignalServiceAddress recipient
: recipients
) {
1204 handleEndSession(recipient
.getNumber().get());
1211 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, ProtocolUntrustedIdentityException
, SelfSendException
, UnsupportedDataMessageException
{
1212 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1214 return cipher
.decrypt(envelope
);
1215 } catch (ProtocolUntrustedIdentityException e
) {
1216 // TODO We don't get the new untrusted identity from ProtocolUntrustedIdentityException anymore ... we need to get it from somewhere else
1217 // account.getSignalProtocolStore().saveIdentity(e.getSender(), e.getUntrustedIdentity(), TrustLevel.UNTRUSTED);
1222 private void handleEndSession(String source
) {
1223 account
.getSignalProtocolStore().deleteAllSessions(source
);
1226 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1227 if (message
.getGroupInfo().isPresent()) {
1228 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1229 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1230 switch (groupInfo
.getType()) {
1232 if (group
== null) {
1233 group
= new GroupInfo(groupInfo
.getGroupId());
1236 if (groupInfo
.getAvatar().isPresent()) {
1237 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1238 if (avatar
.isPointer()) {
1240 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1241 } catch (IOException
| InvalidMessageException e
) {
1242 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getId() + "): " + e
.getMessage());
1247 if (groupInfo
.getName().isPresent()) {
1248 group
.name
= groupInfo
.getName().get();
1251 if (groupInfo
.getMembers().isPresent()) {
1252 group
.addMembers(groupInfo
.getMembers().get());
1255 account
.getGroupStore().updateGroup(group
);
1258 if (group
== null) {
1260 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1261 } catch (IOException
| EncapsulatedExceptions e
) {
1262 e
.printStackTrace();
1267 if (group
== null) {
1269 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1270 } catch (IOException
| EncapsulatedExceptions e
) {
1271 e
.printStackTrace();
1274 group
.removeMember(source
);
1275 account
.getGroupStore().updateGroup(group
);
1279 if (group
!= null) {
1281 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1282 } catch (IOException
| EncapsulatedExceptions e
) {
1283 e
.printStackTrace();
1284 } catch (NotAGroupMemberException e
) {
1285 // We have left this group, so don't send a group update message
1291 if (message
.isEndSession()) {
1292 handleEndSession(isSync ? destination
.getNumber().get() : source
.getNumber().get());
1294 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1295 if (message
.getGroupInfo().isPresent()) {
1296 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1297 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1298 if (group
== null) {
1299 group
= new GroupInfo(groupInfo
.getGroupId());
1301 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1302 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1303 account
.getGroupStore().updateGroup(group
);
1306 ContactInfo contact
= account
.getContactStore().getContact(isSync ? destination
: source
);
1307 if (contact
== null) {
1308 contact
= new ContactInfo(isSync ? destination
: source
);
1310 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1311 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1312 account
.getContactStore().updateContact(contact
);
1316 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1317 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1318 if (attachment
.isPointer()) {
1320 retrieveAttachment(attachment
.asPointer());
1321 } catch (IOException
| InvalidMessageException e
) {
1322 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getId() + "): " + e
.getMessage());
1327 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1328 if (source
.equals(account
.getSelfAddress())) {
1330 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1331 } catch (InvalidInputException ignored
) {
1334 ContactInfo contact
= account
.getContactStore().getContact(source
);
1335 if (contact
== null) {
1336 contact
= new ContactInfo(source
);
1338 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1339 account
.getContactStore().updateContact(contact
);
1341 if (message
.getPreviews().isPresent()) {
1342 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1343 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1344 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1345 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1347 retrieveAttachment(attachment
);
1348 } catch (IOException
| InvalidMessageException e
) {
1349 System
.err
.println("Failed to retrieve attachment (" + attachment
.getId() + "): " + e
.getMessage());
1356 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1357 final File cachePath
= new File(getMessageCachePath());
1358 if (!cachePath
.exists()) {
1361 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1362 if (!dir
.isDirectory()) {
1366 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1367 if (!fileEntry
.isFile()) {
1370 SignalServiceEnvelope envelope
;
1372 envelope
= Utils
.loadEnvelope(fileEntry
);
1373 if (envelope
== null) {
1376 } catch (IOException e
) {
1377 e
.printStackTrace();
1380 SignalServiceContent content
= null;
1381 if (!envelope
.isReceipt()) {
1383 content
= decryptMessage(envelope
);
1384 } catch (Exception e
) {
1387 handleMessage(envelope
, content
, ignoreAttachments
);
1390 handler
.handleMessage(envelope
, content
, null);
1392 Files
.delete(fileEntry
.toPath());
1393 } catch (IOException e
) {
1394 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1397 // Try to delete directory if empty
1402 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1403 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1404 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1407 if (messagePipe
== null) {
1408 messagePipe
= messageReceiver
.createMessagePipe();
1412 SignalServiceEnvelope envelope
;
1413 SignalServiceContent content
= null;
1414 Exception exception
= null;
1415 final long now
= new Date().getTime();
1417 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1418 // store message on disk, before acknowledging receipt to the server
1420 File cacheFile
= getMessageCacheFile(envelope1
.getSourceE164().get(), now
, envelope1
.getTimestamp());
1421 Utils
.storeEnvelope(envelope1
, cacheFile
);
1422 } catch (IOException e
) {
1423 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1426 } catch (TimeoutException e
) {
1427 if (returnOnTimeout
)
1430 } catch (InvalidVersionException e
) {
1431 System
.err
.println("Ignoring error: " + e
.getMessage());
1434 if (!envelope
.isReceipt()) {
1436 content
= decryptMessage(envelope
);
1437 } catch (Exception e
) {
1440 handleMessage(envelope
, content
, ignoreAttachments
);
1443 if (!isMessageBlocked(envelope
, content
)) {
1444 handler
.handleMessage(envelope
, content
, exception
);
1446 if (!(exception
instanceof ProtocolUntrustedIdentityException
)) {
1447 File cacheFile
= null;
1449 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1450 Files
.delete(cacheFile
.toPath());
1451 // Try to delete directory if empty
1452 new File(getMessageCachePath()).delete();
1453 } catch (IOException e
) {
1454 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1459 if (messagePipe
!= null) {
1460 messagePipe
.shutdown();
1466 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1467 SignalServiceAddress source
;
1468 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1469 source
= envelope
.getSourceAddress();
1470 } else if (content
!= null) {
1471 source
= content
.getSender();
1475 ContactInfo sourceContact
= getContact(source
.getNumber().get());
1476 if (sourceContact
!= null && sourceContact
.blocked
) {
1480 if (content
!= null && content
.getDataMessage().isPresent()) {
1481 SignalServiceDataMessage message
= content
.getDataMessage().get();
1482 if (message
.getGroupInfo().isPresent()) {
1483 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
1484 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1485 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1493 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1494 if (content
!= null) {
1495 SignalServiceAddress sender
;
1496 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1497 sender
= envelope
.getSourceAddress();
1499 sender
= content
.getSender();
1501 if (content
.getDataMessage().isPresent()) {
1502 SignalServiceDataMessage message
= content
.getDataMessage().get();
1503 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1505 if (content
.getSyncMessage().isPresent()) {
1506 account
.setMultiDevice(true);
1507 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1508 if (syncMessage
.getSent().isPresent()) {
1509 SentTranscriptMessage message
= syncMessage
.getSent().get();
1510 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1512 if (syncMessage
.getRequest().isPresent()) {
1513 RequestMessage rm
= syncMessage
.getRequest().get();
1514 if (rm
.isContactsRequest()) {
1517 } catch (UntrustedIdentityException
| IOException e
) {
1518 e
.printStackTrace();
1521 if (rm
.isGroupsRequest()) {
1524 } catch (UntrustedIdentityException
| IOException e
) {
1525 e
.printStackTrace();
1528 if (rm
.isBlockedListRequest()) {
1531 } catch (UntrustedIdentityException
| IOException e
) {
1532 e
.printStackTrace();
1535 // TODO Handle rm.isConfigurationRequest();
1537 if (syncMessage
.getGroups().isPresent()) {
1538 File tmpFile
= null;
1540 tmpFile
= IOUtils
.createTempFile();
1541 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1542 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1544 while ((g
= s
.read()) != null) {
1545 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1546 if (syncGroup
== null) {
1547 syncGroup
= new GroupInfo(g
.getId());
1549 if (g
.getName().isPresent()) {
1550 syncGroup
.name
= g
.getName().get();
1552 syncGroup
.addMembers(g
.getMembers());
1553 if (!g
.isActive()) {
1554 syncGroup
.removeMember(account
.getSelfAddress());
1556 // Add ourself to the member set as it's marked as active
1557 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1559 syncGroup
.blocked
= g
.isBlocked();
1560 if (g
.getColor().isPresent()) {
1561 syncGroup
.color
= g
.getColor().get();
1564 if (g
.getAvatar().isPresent()) {
1565 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1567 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1568 syncGroup
.archived
= g
.isArchived();
1569 account
.getGroupStore().updateGroup(syncGroup
);
1572 } catch (Exception e
) {
1573 e
.printStackTrace();
1575 if (tmpFile
!= null) {
1577 Files
.delete(tmpFile
.toPath());
1578 } catch (IOException e
) {
1579 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1584 if (syncMessage
.getBlockedList().isPresent()) {
1585 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1586 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1587 if (address
.getNumber().isPresent()) {
1589 setContactBlocked(address
.getNumber().get(), true);
1590 } catch (InvalidNumberException e
) {
1591 e
.printStackTrace();
1595 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1597 setGroupBlocked(groupId
, true);
1598 } catch (GroupNotFoundException e
) {
1599 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1603 if (syncMessage
.getContacts().isPresent()) {
1604 File tmpFile
= null;
1606 tmpFile
= IOUtils
.createTempFile();
1607 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1608 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1609 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1610 if (contactsMessage
.isComplete()) {
1611 account
.getContactStore().clear();
1614 while ((c
= s
.read()) != null) {
1615 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1616 account
.setProfileKey(c
.getProfileKey().get());
1618 ContactInfo contact
= account
.getContactStore().getContact(c
.getAddress());
1619 if (contact
== null) {
1620 contact
= new ContactInfo(c
.getAddress());
1622 if (c
.getName().isPresent()) {
1623 contact
.name
= c
.getName().get();
1625 if (c
.getColor().isPresent()) {
1626 contact
.color
= c
.getColor().get();
1628 if (c
.getProfileKey().isPresent()) {
1629 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1631 if (c
.getVerified().isPresent()) {
1632 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1633 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1635 if (c
.getExpirationTimer().isPresent()) {
1636 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1638 contact
.blocked
= c
.isBlocked();
1639 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1640 contact
.archived
= c
.isArchived();
1641 account
.getContactStore().updateContact(contact
);
1643 if (c
.getAvatar().isPresent()) {
1644 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
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 contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1660 if (syncMessage
.getVerified().isPresent()) {
1661 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1662 account
.getSignalProtocolStore().saveIdentity(verifiedMessage
.getDestination().getNumber().get(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1664 if (syncMessage
.getConfiguration().isPresent()) {
1671 private File
getContactAvatarFile(String number
) {
1672 return new File(avatarsPath
, "contact-" + number
);
1675 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
{
1676 IOUtils
.createPrivateDirectories(avatarsPath
);
1677 if (attachment
.isPointer()) {
1678 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1679 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1681 SignalServiceAttachmentStream stream
= attachment
.asStream();
1682 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1686 private File
getGroupAvatarFile(byte[] groupId
) {
1687 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1690 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
{
1691 IOUtils
.createPrivateDirectories(avatarsPath
);
1692 if (attachment
.isPointer()) {
1693 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1694 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1696 SignalServiceAttachmentStream stream
= attachment
.asStream();
1697 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1701 public File
getAttachmentFile(long attachmentId
) {
1702 return new File(attachmentsPath
, attachmentId
+ "");
1705 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
{
1706 IOUtils
.createPrivateDirectories(attachmentsPath
);
1707 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getId()), true);
1710 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
{
1711 if (storePreview
&& pointer
.getPreview().isPresent()) {
1712 File previewFile
= new File(outputFile
+ ".preview");
1713 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1714 byte[] preview
= pointer
.getPreview().get();
1715 output
.write(preview
, 0, preview
.length
);
1716 } catch (FileNotFoundException e
) {
1717 e
.printStackTrace();
1722 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1724 File tmpFile
= IOUtils
.createTempFile();
1725 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1726 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1727 byte[] buffer
= new byte[4096];
1730 while ((read
= input
.read(buffer
)) != -1) {
1731 output
.write(buffer
, 0, read
);
1733 } catch (FileNotFoundException e
) {
1734 e
.printStackTrace();
1739 Files
.delete(tmpFile
.toPath());
1740 } catch (IOException e
) {
1741 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1747 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
{
1748 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1749 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1753 public boolean isRemote() {
1757 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1758 File groupsFile
= IOUtils
.createTempFile();
1761 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1762 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1763 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1764 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1765 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1766 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1767 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1771 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1772 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1773 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1774 .withStream(groupsFileStream
)
1775 .withContentType("application/octet-stream")
1776 .withLength(groupsFile
.length())
1779 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1784 Files
.delete(groupsFile
.toPath());
1785 } catch (IOException e
) {
1786 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1791 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1792 File contactsFile
= IOUtils
.createTempFile();
1795 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1796 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1797 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1798 VerifiedMessage verifiedMessage
= null;
1799 if (getIdentities().containsKey(record.number
)) {
1800 JsonIdentityKeyStore
.Identity currentIdentity
= null;
1801 for (JsonIdentityKeyStore
.Identity id
: getIdentities().get(record.number
)) {
1802 if (currentIdentity
== null || id
.getDateAdded().after(currentIdentity
.getDateAdded())) {
1803 currentIdentity
= id
;
1806 if (currentIdentity
!= null) {
1807 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1811 ProfileKey profileKey
= null;
1813 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1814 } catch (InvalidInputException ignored
) {
1816 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1817 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1818 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1819 Optional
.of(record.messageExpirationTime
),
1820 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1823 if (account
.getProfileKey() != null) {
1824 // Send our own profile key as well
1825 out
.write(new DeviceContact(account
.getSelfAddress(),
1826 Optional
.absent(), Optional
.absent(),
1827 Optional
.absent(), Optional
.absent(),
1828 Optional
.of(account
.getProfileKey()),
1829 false, Optional
.absent(), Optional
.absent(), false));
1833 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1834 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1835 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1836 .withStream(contactsFileStream
)
1837 .withContentType("application/octet-stream")
1838 .withLength(contactsFile
.length())
1841 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1846 Files
.delete(contactsFile
.toPath());
1847 } catch (IOException e
) {
1848 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1853 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1854 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1855 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1856 if (record.blocked
) {
1857 addresses
.add(record.getAddress());
1860 List
<byte[]> groupIds
= new ArrayList
<>();
1861 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1862 if (record.blocked
) {
1863 groupIds
.add(record.groupId
);
1866 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1869 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1870 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1871 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1874 public List
<ContactInfo
> getContacts() {
1875 return account
.getContactStore().getContacts();
1878 public ContactInfo
getContact(String number
) {
1879 return account
.getContactStore().getContact(new SignalServiceAddress(null, number
));
1882 public GroupInfo
getGroup(byte[] groupId
) {
1883 return account
.getGroupStore().getGroup(groupId
);
1886 public Map
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities() {
1887 return account
.getSignalProtocolStore().getIdentities();
1890 public Pair
<String
, List
<JsonIdentityKeyStore
.Identity
>> getIdentities(String number
) throws InvalidNumberException
{
1891 String canonicalizedNumber
= Utils
.canonicalizeNumber(number
, account
.getUsername());
1892 return new Pair
<>(canonicalizedNumber
, account
.getSignalProtocolStore().getIdentities(canonicalizedNumber
));
1896 * Trust this the identity with this fingerprint
1898 * @param name username of the identity
1899 * @param fingerprint Fingerprint
1901 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) {
1902 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1906 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1907 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1911 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1913 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1914 } catch (IOException
| UntrustedIdentityException e
) {
1915 e
.printStackTrace();
1924 * Trust this the identity with this safety number
1926 * @param name username of the identity
1927 * @param safetyNumber Safety number
1929 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) {
1930 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1934 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1935 if (!safetyNumber
.equals(computeSafetyNumber(name
, id
.getIdentityKey()))) {
1939 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1941 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1942 } catch (IOException
| UntrustedIdentityException e
) {
1943 e
.printStackTrace();
1952 * Trust all keys of this identity without verification
1954 * @param name username of the identity
1956 public boolean trustIdentityAllKeys(String name
) {
1957 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(name
);
1961 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1962 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1963 account
.getSignalProtocolStore().saveIdentity(name
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1965 sendVerifiedMessage(new SignalServiceAddress(null, name
), id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1966 } catch (IOException
| UntrustedIdentityException e
) {
1967 e
.printStackTrace();
1975 public String
computeSafetyNumber(String theirUsername
, IdentityKey theirIdentityKey
) {
1976 return Utils
.computeSafetyNumber(account
.getUsername(), getIdentity(), theirUsername
, theirIdentityKey
);
1979 public interface ReceiveMessageHandler
{
1981 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);