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 private 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 private 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 private 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
.size() == 0) {
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 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder();
763 ContactInfo contact
= account
.getContactStore().getContact(address
);
764 contact
.messageExpirationTime
= messageExpirationTimer
;
765 account
.getContactStore().updateContact(contact
);
767 messageBuilder
.withExpiration(messageExpirationTimer
);
768 messageBuilder
.asExpirationUpdate();
769 sendMessage(messageBuilder
, Collections
.singleton(address
));
773 * Change the expiration timer for a contact
775 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
776 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
777 setExpirationTimer(address
, messageExpirationTimer
);
781 * Change the expiration timer for a group
783 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
784 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
785 g
.messageExpirationTime
= messageExpirationTimer
;
786 account
.getGroupStore().updateGroup(g
);
790 * Upload the sticker pack from path.
792 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
793 * @return if successful, returns the URL to install the sticker pack in the signal app
795 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
796 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
798 SignalServiceMessageSender messageSender
= getMessageSender();
800 byte[] packKey
= KeyUtils
.createStickerUploadKey();
801 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
804 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
806 } catch (URISyntaxException e
) {
807 throw new AssertionError(e
);
811 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
813 String rootPath
= null;
815 final File file
= new File(path
);
816 if (file
.getName().endsWith(".zip")) {
817 zip
= new ZipFile(file
);
818 } else if (file
.getName().equals("manifest.json")) {
819 rootPath
= file
.getParent();
821 throw new StickerPackInvalidException("Could not find manifest.json");
824 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
826 if (pack
.stickers
== null) {
827 throw new StickerPackInvalidException("Must set a 'stickers' field.");
830 if (pack
.stickers
.isEmpty()) {
831 throw new StickerPackInvalidException("Must include stickers.");
834 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
835 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
836 if (sticker
.file
== null) {
837 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
840 Pair
<InputStream
, Long
> data
;
842 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
843 } catch (IOException ignored
) {
844 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
847 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
848 stickers
.add(stickerInfo
);
851 StickerInfo cover
= null;
852 if (pack
.cover
!= null) {
853 if (pack
.cover
.file
== null) {
854 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
857 Pair
<InputStream
, Long
> data
;
859 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
860 } catch (IOException ignored
) {
861 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
864 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
867 return new SignalServiceStickerManifestUpload(
874 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
875 InputStream inputStream
;
877 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
879 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
881 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
884 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
886 final ZipEntry entry
= zip
.getEntry(subfile
);
887 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
889 final File file
= new File(rootPath
, subfile
);
890 return new Pair
<>(new FileInputStream(file
), file
.length());
894 void requestSyncGroups() throws IOException
{
895 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
896 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
898 sendSyncMessage(message
);
899 } catch (UntrustedIdentityException e
) {
904 void requestSyncContacts() throws IOException
{
905 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
906 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
908 sendSyncMessage(message
);
909 } catch (UntrustedIdentityException e
) {
914 void requestSyncBlocked() throws IOException
{
915 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
916 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
918 sendSyncMessage(message
);
919 } catch (UntrustedIdentityException e
) {
924 void requestSyncConfiguration() throws IOException
{
925 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
926 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
928 sendSyncMessage(message
);
929 } catch (UntrustedIdentityException e
) {
934 private byte[] getSenderCertificate() {
935 // TODO support UUID capable sender certificates
936 // byte[] certificate = accountManager.getSenderCertificate();
939 certificate
= accountManager
.getSenderCertificateLegacy();
940 } catch (IOException e
) {
941 System
.err
.println("Failed to get sender certificate: " + e
);
944 // TODO cache for a day
948 private byte[] getSelfUnidentifiedAccessKey() {
949 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
952 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
953 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
955 return new SignalProfile(
956 encryptedProfile
.getIdentityKey(),
957 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
958 encryptedProfile
.getAvatar(),
959 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
960 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
962 } catch (InvalidCiphertextException e
) {
967 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
968 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
969 if (contact
== null || contact
.profileKey
== null) {
972 ProfileKey theirProfileKey
;
974 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
975 } catch (InvalidInputException
| IOException e
) {
976 throw new AssertionError(e
);
978 SignalProfile targetProfile
;
980 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
981 } catch (IOException e
) {
982 System
.err
.println("Failed to get recipient profile: " + e
);
986 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
990 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
991 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
994 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
997 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
998 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
999 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1001 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1002 return Optional
.absent();
1006 return Optional
.of(new UnidentifiedAccessPair(
1007 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1008 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1010 } catch (InvalidCertificateException e
) {
1011 return Optional
.absent();
1015 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1016 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1017 for (SignalServiceAddress recipient
: recipients
) {
1018 result
.add(getAccessFor(recipient
));
1023 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1024 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1025 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1026 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1028 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1029 return Optional
.absent();
1033 return Optional
.of(new UnidentifiedAccessPair(
1034 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1035 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1037 } catch (InvalidCertificateException e
) {
1038 return Optional
.absent();
1042 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1043 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1045 if (unidentifiedAccess
.isPresent()) {
1046 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1049 return Optional
.absent();
1052 private void sendSyncMessage(SignalServiceSyncMessage message
)
1053 throws IOException
, UntrustedIdentityException
{
1054 SignalServiceMessageSender messageSender
= getMessageSender();
1056 messageSender
.sendMessage(message
, getAccessForSync());
1057 } catch (UntrustedIdentityException e
) {
1058 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1064 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1066 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1067 throws EncapsulatedExceptions
, IOException
{
1068 final long timestamp
= System
.currentTimeMillis();
1069 messageBuilder
.withTimestamp(timestamp
);
1070 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1072 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1073 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1074 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1076 for (SendMessageResult result
: results
) {
1077 if (result
.isUnregisteredFailure()) {
1078 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1079 } else if (result
.isNetworkFailure()) {
1080 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1081 } else if (result
.getIdentityFailure() != null) {
1082 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1085 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1086 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1091 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1092 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1094 for (String number
: numbers
) {
1095 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1097 return signalServiceAddresses
;
1100 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1101 throws IOException
{
1102 if (messagePipe
== null) {
1103 messagePipe
= getMessageReceiver().createMessagePipe();
1105 if (unidentifiedMessagePipe
== null) {
1106 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1108 SignalServiceDataMessage message
= null;
1110 SignalServiceMessageSender messageSender
= getMessageSender();
1112 message
= messageBuilder
.build();
1113 if (message
.getGroupContext().isPresent()) {
1115 final boolean isRecipientUpdate
= false;
1116 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1117 for (SendMessageResult r
: result
) {
1118 if (r
.getIdentityFailure() != null) {
1119 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1123 } catch (UntrustedIdentityException e
) {
1124 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1125 return Collections
.emptyList();
1127 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1128 SignalServiceAddress recipient
= account
.getSelfAddress();
1129 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1130 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1131 message
.getTimestamp(),
1133 message
.getExpiresInSeconds(),
1134 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1136 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1138 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1140 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1141 } catch (UntrustedIdentityException e
) {
1142 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1143 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1147 // Send to all individually, so sync messages are sent correctly
1148 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1149 for (SignalServiceAddress address
: recipients
) {
1150 ContactInfo contact
= account
.getContactStore().getContact(address
);
1151 if (contact
!= null) {
1152 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1153 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1155 messageBuilder
.withExpiration(0);
1156 messageBuilder
.withProfileKey(null);
1158 message
= messageBuilder
.build();
1160 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1161 results
.add(result
);
1162 } catch (UntrustedIdentityException e
) {
1163 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1164 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1170 if (message
!= null && message
.isEndSession()) {
1171 for (SignalServiceAddress recipient
: recipients
) {
1172 handleEndSession(recipient
);
1179 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1180 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1182 return cipher
.decrypt(envelope
);
1183 } catch (ProtocolUntrustedIdentityException e
) {
1184 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1185 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1186 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1187 throw identityException
;
1189 throw new AssertionError(e
);
1193 private void handleEndSession(SignalServiceAddress source
) {
1194 account
.getSignalProtocolStore().deleteAllSessions(source
);
1197 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1198 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1199 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1200 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1201 switch (groupInfo
.getType()) {
1203 if (group
== null) {
1204 group
= new GroupInfo(groupInfo
.getGroupId());
1207 if (groupInfo
.getAvatar().isPresent()) {
1208 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1209 if (avatar
.isPointer()) {
1211 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1212 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1213 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1218 if (groupInfo
.getName().isPresent()) {
1219 group
.name
= groupInfo
.getName().get();
1222 if (groupInfo
.getMembers().isPresent()) {
1223 group
.addMembers(groupInfo
.getMembers().get()
1225 .map(this::resolveSignalServiceAddress
)
1226 .collect(Collectors
.toSet()));
1229 account
.getGroupStore().updateGroup(group
);
1232 if (group
== null) {
1234 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1235 } catch (IOException
| EncapsulatedExceptions e
) {
1236 e
.printStackTrace();
1241 if (group
!= null) {
1242 group
.removeMember(source
);
1243 account
.getGroupStore().updateGroup(group
);
1247 if (group
!= null) {
1249 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1250 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1251 e
.printStackTrace();
1252 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1253 // We have left this group, so don't send a group update message
1259 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1260 if (message
.isEndSession()) {
1261 handleEndSession(conversationPartnerAddress
);
1263 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1264 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1265 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1266 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1267 if (group
== null) {
1268 group
= new GroupInfo(groupInfo
.getGroupId());
1270 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1271 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1272 account
.getGroupStore().updateGroup(group
);
1275 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1276 if (contact
== null) {
1277 contact
= new ContactInfo(conversationPartnerAddress
);
1279 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1280 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1281 account
.getContactStore().updateContact(contact
);
1285 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1286 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1287 if (attachment
.isPointer()) {
1289 retrieveAttachment(attachment
.asPointer());
1290 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1291 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1296 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1297 if (source
.matches(account
.getSelfAddress())) {
1299 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1300 } catch (InvalidInputException ignored
) {
1302 ContactInfo contact
= account
.getContactStore().getContact(source
);
1303 if (contact
!= null) {
1304 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1305 account
.getContactStore().updateContact(contact
);
1308 ContactInfo contact
= account
.getContactStore().getContact(source
);
1309 if (contact
== null) {
1310 contact
= new ContactInfo(source
);
1312 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1313 account
.getContactStore().updateContact(contact
);
1316 if (message
.getPreviews().isPresent()) {
1317 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1318 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1319 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1320 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1322 retrieveAttachment(attachment
);
1323 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1324 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1331 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1332 final File cachePath
= new File(getMessageCachePath());
1333 if (!cachePath
.exists()) {
1336 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1337 if (!dir
.isDirectory()) {
1338 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1342 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1343 if (!fileEntry
.isFile()) {
1346 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1348 // Try to delete directory if empty
1353 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1354 SignalServiceEnvelope envelope
;
1356 envelope
= Utils
.loadEnvelope(fileEntry
);
1357 if (envelope
== null) {
1360 } catch (IOException e
) {
1361 e
.printStackTrace();
1364 SignalServiceContent content
= null;
1365 if (!envelope
.isReceipt()) {
1367 content
= decryptMessage(envelope
);
1368 } catch (Exception e
) {
1371 handleMessage(envelope
, content
, ignoreAttachments
);
1374 handler
.handleMessage(envelope
, content
, null);
1376 Files
.delete(fileEntry
.toPath());
1377 } catch (IOException e
) {
1378 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1382 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1383 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1384 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1386 if (messagePipe
== null) {
1387 messagePipe
= messageReceiver
.createMessagePipe();
1391 SignalServiceEnvelope envelope
;
1392 SignalServiceContent content
= null;
1393 Exception exception
= null;
1394 final long now
= new Date().getTime();
1396 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1397 // store message on disk, before acknowledging receipt to the server
1399 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1400 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1401 Utils
.storeEnvelope(envelope1
, cacheFile
);
1402 } catch (IOException e
) {
1403 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1406 } catch (TimeoutException e
) {
1407 if (returnOnTimeout
)
1410 } catch (InvalidVersionException e
) {
1411 System
.err
.println("Ignoring error: " + e
.getMessage());
1414 if (!envelope
.isReceipt()) {
1416 content
= decryptMessage(envelope
);
1417 } catch (Exception e
) {
1420 handleMessage(envelope
, content
, ignoreAttachments
);
1423 if (!isMessageBlocked(envelope
, content
)) {
1424 handler
.handleMessage(envelope
, content
, exception
);
1426 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1427 File cacheFile
= null;
1429 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1430 Files
.delete(cacheFile
.toPath());
1431 // Try to delete directory if empty
1432 new File(getMessageCachePath()).delete();
1433 } catch (IOException e
) {
1434 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1440 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1441 SignalServiceAddress source
;
1442 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1443 source
= envelope
.getSourceAddress();
1444 } else if (content
!= null) {
1445 source
= content
.getSender();
1449 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1450 if (sourceContact
!= null && sourceContact
.blocked
) {
1454 if (content
!= null && content
.getDataMessage().isPresent()) {
1455 SignalServiceDataMessage message
= content
.getDataMessage().get();
1456 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1457 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1458 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1459 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1467 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1468 if (content
!= null) {
1469 SignalServiceAddress sender
;
1470 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1471 sender
= envelope
.getSourceAddress();
1473 sender
= content
.getSender();
1475 if (content
.getDataMessage().isPresent()) {
1476 SignalServiceDataMessage message
= content
.getDataMessage().get();
1478 if (content
.isNeedsReceipt()) {
1480 sendReceipt(sender
, message
.getTimestamp());
1481 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1482 e
.printStackTrace();
1486 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1488 if (content
.getSyncMessage().isPresent()) {
1489 account
.setMultiDevice(true);
1490 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1491 if (syncMessage
.getSent().isPresent()) {
1492 SentTranscriptMessage message
= syncMessage
.getSent().get();
1493 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1495 if (syncMessage
.getRequest().isPresent()) {
1496 RequestMessage rm
= syncMessage
.getRequest().get();
1497 if (rm
.isContactsRequest()) {
1500 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1501 e
.printStackTrace();
1504 if (rm
.isGroupsRequest()) {
1507 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1508 e
.printStackTrace();
1511 if (rm
.isBlockedListRequest()) {
1514 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1515 e
.printStackTrace();
1518 // TODO Handle rm.isConfigurationRequest();
1520 if (syncMessage
.getGroups().isPresent()) {
1521 File tmpFile
= null;
1523 tmpFile
= IOUtils
.createTempFile();
1524 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1525 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1527 while ((g
= s
.read()) != null) {
1528 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1529 if (syncGroup
== null) {
1530 syncGroup
= new GroupInfo(g
.getId());
1532 if (g
.getName().isPresent()) {
1533 syncGroup
.name
= g
.getName().get();
1535 syncGroup
.addMembers(g
.getMembers()
1537 .map(this::resolveSignalServiceAddress
)
1538 .collect(Collectors
.toSet()));
1539 if (!g
.isActive()) {
1540 syncGroup
.removeMember(account
.getSelfAddress());
1542 // Add ourself to the member set as it's marked as active
1543 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1545 syncGroup
.blocked
= g
.isBlocked();
1546 if (g
.getColor().isPresent()) {
1547 syncGroup
.color
= g
.getColor().get();
1550 if (g
.getAvatar().isPresent()) {
1551 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1553 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1554 syncGroup
.archived
= g
.isArchived();
1555 account
.getGroupStore().updateGroup(syncGroup
);
1558 } catch (Exception e
) {
1559 e
.printStackTrace();
1561 if (tmpFile
!= null) {
1563 Files
.delete(tmpFile
.toPath());
1564 } catch (IOException e
) {
1565 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1570 if (syncMessage
.getBlockedList().isPresent()) {
1571 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1572 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1573 setContactBlocked(resolveSignalServiceAddress(address
), true);
1575 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1577 setGroupBlocked(groupId
, true);
1578 } catch (GroupNotFoundException e
) {
1579 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1583 if (syncMessage
.getContacts().isPresent()) {
1584 File tmpFile
= null;
1586 tmpFile
= IOUtils
.createTempFile();
1587 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1588 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1589 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1590 if (contactsMessage
.isComplete()) {
1591 account
.getContactStore().clear();
1594 while ((c
= s
.read()) != null) {
1595 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1596 account
.setProfileKey(c
.getProfileKey().get());
1598 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1599 ContactInfo contact
= account
.getContactStore().getContact(address
);
1600 if (contact
== null) {
1601 contact
= new ContactInfo(address
);
1603 if (c
.getName().isPresent()) {
1604 contact
.name
= c
.getName().get();
1606 if (c
.getColor().isPresent()) {
1607 contact
.color
= c
.getColor().get();
1609 if (c
.getProfileKey().isPresent()) {
1610 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1612 if (c
.getVerified().isPresent()) {
1613 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1614 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1616 if (c
.getExpirationTimer().isPresent()) {
1617 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1619 contact
.blocked
= c
.isBlocked();
1620 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1621 contact
.archived
= c
.isArchived();
1622 account
.getContactStore().updateContact(contact
);
1624 if (c
.getAvatar().isPresent()) {
1625 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1629 } catch (Exception e
) {
1630 e
.printStackTrace();
1632 if (tmpFile
!= null) {
1634 Files
.delete(tmpFile
.toPath());
1635 } catch (IOException e
) {
1636 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1641 if (syncMessage
.getVerified().isPresent()) {
1642 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1643 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1645 if (syncMessage
.getConfiguration().isPresent()) {
1652 private File
getContactAvatarFile(String number
) {
1653 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1656 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1657 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1658 if (attachment
.isPointer()) {
1659 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1660 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1662 SignalServiceAttachmentStream stream
= attachment
.asStream();
1663 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1667 private File
getGroupAvatarFile(byte[] groupId
) {
1668 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1671 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1672 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1673 if (attachment
.isPointer()) {
1674 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1675 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1677 SignalServiceAttachmentStream stream
= attachment
.asStream();
1678 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1682 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1683 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1686 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1687 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1688 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1691 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1692 if (storePreview
&& pointer
.getPreview().isPresent()) {
1693 File previewFile
= new File(outputFile
+ ".preview");
1694 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1695 byte[] preview
= pointer
.getPreview().get();
1696 output
.write(preview
, 0, preview
.length
);
1697 } catch (FileNotFoundException e
) {
1698 e
.printStackTrace();
1703 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1705 File tmpFile
= IOUtils
.createTempFile();
1706 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1707 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1708 byte[] buffer
= new byte[4096];
1711 while ((read
= input
.read(buffer
)) != -1) {
1712 output
.write(buffer
, 0, read
);
1714 } catch (FileNotFoundException e
) {
1715 e
.printStackTrace();
1720 Files
.delete(tmpFile
.toPath());
1721 } catch (IOException e
) {
1722 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1728 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1729 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1730 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1733 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1734 File groupsFile
= IOUtils
.createTempFile();
1737 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1738 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1739 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1740 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1741 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1742 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1743 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1747 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1748 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1749 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1750 .withStream(groupsFileStream
)
1751 .withContentType("application/octet-stream")
1752 .withLength(groupsFile
.length())
1755 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1760 Files
.delete(groupsFile
.toPath());
1761 } catch (IOException e
) {
1762 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1767 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1768 File contactsFile
= IOUtils
.createTempFile();
1771 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1772 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1773 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1774 VerifiedMessage verifiedMessage
= null;
1775 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1776 if (currentIdentity
!= null) {
1777 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1780 ProfileKey profileKey
= null;
1782 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1783 } catch (InvalidInputException ignored
) {
1785 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1786 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1787 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1788 Optional
.of(record.messageExpirationTime
),
1789 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1792 if (account
.getProfileKey() != null) {
1793 // Send our own profile key as well
1794 out
.write(new DeviceContact(account
.getSelfAddress(),
1795 Optional
.absent(), Optional
.absent(),
1796 Optional
.absent(), Optional
.absent(),
1797 Optional
.of(account
.getProfileKey()),
1798 false, Optional
.absent(), Optional
.absent(), false));
1802 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1803 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1804 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1805 .withStream(contactsFileStream
)
1806 .withContentType("application/octet-stream")
1807 .withLength(contactsFile
.length())
1810 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1815 Files
.delete(contactsFile
.toPath());
1816 } catch (IOException e
) {
1817 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1822 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1823 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1824 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1825 if (record.blocked
) {
1826 addresses
.add(record.getAddress());
1829 List
<byte[]> groupIds
= new ArrayList
<>();
1830 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1831 if (record.blocked
) {
1832 groupIds
.add(record.groupId
);
1835 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1838 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1839 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1840 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1843 public List
<ContactInfo
> getContacts() {
1844 return account
.getContactStore().getContacts();
1847 public ContactInfo
getContact(String number
) {
1848 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1851 public GroupInfo
getGroup(byte[] groupId
) {
1852 return account
.getGroupStore().getGroup(groupId
);
1855 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1856 return account
.getSignalProtocolStore().getIdentities();
1859 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1860 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1864 * Trust this the identity with this fingerprint
1866 * @param name username of the identity
1867 * @param fingerprint Fingerprint
1869 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1870 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1871 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1875 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1876 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1880 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1882 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1883 } catch (IOException
| UntrustedIdentityException e
) {
1884 e
.printStackTrace();
1893 * Trust this the identity with this safety number
1895 * @param name username of the identity
1896 * @param safetyNumber Safety number
1898 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1899 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1900 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1904 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1905 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1909 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1911 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1912 } catch (IOException
| UntrustedIdentityException e
) {
1913 e
.printStackTrace();
1922 * Trust all keys of this identity without verification
1924 * @param name username of the identity
1926 public boolean trustIdentityAllKeys(String name
) {
1927 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1928 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1932 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1933 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1934 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1936 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1937 } catch (IOException
| UntrustedIdentityException e
) {
1938 e
.printStackTrace();
1946 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1947 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1950 void saveAccount() {
1954 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1955 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1956 return resolveSignalServiceAddress(canonicalizedNumber
);
1959 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1960 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1962 return resolveSignalServiceAddress(address
);
1965 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
1966 if (address
.matches(account
.getSelfAddress())) {
1967 return account
.getSelfAddress();
1970 return account
.getRecipientStore().resolveServiceAddress(address
);
1974 public void close() throws IOException
{
1975 if (messagePipe
!= null) {
1976 messagePipe
.shutdown();
1980 if (unidentifiedMessagePipe
!= null) {
1981 unidentifiedMessagePipe
.shutdown();
1982 unidentifiedMessagePipe
= null;
1988 public interface ReceiveMessageHandler
{
1990 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);