2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.signal
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
25 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
26 import org
.asamk
.signal
.util
.IOUtils
;
27 import org
.asamk
.signal
.util
.Util
;
28 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
29 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
30 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
31 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
38 import org
.signal
.libsignal
.metadata
.SelfSendException
;
39 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
40 import org
.signal
.zkgroup
.InvalidInputException
;
41 import org
.signal
.zkgroup
.VerificationFailedException
;
42 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
43 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
44 import org
.whispersystems
.libsignal
.IdentityKey
;
45 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
46 import org
.whispersystems
.libsignal
.InvalidKeyException
;
47 import org
.whispersystems
.libsignal
.InvalidMessageException
;
48 import org
.whispersystems
.libsignal
.InvalidVersionException
;
49 import org
.whispersystems
.libsignal
.ecc
.Curve
;
50 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
51 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
52 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
53 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
54 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
55 import org
.whispersystems
.libsignal
.util
.Medium
;
56 import org
.whispersystems
.libsignal
.util
.Pair
;
57 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
93 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
94 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
95 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
105 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
106 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
107 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
108 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
109 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
110 import org
.whispersystems
.util
.Base64
;
112 import java
.io
.Closeable
;
114 import java
.io
.FileInputStream
;
115 import java
.io
.FileNotFoundException
;
116 import java
.io
.FileOutputStream
;
117 import java
.io
.IOException
;
118 import java
.io
.InputStream
;
119 import java
.io
.OutputStream
;
121 import java
.net
.URISyntaxException
;
122 import java
.net
.URLEncoder
;
123 import java
.nio
.file
.Files
;
124 import java
.nio
.file
.Paths
;
125 import java
.nio
.file
.StandardCopyOption
;
126 import java
.util
.ArrayList
;
127 import java
.util
.Arrays
;
128 import java
.util
.Collection
;
129 import java
.util
.Collections
;
130 import java
.util
.Date
;
131 import java
.util
.HashSet
;
132 import java
.util
.LinkedList
;
133 import java
.util
.List
;
134 import java
.util
.Locale
;
135 import java
.util
.Objects
;
136 import java
.util
.Set
;
137 import java
.util
.UUID
;
138 import java
.util
.concurrent
.TimeUnit
;
139 import java
.util
.concurrent
.TimeoutException
;
140 import java
.util
.stream
.Collectors
;
141 import java
.util
.zip
.ZipEntry
;
142 import java
.util
.zip
.ZipFile
;
144 public class Manager
implements Closeable
{
146 private final SleepTimer timer
= new UptimeSleepTimer();
147 private final SignalServiceConfiguration serviceConfiguration
;
148 private final String userAgent
;
150 private final SignalAccount account
;
151 private final PathConfig pathConfig
;
152 private SignalServiceAccountManager accountManager
;
153 private SignalServiceMessagePipe messagePipe
= null;
154 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
156 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
157 this.account
= account
;
158 this.pathConfig
= pathConfig
;
159 this.serviceConfiguration
= serviceConfiguration
;
160 this.userAgent
= userAgent
;
161 this.accountManager
= createSignalServiceAccountManager();
163 this.account
.setResolver(this::resolveSignalServiceAddress
);
166 public String
getUsername() {
167 return account
.getUsername();
170 public SignalServiceAddress
getSelfAddress() {
171 return account
.getSelfAddress();
174 private SignalServiceAccountManager
createSignalServiceAccountManager() {
175 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
178 private IdentityKeyPair
getIdentityKeyPair() {
179 return account
.getSignalProtocolStore().getIdentityKeyPair();
182 public int getDeviceId() {
183 return account
.getDeviceId();
186 private String
getMessageCachePath() {
187 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
190 private String
getMessageCachePath(String sender
) {
191 if (sender
== null || sender
.isEmpty()) {
192 return getMessageCachePath();
195 return getMessageCachePath() + "/" + sender
.replace("/", "_");
198 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
199 String cachePath
= getMessageCachePath(sender
);
200 IOUtils
.createPrivateDirectories(cachePath
);
201 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
204 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
205 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
207 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
208 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
209 int registrationId
= KeyHelper
.generateRegistrationId(false);
211 ProfileKey profileKey
= KeyUtils
.createProfileKey();
212 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
215 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
218 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
220 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
222 m
.migrateLegacyConfigs();
227 private void migrateLegacyConfigs() {
228 // Copy group avatars that were previously stored in the attachments folder
229 // to the new avatar folder
230 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
231 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
232 File avatarFile
= getGroupAvatarFile(g
.groupId
);
233 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
234 if (!avatarFile
.exists() && attachmentFile
.exists()) {
236 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
237 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
238 } catch (Exception e
) {
243 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
246 if (account
.getProfileKey() == null) {
247 // Old config file, creating new profile key
248 account
.setProfileKey(KeyUtils
.createProfileKey());
253 public void checkAccountState() throws IOException
{
254 if (account
.isRegistered()) {
255 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
259 if (account
.getUuid() == null) {
260 account
.setUuid(accountManager
.getOwnUuid());
266 public boolean isRegistered() {
267 return account
.isRegistered();
270 public void register(boolean voiceVerification
) throws IOException
{
271 account
.setPassword(KeyUtils
.createPassword());
273 // Resetting UUID, because registering doesn't work otherwise
274 account
.setUuid(null);
275 accountManager
= createSignalServiceAccountManager();
277 if (voiceVerification
) {
278 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
280 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
283 account
.setRegistered(false);
287 public void updateAccountAttributes() throws IOException
{
288 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
291 public void setProfileName(String name
) throws IOException
{
292 accountManager
.setProfileName(account
.getProfileKey(), name
);
295 public void setProfileAvatar(File avatar
) throws IOException
{
296 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
297 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
298 streamDetails
.getStream().close();
301 public void removeProfileAvatar() throws IOException
{
302 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
305 public void unregister() throws IOException
{
306 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
307 // If this is the master device, other users can't send messages to this number anymore.
308 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
309 accountManager
.setGcmId(Optional
.absent());
311 account
.setRegistered(false);
315 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
316 List
<DeviceInfo
> devices
= accountManager
.getDevices();
317 account
.setMultiDevice(devices
.size() > 1);
322 public void removeLinkedDevices(int deviceId
) throws IOException
{
323 accountManager
.removeDevice(deviceId
);
324 List
<DeviceInfo
> devices
= accountManager
.getDevices();
325 account
.setMultiDevice(devices
.size() > 1);
329 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
330 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
332 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
335 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
336 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
337 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
339 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
340 account
.setMultiDevice(true);
344 private List
<PreKeyRecord
> generatePreKeys() {
345 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
347 final int offset
= account
.getPreKeyIdOffset();
348 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
349 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
350 ECKeyPair keyPair
= Curve
.generateKeyPair();
351 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
356 account
.addPreKeys(records
);
362 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
364 ECKeyPair keyPair
= Curve
.generateKeyPair();
365 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
366 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
368 account
.addSignedPreKey(record);
372 } catch (InvalidKeyException e
) {
373 throw new AssertionError(e
);
377 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
378 verificationCode
= verificationCode
.replace("-", "");
379 account
.setSignalingKey(KeyUtils
.createSignalingKey());
380 // TODO make unrestricted unidentified access configurable
381 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
383 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
384 // TODO response.isStorageCapable()
385 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
386 account
.setRegistered(true);
387 account
.setUuid(uuid
);
388 account
.setRegistrationLockPin(pin
);
389 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
395 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
396 if (pin
.isPresent()) {
397 account
.setRegistrationLockPin(pin
.get());
398 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
400 account
.setRegistrationLockPin(null);
401 accountManager
.removeRegistrationLockV1();
406 void refreshPreKeys() throws IOException
{
407 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
408 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
409 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
411 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
414 private SignalServiceMessageReceiver
getMessageReceiver() {
415 // TODO implement ZkGroup support
416 final ClientZkProfileOperations clientZkProfileOperations
= null;
417 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
420 private SignalServiceMessageSender
getMessageSender() {
421 // TODO implement ZkGroup support
422 final ClientZkProfileOperations clientZkProfileOperations
= null;
423 final boolean attachmentsV3
= false;
424 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
425 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
428 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
429 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
434 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
435 } catch (IOException ignored
) {
439 SignalServiceMessageReceiver receiver
= getMessageReceiver();
441 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
442 } catch (VerificationFailedException e
) {
443 throw new AssertionError(e
);
447 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
448 File file
= getGroupAvatarFile(groupId
);
449 if (!file
.exists()) {
450 return Optional
.absent();
453 return Optional
.of(Utils
.createAttachment(file
));
456 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
457 File file
= getContactAvatarFile(number
);
458 if (!file
.exists()) {
459 return Optional
.absent();
462 return Optional
.of(Utils
.createAttachment(file
));
465 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
466 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
468 throw new GroupNotFoundException(groupId
);
470 if (!g
.isMember(account
.getSelfAddress())) {
471 throw new NotAGroupMemberException(groupId
, g
.name
);
476 public List
<GroupInfo
> getGroups() {
477 return account
.getGroupStore().getGroups();
480 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
482 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
483 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
484 if (attachments
!= null) {
485 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
487 if (groupId
!= null) {
488 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
491 messageBuilder
.asGroupMessage(group
);
494 final GroupInfo g
= getGroupForSending(groupId
);
496 messageBuilder
.withExpiration(g
.messageExpirationTime
);
498 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
501 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
502 long targetSentTimestamp
, byte[] groupId
)
503 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
504 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
505 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
506 .withReaction(reaction
);
507 if (groupId
!= null) {
508 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
511 messageBuilder
.asGroupMessage(group
);
513 final GroupInfo g
= getGroupForSending(groupId
);
514 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
517 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
518 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
522 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
523 .asGroupMessage(group
);
525 final GroupInfo g
= getGroupForSending(groupId
);
526 g
.removeMember(account
.getSelfAddress());
527 account
.getGroupStore().updateGroup(g
);
529 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
532 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
534 if (groupId
== null) {
536 g
= new GroupInfo(KeyUtils
.createGroupId());
537 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
539 g
= getGroupForSending(groupId
);
546 if (members
!= null) {
547 final Set
<String
> newE164Members
= new HashSet
<>();
548 for (SignalServiceAddress member
: members
) {
549 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
552 newE164Members
.add(member
.getNumber().get());
555 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
556 if (contacts
.size() != newE164Members
.size()) {
557 // Some of the new members are not registered on Signal
558 for (ContactTokenDetails contact
: contacts
) {
559 newE164Members
.remove(contact
.getNumber());
561 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
562 System
.err
.println("Aborting…");
566 g
.addMembers(members
);
569 if (avatarFile
!= null) {
570 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
571 File aFile
= getGroupAvatarFile(g
.groupId
);
572 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
575 account
.getGroupStore().updateGroup(g
);
577 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
579 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
583 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
584 if (groupId
== null) {
587 GroupInfo g
= getGroupForSending(groupId
);
589 if (!g
.isMember(recipient
)) {
593 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
595 // Send group message only to the recipient who requested it
596 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
599 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
600 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
603 .withMembers(new ArrayList
<>(g
.getMembers()));
605 File aFile
= getGroupAvatarFile(g
.groupId
);
606 if (aFile
.exists()) {
608 group
.withAvatar(Utils
.createAttachment(aFile
));
609 } catch (IOException e
) {
610 throw new AttachmentInvalidException(aFile
.toString(), e
);
614 return SignalServiceDataMessage
.newBuilder()
615 .asGroupMessage(group
.build())
616 .withExpiration(g
.messageExpirationTime
);
619 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
620 if (groupId
== null) {
624 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
627 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
628 .asGroupMessage(group
.build());
630 // Send group info request message to the recipient who sent us a message with this groupId
631 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
634 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
635 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
636 Collections
.singletonList(messageId
),
637 System
.currentTimeMillis());
639 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
642 public long sendMessage(String messageText
, List
<String
> attachments
,
643 List
<String
> recipients
)
644 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
645 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
646 if (attachments
!= null) {
647 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
649 // Upload attachments here, so we only upload once even for multiple recipients
650 SignalServiceMessageSender messageSender
= getMessageSender();
651 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
652 for (SignalServiceAttachment attachment
: attachmentStreams
) {
653 if (attachment
.isStream()) {
654 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
655 } else if (attachment
.isPointer()) {
656 attachmentPointers
.add(attachment
.asPointer());
660 messageBuilder
.withAttachments(attachmentPointers
);
662 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
665 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
666 long targetSentTimestamp
, List
<String
> recipients
)
667 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
668 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
669 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
670 .withReaction(reaction
);
671 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
674 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
675 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
676 .asEndSessionMessage();
678 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
680 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
681 } catch (Exception e
) {
682 for (SignalServiceAddress address
: signalServiceAddresses
) {
683 handleEndSession(address
);
690 public String
getContactName(String number
) throws InvalidNumberException
{
691 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
692 if (contact
== null) {
699 public void setContactName(String number
, String name
) throws InvalidNumberException
{
700 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
701 ContactInfo contact
= account
.getContactStore().getContact(address
);
702 if (contact
== null) {
703 contact
= new ContactInfo(address
);
704 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
706 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
709 account
.getContactStore().updateContact(contact
);
713 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
714 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
717 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
718 ContactInfo contact
= account
.getContactStore().getContact(address
);
719 if (contact
== null) {
720 contact
= new ContactInfo(address
);
721 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
723 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
725 contact
.blocked
= blocked
;
726 account
.getContactStore().updateContact(contact
);
730 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
731 GroupInfo group
= getGroup(groupId
);
733 throw new GroupNotFoundException(groupId
);
735 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
736 group
.blocked
= blocked
;
737 account
.getGroupStore().updateGroup(group
);
742 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
743 if (groupId
.length
== 0) {
746 if (name
.isEmpty()) {
749 if (members
.isEmpty()) {
752 if (avatar
.isEmpty()) {
755 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
759 * Change the expiration timer for a contact
761 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
762 ContactInfo contact
= account
.getContactStore().getContact(address
);
763 contact
.messageExpirationTime
= messageExpirationTimer
;
764 account
.getContactStore().updateContact(contact
);
765 sendExpirationTimerUpdate(address
);
769 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
770 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
771 .asExpirationUpdate();
772 sendMessage(messageBuilder
, Collections
.singleton(address
));
776 * Change the expiration timer for a contact
778 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
779 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
780 setExpirationTimer(address
, messageExpirationTimer
);
784 * Change the expiration timer for a group
786 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
787 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
788 g
.messageExpirationTime
= messageExpirationTimer
;
789 account
.getGroupStore().updateGroup(g
);
793 * Upload the sticker pack from path.
795 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
796 * @return if successful, returns the URL to install the sticker pack in the signal app
798 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
799 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
801 SignalServiceMessageSender messageSender
= getMessageSender();
803 byte[] packKey
= KeyUtils
.createStickerUploadKey();
804 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
807 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
809 } catch (URISyntaxException e
) {
810 throw new AssertionError(e
);
814 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
816 String rootPath
= null;
818 final File file
= new File(path
);
819 if (file
.getName().endsWith(".zip")) {
820 zip
= new ZipFile(file
);
821 } else if (file
.getName().equals("manifest.json")) {
822 rootPath
= file
.getParent();
824 throw new StickerPackInvalidException("Could not find manifest.json");
827 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
829 if (pack
.stickers
== null) {
830 throw new StickerPackInvalidException("Must set a 'stickers' field.");
833 if (pack
.stickers
.isEmpty()) {
834 throw new StickerPackInvalidException("Must include stickers.");
837 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
838 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
839 if (sticker
.file
== null) {
840 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
843 Pair
<InputStream
, Long
> data
;
845 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
846 } catch (IOException ignored
) {
847 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
850 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
851 stickers
.add(stickerInfo
);
854 StickerInfo cover
= null;
855 if (pack
.cover
!= null) {
856 if (pack
.cover
.file
== null) {
857 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
860 Pair
<InputStream
, Long
> data
;
862 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
863 } catch (IOException ignored
) {
864 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
867 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
870 return new SignalServiceStickerManifestUpload(
877 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
878 InputStream inputStream
;
880 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
882 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
884 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
887 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
889 final ZipEntry entry
= zip
.getEntry(subfile
);
890 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
892 final File file
= new File(rootPath
, subfile
);
893 return new Pair
<>(new FileInputStream(file
), file
.length());
897 void requestSyncGroups() throws IOException
{
898 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
899 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
901 sendSyncMessage(message
);
902 } catch (UntrustedIdentityException e
) {
907 void requestSyncContacts() throws IOException
{
908 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
909 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
911 sendSyncMessage(message
);
912 } catch (UntrustedIdentityException e
) {
917 void requestSyncBlocked() throws IOException
{
918 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
919 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
921 sendSyncMessage(message
);
922 } catch (UntrustedIdentityException e
) {
927 void requestSyncConfiguration() throws IOException
{
928 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
929 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
931 sendSyncMessage(message
);
932 } catch (UntrustedIdentityException e
) {
937 private byte[] getSenderCertificate() {
938 // TODO support UUID capable sender certificates
939 // byte[] certificate = accountManager.getSenderCertificate();
942 certificate
= accountManager
.getSenderCertificateLegacy();
943 } catch (IOException e
) {
944 System
.err
.println("Failed to get sender certificate: " + e
);
947 // TODO cache for a day
951 private byte[] getSelfUnidentifiedAccessKey() {
952 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
955 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
956 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
958 return new SignalProfile(
959 encryptedProfile
.getIdentityKey(),
960 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
961 encryptedProfile
.getAvatar(),
962 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
963 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
965 } catch (InvalidCiphertextException e
) {
970 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
971 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
972 if (contact
== null || contact
.profileKey
== null) {
975 ProfileKey theirProfileKey
;
977 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
978 } catch (InvalidInputException
| IOException e
) {
979 throw new AssertionError(e
);
981 SignalProfile targetProfile
;
983 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
984 } catch (IOException e
) {
985 System
.err
.println("Failed to get recipient profile: " + e
);
989 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
993 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
994 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
997 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1000 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1001 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1002 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1004 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1005 return Optional
.absent();
1009 return Optional
.of(new UnidentifiedAccessPair(
1010 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1011 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1013 } catch (InvalidCertificateException e
) {
1014 return Optional
.absent();
1018 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1019 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1020 for (SignalServiceAddress recipient
: recipients
) {
1021 result
.add(getAccessFor(recipient
));
1026 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1027 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1028 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1029 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1031 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1032 return Optional
.absent();
1036 return Optional
.of(new UnidentifiedAccessPair(
1037 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1038 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1040 } catch (InvalidCertificateException e
) {
1041 return Optional
.absent();
1045 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1046 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1048 if (unidentifiedAccess
.isPresent()) {
1049 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1052 return Optional
.absent();
1055 private void sendSyncMessage(SignalServiceSyncMessage message
)
1056 throws IOException
, UntrustedIdentityException
{
1057 SignalServiceMessageSender messageSender
= getMessageSender();
1059 messageSender
.sendMessage(message
, getAccessForSync());
1060 } catch (UntrustedIdentityException e
) {
1061 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1067 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1069 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1070 throws EncapsulatedExceptions
, IOException
{
1071 final long timestamp
= System
.currentTimeMillis();
1072 messageBuilder
.withTimestamp(timestamp
);
1073 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1075 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1076 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1077 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1079 for (SendMessageResult result
: results
) {
1080 if (result
.isUnregisteredFailure()) {
1081 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1082 } else if (result
.isNetworkFailure()) {
1083 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1084 } else if (result
.getIdentityFailure() != null) {
1085 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1088 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1089 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1094 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1095 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1097 for (String number
: numbers
) {
1098 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1100 return signalServiceAddresses
;
1103 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1104 throws IOException
{
1105 if (messagePipe
== null) {
1106 messagePipe
= getMessageReceiver().createMessagePipe();
1108 if (unidentifiedMessagePipe
== null) {
1109 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1111 SignalServiceDataMessage message
= null;
1113 message
= messageBuilder
.build();
1114 if (message
.getGroupContext().isPresent()) {
1116 SignalServiceMessageSender messageSender
= getMessageSender();
1117 final boolean isRecipientUpdate
= false;
1118 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1119 for (SendMessageResult r
: result
) {
1120 if (r
.getIdentityFailure() != null) {
1121 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1125 } catch (UntrustedIdentityException e
) {
1126 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1127 return Collections
.emptyList();
1130 // Send to all individually, so sync messages are sent correctly
1131 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1132 for (SignalServiceAddress address
: recipients
) {
1133 ContactInfo contact
= account
.getContactStore().getContact(address
);
1134 if (contact
!= null) {
1135 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1136 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1138 messageBuilder
.withExpiration(0);
1139 messageBuilder
.withProfileKey(null);
1141 message
= messageBuilder
.build();
1142 if (address
.matches(account
.getSelfAddress())) {
1143 results
.add(sendSelfMessage(message
));
1145 results
.add(sendMessage(address
, message
));
1151 if (message
!= null && message
.isEndSession()) {
1152 for (SignalServiceAddress recipient
: recipients
) {
1153 handleEndSession(recipient
);
1160 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1161 SignalServiceMessageSender messageSender
= getMessageSender();
1163 SignalServiceAddress recipient
= account
.getSelfAddress();
1165 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1166 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1167 message
.getTimestamp(),
1169 message
.getExpiresInSeconds(),
1170 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1172 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1175 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1176 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1177 } catch (UntrustedIdentityException e
) {
1178 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1179 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1183 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1184 SignalServiceMessageSender messageSender
= getMessageSender();
1187 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1188 } catch (UntrustedIdentityException e
) {
1189 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1190 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1194 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1195 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1197 return cipher
.decrypt(envelope
);
1198 } catch (ProtocolUntrustedIdentityException e
) {
1199 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1200 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1201 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1202 throw identityException
;
1204 throw new AssertionError(e
);
1208 private void handleEndSession(SignalServiceAddress source
) {
1209 account
.getSignalProtocolStore().deleteAllSessions(source
);
1212 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1213 List
<HandleAction
> actions
= new ArrayList
<>();
1214 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1215 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1216 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1217 switch (groupInfo
.getType()) {
1219 if (group
== null) {
1220 group
= new GroupInfo(groupInfo
.getGroupId());
1223 if (groupInfo
.getAvatar().isPresent()) {
1224 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1225 if (avatar
.isPointer()) {
1227 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1228 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1229 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1234 if (groupInfo
.getName().isPresent()) {
1235 group
.name
= groupInfo
.getName().get();
1238 if (groupInfo
.getMembers().isPresent()) {
1239 group
.addMembers(groupInfo
.getMembers().get()
1241 .map(this::resolveSignalServiceAddress
)
1242 .collect(Collectors
.toSet()));
1245 account
.getGroupStore().updateGroup(group
);
1248 if (group
== null && !isSync
) {
1249 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1253 if (group
!= null) {
1254 group
.removeMember(source
);
1255 account
.getGroupStore().updateGroup(group
);
1259 if (group
!= null && !isSync
) {
1260 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1265 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1266 if (message
.isEndSession()) {
1267 handleEndSession(conversationPartnerAddress
);
1269 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1270 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1271 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1272 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1273 if (group
== null) {
1274 group
= new GroupInfo(groupInfo
.getGroupId());
1276 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1277 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1278 account
.getGroupStore().updateGroup(group
);
1281 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1282 if (contact
== null) {
1283 contact
= new ContactInfo(conversationPartnerAddress
);
1285 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1286 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1287 account
.getContactStore().updateContact(contact
);
1291 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1292 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1293 if (attachment
.isPointer()) {
1295 retrieveAttachment(attachment
.asPointer());
1296 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1297 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1302 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1303 if (source
.matches(account
.getSelfAddress())) {
1305 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1306 } catch (InvalidInputException ignored
) {
1308 ContactInfo contact
= account
.getContactStore().getContact(source
);
1309 if (contact
!= null) {
1310 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1311 account
.getContactStore().updateContact(contact
);
1314 ContactInfo contact
= account
.getContactStore().getContact(source
);
1315 if (contact
== null) {
1316 contact
= new ContactInfo(source
);
1318 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1319 account
.getContactStore().updateContact(contact
);
1322 if (message
.getPreviews().isPresent()) {
1323 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1324 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1325 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1326 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1328 retrieveAttachment(attachment
);
1329 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1330 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1338 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1339 final File cachePath
= new File(getMessageCachePath());
1340 if (!cachePath
.exists()) {
1343 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1344 if (!dir
.isDirectory()) {
1345 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1349 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1350 if (!fileEntry
.isFile()) {
1353 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1355 // Try to delete directory if empty
1360 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1361 SignalServiceEnvelope envelope
;
1363 envelope
= Utils
.loadEnvelope(fileEntry
);
1364 if (envelope
== null) {
1367 } catch (IOException e
) {
1368 e
.printStackTrace();
1371 SignalServiceContent content
= null;
1372 if (!envelope
.isReceipt()) {
1374 content
= decryptMessage(envelope
);
1375 } catch (Exception e
) {
1378 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1379 for (HandleAction action
: actions
) {
1381 action
.execute(this);
1382 } catch (Throwable e
) {
1383 e
.printStackTrace();
1388 handler
.handleMessage(envelope
, content
, null);
1390 Files
.delete(fileEntry
.toPath());
1391 } catch (IOException e
) {
1392 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1396 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1397 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1398 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1400 Set
<HandleAction
> queuedActions
= null;
1402 if (messagePipe
== null) {
1403 messagePipe
= messageReceiver
.createMessagePipe();
1406 boolean hasCaughtUpWithOldMessages
= false;
1409 SignalServiceEnvelope envelope
;
1410 SignalServiceContent content
= null;
1411 Exception exception
= null;
1412 final long now
= new Date().getTime();
1414 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1415 // store message on disk, before acknowledging receipt to the server
1417 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1418 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1419 Utils
.storeEnvelope(envelope1
, cacheFile
);
1420 } catch (IOException e
) {
1421 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1424 if (result
.isPresent()) {
1425 envelope
= result
.get();
1427 // Received indicator that server queue is empty
1428 hasCaughtUpWithOldMessages
= true;
1430 if (queuedActions
!= null) {
1431 for (HandleAction action
: queuedActions
) {
1433 action
.execute(this);
1434 } catch (Throwable e
) {
1435 e
.printStackTrace();
1438 queuedActions
.clear();
1439 queuedActions
= null;
1442 // Continue to wait another timeout for new messages
1445 } catch (TimeoutException e
) {
1446 if (returnOnTimeout
)
1449 } catch (InvalidVersionException e
) {
1450 System
.err
.println("Ignoring error: " + e
.getMessage());
1453 if (envelope
.hasSource()) {
1454 // Store uuid if we don't have it already
1455 SignalServiceAddress source
= envelope
.getSourceAddress();
1456 resolveSignalServiceAddress(source
);
1458 if (!envelope
.isReceipt()) {
1460 content
= decryptMessage(envelope
);
1461 } catch (Exception e
) {
1464 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1465 if (hasCaughtUpWithOldMessages
) {
1466 for (HandleAction action
: actions
) {
1468 action
.execute(this);
1469 } catch (Throwable e
) {
1470 e
.printStackTrace();
1474 if (queuedActions
== null) {
1475 queuedActions
= new HashSet
<>();
1477 queuedActions
.addAll(actions
);
1481 if (!isMessageBlocked(envelope
, content
)) {
1482 handler
.handleMessage(envelope
, content
, exception
);
1484 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1485 File cacheFile
= null;
1487 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1488 Files
.delete(cacheFile
.toPath());
1489 // Try to delete directory if empty
1490 new File(getMessageCachePath()).delete();
1491 } catch (IOException e
) {
1492 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1498 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1499 SignalServiceAddress source
;
1500 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1501 source
= envelope
.getSourceAddress();
1502 } else if (content
!= null) {
1503 source
= content
.getSender();
1507 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1508 if (sourceContact
!= null && sourceContact
.blocked
) {
1512 if (content
!= null && content
.getDataMessage().isPresent()) {
1513 SignalServiceDataMessage message
= content
.getDataMessage().get();
1514 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1515 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1516 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1517 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1525 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1526 List
<HandleAction
> actions
= new ArrayList
<>();
1527 if (content
!= null) {
1528 SignalServiceAddress sender
;
1529 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1530 sender
= envelope
.getSourceAddress();
1532 sender
= content
.getSender();
1534 // Store uuid if we don't have it already
1535 resolveSignalServiceAddress(sender
);
1537 if (content
.getDataMessage().isPresent()) {
1538 SignalServiceDataMessage message
= content
.getDataMessage().get();
1540 if (content
.isNeedsReceipt()) {
1541 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1544 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1546 if (content
.getSyncMessage().isPresent()) {
1547 account
.setMultiDevice(true);
1548 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1549 if (syncMessage
.getSent().isPresent()) {
1550 SentTranscriptMessage message
= syncMessage
.getSent().get();
1551 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1553 if (syncMessage
.getRequest().isPresent()) {
1554 RequestMessage rm
= syncMessage
.getRequest().get();
1555 if (rm
.isContactsRequest()) {
1556 actions
.add(SendSyncContactsAction
.create());
1558 if (rm
.isGroupsRequest()) {
1559 actions
.add(SendSyncGroupsAction
.create());
1561 if (rm
.isBlockedListRequest()) {
1562 actions
.add(SendSyncBlockedListAction
.create());
1564 // TODO Handle rm.isConfigurationRequest();
1566 if (syncMessage
.getGroups().isPresent()) {
1567 File tmpFile
= null;
1569 tmpFile
= IOUtils
.createTempFile();
1570 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1571 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1573 while ((g
= s
.read()) != null) {
1574 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1575 if (syncGroup
== null) {
1576 syncGroup
= new GroupInfo(g
.getId());
1578 if (g
.getName().isPresent()) {
1579 syncGroup
.name
= g
.getName().get();
1581 syncGroup
.addMembers(g
.getMembers()
1583 .map(this::resolveSignalServiceAddress
)
1584 .collect(Collectors
.toSet()));
1585 if (!g
.isActive()) {
1586 syncGroup
.removeMember(account
.getSelfAddress());
1588 // Add ourself to the member set as it's marked as active
1589 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1591 syncGroup
.blocked
= g
.isBlocked();
1592 if (g
.getColor().isPresent()) {
1593 syncGroup
.color
= g
.getColor().get();
1596 if (g
.getAvatar().isPresent()) {
1597 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1599 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1600 syncGroup
.archived
= g
.isArchived();
1601 account
.getGroupStore().updateGroup(syncGroup
);
1604 } catch (Exception e
) {
1605 e
.printStackTrace();
1607 if (tmpFile
!= null) {
1609 Files
.delete(tmpFile
.toPath());
1610 } catch (IOException e
) {
1611 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1616 if (syncMessage
.getBlockedList().isPresent()) {
1617 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1618 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1619 setContactBlocked(resolveSignalServiceAddress(address
), true);
1621 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1623 setGroupBlocked(groupId
, true);
1624 } catch (GroupNotFoundException e
) {
1625 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1629 if (syncMessage
.getContacts().isPresent()) {
1630 File tmpFile
= null;
1632 tmpFile
= IOUtils
.createTempFile();
1633 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1634 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1635 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1636 if (contactsMessage
.isComplete()) {
1637 account
.getContactStore().clear();
1640 while ((c
= s
.read()) != null) {
1641 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1642 account
.setProfileKey(c
.getProfileKey().get());
1644 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1645 ContactInfo contact
= account
.getContactStore().getContact(address
);
1646 if (contact
== null) {
1647 contact
= new ContactInfo(address
);
1649 if (c
.getName().isPresent()) {
1650 contact
.name
= c
.getName().get();
1652 if (c
.getColor().isPresent()) {
1653 contact
.color
= c
.getColor().get();
1655 if (c
.getProfileKey().isPresent()) {
1656 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1658 if (c
.getVerified().isPresent()) {
1659 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1660 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1662 if (c
.getExpirationTimer().isPresent()) {
1663 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1665 contact
.blocked
= c
.isBlocked();
1666 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1667 contact
.archived
= c
.isArchived();
1668 account
.getContactStore().updateContact(contact
);
1670 if (c
.getAvatar().isPresent()) {
1671 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1675 } catch (Exception e
) {
1676 e
.printStackTrace();
1678 if (tmpFile
!= null) {
1680 Files
.delete(tmpFile
.toPath());
1681 } catch (IOException e
) {
1682 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1687 if (syncMessage
.getVerified().isPresent()) {
1688 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1689 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1691 if (syncMessage
.getConfiguration().isPresent()) {
1699 private File
getContactAvatarFile(String number
) {
1700 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1703 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1704 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1705 if (attachment
.isPointer()) {
1706 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1707 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1709 SignalServiceAttachmentStream stream
= attachment
.asStream();
1710 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1714 private File
getGroupAvatarFile(byte[] groupId
) {
1715 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1718 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1719 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1720 if (attachment
.isPointer()) {
1721 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1722 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1724 SignalServiceAttachmentStream stream
= attachment
.asStream();
1725 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1729 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1730 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1733 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1734 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1735 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1738 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1739 if (storePreview
&& pointer
.getPreview().isPresent()) {
1740 File previewFile
= new File(outputFile
+ ".preview");
1741 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1742 byte[] preview
= pointer
.getPreview().get();
1743 output
.write(preview
, 0, preview
.length
);
1744 } catch (FileNotFoundException e
) {
1745 e
.printStackTrace();
1750 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1752 File tmpFile
= IOUtils
.createTempFile();
1753 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1754 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1755 byte[] buffer
= new byte[4096];
1758 while ((read
= input
.read(buffer
)) != -1) {
1759 output
.write(buffer
, 0, read
);
1761 } catch (FileNotFoundException e
) {
1762 e
.printStackTrace();
1767 Files
.delete(tmpFile
.toPath());
1768 } catch (IOException e
) {
1769 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1775 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1776 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1777 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1780 void sendGroups() throws IOException
, UntrustedIdentityException
{
1781 File groupsFile
= IOUtils
.createTempFile();
1784 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1785 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1786 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1787 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1788 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1789 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1790 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1794 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1795 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1796 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1797 .withStream(groupsFileStream
)
1798 .withContentType("application/octet-stream")
1799 .withLength(groupsFile
.length())
1802 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1807 Files
.delete(groupsFile
.toPath());
1808 } catch (IOException e
) {
1809 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1814 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1815 File contactsFile
= IOUtils
.createTempFile();
1818 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1819 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1820 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1821 VerifiedMessage verifiedMessage
= null;
1822 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1823 if (currentIdentity
!= null) {
1824 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1827 ProfileKey profileKey
= null;
1829 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1830 } catch (InvalidInputException ignored
) {
1832 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1833 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1834 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1835 Optional
.of(record.messageExpirationTime
),
1836 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1839 if (account
.getProfileKey() != null) {
1840 // Send our own profile key as well
1841 out
.write(new DeviceContact(account
.getSelfAddress(),
1842 Optional
.absent(), Optional
.absent(),
1843 Optional
.absent(), Optional
.absent(),
1844 Optional
.of(account
.getProfileKey()),
1845 false, Optional
.absent(), Optional
.absent(), false));
1849 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1850 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1851 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1852 .withStream(contactsFileStream
)
1853 .withContentType("application/octet-stream")
1854 .withLength(contactsFile
.length())
1857 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1862 Files
.delete(contactsFile
.toPath());
1863 } catch (IOException e
) {
1864 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1869 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1870 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1871 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1872 if (record.blocked
) {
1873 addresses
.add(record.getAddress());
1876 List
<byte[]> groupIds
= new ArrayList
<>();
1877 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1878 if (record.blocked
) {
1879 groupIds
.add(record.groupId
);
1882 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1885 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1886 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1887 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1890 public List
<ContactInfo
> getContacts() {
1891 return account
.getContactStore().getContacts();
1894 public ContactInfo
getContact(String number
) {
1895 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1898 public GroupInfo
getGroup(byte[] groupId
) {
1899 return account
.getGroupStore().getGroup(groupId
);
1902 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1903 return account
.getSignalProtocolStore().getIdentities();
1906 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1907 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1911 * Trust this the identity with this fingerprint
1913 * @param name username of the identity
1914 * @param fingerprint Fingerprint
1916 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1917 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1918 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1922 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1923 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1927 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1929 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1930 } catch (IOException
| UntrustedIdentityException e
) {
1931 e
.printStackTrace();
1940 * Trust this the identity with this safety number
1942 * @param name username of the identity
1943 * @param safetyNumber Safety number
1945 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1946 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1947 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1951 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1952 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1956 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1958 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1959 } catch (IOException
| UntrustedIdentityException e
) {
1960 e
.printStackTrace();
1969 * Trust all keys of this identity without verification
1971 * @param name username of the identity
1973 public boolean trustIdentityAllKeys(String name
) {
1974 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1975 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1979 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1980 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1981 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1983 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1984 } catch (IOException
| UntrustedIdentityException e
) {
1985 e
.printStackTrace();
1993 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1994 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1997 void saveAccount() {
2001 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2002 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2003 return resolveSignalServiceAddress(canonicalizedNumber
);
2006 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2007 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2009 return resolveSignalServiceAddress(address
);
2012 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2013 if (address
.matches(account
.getSelfAddress())) {
2014 return account
.getSelfAddress();
2017 return account
.getRecipientStore().resolveServiceAddress(address
);
2021 public void close() throws IOException
{
2022 if (messagePipe
!= null) {
2023 messagePipe
.shutdown();
2027 if (unidentifiedMessagePipe
!= null) {
2028 unidentifiedMessagePipe
.shutdown();
2029 unidentifiedMessagePipe
= null;
2035 public interface ReceiveMessageHandler
{
2037 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);