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
.profiles
.ClientZkProfileOperations
;
42 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
43 import org
.whispersystems
.libsignal
.IdentityKey
;
44 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
45 import org
.whispersystems
.libsignal
.InvalidKeyException
;
46 import org
.whispersystems
.libsignal
.InvalidMessageException
;
47 import org
.whispersystems
.libsignal
.InvalidVersionException
;
48 import org
.whispersystems
.libsignal
.ecc
.Curve
;
49 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
50 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
51 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
52 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
53 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
54 import org
.whispersystems
.libsignal
.util
.Medium
;
55 import org
.whispersystems
.libsignal
.util
.Pair
;
56 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
57 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
61 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
67 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
79 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
92 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
93 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
94 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
95 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
99 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
100 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
101 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
102 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
103 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
104 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
105 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
106 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
107 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
108 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
109 import org
.whispersystems
.util
.Base64
;
111 import java
.io
.Closeable
;
113 import java
.io
.FileInputStream
;
114 import java
.io
.FileNotFoundException
;
115 import java
.io
.FileOutputStream
;
116 import java
.io
.IOException
;
117 import java
.io
.InputStream
;
118 import java
.io
.OutputStream
;
120 import java
.net
.URISyntaxException
;
121 import java
.net
.URLEncoder
;
122 import java
.nio
.file
.Files
;
123 import java
.nio
.file
.Paths
;
124 import java
.nio
.file
.StandardCopyOption
;
125 import java
.util
.ArrayList
;
126 import java
.util
.Arrays
;
127 import java
.util
.Collection
;
128 import java
.util
.Collections
;
129 import java
.util
.Date
;
130 import java
.util
.HashSet
;
131 import java
.util
.LinkedList
;
132 import java
.util
.List
;
133 import java
.util
.Locale
;
134 import java
.util
.Objects
;
135 import java
.util
.Set
;
136 import java
.util
.UUID
;
137 import java
.util
.concurrent
.ExecutionException
;
138 import java
.util
.concurrent
.ExecutorService
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.stream
.Collectors
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Closeable
{
147 private final SleepTimer timer
= new UptimeSleepTimer();
148 private final SignalServiceConfiguration serviceConfiguration
;
149 private final String userAgent
;
151 private final SignalAccount account
;
152 private final PathConfig pathConfig
;
153 private SignalServiceAccountManager accountManager
;
154 private SignalServiceMessagePipe messagePipe
= null;
155 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
156 private boolean discoverableByPhoneNumber
= true;
158 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
159 this.account
= account
;
160 this.pathConfig
= pathConfig
;
161 this.serviceConfiguration
= serviceConfiguration
;
162 this.userAgent
= userAgent
;
163 this.accountManager
= createSignalServiceAccountManager();
165 this.account
.setResolver(this::resolveSignalServiceAddress
);
168 public String
getUsername() {
169 return account
.getUsername();
172 public SignalServiceAddress
getSelfAddress() {
173 return account
.getSelfAddress();
176 private SignalServiceAccountManager
createSignalServiceAccountManager() {
177 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
180 private IdentityKeyPair
getIdentityKeyPair() {
181 return account
.getSignalProtocolStore().getIdentityKeyPair();
184 public int getDeviceId() {
185 return account
.getDeviceId();
188 private String
getMessageCachePath() {
189 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
192 private String
getMessageCachePath(String sender
) {
193 if (sender
== null || sender
.isEmpty()) {
194 return getMessageCachePath();
197 return getMessageCachePath() + "/" + sender
.replace("/", "_");
200 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
201 String cachePath
= getMessageCachePath(sender
);
202 IOUtils
.createPrivateDirectories(cachePath
);
203 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
206 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
207 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
209 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
210 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
211 int registrationId
= KeyHelper
.generateRegistrationId(false);
213 ProfileKey profileKey
= KeyUtils
.createProfileKey();
214 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
217 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
220 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
222 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
224 m
.migrateLegacyConfigs();
229 private void migrateLegacyConfigs() {
230 // Copy group avatars that were previously stored in the attachments folder
231 // to the new avatar folder
232 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
233 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
234 File avatarFile
= getGroupAvatarFile(g
.groupId
);
235 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
236 if (!avatarFile
.exists() && attachmentFile
.exists()) {
238 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
239 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
240 } catch (Exception e
) {
245 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
248 if (account
.getProfileKey() == null) {
249 // Old config file, creating new profile key
250 account
.setProfileKey(KeyUtils
.createProfileKey());
255 public void checkAccountState() throws IOException
{
256 if (account
.isRegistered()) {
257 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
261 if (account
.getUuid() == null) {
262 account
.setUuid(accountManager
.getOwnUuid());
268 public boolean isRegistered() {
269 return account
.isRegistered();
272 public void register(boolean voiceVerification
) throws IOException
{
273 account
.setPassword(KeyUtils
.createPassword());
275 // Resetting UUID, because registering doesn't work otherwise
276 account
.setUuid(null);
277 accountManager
= createSignalServiceAccountManager();
279 if (voiceVerification
) {
280 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
282 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
285 account
.setRegistered(false);
289 public void updateAccountAttributes() throws IOException
{
290 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
, discoverableByPhoneNumber
);
293 public void setProfile(String name
, File avatar
) throws IOException
{
294 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
295 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
299 public void unregister() throws IOException
{
300 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
301 // If this is the master device, other users can't send messages to this number anymore.
302 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
303 accountManager
.setGcmId(Optional
.absent());
305 account
.setRegistered(false);
309 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
310 List
<DeviceInfo
> devices
= accountManager
.getDevices();
311 account
.setMultiDevice(devices
.size() > 1);
316 public void removeLinkedDevices(int deviceId
) throws IOException
{
317 accountManager
.removeDevice(deviceId
);
318 List
<DeviceInfo
> devices
= accountManager
.getDevices();
319 account
.setMultiDevice(devices
.size() > 1);
323 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
324 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
326 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
329 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
330 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
331 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
333 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
334 account
.setMultiDevice(true);
338 private List
<PreKeyRecord
> generatePreKeys() {
339 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
341 final int offset
= account
.getPreKeyIdOffset();
342 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
343 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
344 ECKeyPair keyPair
= Curve
.generateKeyPair();
345 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
350 account
.addPreKeys(records
);
356 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
358 ECKeyPair keyPair
= Curve
.generateKeyPair();
359 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
360 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
362 account
.addSignedPreKey(record);
366 } catch (InvalidKeyException e
) {
367 throw new AssertionError(e
);
371 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
372 verificationCode
= verificationCode
.replace("-", "");
373 account
.setSignalingKey(KeyUtils
.createSignalingKey());
374 // TODO make unrestricted unidentified access configurable
375 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
, discoverableByPhoneNumber
);
377 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
378 // TODO response.isStorageCapable()
379 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
380 account
.setRegistered(true);
381 account
.setUuid(uuid
);
382 account
.setRegistrationLockPin(pin
);
383 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
389 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
390 if (pin
.isPresent()) {
391 account
.setRegistrationLockPin(pin
.get());
392 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
394 account
.setRegistrationLockPin(null);
395 accountManager
.removeRegistrationLockV1();
400 void refreshPreKeys() throws IOException
{
401 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
402 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
403 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
405 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
408 private SignalServiceMessageReceiver
getMessageReceiver() {
409 // TODO implement ZkGroup support
410 final ClientZkProfileOperations clientZkProfileOperations
= null;
411 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
414 private SignalServiceMessageSender
getMessageSender() {
415 // TODO implement ZkGroup support
416 final ClientZkProfileOperations clientZkProfileOperations
= null;
417 final boolean attachmentsV3
= false;
418 final ExecutorService executor
= null;
419 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
420 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
);
423 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
424 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
429 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
430 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
434 SignalServiceMessageReceiver receiver
= getMessageReceiver();
436 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
437 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
438 throw new IOException("Failed to retrieve profile", e
);
442 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
443 return decryptProfile(getEncryptedRecipientProfile(address
, unidentifiedAccess
), profileKey
);
446 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
447 File file
= getGroupAvatarFile(groupId
);
448 if (!file
.exists()) {
449 return Optional
.absent();
452 return Optional
.of(Utils
.createAttachment(file
));
455 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
456 File file
= getContactAvatarFile(number
);
457 if (!file
.exists()) {
458 return Optional
.absent();
461 return Optional
.of(Utils
.createAttachment(file
));
464 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
465 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
467 throw new GroupNotFoundException(groupId
);
469 if (!g
.isMember(account
.getSelfAddress())) {
470 throw new NotAGroupMemberException(groupId
, g
.name
);
475 public List
<GroupInfo
> getGroups() {
476 return account
.getGroupStore().getGroups();
479 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
481 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
482 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
483 if (attachments
!= null) {
484 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
486 if (groupId
!= null) {
487 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
490 messageBuilder
.asGroupMessage(group
);
493 final GroupInfo g
= getGroupForSending(groupId
);
495 messageBuilder
.withExpiration(g
.messageExpirationTime
);
497 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
500 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
501 long targetSentTimestamp
, byte[] groupId
)
502 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
503 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
504 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
505 .withReaction(reaction
);
506 if (groupId
!= null) {
507 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
510 messageBuilder
.asGroupMessage(group
);
512 final GroupInfo g
= getGroupForSending(groupId
);
513 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
516 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
517 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
521 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
522 .asGroupMessage(group
);
524 final GroupInfo g
= getGroupForSending(groupId
);
525 g
.removeMember(account
.getSelfAddress());
526 account
.getGroupStore().updateGroup(g
);
528 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
531 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
533 if (groupId
== null) {
535 g
= new GroupInfo(KeyUtils
.createGroupId());
536 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
538 g
= getGroupForSending(groupId
);
545 if (members
!= null) {
546 final Set
<String
> newE164Members
= new HashSet
<>();
547 for (SignalServiceAddress member
: members
) {
548 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
551 newE164Members
.add(member
.getNumber().get());
554 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
555 if (contacts
.size() != newE164Members
.size()) {
556 // Some of the new members are not registered on Signal
557 for (ContactTokenDetails contact
: contacts
) {
558 newE164Members
.remove(contact
.getNumber());
560 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
563 g
.addMembers(members
);
566 if (avatarFile
!= null) {
567 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
568 File aFile
= getGroupAvatarFile(g
.groupId
);
569 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
572 account
.getGroupStore().updateGroup(g
);
574 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
580 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
581 if (groupId
== null) {
584 GroupInfo g
= getGroupForSending(groupId
);
586 if (!g
.isMember(recipient
)) {
590 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
592 // Send group message only to the recipient who requested it
593 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
596 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
597 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
600 .withMembers(new ArrayList
<>(g
.getMembers()));
602 File aFile
= getGroupAvatarFile(g
.groupId
);
603 if (aFile
.exists()) {
605 group
.withAvatar(Utils
.createAttachment(aFile
));
606 } catch (IOException e
) {
607 throw new AttachmentInvalidException(aFile
.toString(), e
);
611 return SignalServiceDataMessage
.newBuilder()
612 .asGroupMessage(group
.build())
613 .withExpiration(g
.messageExpirationTime
);
616 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
617 if (groupId
== null) {
621 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
624 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
625 .asGroupMessage(group
.build());
627 // Send group info request message to the recipient who sent us a message with this groupId
628 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
631 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
632 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
633 Collections
.singletonList(messageId
),
634 System
.currentTimeMillis());
636 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
639 public long sendMessage(String messageText
, List
<String
> attachments
,
640 List
<String
> recipients
)
641 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
642 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
643 if (attachments
!= null) {
644 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
646 // Upload attachments here, so we only upload once even for multiple recipients
647 SignalServiceMessageSender messageSender
= getMessageSender();
648 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
649 for (SignalServiceAttachment attachment
: attachmentStreams
) {
650 if (attachment
.isStream()) {
651 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
652 } else if (attachment
.isPointer()) {
653 attachmentPointers
.add(attachment
.asPointer());
657 messageBuilder
.withAttachments(attachmentPointers
);
659 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
662 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
663 long targetSentTimestamp
, List
<String
> recipients
)
664 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
665 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
666 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
667 .withReaction(reaction
);
668 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
671 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
672 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
673 .asEndSessionMessage();
675 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
677 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
678 } catch (Exception e
) {
679 for (SignalServiceAddress address
: signalServiceAddresses
) {
680 handleEndSession(address
);
687 public String
getContactName(String number
) throws InvalidNumberException
{
688 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
689 if (contact
== null) {
696 public void setContactName(String number
, String name
) throws InvalidNumberException
{
697 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
698 ContactInfo contact
= account
.getContactStore().getContact(address
);
699 if (contact
== null) {
700 contact
= new ContactInfo(address
);
703 account
.getContactStore().updateContact(contact
);
707 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
708 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
711 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
712 ContactInfo contact
= account
.getContactStore().getContact(address
);
713 if (contact
== null) {
714 contact
= new ContactInfo(address
);
716 contact
.blocked
= blocked
;
717 account
.getContactStore().updateContact(contact
);
721 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
722 GroupInfo group
= getGroup(groupId
);
724 throw new GroupNotFoundException(groupId
);
727 group
.blocked
= blocked
;
728 account
.getGroupStore().updateGroup(group
);
732 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
733 if (groupId
.length
== 0) {
736 if (name
.isEmpty()) {
739 if (members
.isEmpty()) {
742 if (avatar
.isEmpty()) {
745 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
749 * Change the expiration timer for a contact
751 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
752 ContactInfo contact
= account
.getContactStore().getContact(address
);
753 contact
.messageExpirationTime
= messageExpirationTimer
;
754 account
.getContactStore().updateContact(contact
);
755 sendExpirationTimerUpdate(address
);
759 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
760 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
761 .asExpirationUpdate();
762 sendMessage(messageBuilder
, Collections
.singleton(address
));
766 * Change the expiration timer for a contact
768 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
769 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
770 setExpirationTimer(address
, messageExpirationTimer
);
774 * Change the expiration timer for a group
776 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
777 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
778 g
.messageExpirationTime
= messageExpirationTimer
;
779 account
.getGroupStore().updateGroup(g
);
783 * Upload the sticker pack from path.
785 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
786 * @return if successful, returns the URL to install the sticker pack in the signal app
788 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
789 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
791 SignalServiceMessageSender messageSender
= getMessageSender();
793 byte[] packKey
= KeyUtils
.createStickerUploadKey();
794 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
797 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
799 } catch (URISyntaxException e
) {
800 throw new AssertionError(e
);
804 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
806 String rootPath
= null;
808 final File file
= new File(path
);
809 if (file
.getName().endsWith(".zip")) {
810 zip
= new ZipFile(file
);
811 } else if (file
.getName().equals("manifest.json")) {
812 rootPath
= file
.getParent();
814 throw new StickerPackInvalidException("Could not find manifest.json");
817 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
819 if (pack
.stickers
== null) {
820 throw new StickerPackInvalidException("Must set a 'stickers' field.");
823 if (pack
.stickers
.isEmpty()) {
824 throw new StickerPackInvalidException("Must include stickers.");
827 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
828 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
829 if (sticker
.file
== null) {
830 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
833 Pair
<InputStream
, Long
> data
;
835 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
836 } catch (IOException ignored
) {
837 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
840 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
841 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
842 stickers
.add(stickerInfo
);
845 StickerInfo cover
= null;
846 if (pack
.cover
!= null) {
847 if (pack
.cover
.file
== null) {
848 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
851 Pair
<InputStream
, Long
> data
;
853 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
854 } catch (IOException ignored
) {
855 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
858 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
859 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
862 return new SignalServiceStickerManifestUpload(
869 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
870 InputStream inputStream
;
872 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
874 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
876 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
879 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
881 final ZipEntry entry
= zip
.getEntry(subfile
);
882 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
884 final File file
= new File(rootPath
, subfile
);
885 return new Pair
<>(new FileInputStream(file
), file
.length());
889 void requestSyncGroups() throws IOException
{
890 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
891 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
893 sendSyncMessage(message
);
894 } catch (UntrustedIdentityException e
) {
899 void requestSyncContacts() throws IOException
{
900 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
901 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
903 sendSyncMessage(message
);
904 } catch (UntrustedIdentityException e
) {
909 void requestSyncBlocked() throws IOException
{
910 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
911 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
913 sendSyncMessage(message
);
914 } catch (UntrustedIdentityException e
) {
919 void requestSyncConfiguration() throws IOException
{
920 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
921 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
923 sendSyncMessage(message
);
924 } catch (UntrustedIdentityException e
) {
929 private byte[] getSenderCertificate() {
930 // TODO support UUID capable sender certificates
931 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
934 certificate
= accountManager
.getSenderCertificate();
935 } catch (IOException e
) {
936 System
.err
.println("Failed to get sender certificate: " + e
);
939 // TODO cache for a day
943 private byte[] getSelfUnidentifiedAccessKey() {
944 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
947 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
948 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
950 return new SignalProfile(
951 encryptedProfile
.getIdentityKey(),
952 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
953 encryptedProfile
.getAvatar(),
954 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
955 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
957 } catch (InvalidCiphertextException e
) {
962 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
963 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
964 if (contact
== null || contact
.profileKey
== null) {
967 ProfileKey theirProfileKey
;
969 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
970 } catch (InvalidInputException
| IOException e
) {
971 throw new AssertionError(e
);
973 SignalProfile targetProfile
;
975 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
976 } catch (IOException e
) {
977 System
.err
.println("Failed to get recipient profile: " + e
);
981 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
985 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
986 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
989 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
992 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
993 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
994 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
996 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
997 return Optional
.absent();
1001 return Optional
.of(new UnidentifiedAccessPair(
1002 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1003 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1005 } catch (InvalidCertificateException e
) {
1006 return Optional
.absent();
1010 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1011 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1012 for (SignalServiceAddress recipient
: recipients
) {
1013 result
.add(getAccessFor(recipient
));
1018 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1019 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1020 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1021 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1023 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1024 return Optional
.absent();
1028 return Optional
.of(new UnidentifiedAccessPair(
1029 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1030 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1032 } catch (InvalidCertificateException e
) {
1033 return Optional
.absent();
1037 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1038 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1040 if (unidentifiedAccess
.isPresent()) {
1041 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1044 return Optional
.absent();
1047 private void sendSyncMessage(SignalServiceSyncMessage message
)
1048 throws IOException
, UntrustedIdentityException
{
1049 SignalServiceMessageSender messageSender
= getMessageSender();
1051 messageSender
.sendMessage(message
, getAccessForSync());
1052 } catch (UntrustedIdentityException e
) {
1053 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1059 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1061 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1062 throws EncapsulatedExceptions
, IOException
{
1063 final long timestamp
= System
.currentTimeMillis();
1064 messageBuilder
.withTimestamp(timestamp
);
1065 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1067 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1068 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1069 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1071 for (SendMessageResult result
: results
) {
1072 if (result
.isUnregisteredFailure()) {
1073 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1074 } else if (result
.isNetworkFailure()) {
1075 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1076 } else if (result
.getIdentityFailure() != null) {
1077 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1080 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1081 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1086 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1087 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1089 for (String number
: numbers
) {
1090 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1092 return signalServiceAddresses
;
1095 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1096 throws IOException
{
1097 if (messagePipe
== null) {
1098 messagePipe
= getMessageReceiver().createMessagePipe();
1100 if (unidentifiedMessagePipe
== null) {
1101 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1103 SignalServiceDataMessage message
= null;
1105 message
= messageBuilder
.build();
1106 if (message
.getGroupContext().isPresent()) {
1108 SignalServiceMessageSender messageSender
= getMessageSender();
1109 final boolean isRecipientUpdate
= false;
1110 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1111 for (SendMessageResult r
: result
) {
1112 if (r
.getIdentityFailure() != null) {
1113 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1117 } catch (UntrustedIdentityException e
) {
1118 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1119 return Collections
.emptyList();
1122 // Send to all individually, so sync messages are sent correctly
1123 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1124 for (SignalServiceAddress address
: recipients
) {
1125 ContactInfo contact
= account
.getContactStore().getContact(address
);
1126 if (contact
!= null) {
1127 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1128 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1130 messageBuilder
.withExpiration(0);
1131 messageBuilder
.withProfileKey(null);
1133 message
= messageBuilder
.build();
1134 if (address
.matches(account
.getSelfAddress())) {
1135 results
.add(sendSelfMessage(message
));
1137 results
.add(sendMessage(address
, message
));
1143 if (message
!= null && message
.isEndSession()) {
1144 for (SignalServiceAddress recipient
: recipients
) {
1145 handleEndSession(recipient
);
1152 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1153 SignalServiceMessageSender messageSender
= getMessageSender();
1155 SignalServiceAddress recipient
= account
.getSelfAddress();
1157 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1158 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1159 message
.getTimestamp(),
1161 message
.getExpiresInSeconds(),
1162 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1164 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1167 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1168 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1169 } catch (UntrustedIdentityException e
) {
1170 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1171 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1175 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1176 SignalServiceMessageSender messageSender
= getMessageSender();
1179 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1180 } catch (UntrustedIdentityException e
) {
1181 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1182 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1186 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1187 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1189 return cipher
.decrypt(envelope
);
1190 } catch (ProtocolUntrustedIdentityException e
) {
1191 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1192 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1193 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1194 throw identityException
;
1196 throw new AssertionError(e
);
1200 private void handleEndSession(SignalServiceAddress source
) {
1201 account
.getSignalProtocolStore().deleteAllSessions(source
);
1204 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1205 List
<HandleAction
> actions
= new ArrayList
<>();
1206 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1207 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1208 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1209 switch (groupInfo
.getType()) {
1211 if (group
== null) {
1212 group
= new GroupInfo(groupInfo
.getGroupId());
1215 if (groupInfo
.getAvatar().isPresent()) {
1216 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1217 if (avatar
.isPointer()) {
1219 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1220 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1221 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1226 if (groupInfo
.getName().isPresent()) {
1227 group
.name
= groupInfo
.getName().get();
1230 if (groupInfo
.getMembers().isPresent()) {
1231 group
.addMembers(groupInfo
.getMembers().get()
1233 .map(this::resolveSignalServiceAddress
)
1234 .collect(Collectors
.toSet()));
1237 account
.getGroupStore().updateGroup(group
);
1240 if (group
== null && !isSync
) {
1241 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1245 if (group
!= null) {
1246 group
.removeMember(source
);
1247 account
.getGroupStore().updateGroup(group
);
1251 if (group
!= null && !isSync
) {
1252 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1257 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1258 if (message
.isEndSession()) {
1259 handleEndSession(conversationPartnerAddress
);
1261 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1262 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1263 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1264 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1265 if (group
== null) {
1266 group
= new GroupInfo(groupInfo
.getGroupId());
1268 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1269 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1270 account
.getGroupStore().updateGroup(group
);
1273 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1274 if (contact
== null) {
1275 contact
= new ContactInfo(conversationPartnerAddress
);
1277 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1278 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1279 account
.getContactStore().updateContact(contact
);
1283 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1284 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1285 if (attachment
.isPointer()) {
1287 retrieveAttachment(attachment
.asPointer());
1288 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1289 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1294 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1295 if (source
.matches(account
.getSelfAddress())) {
1297 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1298 } catch (InvalidInputException ignored
) {
1300 ContactInfo contact
= account
.getContactStore().getContact(source
);
1301 if (contact
!= null) {
1302 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1303 account
.getContactStore().updateContact(contact
);
1306 ContactInfo contact
= account
.getContactStore().getContact(source
);
1307 if (contact
== null) {
1308 contact
= new ContactInfo(source
);
1310 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1311 account
.getContactStore().updateContact(contact
);
1314 if (message
.getPreviews().isPresent()) {
1315 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1316 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1317 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1318 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1320 retrieveAttachment(attachment
);
1321 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1322 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1330 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1331 final File cachePath
= new File(getMessageCachePath());
1332 if (!cachePath
.exists()) {
1335 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1336 if (!dir
.isDirectory()) {
1337 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1341 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1342 if (!fileEntry
.isFile()) {
1345 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1347 // Try to delete directory if empty
1352 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1353 SignalServiceEnvelope envelope
;
1355 envelope
= Utils
.loadEnvelope(fileEntry
);
1356 if (envelope
== null) {
1359 } catch (IOException e
) {
1360 e
.printStackTrace();
1363 SignalServiceContent content
= null;
1364 if (!envelope
.isReceipt()) {
1366 content
= decryptMessage(envelope
);
1367 } catch (Exception e
) {
1370 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1371 for (HandleAction action
: actions
) {
1373 action
.execute(this);
1374 } catch (Throwable e
) {
1375 e
.printStackTrace();
1380 handler
.handleMessage(envelope
, content
, null);
1382 Files
.delete(fileEntry
.toPath());
1383 } catch (IOException e
) {
1384 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1388 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1389 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1390 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1392 Set
<HandleAction
> queuedActions
= null;
1394 if (messagePipe
== null) {
1395 messagePipe
= messageReceiver
.createMessagePipe();
1398 boolean hasCaughtUpWithOldMessages
= false;
1401 SignalServiceEnvelope envelope
;
1402 SignalServiceContent content
= null;
1403 Exception exception
= null;
1404 final long now
= new Date().getTime();
1406 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1407 // store message on disk, before acknowledging receipt to the server
1409 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1410 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1411 Utils
.storeEnvelope(envelope1
, cacheFile
);
1412 } catch (IOException e
) {
1413 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1416 if (result
.isPresent()) {
1417 envelope
= result
.get();
1419 // Received indicator that server queue is empty
1420 hasCaughtUpWithOldMessages
= true;
1422 if (queuedActions
!= null) {
1423 for (HandleAction action
: queuedActions
) {
1425 action
.execute(this);
1426 } catch (Throwable e
) {
1427 e
.printStackTrace();
1430 queuedActions
.clear();
1431 queuedActions
= null;
1434 // Continue to wait another timeout for new messages
1437 } catch (TimeoutException e
) {
1438 if (returnOnTimeout
)
1441 } catch (InvalidVersionException e
) {
1442 System
.err
.println("Ignoring error: " + e
.getMessage());
1445 if (envelope
.hasSource()) {
1446 // Store uuid if we don't have it already
1447 SignalServiceAddress source
= envelope
.getSourceAddress();
1448 resolveSignalServiceAddress(source
);
1450 if (!envelope
.isReceipt()) {
1452 content
= decryptMessage(envelope
);
1453 } catch (Exception e
) {
1456 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1457 if (hasCaughtUpWithOldMessages
) {
1458 for (HandleAction action
: actions
) {
1460 action
.execute(this);
1461 } catch (Throwable e
) {
1462 e
.printStackTrace();
1466 if (queuedActions
== null) {
1467 queuedActions
= new HashSet
<>();
1469 queuedActions
.addAll(actions
);
1473 if (!isMessageBlocked(envelope
, content
)) {
1474 handler
.handleMessage(envelope
, content
, exception
);
1476 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1477 File cacheFile
= null;
1479 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1480 Files
.delete(cacheFile
.toPath());
1481 // Try to delete directory if empty
1482 new File(getMessageCachePath()).delete();
1483 } catch (IOException e
) {
1484 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1490 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1491 SignalServiceAddress source
;
1492 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1493 source
= envelope
.getSourceAddress();
1494 } else if (content
!= null) {
1495 source
= content
.getSender();
1499 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1500 if (sourceContact
!= null && sourceContact
.blocked
) {
1504 if (content
!= null && content
.getDataMessage().isPresent()) {
1505 SignalServiceDataMessage message
= content
.getDataMessage().get();
1506 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1507 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1508 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1509 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1517 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1518 List
<HandleAction
> actions
= new ArrayList
<>();
1519 if (content
!= null) {
1520 SignalServiceAddress sender
;
1521 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1522 sender
= envelope
.getSourceAddress();
1524 sender
= content
.getSender();
1526 // Store uuid if we don't have it already
1527 resolveSignalServiceAddress(sender
);
1529 if (content
.getDataMessage().isPresent()) {
1530 SignalServiceDataMessage message
= content
.getDataMessage().get();
1532 if (content
.isNeedsReceipt()) {
1533 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1536 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1538 if (content
.getSyncMessage().isPresent()) {
1539 account
.setMultiDevice(true);
1540 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1541 if (syncMessage
.getSent().isPresent()) {
1542 SentTranscriptMessage message
= syncMessage
.getSent().get();
1543 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1545 if (syncMessage
.getRequest().isPresent()) {
1546 RequestMessage rm
= syncMessage
.getRequest().get();
1547 if (rm
.isContactsRequest()) {
1548 actions
.add(SendSyncContactsAction
.create());
1550 if (rm
.isGroupsRequest()) {
1551 actions
.add(SendSyncGroupsAction
.create());
1553 if (rm
.isBlockedListRequest()) {
1554 actions
.add(SendSyncBlockedListAction
.create());
1556 // TODO Handle rm.isConfigurationRequest();
1558 if (syncMessage
.getGroups().isPresent()) {
1559 File tmpFile
= null;
1561 tmpFile
= IOUtils
.createTempFile();
1562 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1563 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1565 while ((g
= s
.read()) != null) {
1566 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1567 if (syncGroup
== null) {
1568 syncGroup
= new GroupInfo(g
.getId());
1570 if (g
.getName().isPresent()) {
1571 syncGroup
.name
= g
.getName().get();
1573 syncGroup
.addMembers(g
.getMembers()
1575 .map(this::resolveSignalServiceAddress
)
1576 .collect(Collectors
.toSet()));
1577 if (!g
.isActive()) {
1578 syncGroup
.removeMember(account
.getSelfAddress());
1580 // Add ourself to the member set as it's marked as active
1581 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1583 syncGroup
.blocked
= g
.isBlocked();
1584 if (g
.getColor().isPresent()) {
1585 syncGroup
.color
= g
.getColor().get();
1588 if (g
.getAvatar().isPresent()) {
1589 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1591 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1592 syncGroup
.archived
= g
.isArchived();
1593 account
.getGroupStore().updateGroup(syncGroup
);
1596 } catch (Exception e
) {
1597 e
.printStackTrace();
1599 if (tmpFile
!= null) {
1601 Files
.delete(tmpFile
.toPath());
1602 } catch (IOException e
) {
1603 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1608 if (syncMessage
.getBlockedList().isPresent()) {
1609 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1610 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1611 setContactBlocked(resolveSignalServiceAddress(address
), true);
1613 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1615 setGroupBlocked(groupId
, true);
1616 } catch (GroupNotFoundException e
) {
1617 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1621 if (syncMessage
.getContacts().isPresent()) {
1622 File tmpFile
= null;
1624 tmpFile
= IOUtils
.createTempFile();
1625 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1626 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1627 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1628 if (contactsMessage
.isComplete()) {
1629 account
.getContactStore().clear();
1632 while ((c
= s
.read()) != null) {
1633 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1634 account
.setProfileKey(c
.getProfileKey().get());
1636 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1637 ContactInfo contact
= account
.getContactStore().getContact(address
);
1638 if (contact
== null) {
1639 contact
= new ContactInfo(address
);
1641 if (c
.getName().isPresent()) {
1642 contact
.name
= c
.getName().get();
1644 if (c
.getColor().isPresent()) {
1645 contact
.color
= c
.getColor().get();
1647 if (c
.getProfileKey().isPresent()) {
1648 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1650 if (c
.getVerified().isPresent()) {
1651 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1652 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1654 if (c
.getExpirationTimer().isPresent()) {
1655 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1657 contact
.blocked
= c
.isBlocked();
1658 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1659 contact
.archived
= c
.isArchived();
1660 account
.getContactStore().updateContact(contact
);
1662 if (c
.getAvatar().isPresent()) {
1663 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1667 } catch (Exception e
) {
1668 e
.printStackTrace();
1670 if (tmpFile
!= null) {
1672 Files
.delete(tmpFile
.toPath());
1673 } catch (IOException e
) {
1674 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1679 if (syncMessage
.getVerified().isPresent()) {
1680 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1681 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1683 if (syncMessage
.getConfiguration().isPresent()) {
1691 private File
getContactAvatarFile(String number
) {
1692 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1695 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1696 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1697 if (attachment
.isPointer()) {
1698 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1699 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1701 SignalServiceAttachmentStream stream
= attachment
.asStream();
1702 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1706 private File
getGroupAvatarFile(byte[] groupId
) {
1707 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1710 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1711 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1712 if (attachment
.isPointer()) {
1713 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1714 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1716 SignalServiceAttachmentStream stream
= attachment
.asStream();
1717 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1721 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1722 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1725 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1726 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1727 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1730 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1731 if (storePreview
&& pointer
.getPreview().isPresent()) {
1732 File previewFile
= new File(outputFile
+ ".preview");
1733 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1734 byte[] preview
= pointer
.getPreview().get();
1735 output
.write(preview
, 0, preview
.length
);
1736 } catch (FileNotFoundException e
) {
1737 e
.printStackTrace();
1742 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1744 File tmpFile
= IOUtils
.createTempFile();
1745 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1746 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1747 byte[] buffer
= new byte[4096];
1750 while ((read
= input
.read(buffer
)) != -1) {
1751 output
.write(buffer
, 0, read
);
1753 } catch (FileNotFoundException e
) {
1754 e
.printStackTrace();
1759 Files
.delete(tmpFile
.toPath());
1760 } catch (IOException e
) {
1761 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1767 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1768 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1769 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1772 void sendGroups() throws IOException
, UntrustedIdentityException
{
1773 File groupsFile
= IOUtils
.createTempFile();
1776 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1777 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1778 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1779 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1780 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1781 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1782 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1786 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1787 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1788 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1789 .withStream(groupsFileStream
)
1790 .withContentType("application/octet-stream")
1791 .withLength(groupsFile
.length())
1794 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1799 Files
.delete(groupsFile
.toPath());
1800 } catch (IOException e
) {
1801 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1806 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1807 File contactsFile
= IOUtils
.createTempFile();
1810 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1811 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1812 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1813 VerifiedMessage verifiedMessage
= null;
1814 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1815 if (currentIdentity
!= null) {
1816 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1819 ProfileKey profileKey
= null;
1821 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1822 } catch (InvalidInputException ignored
) {
1824 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1825 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1826 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1827 Optional
.of(record.messageExpirationTime
),
1828 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1831 if (account
.getProfileKey() != null) {
1832 // Send our own profile key as well
1833 out
.write(new DeviceContact(account
.getSelfAddress(),
1834 Optional
.absent(), Optional
.absent(),
1835 Optional
.absent(), Optional
.absent(),
1836 Optional
.of(account
.getProfileKey()),
1837 false, Optional
.absent(), Optional
.absent(), false));
1841 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1842 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1843 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1844 .withStream(contactsFileStream
)
1845 .withContentType("application/octet-stream")
1846 .withLength(contactsFile
.length())
1849 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1854 Files
.delete(contactsFile
.toPath());
1855 } catch (IOException e
) {
1856 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1861 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1862 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1863 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1864 if (record.blocked
) {
1865 addresses
.add(record.getAddress());
1868 List
<byte[]> groupIds
= new ArrayList
<>();
1869 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1870 if (record.blocked
) {
1871 groupIds
.add(record.groupId
);
1874 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1877 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1878 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1879 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1882 public List
<ContactInfo
> getContacts() {
1883 return account
.getContactStore().getContacts();
1886 public ContactInfo
getContact(String number
) {
1887 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1890 public GroupInfo
getGroup(byte[] groupId
) {
1891 return account
.getGroupStore().getGroup(groupId
);
1894 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1895 return account
.getSignalProtocolStore().getIdentities();
1898 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1899 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1903 * Trust this the identity with this fingerprint
1905 * @param name username of the identity
1906 * @param fingerprint Fingerprint
1908 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1909 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1910 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1914 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1915 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1919 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1921 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1922 } catch (IOException
| UntrustedIdentityException e
) {
1923 e
.printStackTrace();
1932 * Trust this the identity with this safety number
1934 * @param name username of the identity
1935 * @param safetyNumber Safety number
1937 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1938 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1939 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1943 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1944 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1948 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1950 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1951 } catch (IOException
| UntrustedIdentityException e
) {
1952 e
.printStackTrace();
1961 * Trust all keys of this identity without verification
1963 * @param name username of the identity
1965 public boolean trustIdentityAllKeys(String name
) {
1966 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1967 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1971 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1972 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1973 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1975 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1976 } catch (IOException
| UntrustedIdentityException e
) {
1977 e
.printStackTrace();
1985 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1986 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1989 void saveAccount() {
1993 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1994 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1995 return resolveSignalServiceAddress(canonicalizedNumber
);
1998 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1999 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2001 return resolveSignalServiceAddress(address
);
2004 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2005 if (address
.matches(account
.getSelfAddress())) {
2006 return account
.getSelfAddress();
2009 return account
.getRecipientStore().resolveServiceAddress(address
);
2013 public void close() throws IOException
{
2014 if (messagePipe
!= null) {
2015 messagePipe
.shutdown();
2019 if (unidentifiedMessagePipe
!= null) {
2020 unidentifiedMessagePipe
.shutdown();
2021 unidentifiedMessagePipe
= null;
2027 public interface ReceiveMessageHandler
{
2029 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);