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
) {
762 ContactInfo c
= account
.getContactStore().getContact(address
);
763 c
.messageExpirationTime
= messageExpirationTimer
;
764 account
.getContactStore().updateContact(c
);
768 * Change the expiration timer for a group
770 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
771 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
772 g
.messageExpirationTime
= messageExpirationTimer
;
773 account
.getGroupStore().updateGroup(g
);
777 * Upload the sticker pack from path.
779 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
780 * @return if successful, returns the URL to install the sticker pack in the signal app
782 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
783 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
785 SignalServiceMessageSender messageSender
= getMessageSender();
787 byte[] packKey
= KeyUtils
.createStickerUploadKey();
788 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
791 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
793 } catch (URISyntaxException e
) {
794 throw new AssertionError(e
);
798 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
800 String rootPath
= null;
802 final File file
= new File(path
);
803 if (file
.getName().endsWith(".zip")) {
804 zip
= new ZipFile(file
);
805 } else if (file
.getName().equals("manifest.json")) {
806 rootPath
= file
.getParent();
808 throw new StickerPackInvalidException("Could not find manifest.json");
811 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
813 if (pack
.stickers
== null) {
814 throw new StickerPackInvalidException("Must set a 'stickers' field.");
817 if (pack
.stickers
.isEmpty()) {
818 throw new StickerPackInvalidException("Must include stickers.");
821 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
822 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
823 if (sticker
.file
== null) {
824 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
827 Pair
<InputStream
, Long
> data
;
829 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
830 } catch (IOException ignored
) {
831 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
834 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
835 stickers
.add(stickerInfo
);
838 StickerInfo cover
= null;
839 if (pack
.cover
!= null) {
840 if (pack
.cover
.file
== null) {
841 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
844 Pair
<InputStream
, Long
> data
;
846 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
847 } catch (IOException ignored
) {
848 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
851 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
854 return new SignalServiceStickerManifestUpload(
861 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
862 InputStream inputStream
;
864 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
866 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
868 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
871 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
873 final ZipEntry entry
= zip
.getEntry(subfile
);
874 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
876 final File file
= new File(rootPath
, subfile
);
877 return new Pair
<>(new FileInputStream(file
), file
.length());
881 void requestSyncGroups() throws IOException
{
882 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
883 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
885 sendSyncMessage(message
);
886 } catch (UntrustedIdentityException e
) {
891 void requestSyncContacts() throws IOException
{
892 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
893 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
895 sendSyncMessage(message
);
896 } catch (UntrustedIdentityException e
) {
901 void requestSyncBlocked() throws IOException
{
902 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
903 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
905 sendSyncMessage(message
);
906 } catch (UntrustedIdentityException e
) {
911 void requestSyncConfiguration() throws IOException
{
912 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
913 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
915 sendSyncMessage(message
);
916 } catch (UntrustedIdentityException e
) {
921 private byte[] getSenderCertificate() {
922 // TODO support UUID capable sender certificates
923 // byte[] certificate = accountManager.getSenderCertificate();
926 certificate
= accountManager
.getSenderCertificateLegacy();
927 } catch (IOException e
) {
928 System
.err
.println("Failed to get sender certificate: " + e
);
931 // TODO cache for a day
935 private byte[] getSelfUnidentifiedAccessKey() {
936 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
939 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
940 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
942 return new SignalProfile(
943 encryptedProfile
.getIdentityKey(),
944 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
945 encryptedProfile
.getAvatar(),
946 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
947 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
949 } catch (InvalidCiphertextException e
) {
954 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
955 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
956 if (contact
== null || contact
.profileKey
== null) {
959 ProfileKey theirProfileKey
;
961 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
962 } catch (InvalidInputException
| IOException e
) {
963 throw new AssertionError(e
);
965 SignalProfile targetProfile
;
967 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
968 } catch (IOException e
) {
969 System
.err
.println("Failed to get recipient profile: " + e
);
973 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
977 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
978 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
981 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
984 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
985 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
986 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
988 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
989 return Optional
.absent();
993 return Optional
.of(new UnidentifiedAccessPair(
994 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
995 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
997 } catch (InvalidCertificateException e
) {
998 return Optional
.absent();
1002 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1003 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1004 for (SignalServiceAddress recipient
: recipients
) {
1005 result
.add(getAccessFor(recipient
));
1010 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1011 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1012 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1013 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1015 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1016 return Optional
.absent();
1020 return Optional
.of(new UnidentifiedAccessPair(
1021 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1022 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1024 } catch (InvalidCertificateException e
) {
1025 return Optional
.absent();
1029 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1030 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1032 if (unidentifiedAccess
.isPresent()) {
1033 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1036 return Optional
.absent();
1039 private void sendSyncMessage(SignalServiceSyncMessage message
)
1040 throws IOException
, UntrustedIdentityException
{
1041 SignalServiceMessageSender messageSender
= getMessageSender();
1043 messageSender
.sendMessage(message
, getAccessForSync());
1044 } catch (UntrustedIdentityException e
) {
1045 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1051 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1053 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1054 throws EncapsulatedExceptions
, IOException
{
1055 final long timestamp
= System
.currentTimeMillis();
1056 messageBuilder
.withTimestamp(timestamp
);
1057 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1059 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1060 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1061 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1063 for (SendMessageResult result
: results
) {
1064 if (result
.isUnregisteredFailure()) {
1065 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1066 } else if (result
.isNetworkFailure()) {
1067 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1068 } else if (result
.getIdentityFailure() != null) {
1069 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1072 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1073 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1078 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1079 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1081 for (String number
: numbers
) {
1082 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1084 return signalServiceAddresses
;
1087 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1088 throws IOException
{
1089 if (messagePipe
== null) {
1090 messagePipe
= getMessageReceiver().createMessagePipe();
1092 if (unidentifiedMessagePipe
== null) {
1093 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1095 SignalServiceDataMessage message
= null;
1097 SignalServiceMessageSender messageSender
= getMessageSender();
1099 message
= messageBuilder
.build();
1100 if (message
.getGroupContext().isPresent()) {
1102 final boolean isRecipientUpdate
= false;
1103 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1104 for (SendMessageResult r
: result
) {
1105 if (r
.getIdentityFailure() != null) {
1106 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1110 } catch (UntrustedIdentityException e
) {
1111 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1112 return Collections
.emptyList();
1114 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1115 SignalServiceAddress recipient
= account
.getSelfAddress();
1116 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1117 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1118 message
.getTimestamp(),
1120 message
.getExpiresInSeconds(),
1121 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1123 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1125 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1127 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1128 } catch (UntrustedIdentityException e
) {
1129 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1130 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1134 // Send to all individually, so sync messages are sent correctly
1135 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1136 for (SignalServiceAddress address
: recipients
) {
1137 ContactInfo contact
= account
.getContactStore().getContact(address
);
1138 if (contact
!= null) {
1139 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1140 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1142 messageBuilder
.withExpiration(0);
1143 messageBuilder
.withProfileKey(null);
1145 message
= messageBuilder
.build();
1147 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1148 results
.add(result
);
1149 } catch (UntrustedIdentityException e
) {
1150 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1151 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1157 if (message
!= null && message
.isEndSession()) {
1158 for (SignalServiceAddress recipient
: recipients
) {
1159 handleEndSession(recipient
);
1166 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1167 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1169 return cipher
.decrypt(envelope
);
1170 } catch (ProtocolUntrustedIdentityException e
) {
1171 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1172 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1173 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1174 throw identityException
;
1176 throw new AssertionError(e
);
1180 private void handleEndSession(SignalServiceAddress source
) {
1181 account
.getSignalProtocolStore().deleteAllSessions(source
);
1184 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1185 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1186 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1187 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1188 switch (groupInfo
.getType()) {
1190 if (group
== null) {
1191 group
= new GroupInfo(groupInfo
.getGroupId());
1194 if (groupInfo
.getAvatar().isPresent()) {
1195 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1196 if (avatar
.isPointer()) {
1198 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1199 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1200 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1205 if (groupInfo
.getName().isPresent()) {
1206 group
.name
= groupInfo
.getName().get();
1209 if (groupInfo
.getMembers().isPresent()) {
1210 group
.addMembers(groupInfo
.getMembers().get()
1212 .map(this::resolveSignalServiceAddress
)
1213 .collect(Collectors
.toSet()));
1216 account
.getGroupStore().updateGroup(group
);
1219 if (group
== null) {
1221 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1222 } catch (IOException
| EncapsulatedExceptions e
) {
1223 e
.printStackTrace();
1228 if (group
!= null) {
1229 group
.removeMember(source
);
1230 account
.getGroupStore().updateGroup(group
);
1234 if (group
!= null) {
1236 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1237 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1238 e
.printStackTrace();
1239 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1240 // We have left this group, so don't send a group update message
1246 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1247 if (message
.isEndSession()) {
1248 handleEndSession(conversationPartnerAddress
);
1250 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1251 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1252 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1253 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1254 if (group
== null) {
1255 group
= new GroupInfo(groupInfo
.getGroupId());
1257 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1258 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1259 account
.getGroupStore().updateGroup(group
);
1262 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1263 if (contact
== null) {
1264 contact
= new ContactInfo(conversationPartnerAddress
);
1266 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1267 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1268 account
.getContactStore().updateContact(contact
);
1272 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1273 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1274 if (attachment
.isPointer()) {
1276 retrieveAttachment(attachment
.asPointer());
1277 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1278 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1283 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1284 if (source
.matches(account
.getSelfAddress())) {
1286 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1287 } catch (InvalidInputException ignored
) {
1289 ContactInfo contact
= account
.getContactStore().getContact(source
);
1290 if (contact
!= null) {
1291 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1292 account
.getContactStore().updateContact(contact
);
1295 ContactInfo contact
= account
.getContactStore().getContact(source
);
1296 if (contact
== null) {
1297 contact
= new ContactInfo(source
);
1299 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1300 account
.getContactStore().updateContact(contact
);
1303 if (message
.getPreviews().isPresent()) {
1304 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1305 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1306 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1307 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1309 retrieveAttachment(attachment
);
1310 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1311 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1318 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1319 final File cachePath
= new File(getMessageCachePath());
1320 if (!cachePath
.exists()) {
1323 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1324 if (!dir
.isDirectory()) {
1325 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1329 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1330 if (!fileEntry
.isFile()) {
1333 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1335 // Try to delete directory if empty
1340 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1341 SignalServiceEnvelope envelope
;
1343 envelope
= Utils
.loadEnvelope(fileEntry
);
1344 if (envelope
== null) {
1347 } catch (IOException e
) {
1348 e
.printStackTrace();
1351 SignalServiceContent content
= null;
1352 if (!envelope
.isReceipt()) {
1354 content
= decryptMessage(envelope
);
1355 } catch (Exception e
) {
1358 handleMessage(envelope
, content
, ignoreAttachments
);
1361 handler
.handleMessage(envelope
, content
, null);
1363 Files
.delete(fileEntry
.toPath());
1364 } catch (IOException e
) {
1365 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1369 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1370 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1371 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1373 if (messagePipe
== null) {
1374 messagePipe
= messageReceiver
.createMessagePipe();
1378 SignalServiceEnvelope envelope
;
1379 SignalServiceContent content
= null;
1380 Exception exception
= null;
1381 final long now
= new Date().getTime();
1383 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1384 // store message on disk, before acknowledging receipt to the server
1386 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1387 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1388 Utils
.storeEnvelope(envelope1
, cacheFile
);
1389 } catch (IOException e
) {
1390 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1393 } catch (TimeoutException e
) {
1394 if (returnOnTimeout
)
1397 } catch (InvalidVersionException e
) {
1398 System
.err
.println("Ignoring error: " + e
.getMessage());
1401 if (!envelope
.isReceipt()) {
1403 content
= decryptMessage(envelope
);
1404 } catch (Exception e
) {
1407 handleMessage(envelope
, content
, ignoreAttachments
);
1410 if (!isMessageBlocked(envelope
, content
)) {
1411 handler
.handleMessage(envelope
, content
, exception
);
1413 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1414 File cacheFile
= null;
1416 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1417 Files
.delete(cacheFile
.toPath());
1418 // Try to delete directory if empty
1419 new File(getMessageCachePath()).delete();
1420 } catch (IOException e
) {
1421 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1427 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1428 SignalServiceAddress source
;
1429 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1430 source
= envelope
.getSourceAddress();
1431 } else if (content
!= null) {
1432 source
= content
.getSender();
1436 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1437 if (sourceContact
!= null && sourceContact
.blocked
) {
1441 if (content
!= null && content
.getDataMessage().isPresent()) {
1442 SignalServiceDataMessage message
= content
.getDataMessage().get();
1443 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1444 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1445 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1446 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1454 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1455 if (content
!= null) {
1456 SignalServiceAddress sender
;
1457 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1458 sender
= envelope
.getSourceAddress();
1460 sender
= content
.getSender();
1462 if (content
.getDataMessage().isPresent()) {
1463 SignalServiceDataMessage message
= content
.getDataMessage().get();
1465 if (content
.isNeedsReceipt()) {
1467 sendReceipt(sender
, message
.getTimestamp());
1468 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1469 e
.printStackTrace();
1473 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1475 if (content
.getSyncMessage().isPresent()) {
1476 account
.setMultiDevice(true);
1477 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1478 if (syncMessage
.getSent().isPresent()) {
1479 SentTranscriptMessage message
= syncMessage
.getSent().get();
1480 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1482 if (syncMessage
.getRequest().isPresent()) {
1483 RequestMessage rm
= syncMessage
.getRequest().get();
1484 if (rm
.isContactsRequest()) {
1487 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1488 e
.printStackTrace();
1491 if (rm
.isGroupsRequest()) {
1494 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1495 e
.printStackTrace();
1498 if (rm
.isBlockedListRequest()) {
1501 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1502 e
.printStackTrace();
1505 // TODO Handle rm.isConfigurationRequest();
1507 if (syncMessage
.getGroups().isPresent()) {
1508 File tmpFile
= null;
1510 tmpFile
= IOUtils
.createTempFile();
1511 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1512 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1514 while ((g
= s
.read()) != null) {
1515 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1516 if (syncGroup
== null) {
1517 syncGroup
= new GroupInfo(g
.getId());
1519 if (g
.getName().isPresent()) {
1520 syncGroup
.name
= g
.getName().get();
1522 syncGroup
.addMembers(g
.getMembers()
1524 .map(this::resolveSignalServiceAddress
)
1525 .collect(Collectors
.toSet()));
1526 if (!g
.isActive()) {
1527 syncGroup
.removeMember(account
.getSelfAddress());
1529 // Add ourself to the member set as it's marked as active
1530 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1532 syncGroup
.blocked
= g
.isBlocked();
1533 if (g
.getColor().isPresent()) {
1534 syncGroup
.color
= g
.getColor().get();
1537 if (g
.getAvatar().isPresent()) {
1538 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1540 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1541 syncGroup
.archived
= g
.isArchived();
1542 account
.getGroupStore().updateGroup(syncGroup
);
1545 } catch (Exception e
) {
1546 e
.printStackTrace();
1548 if (tmpFile
!= null) {
1550 Files
.delete(tmpFile
.toPath());
1551 } catch (IOException e
) {
1552 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1557 if (syncMessage
.getBlockedList().isPresent()) {
1558 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1559 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1560 setContactBlocked(resolveSignalServiceAddress(address
), true);
1562 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1564 setGroupBlocked(groupId
, true);
1565 } catch (GroupNotFoundException e
) {
1566 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1570 if (syncMessage
.getContacts().isPresent()) {
1571 File tmpFile
= null;
1573 tmpFile
= IOUtils
.createTempFile();
1574 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1575 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1576 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1577 if (contactsMessage
.isComplete()) {
1578 account
.getContactStore().clear();
1581 while ((c
= s
.read()) != null) {
1582 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1583 account
.setProfileKey(c
.getProfileKey().get());
1585 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1586 ContactInfo contact
= account
.getContactStore().getContact(address
);
1587 if (contact
== null) {
1588 contact
= new ContactInfo(address
);
1590 if (c
.getName().isPresent()) {
1591 contact
.name
= c
.getName().get();
1593 if (c
.getColor().isPresent()) {
1594 contact
.color
= c
.getColor().get();
1596 if (c
.getProfileKey().isPresent()) {
1597 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1599 if (c
.getVerified().isPresent()) {
1600 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1601 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1603 if (c
.getExpirationTimer().isPresent()) {
1604 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1606 contact
.blocked
= c
.isBlocked();
1607 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1608 contact
.archived
= c
.isArchived();
1609 account
.getContactStore().updateContact(contact
);
1611 if (c
.getAvatar().isPresent()) {
1612 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1616 } catch (Exception e
) {
1617 e
.printStackTrace();
1619 if (tmpFile
!= null) {
1621 Files
.delete(tmpFile
.toPath());
1622 } catch (IOException e
) {
1623 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1628 if (syncMessage
.getVerified().isPresent()) {
1629 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1630 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1632 if (syncMessage
.getConfiguration().isPresent()) {
1639 private File
getContactAvatarFile(String number
) {
1640 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1643 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1644 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1645 if (attachment
.isPointer()) {
1646 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1647 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1649 SignalServiceAttachmentStream stream
= attachment
.asStream();
1650 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1654 private File
getGroupAvatarFile(byte[] groupId
) {
1655 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1658 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1659 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1660 if (attachment
.isPointer()) {
1661 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1662 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1664 SignalServiceAttachmentStream stream
= attachment
.asStream();
1665 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1669 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1670 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1673 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1674 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1675 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1678 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1679 if (storePreview
&& pointer
.getPreview().isPresent()) {
1680 File previewFile
= new File(outputFile
+ ".preview");
1681 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1682 byte[] preview
= pointer
.getPreview().get();
1683 output
.write(preview
, 0, preview
.length
);
1684 } catch (FileNotFoundException e
) {
1685 e
.printStackTrace();
1690 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1692 File tmpFile
= IOUtils
.createTempFile();
1693 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1694 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1695 byte[] buffer
= new byte[4096];
1698 while ((read
= input
.read(buffer
)) != -1) {
1699 output
.write(buffer
, 0, read
);
1701 } catch (FileNotFoundException e
) {
1702 e
.printStackTrace();
1707 Files
.delete(tmpFile
.toPath());
1708 } catch (IOException e
) {
1709 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1715 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1716 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1717 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1720 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1721 File groupsFile
= IOUtils
.createTempFile();
1724 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1725 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1726 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1727 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1728 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1729 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1730 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1734 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1735 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1736 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1737 .withStream(groupsFileStream
)
1738 .withContentType("application/octet-stream")
1739 .withLength(groupsFile
.length())
1742 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1747 Files
.delete(groupsFile
.toPath());
1748 } catch (IOException e
) {
1749 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1754 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1755 File contactsFile
= IOUtils
.createTempFile();
1758 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1759 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1760 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1761 VerifiedMessage verifiedMessage
= null;
1762 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1763 if (currentIdentity
!= null) {
1764 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1767 ProfileKey profileKey
= null;
1769 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1770 } catch (InvalidInputException ignored
) {
1772 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1773 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1774 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1775 Optional
.of(record.messageExpirationTime
),
1776 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1779 if (account
.getProfileKey() != null) {
1780 // Send our own profile key as well
1781 out
.write(new DeviceContact(account
.getSelfAddress(),
1782 Optional
.absent(), Optional
.absent(),
1783 Optional
.absent(), Optional
.absent(),
1784 Optional
.of(account
.getProfileKey()),
1785 false, Optional
.absent(), Optional
.absent(), false));
1789 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1790 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1791 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1792 .withStream(contactsFileStream
)
1793 .withContentType("application/octet-stream")
1794 .withLength(contactsFile
.length())
1797 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1802 Files
.delete(contactsFile
.toPath());
1803 } catch (IOException e
) {
1804 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1809 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1810 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1811 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1812 if (record.blocked
) {
1813 addresses
.add(record.getAddress());
1816 List
<byte[]> groupIds
= new ArrayList
<>();
1817 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1818 if (record.blocked
) {
1819 groupIds
.add(record.groupId
);
1822 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1825 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1826 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1827 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1830 public List
<ContactInfo
> getContacts() {
1831 return account
.getContactStore().getContacts();
1834 public ContactInfo
getContact(String number
) {
1835 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1838 public GroupInfo
getGroup(byte[] groupId
) {
1839 return account
.getGroupStore().getGroup(groupId
);
1842 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1843 return account
.getSignalProtocolStore().getIdentities();
1846 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1847 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1851 * Trust this the identity with this fingerprint
1853 * @param name username of the identity
1854 * @param fingerprint Fingerprint
1856 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1857 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1858 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1862 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1863 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1867 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1869 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1870 } catch (IOException
| UntrustedIdentityException e
) {
1871 e
.printStackTrace();
1880 * Trust this the identity with this safety number
1882 * @param name username of the identity
1883 * @param safetyNumber Safety number
1885 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1886 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1887 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1891 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1892 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1896 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1898 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1899 } catch (IOException
| UntrustedIdentityException e
) {
1900 e
.printStackTrace();
1909 * Trust all keys of this identity without verification
1911 * @param name username of the identity
1913 public boolean trustIdentityAllKeys(String name
) {
1914 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1915 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1919 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1920 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1921 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1923 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1924 } catch (IOException
| UntrustedIdentityException e
) {
1925 e
.printStackTrace();
1933 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1934 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1937 void saveAccount() {
1941 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1942 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1943 return resolveSignalServiceAddress(canonicalizedNumber
);
1946 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1947 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1949 return resolveSignalServiceAddress(address
);
1952 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
1953 if (address
.matches(account
.getSelfAddress())) {
1954 return account
.getSelfAddress();
1957 return account
.getRecipientStore().resolveServiceAddress(address
);
1961 public void close() throws IOException
{
1962 if (messagePipe
!= null) {
1963 messagePipe
.shutdown();
1967 if (unidentifiedMessagePipe
!= null) {
1968 unidentifiedMessagePipe
.shutdown();
1969 unidentifiedMessagePipe
= null;
1975 public interface ReceiveMessageHandler
{
1977 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);