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 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 void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1213 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1214 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1215 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1216 switch (groupInfo
.getType()) {
1218 if (group
== null) {
1219 group
= new GroupInfo(groupInfo
.getGroupId());
1222 if (groupInfo
.getAvatar().isPresent()) {
1223 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1224 if (avatar
.isPointer()) {
1226 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1227 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1228 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1233 if (groupInfo
.getName().isPresent()) {
1234 group
.name
= groupInfo
.getName().get();
1237 if (groupInfo
.getMembers().isPresent()) {
1238 group
.addMembers(groupInfo
.getMembers().get()
1240 .map(this::resolveSignalServiceAddress
)
1241 .collect(Collectors
.toSet()));
1244 account
.getGroupStore().updateGroup(group
);
1247 if (group
== null) {
1249 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1250 } catch (IOException
| EncapsulatedExceptions e
) {
1251 e
.printStackTrace();
1256 if (group
!= null) {
1257 group
.removeMember(source
);
1258 account
.getGroupStore().updateGroup(group
);
1262 if (group
!= null) {
1264 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1265 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1266 e
.printStackTrace();
1267 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1268 // We have left this group, so don't send a group update message
1274 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1275 if (message
.isEndSession()) {
1276 handleEndSession(conversationPartnerAddress
);
1278 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1279 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1280 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1281 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1282 if (group
== null) {
1283 group
= new GroupInfo(groupInfo
.getGroupId());
1285 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1286 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1287 account
.getGroupStore().updateGroup(group
);
1290 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1291 if (contact
== null) {
1292 contact
= new ContactInfo(conversationPartnerAddress
);
1294 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1295 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1296 account
.getContactStore().updateContact(contact
);
1300 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1301 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1302 if (attachment
.isPointer()) {
1304 retrieveAttachment(attachment
.asPointer());
1305 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1306 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1311 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1312 if (source
.matches(account
.getSelfAddress())) {
1314 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1315 } catch (InvalidInputException ignored
) {
1317 ContactInfo contact
= account
.getContactStore().getContact(source
);
1318 if (contact
!= null) {
1319 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1320 account
.getContactStore().updateContact(contact
);
1323 ContactInfo contact
= account
.getContactStore().getContact(source
);
1324 if (contact
== null) {
1325 contact
= new ContactInfo(source
);
1327 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1328 account
.getContactStore().updateContact(contact
);
1331 if (message
.getPreviews().isPresent()) {
1332 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1333 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1334 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1335 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1337 retrieveAttachment(attachment
);
1338 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1339 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1346 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1347 final File cachePath
= new File(getMessageCachePath());
1348 if (!cachePath
.exists()) {
1351 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1352 if (!dir
.isDirectory()) {
1353 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1357 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1358 if (!fileEntry
.isFile()) {
1361 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1363 // Try to delete directory if empty
1368 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1369 SignalServiceEnvelope envelope
;
1371 envelope
= Utils
.loadEnvelope(fileEntry
);
1372 if (envelope
== null) {
1375 } catch (IOException e
) {
1376 e
.printStackTrace();
1379 SignalServiceContent content
= null;
1380 if (!envelope
.isReceipt()) {
1382 content
= decryptMessage(envelope
);
1383 } catch (Exception e
) {
1386 handleMessage(envelope
, content
, ignoreAttachments
);
1389 handler
.handleMessage(envelope
, content
, null);
1391 Files
.delete(fileEntry
.toPath());
1392 } catch (IOException e
) {
1393 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1397 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1398 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1399 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1401 if (messagePipe
== null) {
1402 messagePipe
= messageReceiver
.createMessagePipe();
1406 SignalServiceEnvelope envelope
;
1407 SignalServiceContent content
= null;
1408 Exception exception
= null;
1409 final long now
= new Date().getTime();
1411 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1412 // store message on disk, before acknowledging receipt to the server
1414 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1415 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1416 Utils
.storeEnvelope(envelope1
, cacheFile
);
1417 } catch (IOException e
) {
1418 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1421 } catch (TimeoutException e
) {
1422 if (returnOnTimeout
)
1425 } catch (InvalidVersionException e
) {
1426 System
.err
.println("Ignoring error: " + e
.getMessage());
1429 if (envelope
.hasSource()) {
1430 // Store uuid if we don't have it already
1431 SignalServiceAddress source
= envelope
.getSourceAddress();
1432 resolveSignalServiceAddress(source
);
1434 if (!envelope
.isReceipt()) {
1436 content
= decryptMessage(envelope
);
1437 } catch (Exception e
) {
1440 handleMessage(envelope
, content
, ignoreAttachments
);
1443 if (!isMessageBlocked(envelope
, content
)) {
1444 handler
.handleMessage(envelope
, content
, exception
);
1446 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1447 File cacheFile
= null;
1449 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1450 Files
.delete(cacheFile
.toPath());
1451 // Try to delete directory if empty
1452 new File(getMessageCachePath()).delete();
1453 } catch (IOException e
) {
1454 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1460 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1461 SignalServiceAddress source
;
1462 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1463 source
= envelope
.getSourceAddress();
1464 } else if (content
!= null) {
1465 source
= content
.getSender();
1469 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1470 if (sourceContact
!= null && sourceContact
.blocked
) {
1474 if (content
!= null && content
.getDataMessage().isPresent()) {
1475 SignalServiceDataMessage message
= content
.getDataMessage().get();
1476 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1477 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1478 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1479 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1487 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1488 if (content
!= null) {
1489 SignalServiceAddress sender
;
1490 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1491 sender
= envelope
.getSourceAddress();
1493 sender
= content
.getSender();
1495 // Store uuid if we don't have it already
1496 resolveSignalServiceAddress(sender
);
1498 if (content
.getDataMessage().isPresent()) {
1499 SignalServiceDataMessage message
= content
.getDataMessage().get();
1501 if (content
.isNeedsReceipt()) {
1503 sendReceipt(sender
, message
.getTimestamp());
1504 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1505 e
.printStackTrace();
1509 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1511 if (content
.getSyncMessage().isPresent()) {
1512 account
.setMultiDevice(true);
1513 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1514 if (syncMessage
.getSent().isPresent()) {
1515 SentTranscriptMessage message
= syncMessage
.getSent().get();
1516 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1518 if (syncMessage
.getRequest().isPresent()) {
1519 RequestMessage rm
= syncMessage
.getRequest().get();
1520 if (rm
.isContactsRequest()) {
1523 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1524 e
.printStackTrace();
1527 if (rm
.isGroupsRequest()) {
1530 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1531 e
.printStackTrace();
1534 if (rm
.isBlockedListRequest()) {
1537 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1538 e
.printStackTrace();
1541 // TODO Handle rm.isConfigurationRequest();
1543 if (syncMessage
.getGroups().isPresent()) {
1544 File tmpFile
= null;
1546 tmpFile
= IOUtils
.createTempFile();
1547 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1548 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1550 while ((g
= s
.read()) != null) {
1551 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1552 if (syncGroup
== null) {
1553 syncGroup
= new GroupInfo(g
.getId());
1555 if (g
.getName().isPresent()) {
1556 syncGroup
.name
= g
.getName().get();
1558 syncGroup
.addMembers(g
.getMembers()
1560 .map(this::resolveSignalServiceAddress
)
1561 .collect(Collectors
.toSet()));
1562 if (!g
.isActive()) {
1563 syncGroup
.removeMember(account
.getSelfAddress());
1565 // Add ourself to the member set as it's marked as active
1566 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1568 syncGroup
.blocked
= g
.isBlocked();
1569 if (g
.getColor().isPresent()) {
1570 syncGroup
.color
= g
.getColor().get();
1573 if (g
.getAvatar().isPresent()) {
1574 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1576 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1577 syncGroup
.archived
= g
.isArchived();
1578 account
.getGroupStore().updateGroup(syncGroup
);
1581 } catch (Exception e
) {
1582 e
.printStackTrace();
1584 if (tmpFile
!= null) {
1586 Files
.delete(tmpFile
.toPath());
1587 } catch (IOException e
) {
1588 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1593 if (syncMessage
.getBlockedList().isPresent()) {
1594 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1595 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1596 setContactBlocked(resolveSignalServiceAddress(address
), true);
1598 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1600 setGroupBlocked(groupId
, true);
1601 } catch (GroupNotFoundException e
) {
1602 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1606 if (syncMessage
.getContacts().isPresent()) {
1607 File tmpFile
= null;
1609 tmpFile
= IOUtils
.createTempFile();
1610 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1611 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1612 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1613 if (contactsMessage
.isComplete()) {
1614 account
.getContactStore().clear();
1617 while ((c
= s
.read()) != null) {
1618 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1619 account
.setProfileKey(c
.getProfileKey().get());
1621 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1622 ContactInfo contact
= account
.getContactStore().getContact(address
);
1623 if (contact
== null) {
1624 contact
= new ContactInfo(address
);
1626 if (c
.getName().isPresent()) {
1627 contact
.name
= c
.getName().get();
1629 if (c
.getColor().isPresent()) {
1630 contact
.color
= c
.getColor().get();
1632 if (c
.getProfileKey().isPresent()) {
1633 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1635 if (c
.getVerified().isPresent()) {
1636 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1637 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1639 if (c
.getExpirationTimer().isPresent()) {
1640 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1642 contact
.blocked
= c
.isBlocked();
1643 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1644 contact
.archived
= c
.isArchived();
1645 account
.getContactStore().updateContact(contact
);
1647 if (c
.getAvatar().isPresent()) {
1648 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1652 } catch (Exception e
) {
1653 e
.printStackTrace();
1655 if (tmpFile
!= null) {
1657 Files
.delete(tmpFile
.toPath());
1658 } catch (IOException e
) {
1659 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1664 if (syncMessage
.getVerified().isPresent()) {
1665 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1666 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1668 if (syncMessage
.getConfiguration().isPresent()) {
1675 private File
getContactAvatarFile(String number
) {
1676 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1679 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1680 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1681 if (attachment
.isPointer()) {
1682 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1683 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1685 SignalServiceAttachmentStream stream
= attachment
.asStream();
1686 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1690 private File
getGroupAvatarFile(byte[] groupId
) {
1691 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1694 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1695 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1696 if (attachment
.isPointer()) {
1697 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1698 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1700 SignalServiceAttachmentStream stream
= attachment
.asStream();
1701 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1705 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1706 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1709 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1710 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1711 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1714 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1715 if (storePreview
&& pointer
.getPreview().isPresent()) {
1716 File previewFile
= new File(outputFile
+ ".preview");
1717 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1718 byte[] preview
= pointer
.getPreview().get();
1719 output
.write(preview
, 0, preview
.length
);
1720 } catch (FileNotFoundException e
) {
1721 e
.printStackTrace();
1726 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1728 File tmpFile
= IOUtils
.createTempFile();
1729 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1730 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1731 byte[] buffer
= new byte[4096];
1734 while ((read
= input
.read(buffer
)) != -1) {
1735 output
.write(buffer
, 0, read
);
1737 } catch (FileNotFoundException e
) {
1738 e
.printStackTrace();
1743 Files
.delete(tmpFile
.toPath());
1744 } catch (IOException e
) {
1745 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1751 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1752 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1753 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1756 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1757 File groupsFile
= IOUtils
.createTempFile();
1760 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1761 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1762 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1763 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1764 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1765 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1766 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1770 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1771 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1772 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1773 .withStream(groupsFileStream
)
1774 .withContentType("application/octet-stream")
1775 .withLength(groupsFile
.length())
1778 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1783 Files
.delete(groupsFile
.toPath());
1784 } catch (IOException e
) {
1785 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1790 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1791 File contactsFile
= IOUtils
.createTempFile();
1794 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1795 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1796 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1797 VerifiedMessage verifiedMessage
= null;
1798 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1799 if (currentIdentity
!= null) {
1800 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1803 ProfileKey profileKey
= null;
1805 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1806 } catch (InvalidInputException ignored
) {
1808 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1809 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1810 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1811 Optional
.of(record.messageExpirationTime
),
1812 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1815 if (account
.getProfileKey() != null) {
1816 // Send our own profile key as well
1817 out
.write(new DeviceContact(account
.getSelfAddress(),
1818 Optional
.absent(), Optional
.absent(),
1819 Optional
.absent(), Optional
.absent(),
1820 Optional
.of(account
.getProfileKey()),
1821 false, Optional
.absent(), Optional
.absent(), false));
1825 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1826 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1827 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1828 .withStream(contactsFileStream
)
1829 .withContentType("application/octet-stream")
1830 .withLength(contactsFile
.length())
1833 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1838 Files
.delete(contactsFile
.toPath());
1839 } catch (IOException e
) {
1840 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1845 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1846 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1847 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1848 if (record.blocked
) {
1849 addresses
.add(record.getAddress());
1852 List
<byte[]> groupIds
= new ArrayList
<>();
1853 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1854 if (record.blocked
) {
1855 groupIds
.add(record.groupId
);
1858 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1861 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1862 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1863 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1866 public List
<ContactInfo
> getContacts() {
1867 return account
.getContactStore().getContacts();
1870 public ContactInfo
getContact(String number
) {
1871 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1874 public GroupInfo
getGroup(byte[] groupId
) {
1875 return account
.getGroupStore().getGroup(groupId
);
1878 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1879 return account
.getSignalProtocolStore().getIdentities();
1882 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1883 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1887 * Trust this the identity with this fingerprint
1889 * @param name username of the identity
1890 * @param fingerprint Fingerprint
1892 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1893 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1894 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1898 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1899 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1903 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1905 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1906 } catch (IOException
| UntrustedIdentityException e
) {
1907 e
.printStackTrace();
1916 * Trust this the identity with this safety number
1918 * @param name username of the identity
1919 * @param safetyNumber Safety number
1921 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1922 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1923 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1927 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1928 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1932 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1934 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1935 } catch (IOException
| UntrustedIdentityException e
) {
1936 e
.printStackTrace();
1945 * Trust all keys of this identity without verification
1947 * @param name username of the identity
1949 public boolean trustIdentityAllKeys(String name
) {
1950 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1951 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1955 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1956 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1957 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1959 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1960 } catch (IOException
| UntrustedIdentityException e
) {
1961 e
.printStackTrace();
1969 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1970 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1973 void saveAccount() {
1977 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1978 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1979 return resolveSignalServiceAddress(canonicalizedNumber
);
1982 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1983 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1985 return resolveSignalServiceAddress(address
);
1988 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
1989 if (address
.matches(account
.getSelfAddress())) {
1990 return account
.getSelfAddress();
1993 return account
.getRecipientStore().resolveServiceAddress(address
);
1997 public void close() throws IOException
{
1998 if (messagePipe
!= null) {
1999 messagePipe
.shutdown();
2003 if (unidentifiedMessagePipe
!= null) {
2004 unidentifiedMessagePipe
.shutdown();
2005 unidentifiedMessagePipe
= null;
2011 public interface ReceiveMessageHandler
{
2013 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);