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
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
25 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
26 import org
.asamk
.signal
.util
.IOUtils
;
27 import org
.asamk
.signal
.util
.Util
;
28 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
29 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
30 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
31 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
38 import org
.signal
.libsignal
.metadata
.SelfSendException
;
39 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
40 import org
.signal
.zkgroup
.InvalidInputException
;
41 import org
.signal
.zkgroup
.VerificationFailedException
;
42 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
43 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
44 import org
.whispersystems
.libsignal
.IdentityKey
;
45 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
46 import org
.whispersystems
.libsignal
.InvalidKeyException
;
47 import org
.whispersystems
.libsignal
.InvalidMessageException
;
48 import org
.whispersystems
.libsignal
.InvalidVersionException
;
49 import org
.whispersystems
.libsignal
.ecc
.Curve
;
50 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
51 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
52 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
53 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
54 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
55 import org
.whispersystems
.libsignal
.util
.Medium
;
56 import org
.whispersystems
.libsignal
.util
.Pair
;
57 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
93 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
94 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
95 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
105 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
106 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
107 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
108 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
109 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
110 import org
.whispersystems
.util
.Base64
;
112 import java
.io
.Closeable
;
114 import java
.io
.FileInputStream
;
115 import java
.io
.FileNotFoundException
;
116 import java
.io
.FileOutputStream
;
117 import java
.io
.IOException
;
118 import java
.io
.InputStream
;
119 import java
.io
.OutputStream
;
121 import java
.net
.URISyntaxException
;
122 import java
.net
.URLEncoder
;
123 import java
.nio
.file
.Files
;
124 import java
.nio
.file
.Paths
;
125 import java
.nio
.file
.StandardCopyOption
;
126 import java
.util
.ArrayList
;
127 import java
.util
.Arrays
;
128 import java
.util
.Collection
;
129 import java
.util
.Collections
;
130 import java
.util
.Date
;
131 import java
.util
.HashSet
;
132 import java
.util
.LinkedList
;
133 import java
.util
.List
;
134 import java
.util
.Locale
;
135 import java
.util
.Objects
;
136 import java
.util
.Set
;
137 import java
.util
.UUID
;
138 import java
.util
.concurrent
.TimeUnit
;
139 import java
.util
.concurrent
.TimeoutException
;
140 import java
.util
.stream
.Collectors
;
141 import java
.util
.zip
.ZipEntry
;
142 import java
.util
.zip
.ZipFile
;
144 public class Manager
implements Closeable
{
146 private final SleepTimer timer
= new UptimeSleepTimer();
147 private final SignalServiceConfiguration serviceConfiguration
;
148 private final String userAgent
;
150 private final SignalAccount account
;
151 private final PathConfig pathConfig
;
152 private SignalServiceAccountManager accountManager
;
153 private SignalServiceMessagePipe messagePipe
= null;
154 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
156 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
157 this.account
= account
;
158 this.pathConfig
= pathConfig
;
159 this.serviceConfiguration
= serviceConfiguration
;
160 this.userAgent
= userAgent
;
161 this.accountManager
= createSignalServiceAccountManager();
163 this.account
.setResolver(this::resolveSignalServiceAddress
);
166 public String
getUsername() {
167 return account
.getUsername();
170 public SignalServiceAddress
getSelfAddress() {
171 return account
.getSelfAddress();
174 private SignalServiceAccountManager
createSignalServiceAccountManager() {
175 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
178 private IdentityKeyPair
getIdentityKeyPair() {
179 return account
.getSignalProtocolStore().getIdentityKeyPair();
182 public int getDeviceId() {
183 return account
.getDeviceId();
186 private String
getMessageCachePath() {
187 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
190 private String
getMessageCachePath(String sender
) {
191 if (sender
== null || sender
.isEmpty()) {
192 return getMessageCachePath();
195 return getMessageCachePath() + "/" + sender
.replace("/", "_");
198 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
199 String cachePath
= getMessageCachePath(sender
);
200 IOUtils
.createPrivateDirectories(cachePath
);
201 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
204 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
205 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
207 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
208 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
209 int registrationId
= KeyHelper
.generateRegistrationId(false);
211 ProfileKey profileKey
= KeyUtils
.createProfileKey();
212 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
215 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
218 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
220 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
222 m
.migrateLegacyConfigs();
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(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
234 if (!avatarFile
.exists() && attachmentFile
.exists()) {
236 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
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 public void checkAccountState() throws IOException
{
254 if (account
.isRegistered()) {
255 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
259 if (account
.getUuid() == null) {
260 account
.setUuid(accountManager
.getOwnUuid());
266 public boolean isRegistered() {
267 return account
.isRegistered();
270 public void register(boolean voiceVerification
) throws IOException
{
271 account
.setPassword(KeyUtils
.createPassword());
273 // Resetting UUID, because registering doesn't work otherwise
274 account
.setUuid(null);
275 accountManager
= createSignalServiceAccountManager();
277 if (voiceVerification
) {
278 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
280 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
283 account
.setRegistered(false);
287 public void updateAccountAttributes() throws IOException
{
288 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
291 public void setProfileName(String name
) throws IOException
{
292 accountManager
.setProfileName(account
.getProfileKey(), name
);
295 public void setProfileAvatar(File avatar
) throws IOException
{
296 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
297 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
298 streamDetails
.getStream().close();
301 public void removeProfileAvatar() throws IOException
{
302 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
305 public void unregister() throws IOException
{
306 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
307 // If this is the master device, other users can't send messages to this number anymore.
308 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
309 accountManager
.setGcmId(Optional
.absent());
311 account
.setRegistered(false);
315 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
316 List
<DeviceInfo
> devices
= accountManager
.getDevices();
317 account
.setMultiDevice(devices
.size() > 1);
322 public void removeLinkedDevices(int deviceId
) throws IOException
{
323 accountManager
.removeDevice(deviceId
);
324 List
<DeviceInfo
> devices
= accountManager
.getDevices();
325 account
.setMultiDevice(devices
.size() > 1);
329 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
330 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
332 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
335 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
336 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
337 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
339 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
340 account
.setMultiDevice(true);
344 private List
<PreKeyRecord
> generatePreKeys() {
345 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
347 final int offset
= account
.getPreKeyIdOffset();
348 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
349 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
350 ECKeyPair keyPair
= Curve
.generateKeyPair();
351 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
356 account
.addPreKeys(records
);
362 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
364 ECKeyPair keyPair
= Curve
.generateKeyPair();
365 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
366 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
368 account
.addSignedPreKey(record);
372 } catch (InvalidKeyException e
) {
373 throw new AssertionError(e
);
377 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
378 verificationCode
= verificationCode
.replace("-", "");
379 account
.setSignalingKey(KeyUtils
.createSignalingKey());
380 // TODO make unrestricted unidentified access configurable
381 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
383 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
384 // TODO response.isStorageCapable()
385 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
386 account
.setRegistered(true);
387 account
.setUuid(uuid
);
388 account
.setRegistrationLockPin(pin
);
389 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
395 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
396 if (pin
.isPresent()) {
397 account
.setRegistrationLockPin(pin
.get());
398 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
400 account
.setRegistrationLockPin(null);
401 accountManager
.removeRegistrationLockV1();
406 void refreshPreKeys() throws IOException
{
407 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
408 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
409 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
411 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
414 private SignalServiceMessageReceiver
getMessageReceiver() {
415 // TODO implement ZkGroup support
416 final ClientZkProfileOperations clientZkProfileOperations
= null;
417 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
420 private SignalServiceMessageSender
getMessageSender() {
421 // TODO implement ZkGroup support
422 final ClientZkProfileOperations clientZkProfileOperations
= null;
423 final boolean attachmentsV3
= false;
424 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
425 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
428 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
429 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
434 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
435 } catch (IOException ignored
) {
439 SignalServiceMessageReceiver receiver
= getMessageReceiver();
441 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
442 } catch (VerificationFailedException e
) {
443 throw new AssertionError(e
);
447 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
448 File file
= getGroupAvatarFile(groupId
);
449 if (!file
.exists()) {
450 return Optional
.absent();
453 return Optional
.of(Utils
.createAttachment(file
));
456 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
457 File file
= getContactAvatarFile(number
);
458 if (!file
.exists()) {
459 return Optional
.absent();
462 return Optional
.of(Utils
.createAttachment(file
));
465 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
466 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
468 throw new GroupNotFoundException(groupId
);
470 if (!g
.isMember(account
.getSelfAddress())) {
471 throw new NotAGroupMemberException(groupId
, g
.name
);
476 public List
<GroupInfo
> getGroups() {
477 return account
.getGroupStore().getGroups();
480 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
482 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
483 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
484 if (attachments
!= null) {
485 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
487 if (groupId
!= null) {
488 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
491 messageBuilder
.asGroupMessage(group
);
494 final GroupInfo g
= getGroupForSending(groupId
);
496 messageBuilder
.withExpiration(g
.messageExpirationTime
);
498 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
501 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
502 long targetSentTimestamp
, byte[] groupId
)
503 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
504 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
505 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
506 .withReaction(reaction
);
507 if (groupId
!= null) {
508 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
511 messageBuilder
.asGroupMessage(group
);
513 final GroupInfo g
= getGroupForSending(groupId
);
514 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
517 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
518 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
522 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
523 .asGroupMessage(group
);
525 final GroupInfo g
= getGroupForSending(groupId
);
526 g
.removeMember(account
.getSelfAddress());
527 account
.getGroupStore().updateGroup(g
);
529 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
532 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
534 if (groupId
== null) {
536 g
= new GroupInfo(KeyUtils
.createGroupId());
537 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
539 g
= getGroupForSending(groupId
);
546 if (members
!= null) {
547 final Set
<String
> newE164Members
= new HashSet
<>();
548 for (SignalServiceAddress member
: members
) {
549 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
552 newE164Members
.add(member
.getNumber().get());
555 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
556 if (contacts
.size() != newE164Members
.size()) {
557 // Some of the new members are not registered on Signal
558 for (ContactTokenDetails contact
: contacts
) {
559 newE164Members
.remove(contact
.getNumber());
561 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
562 System
.err
.println("Aborting…");
566 g
.addMembers(members
);
569 if (avatarFile
!= null) {
570 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
571 File aFile
= getGroupAvatarFile(g
.groupId
);
572 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
575 account
.getGroupStore().updateGroup(g
);
577 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
579 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
583 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
584 if (groupId
== null) {
587 GroupInfo g
= getGroupForSending(groupId
);
589 if (!g
.isMember(recipient
)) {
593 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
595 // Send group message only to the recipient who requested it
596 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
599 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
600 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
603 .withMembers(new ArrayList
<>(g
.getMembers()));
605 File aFile
= getGroupAvatarFile(g
.groupId
);
606 if (aFile
.exists()) {
608 group
.withAvatar(Utils
.createAttachment(aFile
));
609 } catch (IOException e
) {
610 throw new AttachmentInvalidException(aFile
.toString(), e
);
614 return SignalServiceDataMessage
.newBuilder()
615 .asGroupMessage(group
.build())
616 .withExpiration(g
.messageExpirationTime
);
619 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
620 if (groupId
== null) {
624 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
627 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
628 .asGroupMessage(group
.build());
630 // Send group info request message to the recipient who sent us a message with this groupId
631 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
634 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
635 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
636 Collections
.singletonList(messageId
),
637 System
.currentTimeMillis());
639 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
642 public long sendMessage(String messageText
, List
<String
> attachments
,
643 List
<String
> recipients
)
644 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
645 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
646 if (attachments
!= null) {
647 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
649 // Upload attachments here, so we only upload once even for multiple recipients
650 SignalServiceMessageSender messageSender
= getMessageSender();
651 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
652 for (SignalServiceAttachment attachment
: attachmentStreams
) {
653 if (attachment
.isStream()) {
654 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
655 } else if (attachment
.isPointer()) {
656 attachmentPointers
.add(attachment
.asPointer());
660 messageBuilder
.withAttachments(attachmentPointers
);
662 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
665 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
666 long targetSentTimestamp
, List
<String
> recipients
)
667 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
668 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
669 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
670 .withReaction(reaction
);
671 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
674 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
675 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
676 .asEndSessionMessage();
678 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
680 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
681 } catch (Exception e
) {
682 for (SignalServiceAddress address
: signalServiceAddresses
) {
683 handleEndSession(address
);
690 public String
getContactName(String number
) throws InvalidNumberException
{
691 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
692 if (contact
== null) {
699 public void setContactName(String number
, String name
) throws InvalidNumberException
{
700 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
701 ContactInfo contact
= account
.getContactStore().getContact(address
);
702 if (contact
== null) {
703 contact
= new ContactInfo(address
);
704 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
706 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
709 account
.getContactStore().updateContact(contact
);
713 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
714 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
717 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
718 ContactInfo contact
= account
.getContactStore().getContact(address
);
719 if (contact
== null) {
720 contact
= new ContactInfo(address
);
721 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
723 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
725 contact
.blocked
= blocked
;
726 account
.getContactStore().updateContact(contact
);
730 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
731 GroupInfo group
= getGroup(groupId
);
733 throw new GroupNotFoundException(groupId
);
735 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
736 group
.blocked
= blocked
;
737 account
.getGroupStore().updateGroup(group
);
742 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
743 if (groupId
.length
== 0) {
746 if (name
.isEmpty()) {
749 if (members
.size() == 0) {
752 if (avatar
.isEmpty()) {
755 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
759 * Change the expiration timer for a contact
761 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
762 ContactInfo contact
= account
.getContactStore().getContact(address
);
763 contact
.messageExpirationTime
= messageExpirationTimer
;
764 account
.getContactStore().updateContact(contact
);
765 sendExpirationTimerUpdate(address
);
769 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
770 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
771 .asExpirationUpdate();
772 sendMessage(messageBuilder
, Collections
.singleton(address
));
776 * Change the expiration timer for a contact
778 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
779 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
780 setExpirationTimer(address
, messageExpirationTimer
);
784 * Change the expiration timer for a group
786 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
787 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
788 g
.messageExpirationTime
= messageExpirationTimer
;
789 account
.getGroupStore().updateGroup(g
);
793 * Upload the sticker pack from path.
795 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
796 * @return if successful, returns the URL to install the sticker pack in the signal app
798 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
799 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
801 SignalServiceMessageSender messageSender
= getMessageSender();
803 byte[] packKey
= KeyUtils
.createStickerUploadKey();
804 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
807 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
809 } catch (URISyntaxException e
) {
810 throw new AssertionError(e
);
814 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
816 String rootPath
= null;
818 final File file
= new File(path
);
819 if (file
.getName().endsWith(".zip")) {
820 zip
= new ZipFile(file
);
821 } else if (file
.getName().equals("manifest.json")) {
822 rootPath
= file
.getParent();
824 throw new StickerPackInvalidException("Could not find manifest.json");
827 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
829 if (pack
.stickers
== null) {
830 throw new StickerPackInvalidException("Must set a 'stickers' field.");
833 if (pack
.stickers
.isEmpty()) {
834 throw new StickerPackInvalidException("Must include stickers.");
837 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
838 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
839 if (sticker
.file
== null) {
840 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
843 Pair
<InputStream
, Long
> data
;
845 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
846 } catch (IOException ignored
) {
847 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
850 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
851 stickers
.add(stickerInfo
);
854 StickerInfo cover
= null;
855 if (pack
.cover
!= null) {
856 if (pack
.cover
.file
== null) {
857 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
860 Pair
<InputStream
, Long
> data
;
862 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
863 } catch (IOException ignored
) {
864 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
867 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
870 return new SignalServiceStickerManifestUpload(
877 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
878 InputStream inputStream
;
880 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
882 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
884 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
887 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
889 final ZipEntry entry
= zip
.getEntry(subfile
);
890 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
892 final File file
= new File(rootPath
, subfile
);
893 return new Pair
<>(new FileInputStream(file
), file
.length());
897 void requestSyncGroups() throws IOException
{
898 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
899 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
901 sendSyncMessage(message
);
902 } catch (UntrustedIdentityException e
) {
907 void requestSyncContacts() throws IOException
{
908 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
909 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
911 sendSyncMessage(message
);
912 } catch (UntrustedIdentityException e
) {
917 void requestSyncBlocked() throws IOException
{
918 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
919 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
921 sendSyncMessage(message
);
922 } catch (UntrustedIdentityException e
) {
927 void requestSyncConfiguration() throws IOException
{
928 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
929 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
931 sendSyncMessage(message
);
932 } catch (UntrustedIdentityException e
) {
937 private byte[] getSenderCertificate() {
938 // TODO support UUID capable sender certificates
939 // byte[] certificate = accountManager.getSenderCertificate();
942 certificate
= accountManager
.getSenderCertificateLegacy();
943 } catch (IOException e
) {
944 System
.err
.println("Failed to get sender certificate: " + e
);
947 // TODO cache for a day
951 private byte[] getSelfUnidentifiedAccessKey() {
952 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
955 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
956 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
958 return new SignalProfile(
959 encryptedProfile
.getIdentityKey(),
960 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
961 encryptedProfile
.getAvatar(),
962 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
963 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
965 } catch (InvalidCiphertextException e
) {
970 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
971 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
972 if (contact
== null || contact
.profileKey
== null) {
975 ProfileKey theirProfileKey
;
977 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
978 } catch (InvalidInputException
| IOException e
) {
979 throw new AssertionError(e
);
981 SignalProfile targetProfile
;
983 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
984 } catch (IOException e
) {
985 System
.err
.println("Failed to get recipient profile: " + e
);
989 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
993 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
994 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
997 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1000 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1001 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1002 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1004 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1005 return Optional
.absent();
1009 return Optional
.of(new UnidentifiedAccessPair(
1010 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1011 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1013 } catch (InvalidCertificateException e
) {
1014 return Optional
.absent();
1018 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1019 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1020 for (SignalServiceAddress recipient
: recipients
) {
1021 result
.add(getAccessFor(recipient
));
1026 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1027 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1028 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1029 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1031 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1032 return Optional
.absent();
1036 return Optional
.of(new UnidentifiedAccessPair(
1037 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1038 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1040 } catch (InvalidCertificateException e
) {
1041 return Optional
.absent();
1045 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1046 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1048 if (unidentifiedAccess
.isPresent()) {
1049 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1052 return Optional
.absent();
1055 private void sendSyncMessage(SignalServiceSyncMessage message
)
1056 throws IOException
, UntrustedIdentityException
{
1057 SignalServiceMessageSender messageSender
= getMessageSender();
1059 messageSender
.sendMessage(message
, getAccessForSync());
1060 } catch (UntrustedIdentityException e
) {
1061 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1067 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1069 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1070 throws EncapsulatedExceptions
, IOException
{
1071 final long timestamp
= System
.currentTimeMillis();
1072 messageBuilder
.withTimestamp(timestamp
);
1073 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1075 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1076 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1077 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1079 for (SendMessageResult result
: results
) {
1080 if (result
.isUnregisteredFailure()) {
1081 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1082 } else if (result
.isNetworkFailure()) {
1083 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1084 } else if (result
.getIdentityFailure() != null) {
1085 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1088 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1089 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1094 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1095 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1097 for (String number
: numbers
) {
1098 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1100 return signalServiceAddresses
;
1103 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1104 throws IOException
{
1105 if (messagePipe
== null) {
1106 messagePipe
= getMessageReceiver().createMessagePipe();
1108 if (unidentifiedMessagePipe
== null) {
1109 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1111 SignalServiceDataMessage message
= null;
1113 message
= messageBuilder
.build();
1114 if (message
.getGroupContext().isPresent()) {
1116 SignalServiceMessageSender messageSender
= getMessageSender();
1117 final boolean isRecipientUpdate
= false;
1118 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1119 for (SendMessageResult r
: result
) {
1120 if (r
.getIdentityFailure() != null) {
1121 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1125 } catch (UntrustedIdentityException e
) {
1126 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1127 return Collections
.emptyList();
1130 // Send to all individually, so sync messages are sent correctly
1131 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1132 for (SignalServiceAddress address
: recipients
) {
1133 ContactInfo contact
= account
.getContactStore().getContact(address
);
1134 if (contact
!= null) {
1135 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1136 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1138 messageBuilder
.withExpiration(0);
1139 messageBuilder
.withProfileKey(null);
1141 message
= messageBuilder
.build();
1142 if (address
.matches(account
.getSelfAddress())) {
1143 results
.add(sendSelfMessage(message
));
1145 results
.add(sendMessage(address
, message
));
1151 if (message
!= null && message
.isEndSession()) {
1152 for (SignalServiceAddress recipient
: recipients
) {
1153 handleEndSession(recipient
);
1160 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1161 SignalServiceMessageSender messageSender
= getMessageSender();
1163 SignalServiceAddress recipient
= account
.getSelfAddress();
1165 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1166 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1167 message
.getTimestamp(),
1169 message
.getExpiresInSeconds(),
1170 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1172 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1175 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1176 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1177 } catch (UntrustedIdentityException e
) {
1178 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1179 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1183 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1184 SignalServiceMessageSender messageSender
= getMessageSender();
1187 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1188 } catch (UntrustedIdentityException e
) {
1189 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1190 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1194 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1195 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1197 return cipher
.decrypt(envelope
);
1198 } catch (ProtocolUntrustedIdentityException e
) {
1199 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1200 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1201 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1202 throw identityException
;
1204 throw new AssertionError(e
);
1208 private void handleEndSession(SignalServiceAddress source
) {
1209 account
.getSignalProtocolStore().deleteAllSessions(source
);
1212 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1213 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1214 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1215 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1216 switch (groupInfo
.getType()) {
1218 if (group
== null) {
1219 group
= new GroupInfo(groupInfo
.getGroupId());
1222 if (groupInfo
.getAvatar().isPresent()) {
1223 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1224 if (avatar
.isPointer()) {
1226 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1227 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1228 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1233 if (groupInfo
.getName().isPresent()) {
1234 group
.name
= groupInfo
.getName().get();
1237 if (groupInfo
.getMembers().isPresent()) {
1238 group
.addMembers(groupInfo
.getMembers().get()
1240 .map(this::resolveSignalServiceAddress
)
1241 .collect(Collectors
.toSet()));
1244 account
.getGroupStore().updateGroup(group
);
1247 if (group
== null) {
1249 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1250 } catch (IOException
| EncapsulatedExceptions e
) {
1251 e
.printStackTrace();
1256 if (group
!= null) {
1257 group
.removeMember(source
);
1258 account
.getGroupStore().updateGroup(group
);
1262 if (group
!= null) {
1264 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1265 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1266 e
.printStackTrace();
1267 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1268 // We have left this group, so don't send a group update message
1274 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1275 if (message
.isEndSession()) {
1276 handleEndSession(conversationPartnerAddress
);
1278 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1279 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1280 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1281 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1282 if (group
== null) {
1283 group
= new GroupInfo(groupInfo
.getGroupId());
1285 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1286 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1287 account
.getGroupStore().updateGroup(group
);
1290 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1291 if (contact
== null) {
1292 contact
= new ContactInfo(conversationPartnerAddress
);
1294 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1295 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1296 account
.getContactStore().updateContact(contact
);
1300 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1301 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1302 if (attachment
.isPointer()) {
1304 retrieveAttachment(attachment
.asPointer());
1305 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1306 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1311 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1312 if (source
.matches(account
.getSelfAddress())) {
1314 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1315 } catch (InvalidInputException ignored
) {
1317 ContactInfo contact
= account
.getContactStore().getContact(source
);
1318 if (contact
!= null) {
1319 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1320 account
.getContactStore().updateContact(contact
);
1323 ContactInfo contact
= account
.getContactStore().getContact(source
);
1324 if (contact
== null) {
1325 contact
= new ContactInfo(source
);
1327 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1328 account
.getContactStore().updateContact(contact
);
1331 if (message
.getPreviews().isPresent()) {
1332 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1333 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1334 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1335 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1337 retrieveAttachment(attachment
);
1338 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1339 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1346 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1347 final File cachePath
= new File(getMessageCachePath());
1348 if (!cachePath
.exists()) {
1351 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1352 if (!dir
.isDirectory()) {
1353 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1357 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1358 if (!fileEntry
.isFile()) {
1361 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1363 // Try to delete directory if empty
1368 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1369 SignalServiceEnvelope envelope
;
1371 envelope
= Utils
.loadEnvelope(fileEntry
);
1372 if (envelope
== null) {
1375 } catch (IOException e
) {
1376 e
.printStackTrace();
1379 SignalServiceContent content
= null;
1380 if (!envelope
.isReceipt()) {
1382 content
= decryptMessage(envelope
);
1383 } catch (Exception e
) {
1386 handleMessage(envelope
, content
, ignoreAttachments
);
1389 handler
.handleMessage(envelope
, content
, null);
1391 Files
.delete(fileEntry
.toPath());
1392 } catch (IOException e
) {
1393 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1397 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1398 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1399 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1401 if (messagePipe
== null) {
1402 messagePipe
= messageReceiver
.createMessagePipe();
1405 boolean hasCaughtUpWithOldMessages
= false;
1408 SignalServiceEnvelope envelope
;
1409 SignalServiceContent content
= null;
1410 Exception exception
= null;
1411 final long now
= new Date().getTime();
1413 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1414 // store message on disk, before acknowledging receipt to the server
1416 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1417 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1418 Utils
.storeEnvelope(envelope1
, cacheFile
);
1419 } catch (IOException e
) {
1420 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1423 if (result
.isPresent()) {
1424 envelope
= result
.get();
1426 // Received indicator that server queue is empty
1427 hasCaughtUpWithOldMessages
= true;
1429 // Continue to wait another timeout for new messages
1432 } catch (TimeoutException e
) {
1433 if (returnOnTimeout
)
1436 } catch (InvalidVersionException e
) {
1437 System
.err
.println("Ignoring error: " + e
.getMessage());
1440 if (envelope
.hasSource()) {
1441 // Store uuid if we don't have it already
1442 SignalServiceAddress source
= envelope
.getSourceAddress();
1443 resolveSignalServiceAddress(source
);
1445 if (!envelope
.isReceipt()) {
1447 content
= decryptMessage(envelope
);
1448 } catch (Exception e
) {
1451 handleMessage(envelope
, content
, ignoreAttachments
);
1454 if (!isMessageBlocked(envelope
, content
)) {
1455 handler
.handleMessage(envelope
, content
, exception
);
1457 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1458 File cacheFile
= null;
1460 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1461 Files
.delete(cacheFile
.toPath());
1462 // Try to delete directory if empty
1463 new File(getMessageCachePath()).delete();
1464 } catch (IOException e
) {
1465 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1471 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1472 SignalServiceAddress source
;
1473 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1474 source
= envelope
.getSourceAddress();
1475 } else if (content
!= null) {
1476 source
= content
.getSender();
1480 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1481 if (sourceContact
!= null && sourceContact
.blocked
) {
1485 if (content
!= null && content
.getDataMessage().isPresent()) {
1486 SignalServiceDataMessage message
= content
.getDataMessage().get();
1487 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1488 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1489 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1490 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1498 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1499 if (content
!= null) {
1500 SignalServiceAddress sender
;
1501 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1502 sender
= envelope
.getSourceAddress();
1504 sender
= content
.getSender();
1506 // Store uuid if we don't have it already
1507 resolveSignalServiceAddress(sender
);
1509 if (content
.getDataMessage().isPresent()) {
1510 SignalServiceDataMessage message
= content
.getDataMessage().get();
1512 if (content
.isNeedsReceipt()) {
1514 sendReceipt(sender
, message
.getTimestamp());
1515 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1516 e
.printStackTrace();
1520 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1522 if (content
.getSyncMessage().isPresent()) {
1523 account
.setMultiDevice(true);
1524 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1525 if (syncMessage
.getSent().isPresent()) {
1526 SentTranscriptMessage message
= syncMessage
.getSent().get();
1527 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1529 if (syncMessage
.getRequest().isPresent()) {
1530 RequestMessage rm
= syncMessage
.getRequest().get();
1531 if (rm
.isContactsRequest()) {
1534 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1535 e
.printStackTrace();
1538 if (rm
.isGroupsRequest()) {
1541 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1542 e
.printStackTrace();
1545 if (rm
.isBlockedListRequest()) {
1548 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1549 e
.printStackTrace();
1552 // TODO Handle rm.isConfigurationRequest();
1554 if (syncMessage
.getGroups().isPresent()) {
1555 File tmpFile
= null;
1557 tmpFile
= IOUtils
.createTempFile();
1558 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1559 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1561 while ((g
= s
.read()) != null) {
1562 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1563 if (syncGroup
== null) {
1564 syncGroup
= new GroupInfo(g
.getId());
1566 if (g
.getName().isPresent()) {
1567 syncGroup
.name
= g
.getName().get();
1569 syncGroup
.addMembers(g
.getMembers()
1571 .map(this::resolveSignalServiceAddress
)
1572 .collect(Collectors
.toSet()));
1573 if (!g
.isActive()) {
1574 syncGroup
.removeMember(account
.getSelfAddress());
1576 // Add ourself to the member set as it's marked as active
1577 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1579 syncGroup
.blocked
= g
.isBlocked();
1580 if (g
.getColor().isPresent()) {
1581 syncGroup
.color
= g
.getColor().get();
1584 if (g
.getAvatar().isPresent()) {
1585 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1587 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1588 syncGroup
.archived
= g
.isArchived();
1589 account
.getGroupStore().updateGroup(syncGroup
);
1592 } catch (Exception e
) {
1593 e
.printStackTrace();
1595 if (tmpFile
!= null) {
1597 Files
.delete(tmpFile
.toPath());
1598 } catch (IOException e
) {
1599 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1604 if (syncMessage
.getBlockedList().isPresent()) {
1605 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1606 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1607 setContactBlocked(resolveSignalServiceAddress(address
), true);
1609 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1611 setGroupBlocked(groupId
, true);
1612 } catch (GroupNotFoundException e
) {
1613 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1617 if (syncMessage
.getContacts().isPresent()) {
1618 File tmpFile
= null;
1620 tmpFile
= IOUtils
.createTempFile();
1621 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1622 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1623 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1624 if (contactsMessage
.isComplete()) {
1625 account
.getContactStore().clear();
1628 while ((c
= s
.read()) != null) {
1629 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1630 account
.setProfileKey(c
.getProfileKey().get());
1632 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1633 ContactInfo contact
= account
.getContactStore().getContact(address
);
1634 if (contact
== null) {
1635 contact
= new ContactInfo(address
);
1637 if (c
.getName().isPresent()) {
1638 contact
.name
= c
.getName().get();
1640 if (c
.getColor().isPresent()) {
1641 contact
.color
= c
.getColor().get();
1643 if (c
.getProfileKey().isPresent()) {
1644 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1646 if (c
.getVerified().isPresent()) {
1647 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1648 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1650 if (c
.getExpirationTimer().isPresent()) {
1651 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1653 contact
.blocked
= c
.isBlocked();
1654 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1655 contact
.archived
= c
.isArchived();
1656 account
.getContactStore().updateContact(contact
);
1658 if (c
.getAvatar().isPresent()) {
1659 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1663 } catch (Exception e
) {
1664 e
.printStackTrace();
1666 if (tmpFile
!= null) {
1668 Files
.delete(tmpFile
.toPath());
1669 } catch (IOException e
) {
1670 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1675 if (syncMessage
.getVerified().isPresent()) {
1676 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1677 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1679 if (syncMessage
.getConfiguration().isPresent()) {
1686 private File
getContactAvatarFile(String number
) {
1687 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1690 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1691 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1692 if (attachment
.isPointer()) {
1693 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1694 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1696 SignalServiceAttachmentStream stream
= attachment
.asStream();
1697 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1701 private File
getGroupAvatarFile(byte[] groupId
) {
1702 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1705 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1706 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1707 if (attachment
.isPointer()) {
1708 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1709 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1711 SignalServiceAttachmentStream stream
= attachment
.asStream();
1712 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1716 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1717 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1720 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1721 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1722 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1725 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1726 if (storePreview
&& pointer
.getPreview().isPresent()) {
1727 File previewFile
= new File(outputFile
+ ".preview");
1728 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1729 byte[] preview
= pointer
.getPreview().get();
1730 output
.write(preview
, 0, preview
.length
);
1731 } catch (FileNotFoundException e
) {
1732 e
.printStackTrace();
1737 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1739 File tmpFile
= IOUtils
.createTempFile();
1740 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1741 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1742 byte[] buffer
= new byte[4096];
1745 while ((read
= input
.read(buffer
)) != -1) {
1746 output
.write(buffer
, 0, read
);
1748 } catch (FileNotFoundException e
) {
1749 e
.printStackTrace();
1754 Files
.delete(tmpFile
.toPath());
1755 } catch (IOException e
) {
1756 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1762 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1763 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1764 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1767 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1768 File groupsFile
= IOUtils
.createTempFile();
1771 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1772 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1773 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1774 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1775 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1776 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1777 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1781 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1782 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1783 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1784 .withStream(groupsFileStream
)
1785 .withContentType("application/octet-stream")
1786 .withLength(groupsFile
.length())
1789 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1794 Files
.delete(groupsFile
.toPath());
1795 } catch (IOException e
) {
1796 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1801 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1802 File contactsFile
= IOUtils
.createTempFile();
1805 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1806 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1807 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1808 VerifiedMessage verifiedMessage
= null;
1809 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1810 if (currentIdentity
!= null) {
1811 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1814 ProfileKey profileKey
= null;
1816 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1817 } catch (InvalidInputException ignored
) {
1819 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1820 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1821 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1822 Optional
.of(record.messageExpirationTime
),
1823 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1826 if (account
.getProfileKey() != null) {
1827 // Send our own profile key as well
1828 out
.write(new DeviceContact(account
.getSelfAddress(),
1829 Optional
.absent(), Optional
.absent(),
1830 Optional
.absent(), Optional
.absent(),
1831 Optional
.of(account
.getProfileKey()),
1832 false, Optional
.absent(), Optional
.absent(), false));
1836 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1837 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1838 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1839 .withStream(contactsFileStream
)
1840 .withContentType("application/octet-stream")
1841 .withLength(contactsFile
.length())
1844 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1849 Files
.delete(contactsFile
.toPath());
1850 } catch (IOException e
) {
1851 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1856 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1857 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1858 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1859 if (record.blocked
) {
1860 addresses
.add(record.getAddress());
1863 List
<byte[]> groupIds
= new ArrayList
<>();
1864 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1865 if (record.blocked
) {
1866 groupIds
.add(record.groupId
);
1869 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1872 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1873 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1874 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1877 public List
<ContactInfo
> getContacts() {
1878 return account
.getContactStore().getContacts();
1881 public ContactInfo
getContact(String number
) {
1882 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1885 public GroupInfo
getGroup(byte[] groupId
) {
1886 return account
.getGroupStore().getGroup(groupId
);
1889 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1890 return account
.getSignalProtocolStore().getIdentities();
1893 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1894 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1898 * Trust this the identity with this fingerprint
1900 * @param name username of the identity
1901 * @param fingerprint Fingerprint
1903 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1904 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1905 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1909 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1910 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1914 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1916 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1917 } catch (IOException
| UntrustedIdentityException e
) {
1918 e
.printStackTrace();
1927 * Trust this the identity with this safety number
1929 * @param name username of the identity
1930 * @param safetyNumber Safety number
1932 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1933 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1934 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1938 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1939 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1943 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1945 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1946 } catch (IOException
| UntrustedIdentityException e
) {
1947 e
.printStackTrace();
1956 * Trust all keys of this identity without verification
1958 * @param name username of the identity
1960 public boolean trustIdentityAllKeys(String name
) {
1961 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1962 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1966 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1967 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1968 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1970 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1971 } catch (IOException
| UntrustedIdentityException e
) {
1972 e
.printStackTrace();
1980 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1981 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1984 void saveAccount() {
1988 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1989 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1990 return resolveSignalServiceAddress(canonicalizedNumber
);
1993 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1994 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1996 return resolveSignalServiceAddress(address
);
1999 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2000 if (address
.matches(account
.getSelfAddress())) {
2001 return account
.getSelfAddress();
2004 return account
.getRecipientStore().resolveServiceAddress(address
);
2008 public void close() throws IOException
{
2009 if (messagePipe
!= null) {
2010 messagePipe
.shutdown();
2014 if (unidentifiedMessagePipe
!= null) {
2015 unidentifiedMessagePipe
.shutdown();
2016 unidentifiedMessagePipe
= null;
2022 public interface ReceiveMessageHandler
{
2024 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);