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;
157 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
158 this.account
= account
;
159 this.pathConfig
= pathConfig
;
160 this.serviceConfiguration
= serviceConfiguration
;
161 this.userAgent
= userAgent
;
162 this.accountManager
= createSignalServiceAccountManager();
164 this.account
.setResolver(this::resolveSignalServiceAddress
);
167 public String
getUsername() {
168 return account
.getUsername();
171 public SignalServiceAddress
getSelfAddress() {
172 return account
.getSelfAddress();
175 private SignalServiceAccountManager
createSignalServiceAccountManager() {
176 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
179 private IdentityKeyPair
getIdentityKeyPair() {
180 return account
.getSignalProtocolStore().getIdentityKeyPair();
183 public int getDeviceId() {
184 return account
.getDeviceId();
187 private String
getMessageCachePath() {
188 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
191 private String
getMessageCachePath(String sender
) {
192 if (sender
== null || sender
.isEmpty()) {
193 return getMessageCachePath();
196 return getMessageCachePath() + "/" + sender
.replace("/", "_");
199 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
200 String cachePath
= getMessageCachePath(sender
);
201 IOUtils
.createPrivateDirectories(cachePath
);
202 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
205 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
206 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
208 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
209 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
210 int registrationId
= KeyHelper
.generateRegistrationId(false);
212 ProfileKey profileKey
= KeyUtils
.createProfileKey();
213 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
216 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
219 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
221 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
223 m
.migrateLegacyConfigs();
228 private void migrateLegacyConfigs() {
229 // Copy group avatars that were previously stored in the attachments folder
230 // to the new avatar folder
231 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
232 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
233 File avatarFile
= getGroupAvatarFile(g
.groupId
);
234 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
235 if (!avatarFile
.exists() && attachmentFile
.exists()) {
237 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
238 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
239 } catch (Exception e
) {
244 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
247 if (account
.getProfileKey() == null) {
248 // Old config file, creating new profile key
249 account
.setProfileKey(KeyUtils
.createProfileKey());
254 public void checkAccountState() throws IOException
{
255 if (account
.isRegistered()) {
256 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
260 if (account
.getUuid() == null) {
261 account
.setUuid(accountManager
.getOwnUuid());
267 public boolean isRegistered() {
268 return account
.isRegistered();
271 public void register(boolean voiceVerification
) throws IOException
{
272 account
.setPassword(KeyUtils
.createPassword());
274 // Resetting UUID, because registering doesn't work otherwise
275 account
.setUuid(null);
276 accountManager
= createSignalServiceAccountManager();
278 if (voiceVerification
) {
279 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
281 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
284 account
.setRegistered(false);
288 public void updateAccountAttributes() throws IOException
{
289 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
292 public void setProfile(String name
, File avatar
) throws IOException
{
293 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
294 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
298 public void unregister() throws IOException
{
299 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
300 // If this is the master device, other users can't send messages to this number anymore.
301 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
302 accountManager
.setGcmId(Optional
.absent());
304 account
.setRegistered(false);
308 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
309 List
<DeviceInfo
> devices
= accountManager
.getDevices();
310 account
.setMultiDevice(devices
.size() > 1);
315 public void removeLinkedDevices(int deviceId
) throws IOException
{
316 accountManager
.removeDevice(deviceId
);
317 List
<DeviceInfo
> devices
= accountManager
.getDevices();
318 account
.setMultiDevice(devices
.size() > 1);
322 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
323 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
325 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
328 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
329 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
330 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
332 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
333 account
.setMultiDevice(true);
337 private List
<PreKeyRecord
> generatePreKeys() {
338 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
340 final int offset
= account
.getPreKeyIdOffset();
341 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
342 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
343 ECKeyPair keyPair
= Curve
.generateKeyPair();
344 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
349 account
.addPreKeys(records
);
355 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
357 ECKeyPair keyPair
= Curve
.generateKeyPair();
358 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
359 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
361 account
.addSignedPreKey(record);
365 } catch (InvalidKeyException e
) {
366 throw new AssertionError(e
);
370 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
371 verificationCode
= verificationCode
.replace("-", "");
372 account
.setSignalingKey(KeyUtils
.createSignalingKey());
373 // TODO make unrestricted unidentified access configurable
374 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
376 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
377 // TODO response.isStorageCapable()
378 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
379 account
.setRegistered(true);
380 account
.setUuid(uuid
);
381 account
.setRegistrationLockPin(pin
);
382 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
388 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
389 if (pin
.isPresent()) {
390 account
.setRegistrationLockPin(pin
.get());
391 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
393 account
.setRegistrationLockPin(null);
394 accountManager
.removeRegistrationLockV1();
399 void refreshPreKeys() throws IOException
{
400 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
401 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
402 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
404 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
407 private SignalServiceMessageReceiver
getMessageReceiver() {
408 // TODO implement ZkGroup support
409 final ClientZkProfileOperations clientZkProfileOperations
= null;
410 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
413 private SignalServiceMessageSender
getMessageSender() {
414 // TODO implement ZkGroup support
415 final ClientZkProfileOperations clientZkProfileOperations
= null;
416 final boolean attachmentsV3
= false;
417 final ExecutorService executor
= null;
418 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
419 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
);
422 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
423 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
428 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
429 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
433 SignalServiceMessageReceiver receiver
= getMessageReceiver();
435 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
436 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
437 throw new IOException("Failed to retrieve profile", e
);
441 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
442 return decryptProfile(getEncryptedRecipientProfile(address
, unidentifiedAccess
), profileKey
);
445 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
446 File file
= getGroupAvatarFile(groupId
);
447 if (!file
.exists()) {
448 return Optional
.absent();
451 return Optional
.of(Utils
.createAttachment(file
));
454 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
455 File file
= getContactAvatarFile(number
);
456 if (!file
.exists()) {
457 return Optional
.absent();
460 return Optional
.of(Utils
.createAttachment(file
));
463 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
464 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
466 throw new GroupNotFoundException(groupId
);
468 if (!g
.isMember(account
.getSelfAddress())) {
469 throw new NotAGroupMemberException(groupId
, g
.name
);
474 public List
<GroupInfo
> getGroups() {
475 return account
.getGroupStore().getGroups();
478 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
480 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
481 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
482 if (attachments
!= null) {
483 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
485 if (groupId
!= null) {
486 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
489 messageBuilder
.asGroupMessage(group
);
492 final GroupInfo g
= getGroupForSending(groupId
);
494 messageBuilder
.withExpiration(g
.messageExpirationTime
);
496 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
499 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
500 long targetSentTimestamp
, byte[] groupId
)
501 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
502 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
503 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
504 .withReaction(reaction
);
505 if (groupId
!= null) {
506 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
509 messageBuilder
.asGroupMessage(group
);
511 final GroupInfo g
= getGroupForSending(groupId
);
512 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
515 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
516 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
520 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
521 .asGroupMessage(group
);
523 final GroupInfo g
= getGroupForSending(groupId
);
524 g
.removeMember(account
.getSelfAddress());
525 account
.getGroupStore().updateGroup(g
);
527 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
530 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
532 if (groupId
== null) {
534 g
= new GroupInfo(KeyUtils
.createGroupId());
535 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
537 g
= getGroupForSending(groupId
);
544 if (members
!= null) {
545 final Set
<String
> newE164Members
= new HashSet
<>();
546 for (SignalServiceAddress member
: members
) {
547 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
550 newE164Members
.add(member
.getNumber().get());
553 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
554 if (contacts
.size() != newE164Members
.size()) {
555 // Some of the new members are not registered on Signal
556 for (ContactTokenDetails contact
: contacts
) {
557 newE164Members
.remove(contact
.getNumber());
559 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
562 g
.addMembers(members
);
565 if (avatarFile
!= null) {
566 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
567 File aFile
= getGroupAvatarFile(g
.groupId
);
568 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
571 account
.getGroupStore().updateGroup(g
);
573 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
575 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
580 if (groupId
== null) {
583 GroupInfo g
= getGroupForSending(groupId
);
585 if (!g
.isMember(recipient
)) {
589 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
591 // Send group message only to the recipient who requested it
592 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
595 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
596 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
599 .withMembers(new ArrayList
<>(g
.getMembers()));
601 File aFile
= getGroupAvatarFile(g
.groupId
);
602 if (aFile
.exists()) {
604 group
.withAvatar(Utils
.createAttachment(aFile
));
605 } catch (IOException e
) {
606 throw new AttachmentInvalidException(aFile
.toString(), e
);
610 return SignalServiceDataMessage
.newBuilder()
611 .asGroupMessage(group
.build())
612 .withExpiration(g
.messageExpirationTime
);
615 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
616 if (groupId
== null) {
620 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
623 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
624 .asGroupMessage(group
.build());
626 // Send group info request message to the recipient who sent us a message with this groupId
627 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
630 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
631 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
632 Collections
.singletonList(messageId
),
633 System
.currentTimeMillis());
635 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
638 public long sendMessage(String messageText
, List
<String
> attachments
,
639 List
<String
> recipients
)
640 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
641 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
642 if (attachments
!= null) {
643 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
645 // Upload attachments here, so we only upload once even for multiple recipients
646 SignalServiceMessageSender messageSender
= getMessageSender();
647 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
648 for (SignalServiceAttachment attachment
: attachmentStreams
) {
649 if (attachment
.isStream()) {
650 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
651 } else if (attachment
.isPointer()) {
652 attachmentPointers
.add(attachment
.asPointer());
656 messageBuilder
.withAttachments(attachmentPointers
);
658 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
661 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
662 long targetSentTimestamp
, List
<String
> recipients
)
663 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
664 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
665 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
666 .withReaction(reaction
);
667 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
670 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
671 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
672 .asEndSessionMessage();
674 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
676 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
677 } catch (Exception e
) {
678 for (SignalServiceAddress address
: signalServiceAddresses
) {
679 handleEndSession(address
);
686 public String
getContactName(String number
) throws InvalidNumberException
{
687 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
688 if (contact
== null) {
695 public void setContactName(String number
, String name
) throws InvalidNumberException
{
696 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
697 ContactInfo contact
= account
.getContactStore().getContact(address
);
698 if (contact
== null) {
699 contact
= new ContactInfo(address
);
702 account
.getContactStore().updateContact(contact
);
706 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
707 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
710 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
711 ContactInfo contact
= account
.getContactStore().getContact(address
);
712 if (contact
== null) {
713 contact
= new ContactInfo(address
);
715 contact
.blocked
= blocked
;
716 account
.getContactStore().updateContact(contact
);
720 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
721 GroupInfo group
= getGroup(groupId
);
723 throw new GroupNotFoundException(groupId
);
726 group
.blocked
= blocked
;
727 account
.getGroupStore().updateGroup(group
);
731 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
732 if (groupId
.length
== 0) {
735 if (name
.isEmpty()) {
738 if (members
.isEmpty()) {
741 if (avatar
.isEmpty()) {
744 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
748 * Change the expiration timer for a contact
750 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
751 ContactInfo contact
= account
.getContactStore().getContact(address
);
752 contact
.messageExpirationTime
= messageExpirationTimer
;
753 account
.getContactStore().updateContact(contact
);
754 sendExpirationTimerUpdate(address
);
758 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
759 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
760 .asExpirationUpdate();
761 sendMessage(messageBuilder
, Collections
.singleton(address
));
765 * Change the expiration timer for a contact
767 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
768 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
769 setExpirationTimer(address
, messageExpirationTimer
);
773 * Change the expiration timer for a group
775 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
776 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
777 g
.messageExpirationTime
= messageExpirationTimer
;
778 account
.getGroupStore().updateGroup(g
);
782 * Upload the sticker pack from path.
784 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
785 * @return if successful, returns the URL to install the sticker pack in the signal app
787 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
788 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
790 SignalServiceMessageSender messageSender
= getMessageSender();
792 byte[] packKey
= KeyUtils
.createStickerUploadKey();
793 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
796 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
798 } catch (URISyntaxException e
) {
799 throw new AssertionError(e
);
803 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
805 String rootPath
= null;
807 final File file
= new File(path
);
808 if (file
.getName().endsWith(".zip")) {
809 zip
= new ZipFile(file
);
810 } else if (file
.getName().equals("manifest.json")) {
811 rootPath
= file
.getParent();
813 throw new StickerPackInvalidException("Could not find manifest.json");
816 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
818 if (pack
.stickers
== null) {
819 throw new StickerPackInvalidException("Must set a 'stickers' field.");
822 if (pack
.stickers
.isEmpty()) {
823 throw new StickerPackInvalidException("Must include stickers.");
826 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
827 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
828 if (sticker
.file
== null) {
829 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
832 Pair
<InputStream
, Long
> data
;
834 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
835 } catch (IOException ignored
) {
836 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
839 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
840 stickers
.add(stickerInfo
);
843 StickerInfo cover
= null;
844 if (pack
.cover
!= null) {
845 if (pack
.cover
.file
== null) {
846 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
849 Pair
<InputStream
, Long
> data
;
851 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
852 } catch (IOException ignored
) {
853 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
856 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
859 return new SignalServiceStickerManifestUpload(
866 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
867 InputStream inputStream
;
869 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
871 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
873 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
876 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
878 final ZipEntry entry
= zip
.getEntry(subfile
);
879 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
881 final File file
= new File(rootPath
, subfile
);
882 return new Pair
<>(new FileInputStream(file
), file
.length());
886 void requestSyncGroups() throws IOException
{
887 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
888 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
890 sendSyncMessage(message
);
891 } catch (UntrustedIdentityException e
) {
896 void requestSyncContacts() throws IOException
{
897 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
898 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
900 sendSyncMessage(message
);
901 } catch (UntrustedIdentityException e
) {
906 void requestSyncBlocked() throws IOException
{
907 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
908 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
910 sendSyncMessage(message
);
911 } catch (UntrustedIdentityException e
) {
916 void requestSyncConfiguration() throws IOException
{
917 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
918 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
920 sendSyncMessage(message
);
921 } catch (UntrustedIdentityException e
) {
926 private byte[] getSenderCertificate() {
927 // TODO support UUID capable sender certificates
928 // byte[] certificate = accountManager.getSenderCertificate();
931 certificate
= accountManager
.getSenderCertificateLegacy();
932 } catch (IOException e
) {
933 System
.err
.println("Failed to get sender certificate: " + e
);
936 // TODO cache for a day
940 private byte[] getSelfUnidentifiedAccessKey() {
941 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
944 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
945 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
947 return new SignalProfile(
948 encryptedProfile
.getIdentityKey(),
949 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
950 encryptedProfile
.getAvatar(),
951 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
952 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
954 } catch (InvalidCiphertextException e
) {
959 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
960 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
961 if (contact
== null || contact
.profileKey
== null) {
964 ProfileKey theirProfileKey
;
966 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
967 } catch (InvalidInputException
| IOException e
) {
968 throw new AssertionError(e
);
970 SignalProfile targetProfile
;
972 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
973 } catch (IOException e
) {
974 System
.err
.println("Failed to get recipient profile: " + e
);
978 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
982 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
983 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
986 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
989 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
990 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
991 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
993 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
994 return Optional
.absent();
998 return Optional
.of(new UnidentifiedAccessPair(
999 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1000 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1002 } catch (InvalidCertificateException e
) {
1003 return Optional
.absent();
1007 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1008 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1009 for (SignalServiceAddress recipient
: recipients
) {
1010 result
.add(getAccessFor(recipient
));
1015 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1016 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1017 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1018 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1020 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1021 return Optional
.absent();
1025 return Optional
.of(new UnidentifiedAccessPair(
1026 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1027 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1029 } catch (InvalidCertificateException e
) {
1030 return Optional
.absent();
1034 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1035 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1037 if (unidentifiedAccess
.isPresent()) {
1038 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1041 return Optional
.absent();
1044 private void sendSyncMessage(SignalServiceSyncMessage message
)
1045 throws IOException
, UntrustedIdentityException
{
1046 SignalServiceMessageSender messageSender
= getMessageSender();
1048 messageSender
.sendMessage(message
, getAccessForSync());
1049 } catch (UntrustedIdentityException e
) {
1050 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1056 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1058 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1059 throws EncapsulatedExceptions
, IOException
{
1060 final long timestamp
= System
.currentTimeMillis();
1061 messageBuilder
.withTimestamp(timestamp
);
1062 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1064 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1065 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1066 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1068 for (SendMessageResult result
: results
) {
1069 if (result
.isUnregisteredFailure()) {
1070 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1071 } else if (result
.isNetworkFailure()) {
1072 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1073 } else if (result
.getIdentityFailure() != null) {
1074 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1077 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1078 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1083 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1084 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1086 for (String number
: numbers
) {
1087 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1089 return signalServiceAddresses
;
1092 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1093 throws IOException
{
1094 if (messagePipe
== null) {
1095 messagePipe
= getMessageReceiver().createMessagePipe();
1097 if (unidentifiedMessagePipe
== null) {
1098 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1100 SignalServiceDataMessage message
= null;
1102 message
= messageBuilder
.build();
1103 if (message
.getGroupContext().isPresent()) {
1105 SignalServiceMessageSender messageSender
= getMessageSender();
1106 final boolean isRecipientUpdate
= false;
1107 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1108 for (SendMessageResult r
: result
) {
1109 if (r
.getIdentityFailure() != null) {
1110 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1114 } catch (UntrustedIdentityException e
) {
1115 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1116 return Collections
.emptyList();
1119 // Send to all individually, so sync messages are sent correctly
1120 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1121 for (SignalServiceAddress address
: recipients
) {
1122 ContactInfo contact
= account
.getContactStore().getContact(address
);
1123 if (contact
!= null) {
1124 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1125 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1127 messageBuilder
.withExpiration(0);
1128 messageBuilder
.withProfileKey(null);
1130 message
= messageBuilder
.build();
1131 if (address
.matches(account
.getSelfAddress())) {
1132 results
.add(sendSelfMessage(message
));
1134 results
.add(sendMessage(address
, message
));
1140 if (message
!= null && message
.isEndSession()) {
1141 for (SignalServiceAddress recipient
: recipients
) {
1142 handleEndSession(recipient
);
1149 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1150 SignalServiceMessageSender messageSender
= getMessageSender();
1152 SignalServiceAddress recipient
= account
.getSelfAddress();
1154 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1155 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1156 message
.getTimestamp(),
1158 message
.getExpiresInSeconds(),
1159 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1161 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1164 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1165 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1166 } catch (UntrustedIdentityException e
) {
1167 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1168 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1172 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1173 SignalServiceMessageSender messageSender
= getMessageSender();
1176 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1177 } catch (UntrustedIdentityException e
) {
1178 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1179 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1183 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1184 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1186 return cipher
.decrypt(envelope
);
1187 } catch (ProtocolUntrustedIdentityException e
) {
1188 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1189 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1190 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1191 throw identityException
;
1193 throw new AssertionError(e
);
1197 private void handleEndSession(SignalServiceAddress source
) {
1198 account
.getSignalProtocolStore().deleteAllSessions(source
);
1201 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1202 List
<HandleAction
> actions
= new ArrayList
<>();
1203 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1204 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1205 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1206 switch (groupInfo
.getType()) {
1208 if (group
== null) {
1209 group
= new GroupInfo(groupInfo
.getGroupId());
1212 if (groupInfo
.getAvatar().isPresent()) {
1213 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1214 if (avatar
.isPointer()) {
1216 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1217 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1218 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1223 if (groupInfo
.getName().isPresent()) {
1224 group
.name
= groupInfo
.getName().get();
1227 if (groupInfo
.getMembers().isPresent()) {
1228 group
.addMembers(groupInfo
.getMembers().get()
1230 .map(this::resolveSignalServiceAddress
)
1231 .collect(Collectors
.toSet()));
1234 account
.getGroupStore().updateGroup(group
);
1237 if (group
== null && !isSync
) {
1238 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1242 if (group
!= null) {
1243 group
.removeMember(source
);
1244 account
.getGroupStore().updateGroup(group
);
1248 if (group
!= null && !isSync
) {
1249 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1254 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1255 if (message
.isEndSession()) {
1256 handleEndSession(conversationPartnerAddress
);
1258 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1259 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1260 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1261 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1262 if (group
== null) {
1263 group
= new GroupInfo(groupInfo
.getGroupId());
1265 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1266 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1267 account
.getGroupStore().updateGroup(group
);
1270 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1271 if (contact
== null) {
1272 contact
= new ContactInfo(conversationPartnerAddress
);
1274 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1275 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1276 account
.getContactStore().updateContact(contact
);
1280 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1281 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1282 if (attachment
.isPointer()) {
1284 retrieveAttachment(attachment
.asPointer());
1285 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1286 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1291 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1292 if (source
.matches(account
.getSelfAddress())) {
1294 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1295 } catch (InvalidInputException ignored
) {
1297 ContactInfo contact
= account
.getContactStore().getContact(source
);
1298 if (contact
!= null) {
1299 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1300 account
.getContactStore().updateContact(contact
);
1303 ContactInfo contact
= account
.getContactStore().getContact(source
);
1304 if (contact
== null) {
1305 contact
= new ContactInfo(source
);
1307 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1308 account
.getContactStore().updateContact(contact
);
1311 if (message
.getPreviews().isPresent()) {
1312 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1313 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1314 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1315 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1317 retrieveAttachment(attachment
);
1318 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1319 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1327 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1328 final File cachePath
= new File(getMessageCachePath());
1329 if (!cachePath
.exists()) {
1332 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1333 if (!dir
.isDirectory()) {
1334 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1338 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1339 if (!fileEntry
.isFile()) {
1342 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1344 // Try to delete directory if empty
1349 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1350 SignalServiceEnvelope envelope
;
1352 envelope
= Utils
.loadEnvelope(fileEntry
);
1353 if (envelope
== null) {
1356 } catch (IOException e
) {
1357 e
.printStackTrace();
1360 SignalServiceContent content
= null;
1361 if (!envelope
.isReceipt()) {
1363 content
= decryptMessage(envelope
);
1364 } catch (Exception e
) {
1367 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1368 for (HandleAction action
: actions
) {
1370 action
.execute(this);
1371 } catch (Throwable e
) {
1372 e
.printStackTrace();
1377 handler
.handleMessage(envelope
, content
, null);
1379 Files
.delete(fileEntry
.toPath());
1380 } catch (IOException e
) {
1381 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1385 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1386 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1387 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1389 Set
<HandleAction
> queuedActions
= null;
1391 if (messagePipe
== null) {
1392 messagePipe
= messageReceiver
.createMessagePipe();
1395 boolean hasCaughtUpWithOldMessages
= false;
1398 SignalServiceEnvelope envelope
;
1399 SignalServiceContent content
= null;
1400 Exception exception
= null;
1401 final long now
= new Date().getTime();
1403 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1404 // store message on disk, before acknowledging receipt to the server
1406 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1407 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1408 Utils
.storeEnvelope(envelope1
, cacheFile
);
1409 } catch (IOException e
) {
1410 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1413 if (result
.isPresent()) {
1414 envelope
= result
.get();
1416 // Received indicator that server queue is empty
1417 hasCaughtUpWithOldMessages
= true;
1419 if (queuedActions
!= null) {
1420 for (HandleAction action
: queuedActions
) {
1422 action
.execute(this);
1423 } catch (Throwable e
) {
1424 e
.printStackTrace();
1427 queuedActions
.clear();
1428 queuedActions
= null;
1431 // Continue to wait another timeout for new messages
1434 } catch (TimeoutException e
) {
1435 if (returnOnTimeout
)
1438 } catch (InvalidVersionException e
) {
1439 System
.err
.println("Ignoring error: " + e
.getMessage());
1442 if (envelope
.hasSource()) {
1443 // Store uuid if we don't have it already
1444 SignalServiceAddress source
= envelope
.getSourceAddress();
1445 resolveSignalServiceAddress(source
);
1447 if (!envelope
.isReceipt()) {
1449 content
= decryptMessage(envelope
);
1450 } catch (Exception e
) {
1453 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1454 if (hasCaughtUpWithOldMessages
) {
1455 for (HandleAction action
: actions
) {
1457 action
.execute(this);
1458 } catch (Throwable e
) {
1459 e
.printStackTrace();
1463 if (queuedActions
== null) {
1464 queuedActions
= new HashSet
<>();
1466 queuedActions
.addAll(actions
);
1470 if (!isMessageBlocked(envelope
, content
)) {
1471 handler
.handleMessage(envelope
, content
, exception
);
1473 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1474 File cacheFile
= null;
1476 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1477 Files
.delete(cacheFile
.toPath());
1478 // Try to delete directory if empty
1479 new File(getMessageCachePath()).delete();
1480 } catch (IOException e
) {
1481 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1487 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1488 SignalServiceAddress source
;
1489 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1490 source
= envelope
.getSourceAddress();
1491 } else if (content
!= null) {
1492 source
= content
.getSender();
1496 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1497 if (sourceContact
!= null && sourceContact
.blocked
) {
1501 if (content
!= null && content
.getDataMessage().isPresent()) {
1502 SignalServiceDataMessage message
= content
.getDataMessage().get();
1503 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1504 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1505 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1506 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1514 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1515 List
<HandleAction
> actions
= new ArrayList
<>();
1516 if (content
!= null) {
1517 SignalServiceAddress sender
;
1518 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1519 sender
= envelope
.getSourceAddress();
1521 sender
= content
.getSender();
1523 // Store uuid if we don't have it already
1524 resolveSignalServiceAddress(sender
);
1526 if (content
.getDataMessage().isPresent()) {
1527 SignalServiceDataMessage message
= content
.getDataMessage().get();
1529 if (content
.isNeedsReceipt()) {
1530 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1533 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1535 if (content
.getSyncMessage().isPresent()) {
1536 account
.setMultiDevice(true);
1537 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1538 if (syncMessage
.getSent().isPresent()) {
1539 SentTranscriptMessage message
= syncMessage
.getSent().get();
1540 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1542 if (syncMessage
.getRequest().isPresent()) {
1543 RequestMessage rm
= syncMessage
.getRequest().get();
1544 if (rm
.isContactsRequest()) {
1545 actions
.add(SendSyncContactsAction
.create());
1547 if (rm
.isGroupsRequest()) {
1548 actions
.add(SendSyncGroupsAction
.create());
1550 if (rm
.isBlockedListRequest()) {
1551 actions
.add(SendSyncBlockedListAction
.create());
1553 // TODO Handle rm.isConfigurationRequest();
1555 if (syncMessage
.getGroups().isPresent()) {
1556 File tmpFile
= null;
1558 tmpFile
= IOUtils
.createTempFile();
1559 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1560 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1562 while ((g
= s
.read()) != null) {
1563 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1564 if (syncGroup
== null) {
1565 syncGroup
= new GroupInfo(g
.getId());
1567 if (g
.getName().isPresent()) {
1568 syncGroup
.name
= g
.getName().get();
1570 syncGroup
.addMembers(g
.getMembers()
1572 .map(this::resolveSignalServiceAddress
)
1573 .collect(Collectors
.toSet()));
1574 if (!g
.isActive()) {
1575 syncGroup
.removeMember(account
.getSelfAddress());
1577 // Add ourself to the member set as it's marked as active
1578 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1580 syncGroup
.blocked
= g
.isBlocked();
1581 if (g
.getColor().isPresent()) {
1582 syncGroup
.color
= g
.getColor().get();
1585 if (g
.getAvatar().isPresent()) {
1586 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1588 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1589 syncGroup
.archived
= g
.isArchived();
1590 account
.getGroupStore().updateGroup(syncGroup
);
1593 } catch (Exception e
) {
1594 e
.printStackTrace();
1596 if (tmpFile
!= null) {
1598 Files
.delete(tmpFile
.toPath());
1599 } catch (IOException e
) {
1600 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1605 if (syncMessage
.getBlockedList().isPresent()) {
1606 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1607 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1608 setContactBlocked(resolveSignalServiceAddress(address
), true);
1610 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1612 setGroupBlocked(groupId
, true);
1613 } catch (GroupNotFoundException e
) {
1614 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1618 if (syncMessage
.getContacts().isPresent()) {
1619 File tmpFile
= null;
1621 tmpFile
= IOUtils
.createTempFile();
1622 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1623 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1624 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1625 if (contactsMessage
.isComplete()) {
1626 account
.getContactStore().clear();
1629 while ((c
= s
.read()) != null) {
1630 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1631 account
.setProfileKey(c
.getProfileKey().get());
1633 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1634 ContactInfo contact
= account
.getContactStore().getContact(address
);
1635 if (contact
== null) {
1636 contact
= new ContactInfo(address
);
1638 if (c
.getName().isPresent()) {
1639 contact
.name
= c
.getName().get();
1641 if (c
.getColor().isPresent()) {
1642 contact
.color
= c
.getColor().get();
1644 if (c
.getProfileKey().isPresent()) {
1645 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1647 if (c
.getVerified().isPresent()) {
1648 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1649 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1651 if (c
.getExpirationTimer().isPresent()) {
1652 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1654 contact
.blocked
= c
.isBlocked();
1655 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1656 contact
.archived
= c
.isArchived();
1657 account
.getContactStore().updateContact(contact
);
1659 if (c
.getAvatar().isPresent()) {
1660 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1664 } catch (Exception e
) {
1665 e
.printStackTrace();
1667 if (tmpFile
!= null) {
1669 Files
.delete(tmpFile
.toPath());
1670 } catch (IOException e
) {
1671 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1676 if (syncMessage
.getVerified().isPresent()) {
1677 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1678 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1680 if (syncMessage
.getConfiguration().isPresent()) {
1688 private File
getContactAvatarFile(String number
) {
1689 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1692 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1693 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1694 if (attachment
.isPointer()) {
1695 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1696 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1698 SignalServiceAttachmentStream stream
= attachment
.asStream();
1699 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1703 private File
getGroupAvatarFile(byte[] groupId
) {
1704 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1707 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1708 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1709 if (attachment
.isPointer()) {
1710 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1711 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1713 SignalServiceAttachmentStream stream
= attachment
.asStream();
1714 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1718 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1719 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1722 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1723 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1724 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1727 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1728 if (storePreview
&& pointer
.getPreview().isPresent()) {
1729 File previewFile
= new File(outputFile
+ ".preview");
1730 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1731 byte[] preview
= pointer
.getPreview().get();
1732 output
.write(preview
, 0, preview
.length
);
1733 } catch (FileNotFoundException e
) {
1734 e
.printStackTrace();
1739 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1741 File tmpFile
= IOUtils
.createTempFile();
1742 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1743 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1744 byte[] buffer
= new byte[4096];
1747 while ((read
= input
.read(buffer
)) != -1) {
1748 output
.write(buffer
, 0, read
);
1750 } catch (FileNotFoundException e
) {
1751 e
.printStackTrace();
1756 Files
.delete(tmpFile
.toPath());
1757 } catch (IOException e
) {
1758 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1764 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1765 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1766 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1769 void sendGroups() throws IOException
, UntrustedIdentityException
{
1770 File groupsFile
= IOUtils
.createTempFile();
1773 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1774 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1775 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1776 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1777 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1778 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1779 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1783 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1784 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1785 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1786 .withStream(groupsFileStream
)
1787 .withContentType("application/octet-stream")
1788 .withLength(groupsFile
.length())
1791 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1796 Files
.delete(groupsFile
.toPath());
1797 } catch (IOException e
) {
1798 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1803 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1804 File contactsFile
= IOUtils
.createTempFile();
1807 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1808 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1809 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1810 VerifiedMessage verifiedMessage
= null;
1811 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1812 if (currentIdentity
!= null) {
1813 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1816 ProfileKey profileKey
= null;
1818 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1819 } catch (InvalidInputException ignored
) {
1821 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1822 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1823 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1824 Optional
.of(record.messageExpirationTime
),
1825 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1828 if (account
.getProfileKey() != null) {
1829 // Send our own profile key as well
1830 out
.write(new DeviceContact(account
.getSelfAddress(),
1831 Optional
.absent(), Optional
.absent(),
1832 Optional
.absent(), Optional
.absent(),
1833 Optional
.of(account
.getProfileKey()),
1834 false, Optional
.absent(), Optional
.absent(), false));
1838 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1839 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1840 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1841 .withStream(contactsFileStream
)
1842 .withContentType("application/octet-stream")
1843 .withLength(contactsFile
.length())
1846 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1851 Files
.delete(contactsFile
.toPath());
1852 } catch (IOException e
) {
1853 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1858 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1859 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1860 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1861 if (record.blocked
) {
1862 addresses
.add(record.getAddress());
1865 List
<byte[]> groupIds
= new ArrayList
<>();
1866 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1867 if (record.blocked
) {
1868 groupIds
.add(record.groupId
);
1871 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1874 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1875 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1876 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1879 public List
<ContactInfo
> getContacts() {
1880 return account
.getContactStore().getContacts();
1883 public ContactInfo
getContact(String number
) {
1884 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1887 public GroupInfo
getGroup(byte[] groupId
) {
1888 return account
.getGroupStore().getGroup(groupId
);
1891 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1892 return account
.getSignalProtocolStore().getIdentities();
1895 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1896 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1900 * Trust this the identity with this fingerprint
1902 * @param name username of the identity
1903 * @param fingerprint Fingerprint
1905 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1906 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1907 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1911 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1912 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1916 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1918 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1919 } catch (IOException
| UntrustedIdentityException e
) {
1920 e
.printStackTrace();
1929 * Trust this the identity with this safety number
1931 * @param name username of the identity
1932 * @param safetyNumber Safety number
1934 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1935 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1936 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1940 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1941 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1945 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1947 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1948 } catch (IOException
| UntrustedIdentityException e
) {
1949 e
.printStackTrace();
1958 * Trust all keys of this identity without verification
1960 * @param name username of the identity
1962 public boolean trustIdentityAllKeys(String name
) {
1963 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1964 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1968 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1969 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1970 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1972 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1973 } catch (IOException
| UntrustedIdentityException e
) {
1974 e
.printStackTrace();
1982 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1983 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1986 void saveAccount() {
1990 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1991 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1992 return resolveSignalServiceAddress(canonicalizedNumber
);
1995 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1996 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1998 return resolveSignalServiceAddress(address
);
2001 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2002 if (address
.matches(account
.getSelfAddress())) {
2003 return account
.getSelfAddress();
2006 return account
.getRecipientStore().resolveServiceAddress(address
);
2010 public void close() throws IOException
{
2011 if (messagePipe
!= null) {
2012 messagePipe
.shutdown();
2016 if (unidentifiedMessagePipe
!= null) {
2017 unidentifiedMessagePipe
.shutdown();
2018 unidentifiedMessagePipe
= null;
2024 public interface ReceiveMessageHandler
{
2026 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);