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
getEncryptedRecipientProfile(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 SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
448 return decryptProfile(getEncryptedRecipientProfile(address
, unidentifiedAccess
), profileKey
);
451 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
452 File file
= getGroupAvatarFile(groupId
);
453 if (!file
.exists()) {
454 return Optional
.absent();
457 return Optional
.of(Utils
.createAttachment(file
));
460 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
461 File file
= getContactAvatarFile(number
);
462 if (!file
.exists()) {
463 return Optional
.absent();
466 return Optional
.of(Utils
.createAttachment(file
));
469 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
470 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
472 throw new GroupNotFoundException(groupId
);
474 if (!g
.isMember(account
.getSelfAddress())) {
475 throw new NotAGroupMemberException(groupId
, g
.name
);
480 public List
<GroupInfo
> getGroups() {
481 return account
.getGroupStore().getGroups();
484 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
486 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
487 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
488 if (attachments
!= null) {
489 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
491 if (groupId
!= null) {
492 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
495 messageBuilder
.asGroupMessage(group
);
498 final GroupInfo g
= getGroupForSending(groupId
);
500 messageBuilder
.withExpiration(g
.messageExpirationTime
);
502 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
505 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
506 long targetSentTimestamp
, byte[] groupId
)
507 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
508 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
509 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
510 .withReaction(reaction
);
511 if (groupId
!= null) {
512 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
515 messageBuilder
.asGroupMessage(group
);
517 final GroupInfo g
= getGroupForSending(groupId
);
518 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
521 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
522 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
526 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
527 .asGroupMessage(group
);
529 final GroupInfo g
= getGroupForSending(groupId
);
530 g
.removeMember(account
.getSelfAddress());
531 account
.getGroupStore().updateGroup(g
);
533 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
536 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
538 if (groupId
== null) {
540 g
= new GroupInfo(KeyUtils
.createGroupId());
541 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
543 g
= getGroupForSending(groupId
);
550 if (members
!= null) {
551 final Set
<String
> newE164Members
= new HashSet
<>();
552 for (SignalServiceAddress member
: members
) {
553 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
556 newE164Members
.add(member
.getNumber().get());
559 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
560 if (contacts
.size() != newE164Members
.size()) {
561 // Some of the new members are not registered on Signal
562 for (ContactTokenDetails contact
: contacts
) {
563 newE164Members
.remove(contact
.getNumber());
565 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
566 System
.err
.println("Aborting…");
570 g
.addMembers(members
);
573 if (avatarFile
!= null) {
574 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
575 File aFile
= getGroupAvatarFile(g
.groupId
);
576 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
579 account
.getGroupStore().updateGroup(g
);
581 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
583 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
587 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
588 if (groupId
== null) {
591 GroupInfo g
= getGroupForSending(groupId
);
593 if (!g
.isMember(recipient
)) {
597 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
599 // Send group message only to the recipient who requested it
600 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
603 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
604 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
607 .withMembers(new ArrayList
<>(g
.getMembers()));
609 File aFile
= getGroupAvatarFile(g
.groupId
);
610 if (aFile
.exists()) {
612 group
.withAvatar(Utils
.createAttachment(aFile
));
613 } catch (IOException e
) {
614 throw new AttachmentInvalidException(aFile
.toString(), e
);
618 return SignalServiceDataMessage
.newBuilder()
619 .asGroupMessage(group
.build())
620 .withExpiration(g
.messageExpirationTime
);
623 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
624 if (groupId
== null) {
628 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
631 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
632 .asGroupMessage(group
.build());
634 // Send group info request message to the recipient who sent us a message with this groupId
635 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
638 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
639 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
640 Collections
.singletonList(messageId
),
641 System
.currentTimeMillis());
643 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
646 public long sendMessage(String messageText
, List
<String
> attachments
,
647 List
<String
> recipients
)
648 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
649 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
650 if (attachments
!= null) {
651 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
653 // Upload attachments here, so we only upload once even for multiple recipients
654 SignalServiceMessageSender messageSender
= getMessageSender();
655 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
656 for (SignalServiceAttachment attachment
: attachmentStreams
) {
657 if (attachment
.isStream()) {
658 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
659 } else if (attachment
.isPointer()) {
660 attachmentPointers
.add(attachment
.asPointer());
664 messageBuilder
.withAttachments(attachmentPointers
);
666 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
669 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
670 long targetSentTimestamp
, List
<String
> recipients
)
671 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
672 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
673 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
674 .withReaction(reaction
);
675 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
678 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
679 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
680 .asEndSessionMessage();
682 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
684 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
685 } catch (Exception e
) {
686 for (SignalServiceAddress address
: signalServiceAddresses
) {
687 handleEndSession(address
);
694 public String
getContactName(String number
) throws InvalidNumberException
{
695 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
696 if (contact
== null) {
703 public void setContactName(String number
, String name
) throws InvalidNumberException
{
704 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
705 ContactInfo contact
= account
.getContactStore().getContact(address
);
706 if (contact
== null) {
707 contact
= new ContactInfo(address
);
708 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
710 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
713 account
.getContactStore().updateContact(contact
);
717 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
718 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
721 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
722 ContactInfo contact
= account
.getContactStore().getContact(address
);
723 if (contact
== null) {
724 contact
= new ContactInfo(address
);
725 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
727 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
729 contact
.blocked
= blocked
;
730 account
.getContactStore().updateContact(contact
);
734 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
735 GroupInfo group
= getGroup(groupId
);
737 throw new GroupNotFoundException(groupId
);
739 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
740 group
.blocked
= blocked
;
741 account
.getGroupStore().updateGroup(group
);
746 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
747 if (groupId
.length
== 0) {
750 if (name
.isEmpty()) {
753 if (members
.isEmpty()) {
756 if (avatar
.isEmpty()) {
759 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
763 * Change the expiration timer for a contact
765 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
766 ContactInfo contact
= account
.getContactStore().getContact(address
);
767 contact
.messageExpirationTime
= messageExpirationTimer
;
768 account
.getContactStore().updateContact(contact
);
769 sendExpirationTimerUpdate(address
);
773 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
774 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
775 .asExpirationUpdate();
776 sendMessage(messageBuilder
, Collections
.singleton(address
));
780 * Change the expiration timer for a contact
782 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
783 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
784 setExpirationTimer(address
, messageExpirationTimer
);
788 * Change the expiration timer for a group
790 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
791 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
792 g
.messageExpirationTime
= messageExpirationTimer
;
793 account
.getGroupStore().updateGroup(g
);
797 * Upload the sticker pack from path.
799 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
800 * @return if successful, returns the URL to install the sticker pack in the signal app
802 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
803 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
805 SignalServiceMessageSender messageSender
= getMessageSender();
807 byte[] packKey
= KeyUtils
.createStickerUploadKey();
808 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
811 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
813 } catch (URISyntaxException e
) {
814 throw new AssertionError(e
);
818 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
820 String rootPath
= null;
822 final File file
= new File(path
);
823 if (file
.getName().endsWith(".zip")) {
824 zip
= new ZipFile(file
);
825 } else if (file
.getName().equals("manifest.json")) {
826 rootPath
= file
.getParent();
828 throw new StickerPackInvalidException("Could not find manifest.json");
831 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
833 if (pack
.stickers
== null) {
834 throw new StickerPackInvalidException("Must set a 'stickers' field.");
837 if (pack
.stickers
.isEmpty()) {
838 throw new StickerPackInvalidException("Must include stickers.");
841 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
842 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
843 if (sticker
.file
== null) {
844 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
847 Pair
<InputStream
, Long
> data
;
849 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
850 } catch (IOException ignored
) {
851 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
854 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
855 stickers
.add(stickerInfo
);
858 StickerInfo cover
= null;
859 if (pack
.cover
!= null) {
860 if (pack
.cover
.file
== null) {
861 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
864 Pair
<InputStream
, Long
> data
;
866 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
867 } catch (IOException ignored
) {
868 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
871 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
874 return new SignalServiceStickerManifestUpload(
881 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
882 InputStream inputStream
;
884 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
886 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
888 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
891 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
893 final ZipEntry entry
= zip
.getEntry(subfile
);
894 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
896 final File file
= new File(rootPath
, subfile
);
897 return new Pair
<>(new FileInputStream(file
), file
.length());
901 void requestSyncGroups() throws IOException
{
902 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
903 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
905 sendSyncMessage(message
);
906 } catch (UntrustedIdentityException e
) {
911 void requestSyncContacts() throws IOException
{
912 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
913 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
915 sendSyncMessage(message
);
916 } catch (UntrustedIdentityException e
) {
921 void requestSyncBlocked() throws IOException
{
922 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
923 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
925 sendSyncMessage(message
);
926 } catch (UntrustedIdentityException e
) {
931 void requestSyncConfiguration() throws IOException
{
932 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
933 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
935 sendSyncMessage(message
);
936 } catch (UntrustedIdentityException e
) {
941 private byte[] getSenderCertificate() {
942 // TODO support UUID capable sender certificates
943 // byte[] certificate = accountManager.getSenderCertificate();
946 certificate
= accountManager
.getSenderCertificateLegacy();
947 } catch (IOException e
) {
948 System
.err
.println("Failed to get sender certificate: " + e
);
951 // TODO cache for a day
955 private byte[] getSelfUnidentifiedAccessKey() {
956 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
959 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
960 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
962 return new SignalProfile(
963 encryptedProfile
.getIdentityKey(),
964 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
965 encryptedProfile
.getAvatar(),
966 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
967 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
969 } catch (InvalidCiphertextException e
) {
974 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
975 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
976 if (contact
== null || contact
.profileKey
== null) {
979 ProfileKey theirProfileKey
;
981 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
982 } catch (InvalidInputException
| IOException e
) {
983 throw new AssertionError(e
);
985 SignalProfile targetProfile
;
987 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
988 } catch (IOException e
) {
989 System
.err
.println("Failed to get recipient profile: " + e
);
993 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
997 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
998 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1001 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1004 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1005 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1006 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1008 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1009 return Optional
.absent();
1013 return Optional
.of(new UnidentifiedAccessPair(
1014 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1015 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1017 } catch (InvalidCertificateException e
) {
1018 return Optional
.absent();
1022 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1023 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1024 for (SignalServiceAddress recipient
: recipients
) {
1025 result
.add(getAccessFor(recipient
));
1030 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1031 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1032 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1033 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1035 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1036 return Optional
.absent();
1040 return Optional
.of(new UnidentifiedAccessPair(
1041 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1042 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1044 } catch (InvalidCertificateException e
) {
1045 return Optional
.absent();
1049 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1050 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1052 if (unidentifiedAccess
.isPresent()) {
1053 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1056 return Optional
.absent();
1059 private void sendSyncMessage(SignalServiceSyncMessage message
)
1060 throws IOException
, UntrustedIdentityException
{
1061 SignalServiceMessageSender messageSender
= getMessageSender();
1063 messageSender
.sendMessage(message
, getAccessForSync());
1064 } catch (UntrustedIdentityException e
) {
1065 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1071 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1073 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1074 throws EncapsulatedExceptions
, IOException
{
1075 final long timestamp
= System
.currentTimeMillis();
1076 messageBuilder
.withTimestamp(timestamp
);
1077 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1079 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1080 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1081 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1083 for (SendMessageResult result
: results
) {
1084 if (result
.isUnregisteredFailure()) {
1085 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1086 } else if (result
.isNetworkFailure()) {
1087 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1088 } else if (result
.getIdentityFailure() != null) {
1089 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1092 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1093 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1098 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1099 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1101 for (String number
: numbers
) {
1102 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1104 return signalServiceAddresses
;
1107 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1108 throws IOException
{
1109 if (messagePipe
== null) {
1110 messagePipe
= getMessageReceiver().createMessagePipe();
1112 if (unidentifiedMessagePipe
== null) {
1113 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1115 SignalServiceDataMessage message
= null;
1117 message
= messageBuilder
.build();
1118 if (message
.getGroupContext().isPresent()) {
1120 SignalServiceMessageSender messageSender
= getMessageSender();
1121 final boolean isRecipientUpdate
= false;
1122 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1123 for (SendMessageResult r
: result
) {
1124 if (r
.getIdentityFailure() != null) {
1125 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1129 } catch (UntrustedIdentityException e
) {
1130 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1131 return Collections
.emptyList();
1134 // Send to all individually, so sync messages are sent correctly
1135 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1136 for (SignalServiceAddress address
: recipients
) {
1137 ContactInfo contact
= account
.getContactStore().getContact(address
);
1138 if (contact
!= null) {
1139 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1140 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1142 messageBuilder
.withExpiration(0);
1143 messageBuilder
.withProfileKey(null);
1145 message
= messageBuilder
.build();
1146 if (address
.matches(account
.getSelfAddress())) {
1147 results
.add(sendSelfMessage(message
));
1149 results
.add(sendMessage(address
, message
));
1155 if (message
!= null && message
.isEndSession()) {
1156 for (SignalServiceAddress recipient
: recipients
) {
1157 handleEndSession(recipient
);
1164 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1165 SignalServiceMessageSender messageSender
= getMessageSender();
1167 SignalServiceAddress recipient
= account
.getSelfAddress();
1169 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1170 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1171 message
.getTimestamp(),
1173 message
.getExpiresInSeconds(),
1174 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1176 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1179 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1180 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1181 } catch (UntrustedIdentityException e
) {
1182 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1183 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1187 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1188 SignalServiceMessageSender messageSender
= getMessageSender();
1191 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1192 } catch (UntrustedIdentityException e
) {
1193 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1194 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1198 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1199 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1201 return cipher
.decrypt(envelope
);
1202 } catch (ProtocolUntrustedIdentityException e
) {
1203 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1204 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1205 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1206 throw identityException
;
1208 throw new AssertionError(e
);
1212 private void handleEndSession(SignalServiceAddress source
) {
1213 account
.getSignalProtocolStore().deleteAllSessions(source
);
1216 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1217 List
<HandleAction
> actions
= new ArrayList
<>();
1218 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1219 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1220 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1221 switch (groupInfo
.getType()) {
1223 if (group
== null) {
1224 group
= new GroupInfo(groupInfo
.getGroupId());
1227 if (groupInfo
.getAvatar().isPresent()) {
1228 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1229 if (avatar
.isPointer()) {
1231 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1232 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1233 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1238 if (groupInfo
.getName().isPresent()) {
1239 group
.name
= groupInfo
.getName().get();
1242 if (groupInfo
.getMembers().isPresent()) {
1243 group
.addMembers(groupInfo
.getMembers().get()
1245 .map(this::resolveSignalServiceAddress
)
1246 .collect(Collectors
.toSet()));
1249 account
.getGroupStore().updateGroup(group
);
1252 if (group
== null && !isSync
) {
1253 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1257 if (group
!= null) {
1258 group
.removeMember(source
);
1259 account
.getGroupStore().updateGroup(group
);
1263 if (group
!= null && !isSync
) {
1264 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1269 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1270 if (message
.isEndSession()) {
1271 handleEndSession(conversationPartnerAddress
);
1273 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1274 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1275 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1276 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1277 if (group
== null) {
1278 group
= new GroupInfo(groupInfo
.getGroupId());
1280 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1281 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1282 account
.getGroupStore().updateGroup(group
);
1285 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1286 if (contact
== null) {
1287 contact
= new ContactInfo(conversationPartnerAddress
);
1289 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1290 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1291 account
.getContactStore().updateContact(contact
);
1295 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1296 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1297 if (attachment
.isPointer()) {
1299 retrieveAttachment(attachment
.asPointer());
1300 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1301 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1306 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1307 if (source
.matches(account
.getSelfAddress())) {
1309 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1310 } catch (InvalidInputException ignored
) {
1312 ContactInfo contact
= account
.getContactStore().getContact(source
);
1313 if (contact
!= null) {
1314 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1315 account
.getContactStore().updateContact(contact
);
1318 ContactInfo contact
= account
.getContactStore().getContact(source
);
1319 if (contact
== null) {
1320 contact
= new ContactInfo(source
);
1322 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1323 account
.getContactStore().updateContact(contact
);
1326 if (message
.getPreviews().isPresent()) {
1327 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1328 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1329 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1330 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1332 retrieveAttachment(attachment
);
1333 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1334 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1342 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1343 final File cachePath
= new File(getMessageCachePath());
1344 if (!cachePath
.exists()) {
1347 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1348 if (!dir
.isDirectory()) {
1349 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1353 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1354 if (!fileEntry
.isFile()) {
1357 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1359 // Try to delete directory if empty
1364 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1365 SignalServiceEnvelope envelope
;
1367 envelope
= Utils
.loadEnvelope(fileEntry
);
1368 if (envelope
== null) {
1371 } catch (IOException e
) {
1372 e
.printStackTrace();
1375 SignalServiceContent content
= null;
1376 if (!envelope
.isReceipt()) {
1378 content
= decryptMessage(envelope
);
1379 } catch (Exception e
) {
1382 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1383 for (HandleAction action
: actions
) {
1385 action
.execute(this);
1386 } catch (Throwable e
) {
1387 e
.printStackTrace();
1392 handler
.handleMessage(envelope
, content
, null);
1394 Files
.delete(fileEntry
.toPath());
1395 } catch (IOException e
) {
1396 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1400 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1401 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1402 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1404 Set
<HandleAction
> queuedActions
= null;
1406 if (messagePipe
== null) {
1407 messagePipe
= messageReceiver
.createMessagePipe();
1410 boolean hasCaughtUpWithOldMessages
= false;
1413 SignalServiceEnvelope envelope
;
1414 SignalServiceContent content
= null;
1415 Exception exception
= null;
1416 final long now
= new Date().getTime();
1418 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1419 // store message on disk, before acknowledging receipt to the server
1421 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1422 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1423 Utils
.storeEnvelope(envelope1
, cacheFile
);
1424 } catch (IOException e
) {
1425 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1428 if (result
.isPresent()) {
1429 envelope
= result
.get();
1431 // Received indicator that server queue is empty
1432 hasCaughtUpWithOldMessages
= true;
1434 if (queuedActions
!= null) {
1435 for (HandleAction action
: queuedActions
) {
1437 action
.execute(this);
1438 } catch (Throwable e
) {
1439 e
.printStackTrace();
1442 queuedActions
.clear();
1443 queuedActions
= null;
1446 // Continue to wait another timeout for new messages
1449 } catch (TimeoutException e
) {
1450 if (returnOnTimeout
)
1453 } catch (InvalidVersionException e
) {
1454 System
.err
.println("Ignoring error: " + e
.getMessage());
1457 if (envelope
.hasSource()) {
1458 // Store uuid if we don't have it already
1459 SignalServiceAddress source
= envelope
.getSourceAddress();
1460 resolveSignalServiceAddress(source
);
1462 if (!envelope
.isReceipt()) {
1464 content
= decryptMessage(envelope
);
1465 } catch (Exception e
) {
1468 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1469 if (hasCaughtUpWithOldMessages
) {
1470 for (HandleAction action
: actions
) {
1472 action
.execute(this);
1473 } catch (Throwable e
) {
1474 e
.printStackTrace();
1478 if (queuedActions
== null) {
1479 queuedActions
= new HashSet
<>();
1481 queuedActions
.addAll(actions
);
1485 if (!isMessageBlocked(envelope
, content
)) {
1486 handler
.handleMessage(envelope
, content
, exception
);
1488 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1489 File cacheFile
= null;
1491 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1492 Files
.delete(cacheFile
.toPath());
1493 // Try to delete directory if empty
1494 new File(getMessageCachePath()).delete();
1495 } catch (IOException e
) {
1496 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1502 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1503 SignalServiceAddress source
;
1504 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1505 source
= envelope
.getSourceAddress();
1506 } else if (content
!= null) {
1507 source
= content
.getSender();
1511 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1512 if (sourceContact
!= null && sourceContact
.blocked
) {
1516 if (content
!= null && content
.getDataMessage().isPresent()) {
1517 SignalServiceDataMessage message
= content
.getDataMessage().get();
1518 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1519 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1520 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1521 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1529 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1530 List
<HandleAction
> actions
= new ArrayList
<>();
1531 if (content
!= null) {
1532 SignalServiceAddress sender
;
1533 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1534 sender
= envelope
.getSourceAddress();
1536 sender
= content
.getSender();
1538 // Store uuid if we don't have it already
1539 resolveSignalServiceAddress(sender
);
1541 if (content
.getDataMessage().isPresent()) {
1542 SignalServiceDataMessage message
= content
.getDataMessage().get();
1544 if (content
.isNeedsReceipt()) {
1545 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1548 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1550 if (content
.getSyncMessage().isPresent()) {
1551 account
.setMultiDevice(true);
1552 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1553 if (syncMessage
.getSent().isPresent()) {
1554 SentTranscriptMessage message
= syncMessage
.getSent().get();
1555 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1557 if (syncMessage
.getRequest().isPresent()) {
1558 RequestMessage rm
= syncMessage
.getRequest().get();
1559 if (rm
.isContactsRequest()) {
1560 actions
.add(SendSyncContactsAction
.create());
1562 if (rm
.isGroupsRequest()) {
1563 actions
.add(SendSyncGroupsAction
.create());
1565 if (rm
.isBlockedListRequest()) {
1566 actions
.add(SendSyncBlockedListAction
.create());
1568 // TODO Handle rm.isConfigurationRequest();
1570 if (syncMessage
.getGroups().isPresent()) {
1571 File tmpFile
= null;
1573 tmpFile
= IOUtils
.createTempFile();
1574 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1575 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1577 while ((g
= s
.read()) != null) {
1578 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1579 if (syncGroup
== null) {
1580 syncGroup
= new GroupInfo(g
.getId());
1582 if (g
.getName().isPresent()) {
1583 syncGroup
.name
= g
.getName().get();
1585 syncGroup
.addMembers(g
.getMembers()
1587 .map(this::resolveSignalServiceAddress
)
1588 .collect(Collectors
.toSet()));
1589 if (!g
.isActive()) {
1590 syncGroup
.removeMember(account
.getSelfAddress());
1592 // Add ourself to the member set as it's marked as active
1593 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1595 syncGroup
.blocked
= g
.isBlocked();
1596 if (g
.getColor().isPresent()) {
1597 syncGroup
.color
= g
.getColor().get();
1600 if (g
.getAvatar().isPresent()) {
1601 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1603 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1604 syncGroup
.archived
= g
.isArchived();
1605 account
.getGroupStore().updateGroup(syncGroup
);
1608 } catch (Exception e
) {
1609 e
.printStackTrace();
1611 if (tmpFile
!= null) {
1613 Files
.delete(tmpFile
.toPath());
1614 } catch (IOException e
) {
1615 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1620 if (syncMessage
.getBlockedList().isPresent()) {
1621 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1622 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1623 setContactBlocked(resolveSignalServiceAddress(address
), true);
1625 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1627 setGroupBlocked(groupId
, true);
1628 } catch (GroupNotFoundException e
) {
1629 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1633 if (syncMessage
.getContacts().isPresent()) {
1634 File tmpFile
= null;
1636 tmpFile
= IOUtils
.createTempFile();
1637 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1638 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1639 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1640 if (contactsMessage
.isComplete()) {
1641 account
.getContactStore().clear();
1644 while ((c
= s
.read()) != null) {
1645 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1646 account
.setProfileKey(c
.getProfileKey().get());
1648 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1649 ContactInfo contact
= account
.getContactStore().getContact(address
);
1650 if (contact
== null) {
1651 contact
= new ContactInfo(address
);
1653 if (c
.getName().isPresent()) {
1654 contact
.name
= c
.getName().get();
1656 if (c
.getColor().isPresent()) {
1657 contact
.color
= c
.getColor().get();
1659 if (c
.getProfileKey().isPresent()) {
1660 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1662 if (c
.getVerified().isPresent()) {
1663 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1664 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1666 if (c
.getExpirationTimer().isPresent()) {
1667 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1669 contact
.blocked
= c
.isBlocked();
1670 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1671 contact
.archived
= c
.isArchived();
1672 account
.getContactStore().updateContact(contact
);
1674 if (c
.getAvatar().isPresent()) {
1675 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1679 } catch (Exception e
) {
1680 e
.printStackTrace();
1682 if (tmpFile
!= null) {
1684 Files
.delete(tmpFile
.toPath());
1685 } catch (IOException e
) {
1686 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1691 if (syncMessage
.getVerified().isPresent()) {
1692 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1693 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1695 if (syncMessage
.getConfiguration().isPresent()) {
1703 private File
getContactAvatarFile(String number
) {
1704 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1707 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1708 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1709 if (attachment
.isPointer()) {
1710 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1711 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1713 SignalServiceAttachmentStream stream
= attachment
.asStream();
1714 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1718 private File
getGroupAvatarFile(byte[] groupId
) {
1719 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1722 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1723 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1724 if (attachment
.isPointer()) {
1725 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1726 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1728 SignalServiceAttachmentStream stream
= attachment
.asStream();
1729 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1733 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1734 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1737 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1738 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1739 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1742 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1743 if (storePreview
&& pointer
.getPreview().isPresent()) {
1744 File previewFile
= new File(outputFile
+ ".preview");
1745 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1746 byte[] preview
= pointer
.getPreview().get();
1747 output
.write(preview
, 0, preview
.length
);
1748 } catch (FileNotFoundException e
) {
1749 e
.printStackTrace();
1754 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1756 File tmpFile
= IOUtils
.createTempFile();
1757 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1758 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1759 byte[] buffer
= new byte[4096];
1762 while ((read
= input
.read(buffer
)) != -1) {
1763 output
.write(buffer
, 0, read
);
1765 } catch (FileNotFoundException e
) {
1766 e
.printStackTrace();
1771 Files
.delete(tmpFile
.toPath());
1772 } catch (IOException e
) {
1773 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1779 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1780 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1781 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1784 void sendGroups() throws IOException
, UntrustedIdentityException
{
1785 File groupsFile
= IOUtils
.createTempFile();
1788 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1789 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1790 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1791 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1792 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1793 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1794 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1798 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1799 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1800 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1801 .withStream(groupsFileStream
)
1802 .withContentType("application/octet-stream")
1803 .withLength(groupsFile
.length())
1806 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1811 Files
.delete(groupsFile
.toPath());
1812 } catch (IOException e
) {
1813 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1818 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1819 File contactsFile
= IOUtils
.createTempFile();
1822 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1823 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1824 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1825 VerifiedMessage verifiedMessage
= null;
1826 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1827 if (currentIdentity
!= null) {
1828 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1831 ProfileKey profileKey
= null;
1833 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1834 } catch (InvalidInputException ignored
) {
1836 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1837 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1838 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1839 Optional
.of(record.messageExpirationTime
),
1840 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1843 if (account
.getProfileKey() != null) {
1844 // Send our own profile key as well
1845 out
.write(new DeviceContact(account
.getSelfAddress(),
1846 Optional
.absent(), Optional
.absent(),
1847 Optional
.absent(), Optional
.absent(),
1848 Optional
.of(account
.getProfileKey()),
1849 false, Optional
.absent(), Optional
.absent(), false));
1853 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1854 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1855 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1856 .withStream(contactsFileStream
)
1857 .withContentType("application/octet-stream")
1858 .withLength(contactsFile
.length())
1861 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1866 Files
.delete(contactsFile
.toPath());
1867 } catch (IOException e
) {
1868 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1873 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1874 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1875 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1876 if (record.blocked
) {
1877 addresses
.add(record.getAddress());
1880 List
<byte[]> groupIds
= new ArrayList
<>();
1881 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1882 if (record.blocked
) {
1883 groupIds
.add(record.groupId
);
1886 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1889 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1890 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1891 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1894 public List
<ContactInfo
> getContacts() {
1895 return account
.getContactStore().getContacts();
1898 public ContactInfo
getContact(String number
) {
1899 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1902 public GroupInfo
getGroup(byte[] groupId
) {
1903 return account
.getGroupStore().getGroup(groupId
);
1906 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1907 return account
.getSignalProtocolStore().getIdentities();
1910 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1911 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1915 * Trust this the identity with this fingerprint
1917 * @param name username of the identity
1918 * @param fingerprint Fingerprint
1920 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1921 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1922 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1926 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1927 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1931 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1933 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1934 } catch (IOException
| UntrustedIdentityException e
) {
1935 e
.printStackTrace();
1944 * Trust this the identity with this safety number
1946 * @param name username of the identity
1947 * @param safetyNumber Safety number
1949 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1950 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1951 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1955 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1956 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1960 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1962 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1963 } catch (IOException
| UntrustedIdentityException e
) {
1964 e
.printStackTrace();
1973 * Trust all keys of this identity without verification
1975 * @param name username of the identity
1977 public boolean trustIdentityAllKeys(String name
) {
1978 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1979 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1983 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1984 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1985 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1987 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1988 } catch (IOException
| UntrustedIdentityException e
) {
1989 e
.printStackTrace();
1997 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1998 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2001 void saveAccount() {
2005 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2006 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2007 return resolveSignalServiceAddress(canonicalizedNumber
);
2010 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2011 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2013 return resolveSignalServiceAddress(address
);
2016 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2017 if (address
.matches(account
.getSelfAddress())) {
2018 return account
.getSelfAddress();
2021 return account
.getRecipientStore().resolveServiceAddress(address
);
2025 public void close() throws IOException
{
2026 if (messagePipe
!= null) {
2027 messagePipe
.shutdown();
2031 if (unidentifiedMessagePipe
!= null) {
2032 unidentifiedMessagePipe
.shutdown();
2033 unidentifiedMessagePipe
= null;
2039 public interface ReceiveMessageHandler
{
2041 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);