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 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
560 System
.err
.println("Aborting…");
564 g
.addMembers(members
);
567 if (avatarFile
!= null) {
568 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
569 File aFile
= getGroupAvatarFile(g
.groupId
);
570 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
573 account
.getGroupStore().updateGroup(g
);
575 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
577 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
581 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
582 if (groupId
== null) {
585 GroupInfo g
= getGroupForSending(groupId
);
587 if (!g
.isMember(recipient
)) {
591 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
593 // Send group message only to the recipient who requested it
594 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
597 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
598 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
601 .withMembers(new ArrayList
<>(g
.getMembers()));
603 File aFile
= getGroupAvatarFile(g
.groupId
);
604 if (aFile
.exists()) {
606 group
.withAvatar(Utils
.createAttachment(aFile
));
607 } catch (IOException e
) {
608 throw new AttachmentInvalidException(aFile
.toString(), e
);
612 return SignalServiceDataMessage
.newBuilder()
613 .asGroupMessage(group
.build())
614 .withExpiration(g
.messageExpirationTime
);
617 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
618 if (groupId
== null) {
622 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
625 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
626 .asGroupMessage(group
.build());
628 // Send group info request message to the recipient who sent us a message with this groupId
629 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
632 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
633 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
634 Collections
.singletonList(messageId
),
635 System
.currentTimeMillis());
637 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
640 public long sendMessage(String messageText
, List
<String
> attachments
,
641 List
<String
> recipients
)
642 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
643 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
644 if (attachments
!= null) {
645 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
647 // Upload attachments here, so we only upload once even for multiple recipients
648 SignalServiceMessageSender messageSender
= getMessageSender();
649 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
650 for (SignalServiceAttachment attachment
: attachmentStreams
) {
651 if (attachment
.isStream()) {
652 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
653 } else if (attachment
.isPointer()) {
654 attachmentPointers
.add(attachment
.asPointer());
658 messageBuilder
.withAttachments(attachmentPointers
);
660 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
663 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
664 long targetSentTimestamp
, List
<String
> recipients
)
665 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
666 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
667 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
668 .withReaction(reaction
);
669 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
672 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
673 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
674 .asEndSessionMessage();
676 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
678 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
679 } catch (Exception e
) {
680 for (SignalServiceAddress address
: signalServiceAddresses
) {
681 handleEndSession(address
);
688 public String
getContactName(String number
) throws InvalidNumberException
{
689 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
690 if (contact
== null) {
697 public void setContactName(String number
, String name
) throws InvalidNumberException
{
698 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
699 ContactInfo contact
= account
.getContactStore().getContact(address
);
700 if (contact
== null) {
701 contact
= new ContactInfo(address
);
704 account
.getContactStore().updateContact(contact
);
708 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
709 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
712 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
713 ContactInfo contact
= account
.getContactStore().getContact(address
);
714 if (contact
== null) {
715 contact
= new ContactInfo(address
);
717 contact
.blocked
= blocked
;
718 account
.getContactStore().updateContact(contact
);
722 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
723 GroupInfo group
= getGroup(groupId
);
725 throw new GroupNotFoundException(groupId
);
728 group
.blocked
= blocked
;
729 account
.getGroupStore().updateGroup(group
);
733 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
734 if (groupId
.length
== 0) {
737 if (name
.isEmpty()) {
740 if (members
.isEmpty()) {
743 if (avatar
.isEmpty()) {
746 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
750 * Change the expiration timer for a contact
752 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
753 ContactInfo contact
= account
.getContactStore().getContact(address
);
754 contact
.messageExpirationTime
= messageExpirationTimer
;
755 account
.getContactStore().updateContact(contact
);
756 sendExpirationTimerUpdate(address
);
760 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
761 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
762 .asExpirationUpdate();
763 sendMessage(messageBuilder
, Collections
.singleton(address
));
767 * Change the expiration timer for a contact
769 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
770 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
771 setExpirationTimer(address
, messageExpirationTimer
);
775 * Change the expiration timer for a group
777 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
778 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
779 g
.messageExpirationTime
= messageExpirationTimer
;
780 account
.getGroupStore().updateGroup(g
);
784 * Upload the sticker pack from path.
786 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
787 * @return if successful, returns the URL to install the sticker pack in the signal app
789 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
790 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
792 SignalServiceMessageSender messageSender
= getMessageSender();
794 byte[] packKey
= KeyUtils
.createStickerUploadKey();
795 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
798 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
800 } catch (URISyntaxException e
) {
801 throw new AssertionError(e
);
805 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
807 String rootPath
= null;
809 final File file
= new File(path
);
810 if (file
.getName().endsWith(".zip")) {
811 zip
= new ZipFile(file
);
812 } else if (file
.getName().equals("manifest.json")) {
813 rootPath
= file
.getParent();
815 throw new StickerPackInvalidException("Could not find manifest.json");
818 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
820 if (pack
.stickers
== null) {
821 throw new StickerPackInvalidException("Must set a 'stickers' field.");
824 if (pack
.stickers
.isEmpty()) {
825 throw new StickerPackInvalidException("Must include stickers.");
828 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
829 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
830 if (sticker
.file
== null) {
831 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
834 Pair
<InputStream
, Long
> data
;
836 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
837 } catch (IOException ignored
) {
838 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
841 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
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 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
861 return new SignalServiceStickerManifestUpload(
868 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
869 InputStream inputStream
;
871 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
873 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
875 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
878 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
880 final ZipEntry entry
= zip
.getEntry(subfile
);
881 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
883 final File file
= new File(rootPath
, subfile
);
884 return new Pair
<>(new FileInputStream(file
), file
.length());
888 void requestSyncGroups() throws IOException
{
889 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
890 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
892 sendSyncMessage(message
);
893 } catch (UntrustedIdentityException e
) {
898 void requestSyncContacts() throws IOException
{
899 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
900 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
902 sendSyncMessage(message
);
903 } catch (UntrustedIdentityException e
) {
908 void requestSyncBlocked() throws IOException
{
909 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
910 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
912 sendSyncMessage(message
);
913 } catch (UntrustedIdentityException e
) {
918 void requestSyncConfiguration() throws IOException
{
919 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
920 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
922 sendSyncMessage(message
);
923 } catch (UntrustedIdentityException e
) {
928 private byte[] getSenderCertificate() {
929 // TODO support UUID capable sender certificates
930 // byte[] certificate = accountManager.getSenderCertificate();
933 certificate
= accountManager
.getSenderCertificateLegacy();
934 } catch (IOException e
) {
935 System
.err
.println("Failed to get sender certificate: " + e
);
938 // TODO cache for a day
942 private byte[] getSelfUnidentifiedAccessKey() {
943 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
946 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
947 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
949 return new SignalProfile(
950 encryptedProfile
.getIdentityKey(),
951 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
952 encryptedProfile
.getAvatar(),
953 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
954 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
956 } catch (InvalidCiphertextException e
) {
961 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
962 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
963 if (contact
== null || contact
.profileKey
== null) {
966 ProfileKey theirProfileKey
;
968 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
969 } catch (InvalidInputException
| IOException e
) {
970 throw new AssertionError(e
);
972 SignalProfile targetProfile
;
974 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
975 } catch (IOException e
) {
976 System
.err
.println("Failed to get recipient profile: " + e
);
980 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
984 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
985 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
988 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
991 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
992 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
993 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
995 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
996 return Optional
.absent();
1000 return Optional
.of(new UnidentifiedAccessPair(
1001 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1002 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1004 } catch (InvalidCertificateException e
) {
1005 return Optional
.absent();
1009 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1010 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1011 for (SignalServiceAddress recipient
: recipients
) {
1012 result
.add(getAccessFor(recipient
));
1017 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1018 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1019 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1020 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1022 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1023 return Optional
.absent();
1027 return Optional
.of(new UnidentifiedAccessPair(
1028 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1029 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1031 } catch (InvalidCertificateException e
) {
1032 return Optional
.absent();
1036 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1037 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1039 if (unidentifiedAccess
.isPresent()) {
1040 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1043 return Optional
.absent();
1046 private void sendSyncMessage(SignalServiceSyncMessage message
)
1047 throws IOException
, UntrustedIdentityException
{
1048 SignalServiceMessageSender messageSender
= getMessageSender();
1050 messageSender
.sendMessage(message
, getAccessForSync());
1051 } catch (UntrustedIdentityException e
) {
1052 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1058 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1060 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1061 throws EncapsulatedExceptions
, IOException
{
1062 final long timestamp
= System
.currentTimeMillis();
1063 messageBuilder
.withTimestamp(timestamp
);
1064 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1066 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1067 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1068 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1070 for (SendMessageResult result
: results
) {
1071 if (result
.isUnregisteredFailure()) {
1072 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1073 } else if (result
.isNetworkFailure()) {
1074 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1075 } else if (result
.getIdentityFailure() != null) {
1076 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1079 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1080 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1085 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1086 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1088 for (String number
: numbers
) {
1089 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1091 return signalServiceAddresses
;
1094 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1095 throws IOException
{
1096 if (messagePipe
== null) {
1097 messagePipe
= getMessageReceiver().createMessagePipe();
1099 if (unidentifiedMessagePipe
== null) {
1100 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1102 SignalServiceDataMessage message
= null;
1104 message
= messageBuilder
.build();
1105 if (message
.getGroupContext().isPresent()) {
1107 SignalServiceMessageSender messageSender
= getMessageSender();
1108 final boolean isRecipientUpdate
= false;
1109 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1110 for (SendMessageResult r
: result
) {
1111 if (r
.getIdentityFailure() != null) {
1112 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1116 } catch (UntrustedIdentityException e
) {
1117 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1118 return Collections
.emptyList();
1121 // Send to all individually, so sync messages are sent correctly
1122 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1123 for (SignalServiceAddress address
: recipients
) {
1124 ContactInfo contact
= account
.getContactStore().getContact(address
);
1125 if (contact
!= null) {
1126 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1127 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1129 messageBuilder
.withExpiration(0);
1130 messageBuilder
.withProfileKey(null);
1132 message
= messageBuilder
.build();
1133 if (address
.matches(account
.getSelfAddress())) {
1134 results
.add(sendSelfMessage(message
));
1136 results
.add(sendMessage(address
, message
));
1142 if (message
!= null && message
.isEndSession()) {
1143 for (SignalServiceAddress recipient
: recipients
) {
1144 handleEndSession(recipient
);
1151 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1152 SignalServiceMessageSender messageSender
= getMessageSender();
1154 SignalServiceAddress recipient
= account
.getSelfAddress();
1156 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1157 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1158 message
.getTimestamp(),
1160 message
.getExpiresInSeconds(),
1161 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1163 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1166 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1167 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1168 } catch (UntrustedIdentityException e
) {
1169 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1170 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1174 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1175 SignalServiceMessageSender messageSender
= getMessageSender();
1178 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1179 } catch (UntrustedIdentityException e
) {
1180 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1181 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1185 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1186 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1188 return cipher
.decrypt(envelope
);
1189 } catch (ProtocolUntrustedIdentityException e
) {
1190 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1191 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1192 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1193 throw identityException
;
1195 throw new AssertionError(e
);
1199 private void handleEndSession(SignalServiceAddress source
) {
1200 account
.getSignalProtocolStore().deleteAllSessions(source
);
1203 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1204 List
<HandleAction
> actions
= new ArrayList
<>();
1205 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1206 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1207 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1208 switch (groupInfo
.getType()) {
1210 if (group
== null) {
1211 group
= new GroupInfo(groupInfo
.getGroupId());
1214 if (groupInfo
.getAvatar().isPresent()) {
1215 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1216 if (avatar
.isPointer()) {
1218 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1219 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1220 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1225 if (groupInfo
.getName().isPresent()) {
1226 group
.name
= groupInfo
.getName().get();
1229 if (groupInfo
.getMembers().isPresent()) {
1230 group
.addMembers(groupInfo
.getMembers().get()
1232 .map(this::resolveSignalServiceAddress
)
1233 .collect(Collectors
.toSet()));
1236 account
.getGroupStore().updateGroup(group
);
1239 if (group
== null && !isSync
) {
1240 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1244 if (group
!= null) {
1245 group
.removeMember(source
);
1246 account
.getGroupStore().updateGroup(group
);
1250 if (group
!= null && !isSync
) {
1251 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1256 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1257 if (message
.isEndSession()) {
1258 handleEndSession(conversationPartnerAddress
);
1260 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1261 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1262 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1263 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1264 if (group
== null) {
1265 group
= new GroupInfo(groupInfo
.getGroupId());
1267 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1268 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1269 account
.getGroupStore().updateGroup(group
);
1272 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1273 if (contact
== null) {
1274 contact
= new ContactInfo(conversationPartnerAddress
);
1276 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1277 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1278 account
.getContactStore().updateContact(contact
);
1282 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1283 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1284 if (attachment
.isPointer()) {
1286 retrieveAttachment(attachment
.asPointer());
1287 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1288 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1293 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1294 if (source
.matches(account
.getSelfAddress())) {
1296 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1297 } catch (InvalidInputException ignored
) {
1299 ContactInfo contact
= account
.getContactStore().getContact(source
);
1300 if (contact
!= null) {
1301 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1302 account
.getContactStore().updateContact(contact
);
1305 ContactInfo contact
= account
.getContactStore().getContact(source
);
1306 if (contact
== null) {
1307 contact
= new ContactInfo(source
);
1309 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1310 account
.getContactStore().updateContact(contact
);
1313 if (message
.getPreviews().isPresent()) {
1314 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1315 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1316 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1317 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1319 retrieveAttachment(attachment
);
1320 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1321 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1329 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1330 final File cachePath
= new File(getMessageCachePath());
1331 if (!cachePath
.exists()) {
1334 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1335 if (!dir
.isDirectory()) {
1336 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1340 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1341 if (!fileEntry
.isFile()) {
1344 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1346 // Try to delete directory if empty
1351 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1352 SignalServiceEnvelope envelope
;
1354 envelope
= Utils
.loadEnvelope(fileEntry
);
1355 if (envelope
== null) {
1358 } catch (IOException e
) {
1359 e
.printStackTrace();
1362 SignalServiceContent content
= null;
1363 if (!envelope
.isReceipt()) {
1365 content
= decryptMessage(envelope
);
1366 } catch (Exception e
) {
1369 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1370 for (HandleAction action
: actions
) {
1372 action
.execute(this);
1373 } catch (Throwable e
) {
1374 e
.printStackTrace();
1379 handler
.handleMessage(envelope
, content
, null);
1381 Files
.delete(fileEntry
.toPath());
1382 } catch (IOException e
) {
1383 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1387 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1388 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1389 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1391 Set
<HandleAction
> queuedActions
= null;
1393 if (messagePipe
== null) {
1394 messagePipe
= messageReceiver
.createMessagePipe();
1397 boolean hasCaughtUpWithOldMessages
= false;
1400 SignalServiceEnvelope envelope
;
1401 SignalServiceContent content
= null;
1402 Exception exception
= null;
1403 final long now
= new Date().getTime();
1405 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1406 // store message on disk, before acknowledging receipt to the server
1408 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1409 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1410 Utils
.storeEnvelope(envelope1
, cacheFile
);
1411 } catch (IOException e
) {
1412 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1415 if (result
.isPresent()) {
1416 envelope
= result
.get();
1418 // Received indicator that server queue is empty
1419 hasCaughtUpWithOldMessages
= true;
1421 if (queuedActions
!= null) {
1422 for (HandleAction action
: queuedActions
) {
1424 action
.execute(this);
1425 } catch (Throwable e
) {
1426 e
.printStackTrace();
1429 queuedActions
.clear();
1430 queuedActions
= null;
1433 // Continue to wait another timeout for new messages
1436 } catch (TimeoutException e
) {
1437 if (returnOnTimeout
)
1440 } catch (InvalidVersionException e
) {
1441 System
.err
.println("Ignoring error: " + e
.getMessage());
1444 if (envelope
.hasSource()) {
1445 // Store uuid if we don't have it already
1446 SignalServiceAddress source
= envelope
.getSourceAddress();
1447 resolveSignalServiceAddress(source
);
1449 if (!envelope
.isReceipt()) {
1451 content
= decryptMessage(envelope
);
1452 } catch (Exception e
) {
1455 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1456 if (hasCaughtUpWithOldMessages
) {
1457 for (HandleAction action
: actions
) {
1459 action
.execute(this);
1460 } catch (Throwable e
) {
1461 e
.printStackTrace();
1465 if (queuedActions
== null) {
1466 queuedActions
= new HashSet
<>();
1468 queuedActions
.addAll(actions
);
1472 if (!isMessageBlocked(envelope
, content
)) {
1473 handler
.handleMessage(envelope
, content
, exception
);
1475 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1476 File cacheFile
= null;
1478 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1479 Files
.delete(cacheFile
.toPath());
1480 // Try to delete directory if empty
1481 new File(getMessageCachePath()).delete();
1482 } catch (IOException e
) {
1483 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1489 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1490 SignalServiceAddress source
;
1491 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1492 source
= envelope
.getSourceAddress();
1493 } else if (content
!= null) {
1494 source
= content
.getSender();
1498 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1499 if (sourceContact
!= null && sourceContact
.blocked
) {
1503 if (content
!= null && content
.getDataMessage().isPresent()) {
1504 SignalServiceDataMessage message
= content
.getDataMessage().get();
1505 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1506 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1507 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1508 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1516 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1517 List
<HandleAction
> actions
= new ArrayList
<>();
1518 if (content
!= null) {
1519 SignalServiceAddress sender
;
1520 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1521 sender
= envelope
.getSourceAddress();
1523 sender
= content
.getSender();
1525 // Store uuid if we don't have it already
1526 resolveSignalServiceAddress(sender
);
1528 if (content
.getDataMessage().isPresent()) {
1529 SignalServiceDataMessage message
= content
.getDataMessage().get();
1531 if (content
.isNeedsReceipt()) {
1532 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1535 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1537 if (content
.getSyncMessage().isPresent()) {
1538 account
.setMultiDevice(true);
1539 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1540 if (syncMessage
.getSent().isPresent()) {
1541 SentTranscriptMessage message
= syncMessage
.getSent().get();
1542 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1544 if (syncMessage
.getRequest().isPresent()) {
1545 RequestMessage rm
= syncMessage
.getRequest().get();
1546 if (rm
.isContactsRequest()) {
1547 actions
.add(SendSyncContactsAction
.create());
1549 if (rm
.isGroupsRequest()) {
1550 actions
.add(SendSyncGroupsAction
.create());
1552 if (rm
.isBlockedListRequest()) {
1553 actions
.add(SendSyncBlockedListAction
.create());
1555 // TODO Handle rm.isConfigurationRequest();
1557 if (syncMessage
.getGroups().isPresent()) {
1558 File tmpFile
= null;
1560 tmpFile
= IOUtils
.createTempFile();
1561 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1562 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1564 while ((g
= s
.read()) != null) {
1565 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1566 if (syncGroup
== null) {
1567 syncGroup
= new GroupInfo(g
.getId());
1569 if (g
.getName().isPresent()) {
1570 syncGroup
.name
= g
.getName().get();
1572 syncGroup
.addMembers(g
.getMembers()
1574 .map(this::resolveSignalServiceAddress
)
1575 .collect(Collectors
.toSet()));
1576 if (!g
.isActive()) {
1577 syncGroup
.removeMember(account
.getSelfAddress());
1579 // Add ourself to the member set as it's marked as active
1580 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1582 syncGroup
.blocked
= g
.isBlocked();
1583 if (g
.getColor().isPresent()) {
1584 syncGroup
.color
= g
.getColor().get();
1587 if (g
.getAvatar().isPresent()) {
1588 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1590 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1591 syncGroup
.archived
= g
.isArchived();
1592 account
.getGroupStore().updateGroup(syncGroup
);
1595 } catch (Exception e
) {
1596 e
.printStackTrace();
1598 if (tmpFile
!= null) {
1600 Files
.delete(tmpFile
.toPath());
1601 } catch (IOException e
) {
1602 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1607 if (syncMessage
.getBlockedList().isPresent()) {
1608 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1609 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1610 setContactBlocked(resolveSignalServiceAddress(address
), true);
1612 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1614 setGroupBlocked(groupId
, true);
1615 } catch (GroupNotFoundException e
) {
1616 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1620 if (syncMessage
.getContacts().isPresent()) {
1621 File tmpFile
= null;
1623 tmpFile
= IOUtils
.createTempFile();
1624 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1625 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1626 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1627 if (contactsMessage
.isComplete()) {
1628 account
.getContactStore().clear();
1631 while ((c
= s
.read()) != null) {
1632 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1633 account
.setProfileKey(c
.getProfileKey().get());
1635 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1636 ContactInfo contact
= account
.getContactStore().getContact(address
);
1637 if (contact
== null) {
1638 contact
= new ContactInfo(address
);
1640 if (c
.getName().isPresent()) {
1641 contact
.name
= c
.getName().get();
1643 if (c
.getColor().isPresent()) {
1644 contact
.color
= c
.getColor().get();
1646 if (c
.getProfileKey().isPresent()) {
1647 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1649 if (c
.getVerified().isPresent()) {
1650 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1651 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1653 if (c
.getExpirationTimer().isPresent()) {
1654 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1656 contact
.blocked
= c
.isBlocked();
1657 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1658 contact
.archived
= c
.isArchived();
1659 account
.getContactStore().updateContact(contact
);
1661 if (c
.getAvatar().isPresent()) {
1662 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1666 } catch (Exception e
) {
1667 e
.printStackTrace();
1669 if (tmpFile
!= null) {
1671 Files
.delete(tmpFile
.toPath());
1672 } catch (IOException e
) {
1673 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1678 if (syncMessage
.getVerified().isPresent()) {
1679 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1680 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1682 if (syncMessage
.getConfiguration().isPresent()) {
1690 private File
getContactAvatarFile(String number
) {
1691 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1694 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1695 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1696 if (attachment
.isPointer()) {
1697 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1698 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1700 SignalServiceAttachmentStream stream
= attachment
.asStream();
1701 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1705 private File
getGroupAvatarFile(byte[] groupId
) {
1706 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1709 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1710 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1711 if (attachment
.isPointer()) {
1712 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1713 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1715 SignalServiceAttachmentStream stream
= attachment
.asStream();
1716 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1720 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1721 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1724 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1725 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1726 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1729 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1730 if (storePreview
&& pointer
.getPreview().isPresent()) {
1731 File previewFile
= new File(outputFile
+ ".preview");
1732 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1733 byte[] preview
= pointer
.getPreview().get();
1734 output
.write(preview
, 0, preview
.length
);
1735 } catch (FileNotFoundException e
) {
1736 e
.printStackTrace();
1741 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1743 File tmpFile
= IOUtils
.createTempFile();
1744 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1745 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1746 byte[] buffer
= new byte[4096];
1749 while ((read
= input
.read(buffer
)) != -1) {
1750 output
.write(buffer
, 0, read
);
1752 } catch (FileNotFoundException e
) {
1753 e
.printStackTrace();
1758 Files
.delete(tmpFile
.toPath());
1759 } catch (IOException e
) {
1760 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1766 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1767 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1768 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1771 void sendGroups() throws IOException
, UntrustedIdentityException
{
1772 File groupsFile
= IOUtils
.createTempFile();
1775 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1776 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1777 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1778 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1779 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1780 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1781 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1785 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1786 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1787 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1788 .withStream(groupsFileStream
)
1789 .withContentType("application/octet-stream")
1790 .withLength(groupsFile
.length())
1793 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1798 Files
.delete(groupsFile
.toPath());
1799 } catch (IOException e
) {
1800 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1805 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1806 File contactsFile
= IOUtils
.createTempFile();
1809 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1810 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1811 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1812 VerifiedMessage verifiedMessage
= null;
1813 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1814 if (currentIdentity
!= null) {
1815 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1818 ProfileKey profileKey
= null;
1820 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1821 } catch (InvalidInputException ignored
) {
1823 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1824 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1825 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1826 Optional
.of(record.messageExpirationTime
),
1827 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1830 if (account
.getProfileKey() != null) {
1831 // Send our own profile key as well
1832 out
.write(new DeviceContact(account
.getSelfAddress(),
1833 Optional
.absent(), Optional
.absent(),
1834 Optional
.absent(), Optional
.absent(),
1835 Optional
.of(account
.getProfileKey()),
1836 false, Optional
.absent(), Optional
.absent(), false));
1840 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1841 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1842 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1843 .withStream(contactsFileStream
)
1844 .withContentType("application/octet-stream")
1845 .withLength(contactsFile
.length())
1848 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1853 Files
.delete(contactsFile
.toPath());
1854 } catch (IOException e
) {
1855 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1860 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1861 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1862 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1863 if (record.blocked
) {
1864 addresses
.add(record.getAddress());
1867 List
<byte[]> groupIds
= new ArrayList
<>();
1868 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1869 if (record.blocked
) {
1870 groupIds
.add(record.groupId
);
1873 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1876 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1877 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1878 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1881 public List
<ContactInfo
> getContacts() {
1882 return account
.getContactStore().getContacts();
1885 public ContactInfo
getContact(String number
) {
1886 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1889 public GroupInfo
getGroup(byte[] groupId
) {
1890 return account
.getGroupStore().getGroup(groupId
);
1893 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1894 return account
.getSignalProtocolStore().getIdentities();
1897 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1898 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1902 * Trust this the identity with this fingerprint
1904 * @param name username of the identity
1905 * @param fingerprint Fingerprint
1907 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1908 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1909 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1913 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1914 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1918 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1920 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1921 } catch (IOException
| UntrustedIdentityException e
) {
1922 e
.printStackTrace();
1931 * Trust this the identity with this safety number
1933 * @param name username of the identity
1934 * @param safetyNumber Safety number
1936 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1937 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1938 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1942 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1943 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1947 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1949 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1950 } catch (IOException
| UntrustedIdentityException e
) {
1951 e
.printStackTrace();
1960 * Trust all keys of this identity without verification
1962 * @param name username of the identity
1964 public boolean trustIdentityAllKeys(String name
) {
1965 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1966 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1970 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1971 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1972 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1974 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1975 } catch (IOException
| UntrustedIdentityException e
) {
1976 e
.printStackTrace();
1984 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1985 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1988 void saveAccount() {
1992 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1993 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1994 return resolveSignalServiceAddress(canonicalizedNumber
);
1997 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1998 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2000 return resolveSignalServiceAddress(address
);
2003 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2004 if (address
.matches(account
.getSelfAddress())) {
2005 return account
.getSelfAddress();
2008 return account
.getRecipientStore().resolveServiceAddress(address
);
2012 public void close() throws IOException
{
2013 if (messagePipe
!= null) {
2014 messagePipe
.shutdown();
2018 if (unidentifiedMessagePipe
!= null) {
2019 unidentifiedMessagePipe
.shutdown();
2020 unidentifiedMessagePipe
= null;
2026 public interface ReceiveMessageHandler
{
2028 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);