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
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.NotAGroupMemberException
;
25 import org
.asamk
.signal
.StickerPackInvalidException
;
26 import org
.asamk
.signal
.TrustLevel
;
27 import org
.asamk
.signal
.storage
.SignalAccount
;
28 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
29 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
30 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
31 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
32 import org
.asamk
.signal
.util
.IOUtils
;
33 import org
.asamk
.signal
.util
.Util
;
34 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
44 import org
.signal
.libsignal
.metadata
.SelfSendException
;
45 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
46 import org
.signal
.zkgroup
.InvalidInputException
;
47 import org
.signal
.zkgroup
.VerificationFailedException
;
48 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
49 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
50 import org
.whispersystems
.libsignal
.IdentityKey
;
51 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
52 import org
.whispersystems
.libsignal
.InvalidKeyException
;
53 import org
.whispersystems
.libsignal
.InvalidMessageException
;
54 import org
.whispersystems
.libsignal
.InvalidVersionException
;
55 import org
.whispersystems
.libsignal
.ecc
.Curve
;
56 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
57 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
58 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
59 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
60 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
61 import org
.whispersystems
.libsignal
.util
.Medium
;
62 import org
.whispersystems
.libsignal
.util
.Pair
;
63 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
64 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
99 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
100 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
101 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
105 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
106 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
107 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
108 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
109 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
110 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
111 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
112 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
113 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
114 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
115 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
116 import org
.whispersystems
.util
.Base64
;
118 import java
.io
.Closeable
;
120 import java
.io
.FileInputStream
;
121 import java
.io
.FileNotFoundException
;
122 import java
.io
.FileOutputStream
;
123 import java
.io
.IOException
;
124 import java
.io
.InputStream
;
125 import java
.io
.OutputStream
;
127 import java
.net
.URISyntaxException
;
128 import java
.net
.URLEncoder
;
129 import java
.nio
.file
.Files
;
130 import java
.nio
.file
.Paths
;
131 import java
.nio
.file
.StandardCopyOption
;
132 import java
.util
.ArrayList
;
133 import java
.util
.Arrays
;
134 import java
.util
.Collection
;
135 import java
.util
.Collections
;
136 import java
.util
.Date
;
137 import java
.util
.HashSet
;
138 import java
.util
.LinkedList
;
139 import java
.util
.List
;
140 import java
.util
.Locale
;
141 import java
.util
.Objects
;
142 import java
.util
.Set
;
143 import java
.util
.UUID
;
144 import java
.util
.concurrent
.TimeUnit
;
145 import java
.util
.concurrent
.TimeoutException
;
146 import java
.util
.stream
.Collectors
;
147 import java
.util
.zip
.ZipEntry
;
148 import java
.util
.zip
.ZipFile
;
150 public class Manager
implements Signal
, Closeable
{
152 private final SleepTimer timer
= new UptimeSleepTimer();
153 private final SignalServiceConfiguration serviceConfiguration
;
154 private final String userAgent
;
156 private final SignalAccount account
;
157 private final PathConfig pathConfig
;
158 private SignalServiceAccountManager accountManager
;
159 private SignalServiceMessagePipe messagePipe
= null;
160 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
162 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
163 this.account
= account
;
164 this.pathConfig
= pathConfig
;
165 this.serviceConfiguration
= serviceConfiguration
;
166 this.userAgent
= userAgent
;
167 this.accountManager
= createSignalServiceAccountManager();
169 this.account
.setResolver(this::resolveSignalServiceAddress
);
172 public String
getUsername() {
173 return account
.getUsername();
176 public SignalServiceAddress
getSelfAddress() {
177 return account
.getSelfAddress();
180 private SignalServiceAccountManager
createSignalServiceAccountManager() {
181 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
184 private IdentityKeyPair
getIdentityKeyPair() {
185 return account
.getSignalProtocolStore().getIdentityKeyPair();
188 public int getDeviceId() {
189 return account
.getDeviceId();
192 private String
getMessageCachePath() {
193 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
196 private String
getMessageCachePath(String sender
) {
197 if (sender
== null || sender
.isEmpty()) {
198 return getMessageCachePath();
201 return getMessageCachePath() + "/" + sender
.replace("/", "_");
204 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
205 String cachePath
= getMessageCachePath(sender
);
206 IOUtils
.createPrivateDirectories(cachePath
);
207 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
210 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
211 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
213 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
214 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
215 int registrationId
= KeyHelper
.generateRegistrationId(false);
217 ProfileKey profileKey
= KeyUtils
.createProfileKey();
218 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
221 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
224 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
226 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
228 m
.migrateLegacyConfigs();
233 private void migrateLegacyConfigs() {
234 // Copy group avatars that were previously stored in the attachments folder
235 // to the new avatar folder
236 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
237 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
238 File avatarFile
= getGroupAvatarFile(g
.groupId
);
239 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
240 if (!avatarFile
.exists() && attachmentFile
.exists()) {
242 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
243 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
244 } catch (Exception e
) {
249 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
252 if (account
.getProfileKey() == null) {
253 // Old config file, creating new profile key
254 account
.setProfileKey(KeyUtils
.createProfileKey());
259 public void checkAccountState() throws IOException
{
260 if (account
.isRegistered()) {
261 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
265 if (account
.getUuid() == null) {
266 account
.setUuid(accountManager
.getOwnUuid());
272 public boolean isRegistered() {
273 return account
.isRegistered();
276 public void register(boolean voiceVerification
) throws IOException
{
277 account
.setPassword(KeyUtils
.createPassword());
279 // Resetting UUID, because registering doesn't work otherwise
280 account
.setUuid(null);
281 accountManager
= createSignalServiceAccountManager();
283 if (voiceVerification
) {
284 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
286 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
289 account
.setRegistered(false);
293 public void updateAccountAttributes() throws IOException
{
294 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
297 public void setProfileName(String name
) throws IOException
{
298 accountManager
.setProfileName(account
.getProfileKey(), name
);
301 public void setProfileAvatar(File avatar
) throws IOException
{
302 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
303 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
304 streamDetails
.getStream().close();
307 public void removeProfileAvatar() throws IOException
{
308 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
311 public void unregister() throws IOException
{
312 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
313 // If this is the master device, other users can't send messages to this number anymore.
314 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
315 accountManager
.setGcmId(Optional
.absent());
317 account
.setRegistered(false);
321 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
322 List
<DeviceInfo
> devices
= accountManager
.getDevices();
323 account
.setMultiDevice(devices
.size() > 1);
328 public void removeLinkedDevices(int deviceId
) throws IOException
{
329 accountManager
.removeDevice(deviceId
);
330 List
<DeviceInfo
> devices
= accountManager
.getDevices();
331 account
.setMultiDevice(devices
.size() > 1);
335 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
336 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
338 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
341 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
342 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
343 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
345 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
346 account
.setMultiDevice(true);
350 private List
<PreKeyRecord
> generatePreKeys() {
351 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
353 final int offset
= account
.getPreKeyIdOffset();
354 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
355 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
356 ECKeyPair keyPair
= Curve
.generateKeyPair();
357 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
362 account
.addPreKeys(records
);
368 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
370 ECKeyPair keyPair
= Curve
.generateKeyPair();
371 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
372 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
374 account
.addSignedPreKey(record);
378 } catch (InvalidKeyException e
) {
379 throw new AssertionError(e
);
383 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
384 verificationCode
= verificationCode
.replace("-", "");
385 account
.setSignalingKey(KeyUtils
.createSignalingKey());
386 // TODO make unrestricted unidentified access configurable
387 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
389 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
390 // TODO response.isStorageCapable()
391 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
392 account
.setRegistered(true);
393 account
.setUuid(uuid
);
394 account
.setRegistrationLockPin(pin
);
395 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
401 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
402 if (pin
.isPresent()) {
403 account
.setRegistrationLockPin(pin
.get());
404 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
406 account
.setRegistrationLockPin(null);
407 accountManager
.removeRegistrationLockV1();
412 void refreshPreKeys() throws IOException
{
413 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
414 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
415 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
417 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
420 private SignalServiceMessageReceiver
getMessageReceiver() {
421 // TODO implement ZkGroup support
422 final ClientZkProfileOperations clientZkProfileOperations
= null;
423 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
426 private SignalServiceMessageSender
getMessageSender() {
427 // TODO implement ZkGroup support
428 final ClientZkProfileOperations clientZkProfileOperations
= null;
429 final boolean attachmentsV3
= false;
430 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
431 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
434 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
435 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
440 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
441 } catch (IOException ignored
) {
445 SignalServiceMessageReceiver receiver
= getMessageReceiver();
447 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
448 } catch (VerificationFailedException e
) {
449 throw new AssertionError(e
);
453 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
454 File file
= getGroupAvatarFile(groupId
);
455 if (!file
.exists()) {
456 return Optional
.absent();
459 return Optional
.of(Utils
.createAttachment(file
));
462 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
463 File file
= getContactAvatarFile(number
);
464 if (!file
.exists()) {
465 return Optional
.absent();
468 return Optional
.of(Utils
.createAttachment(file
));
471 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
472 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
474 throw new GroupNotFoundException(groupId
);
476 if (!g
.isMember(account
.getSelfAddress())) {
477 throw new NotAGroupMemberException(groupId
, g
.name
);
482 public List
<GroupInfo
> getGroups() {
483 return account
.getGroupStore().getGroups();
487 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
489 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
490 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
491 if (attachments
!= null) {
492 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
494 if (groupId
!= null) {
495 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
498 messageBuilder
.asGroupMessage(group
);
501 final GroupInfo g
= getGroupForSending(groupId
);
503 messageBuilder
.withExpiration(g
.messageExpirationTime
);
505 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
508 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
509 long targetSentTimestamp
, byte[] groupId
)
510 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
511 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
512 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
513 .withReaction(reaction
);
514 if (groupId
!= null) {
515 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
518 messageBuilder
.asGroupMessage(group
);
520 final GroupInfo g
= getGroupForSending(groupId
);
521 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
524 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
525 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
529 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
530 .asGroupMessage(group
);
532 final GroupInfo g
= getGroupForSending(groupId
);
533 g
.removeMember(account
.getSelfAddress());
534 account
.getGroupStore().updateGroup(g
);
536 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
539 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
541 if (groupId
== null) {
543 g
= new GroupInfo(KeyUtils
.createGroupId());
544 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
546 g
= getGroupForSending(groupId
);
553 if (members
!= null) {
554 final Set
<String
> newE164Members
= new HashSet
<>();
555 for (SignalServiceAddress member
: members
) {
556 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
559 newE164Members
.add(member
.getNumber().get());
562 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
563 if (contacts
.size() != newE164Members
.size()) {
564 // Some of the new members are not registered on Signal
565 for (ContactTokenDetails contact
: contacts
) {
566 newE164Members
.remove(contact
.getNumber());
568 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
569 System
.err
.println("Aborting…");
573 g
.addMembers(members
);
576 if (avatarFile
!= null) {
577 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
578 File aFile
= getGroupAvatarFile(g
.groupId
);
579 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
582 account
.getGroupStore().updateGroup(g
);
584 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
586 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
590 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
591 if (groupId
== null) {
594 GroupInfo g
= getGroupForSending(groupId
);
596 if (!g
.isMember(recipient
)) {
600 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
602 // Send group message only to the recipient who requested it
603 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
606 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) {
607 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
610 .withMembers(new ArrayList
<>(g
.getMembers()));
612 File aFile
= getGroupAvatarFile(g
.groupId
);
613 if (aFile
.exists()) {
615 group
.withAvatar(Utils
.createAttachment(aFile
));
616 } catch (IOException e
) {
617 throw new AttachmentInvalidException(aFile
.toString(), e
);
621 return SignalServiceDataMessage
.newBuilder()
622 .asGroupMessage(group
.build())
623 .withExpiration(g
.messageExpirationTime
);
626 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
627 if (groupId
== null) {
631 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
634 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
635 .asGroupMessage(group
.build());
637 // Send group info request message to the recipient who sent us a message with this groupId
638 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
641 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
642 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
643 Collections
.singletonList(messageId
),
644 System
.currentTimeMillis());
646 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
650 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
651 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
652 List
<String
> recipients
= new ArrayList
<>(1);
653 recipients
.add(recipient
);
654 return sendMessage(message
, attachments
, recipients
);
658 public long sendMessage(String messageText
, List
<String
> attachments
,
659 List
<String
> recipients
)
660 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
661 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
662 if (attachments
!= null) {
663 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
665 // Upload attachments here, so we only upload once even for multiple recipients
666 SignalServiceMessageSender messageSender
= getMessageSender();
667 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
668 for (SignalServiceAttachment attachment
: attachmentStreams
) {
669 if (attachment
.isStream()) {
670 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
671 } else if (attachment
.isPointer()) {
672 attachmentPointers
.add(attachment
.asPointer());
676 messageBuilder
.withAttachments(attachmentPointers
);
678 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
681 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
682 long targetSentTimestamp
, List
<String
> recipients
)
683 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
684 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
685 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
686 .withReaction(reaction
);
687 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
691 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
692 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
693 .asEndSessionMessage();
695 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
697 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
698 } catch (Exception e
) {
699 for (SignalServiceAddress address
: signalServiceAddresses
) {
700 handleEndSession(address
);
708 public String
getContactName(String number
) throws InvalidNumberException
{
709 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
710 if (contact
== null) {
718 public void setContactName(String number
, String name
) throws InvalidNumberException
{
719 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
720 ContactInfo contact
= account
.getContactStore().getContact(address
);
721 if (contact
== null) {
722 contact
= new ContactInfo(address
);
723 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
725 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
728 account
.getContactStore().updateContact(contact
);
733 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
734 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
737 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
738 ContactInfo contact
= account
.getContactStore().getContact(address
);
739 if (contact
== null) {
740 contact
= new ContactInfo(address
);
741 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
743 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
745 contact
.blocked
= blocked
;
746 account
.getContactStore().updateContact(contact
);
751 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
752 GroupInfo group
= getGroup(groupId
);
754 throw new GroupNotFoundException(groupId
);
756 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
757 group
.blocked
= blocked
;
758 account
.getGroupStore().updateGroup(group
);
764 public List
<byte[]> getGroupIds() {
765 List
<GroupInfo
> groups
= getGroups();
766 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
767 for (GroupInfo group
: groups
) {
768 ids
.add(group
.groupId
);
774 public String
getGroupName(byte[] groupId
) {
775 GroupInfo group
= getGroup(groupId
);
784 public List
<String
> getGroupMembers(byte[] groupId
) {
785 GroupInfo group
= getGroup(groupId
);
787 return Collections
.emptyList();
789 return new ArrayList
<>(group
.getMembersE164());
794 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
795 if (groupId
.length
== 0) {
798 if (name
.isEmpty()) {
801 if (members
.size() == 0) {
804 if (avatar
.isEmpty()) {
807 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
811 * Change the expiration timer for a contact
813 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
814 ContactInfo c
= account
.getContactStore().getContact(address
);
815 c
.messageExpirationTime
= messageExpirationTimer
;
816 account
.getContactStore().updateContact(c
);
820 * Change the expiration timer for a group
822 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
823 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
824 g
.messageExpirationTime
= messageExpirationTimer
;
825 account
.getGroupStore().updateGroup(g
);
829 * Upload the sticker pack from path.
831 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
832 * @return if successful, returns the URL to install the sticker pack in the signal app
834 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
835 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
837 SignalServiceMessageSender messageSender
= getMessageSender();
839 byte[] packKey
= KeyUtils
.createStickerUploadKey();
840 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
843 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
845 } catch (URISyntaxException e
) {
846 throw new AssertionError(e
);
850 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
852 String rootPath
= null;
854 final File file
= new File(path
);
855 if (file
.getName().endsWith(".zip")) {
856 zip
= new ZipFile(file
);
857 } else if (file
.getName().equals("manifest.json")) {
858 rootPath
= file
.getParent();
860 throw new StickerPackInvalidException("Could not find manifest.json");
863 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
865 if (pack
.stickers
== null) {
866 throw new StickerPackInvalidException("Must set a 'stickers' field.");
869 if (pack
.stickers
.isEmpty()) {
870 throw new StickerPackInvalidException("Must include stickers.");
873 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
874 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
875 if (sticker
.file
== null) {
876 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
879 Pair
<InputStream
, Long
> data
;
881 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
882 } catch (IOException ignored
) {
883 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
886 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
887 stickers
.add(stickerInfo
);
890 StickerInfo cover
= null;
891 if (pack
.cover
!= null) {
892 if (pack
.cover
.file
== null) {
893 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
896 Pair
<InputStream
, Long
> data
;
898 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
899 } catch (IOException ignored
) {
900 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
903 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
906 return new SignalServiceStickerManifestUpload(
913 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
914 InputStream inputStream
;
916 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
918 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
920 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
923 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
925 final ZipEntry entry
= zip
.getEntry(subfile
);
926 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
928 final File file
= new File(rootPath
, subfile
);
929 return new Pair
<>(new FileInputStream(file
), file
.length());
933 void requestSyncGroups() throws IOException
{
934 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
935 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
937 sendSyncMessage(message
);
938 } catch (UntrustedIdentityException e
) {
943 void requestSyncContacts() throws IOException
{
944 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
945 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
947 sendSyncMessage(message
);
948 } catch (UntrustedIdentityException e
) {
953 void requestSyncBlocked() throws IOException
{
954 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
955 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
957 sendSyncMessage(message
);
958 } catch (UntrustedIdentityException e
) {
963 void requestSyncConfiguration() throws IOException
{
964 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
965 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
967 sendSyncMessage(message
);
968 } catch (UntrustedIdentityException e
) {
973 private byte[] getSenderCertificate() {
974 // TODO support UUID capable sender certificates
975 // byte[] certificate = accountManager.getSenderCertificate();
978 certificate
= accountManager
.getSenderCertificateLegacy();
979 } catch (IOException e
) {
980 System
.err
.println("Failed to get sender certificate: " + e
);
983 // TODO cache for a day
987 private byte[] getSelfUnidentifiedAccessKey() {
988 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
991 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
992 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
994 return new SignalProfile(
995 encryptedProfile
.getIdentityKey(),
996 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
997 encryptedProfile
.getAvatar(),
998 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
999 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1001 } catch (InvalidCiphertextException e
) {
1006 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1007 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1008 if (contact
== null || contact
.profileKey
== null) {
1011 ProfileKey theirProfileKey
;
1013 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1014 } catch (InvalidInputException
| IOException e
) {
1015 throw new AssertionError(e
);
1017 SignalProfile targetProfile
;
1019 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1020 } catch (IOException e
) {
1021 System
.err
.println("Failed to get recipient profile: " + e
);
1025 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1029 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1030 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1033 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1036 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1037 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1038 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1040 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1041 return Optional
.absent();
1045 return Optional
.of(new UnidentifiedAccessPair(
1046 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1047 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1049 } catch (InvalidCertificateException e
) {
1050 return Optional
.absent();
1054 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1055 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1056 for (SignalServiceAddress recipient
: recipients
) {
1057 result
.add(getAccessFor(recipient
));
1062 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1063 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1064 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1065 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1067 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1068 return Optional
.absent();
1072 return Optional
.of(new UnidentifiedAccessPair(
1073 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1074 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1076 } catch (InvalidCertificateException e
) {
1077 return Optional
.absent();
1081 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1082 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1084 if (unidentifiedAccess
.isPresent()) {
1085 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1088 return Optional
.absent();
1091 private void sendSyncMessage(SignalServiceSyncMessage message
)
1092 throws IOException
, UntrustedIdentityException
{
1093 SignalServiceMessageSender messageSender
= getMessageSender();
1095 messageSender
.sendMessage(message
, getAccessForSync());
1096 } catch (UntrustedIdentityException e
) {
1097 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1103 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1105 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1106 throws EncapsulatedExceptions
, IOException
{
1107 final long timestamp
= System
.currentTimeMillis();
1108 messageBuilder
.withTimestamp(timestamp
);
1109 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1111 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1112 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1113 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1115 for (SendMessageResult result
: results
) {
1116 if (result
.isUnregisteredFailure()) {
1117 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1118 } else if (result
.isNetworkFailure()) {
1119 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1120 } else if (result
.getIdentityFailure() != null) {
1121 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1124 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1125 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1130 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1131 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1133 for (String number
: numbers
) {
1134 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1136 return signalServiceAddresses
;
1139 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1140 throws IOException
{
1141 if (messagePipe
== null) {
1142 messagePipe
= getMessageReceiver().createMessagePipe();
1144 if (unidentifiedMessagePipe
== null) {
1145 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1147 SignalServiceDataMessage message
= null;
1149 SignalServiceMessageSender messageSender
= getMessageSender();
1151 message
= messageBuilder
.build();
1152 if (message
.getGroupContext().isPresent()) {
1154 final boolean isRecipientUpdate
= false;
1155 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1156 for (SendMessageResult r
: result
) {
1157 if (r
.getIdentityFailure() != null) {
1158 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1162 } catch (UntrustedIdentityException e
) {
1163 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1164 return Collections
.emptyList();
1166 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1167 SignalServiceAddress recipient
= account
.getSelfAddress();
1168 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1169 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1170 message
.getTimestamp(),
1172 message
.getExpiresInSeconds(),
1173 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1175 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1177 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1179 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1180 } catch (UntrustedIdentityException e
) {
1181 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1182 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1186 // Send to all individually, so sync messages are sent correctly
1187 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1188 for (SignalServiceAddress address
: recipients
) {
1189 ContactInfo contact
= account
.getContactStore().getContact(address
);
1190 if (contact
!= null) {
1191 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1192 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1194 messageBuilder
.withExpiration(0);
1195 messageBuilder
.withProfileKey(null);
1197 message
= messageBuilder
.build();
1199 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1200 results
.add(result
);
1201 } catch (UntrustedIdentityException e
) {
1202 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1203 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1209 if (message
!= null && message
.isEndSession()) {
1210 for (SignalServiceAddress recipient
: recipients
) {
1211 handleEndSession(recipient
);
1218 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1219 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1221 return cipher
.decrypt(envelope
);
1222 } catch (ProtocolUntrustedIdentityException e
) {
1223 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1224 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1225 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1226 throw identityException
;
1228 throw new AssertionError(e
);
1232 private void handleEndSession(SignalServiceAddress source
) {
1233 account
.getSignalProtocolStore().deleteAllSessions(source
);
1236 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1237 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1238 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1239 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1240 switch (groupInfo
.getType()) {
1242 if (group
== null) {
1243 group
= new GroupInfo(groupInfo
.getGroupId());
1246 if (groupInfo
.getAvatar().isPresent()) {
1247 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1248 if (avatar
.isPointer()) {
1250 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1251 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1252 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1257 if (groupInfo
.getName().isPresent()) {
1258 group
.name
= groupInfo
.getName().get();
1261 if (groupInfo
.getMembers().isPresent()) {
1262 group
.addMembers(groupInfo
.getMembers().get()
1264 .map(this::resolveSignalServiceAddress
)
1265 .collect(Collectors
.toSet()));
1268 account
.getGroupStore().updateGroup(group
);
1271 if (group
== null) {
1273 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1274 } catch (IOException
| EncapsulatedExceptions e
) {
1275 e
.printStackTrace();
1280 if (group
!= null) {
1281 group
.removeMember(source
);
1282 account
.getGroupStore().updateGroup(group
);
1286 if (group
!= null) {
1288 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1289 } catch (IOException
| EncapsulatedExceptions e
) {
1290 e
.printStackTrace();
1291 } catch (NotAGroupMemberException e
) {
1292 // We have left this group, so don't send a group update message
1298 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1299 if (message
.isEndSession()) {
1300 handleEndSession(conversationPartnerAddress
);
1302 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1303 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1304 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1305 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1306 if (group
== null) {
1307 group
= new GroupInfo(groupInfo
.getGroupId());
1309 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1310 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1311 account
.getGroupStore().updateGroup(group
);
1314 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1315 if (contact
== null) {
1316 contact
= new ContactInfo(conversationPartnerAddress
);
1318 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1319 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1320 account
.getContactStore().updateContact(contact
);
1324 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1325 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1326 if (attachment
.isPointer()) {
1328 retrieveAttachment(attachment
.asPointer());
1329 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1330 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1335 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1336 if (source
.matches(account
.getSelfAddress())) {
1338 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1339 } catch (InvalidInputException ignored
) {
1341 ContactInfo contact
= account
.getContactStore().getContact(source
);
1342 if (contact
!= null) {
1343 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1344 account
.getContactStore().updateContact(contact
);
1347 ContactInfo contact
= account
.getContactStore().getContact(source
);
1348 if (contact
== null) {
1349 contact
= new ContactInfo(source
);
1351 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1352 account
.getContactStore().updateContact(contact
);
1355 if (message
.getPreviews().isPresent()) {
1356 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1357 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1358 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1359 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1361 retrieveAttachment(attachment
);
1362 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1363 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1370 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1371 final File cachePath
= new File(getMessageCachePath());
1372 if (!cachePath
.exists()) {
1375 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1376 if (!dir
.isDirectory()) {
1377 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1381 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1382 if (!fileEntry
.isFile()) {
1385 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1387 // Try to delete directory if empty
1392 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1393 SignalServiceEnvelope envelope
;
1395 envelope
= Utils
.loadEnvelope(fileEntry
);
1396 if (envelope
== null) {
1399 } catch (IOException e
) {
1400 e
.printStackTrace();
1403 SignalServiceContent content
= null;
1404 if (!envelope
.isReceipt()) {
1406 content
= decryptMessage(envelope
);
1407 } catch (Exception e
) {
1410 handleMessage(envelope
, content
, ignoreAttachments
);
1413 handler
.handleMessage(envelope
, content
, null);
1415 Files
.delete(fileEntry
.toPath());
1416 } catch (IOException e
) {
1417 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1421 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1422 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1423 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1425 if (messagePipe
== null) {
1426 messagePipe
= messageReceiver
.createMessagePipe();
1430 SignalServiceEnvelope envelope
;
1431 SignalServiceContent content
= null;
1432 Exception exception
= null;
1433 final long now
= new Date().getTime();
1435 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1436 // store message on disk, before acknowledging receipt to the server
1438 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1439 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1440 Utils
.storeEnvelope(envelope1
, cacheFile
);
1441 } catch (IOException e
) {
1442 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1445 } catch (TimeoutException e
) {
1446 if (returnOnTimeout
)
1449 } catch (InvalidVersionException e
) {
1450 System
.err
.println("Ignoring error: " + e
.getMessage());
1453 if (!envelope
.isReceipt()) {
1455 content
= decryptMessage(envelope
);
1456 } catch (Exception e
) {
1459 handleMessage(envelope
, content
, ignoreAttachments
);
1462 if (!isMessageBlocked(envelope
, content
)) {
1463 handler
.handleMessage(envelope
, content
, exception
);
1465 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1466 File cacheFile
= null;
1468 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1469 Files
.delete(cacheFile
.toPath());
1470 // Try to delete directory if empty
1471 new File(getMessageCachePath()).delete();
1472 } catch (IOException e
) {
1473 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1479 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1480 SignalServiceAddress source
;
1481 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1482 source
= envelope
.getSourceAddress();
1483 } else if (content
!= null) {
1484 source
= content
.getSender();
1488 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1489 if (sourceContact
!= null && sourceContact
.blocked
) {
1493 if (content
!= null && content
.getDataMessage().isPresent()) {
1494 SignalServiceDataMessage message
= content
.getDataMessage().get();
1495 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1496 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1497 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1498 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1506 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1507 if (content
!= null) {
1508 SignalServiceAddress sender
;
1509 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1510 sender
= envelope
.getSourceAddress();
1512 sender
= content
.getSender();
1514 if (content
.getDataMessage().isPresent()) {
1515 SignalServiceDataMessage message
= content
.getDataMessage().get();
1517 if (content
.isNeedsReceipt()) {
1519 sendReceipt(sender
, message
.getTimestamp());
1520 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1521 e
.printStackTrace();
1525 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1527 if (content
.getSyncMessage().isPresent()) {
1528 account
.setMultiDevice(true);
1529 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1530 if (syncMessage
.getSent().isPresent()) {
1531 SentTranscriptMessage message
= syncMessage
.getSent().get();
1532 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1534 if (syncMessage
.getRequest().isPresent()) {
1535 RequestMessage rm
= syncMessage
.getRequest().get();
1536 if (rm
.isContactsRequest()) {
1539 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1540 e
.printStackTrace();
1543 if (rm
.isGroupsRequest()) {
1546 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1547 e
.printStackTrace();
1550 if (rm
.isBlockedListRequest()) {
1553 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1554 e
.printStackTrace();
1557 // TODO Handle rm.isConfigurationRequest();
1559 if (syncMessage
.getGroups().isPresent()) {
1560 File tmpFile
= null;
1562 tmpFile
= IOUtils
.createTempFile();
1563 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1564 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1566 while ((g
= s
.read()) != null) {
1567 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1568 if (syncGroup
== null) {
1569 syncGroup
= new GroupInfo(g
.getId());
1571 if (g
.getName().isPresent()) {
1572 syncGroup
.name
= g
.getName().get();
1574 syncGroup
.addMembers(g
.getMembers()
1576 .map(this::resolveSignalServiceAddress
)
1577 .collect(Collectors
.toSet()));
1578 if (!g
.isActive()) {
1579 syncGroup
.removeMember(account
.getSelfAddress());
1581 // Add ourself to the member set as it's marked as active
1582 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1584 syncGroup
.blocked
= g
.isBlocked();
1585 if (g
.getColor().isPresent()) {
1586 syncGroup
.color
= g
.getColor().get();
1589 if (g
.getAvatar().isPresent()) {
1590 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1592 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1593 syncGroup
.archived
= g
.isArchived();
1594 account
.getGroupStore().updateGroup(syncGroup
);
1597 } catch (Exception e
) {
1598 e
.printStackTrace();
1600 if (tmpFile
!= null) {
1602 Files
.delete(tmpFile
.toPath());
1603 } catch (IOException e
) {
1604 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1609 if (syncMessage
.getBlockedList().isPresent()) {
1610 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1611 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1612 setContactBlocked(resolveSignalServiceAddress(address
), true);
1614 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1616 setGroupBlocked(groupId
, true);
1617 } catch (GroupNotFoundException e
) {
1618 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1622 if (syncMessage
.getContacts().isPresent()) {
1623 File tmpFile
= null;
1625 tmpFile
= IOUtils
.createTempFile();
1626 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1627 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1628 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1629 if (contactsMessage
.isComplete()) {
1630 account
.getContactStore().clear();
1633 while ((c
= s
.read()) != null) {
1634 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1635 account
.setProfileKey(c
.getProfileKey().get());
1637 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1638 ContactInfo contact
= account
.getContactStore().getContact(address
);
1639 if (contact
== null) {
1640 contact
= new ContactInfo(address
);
1642 if (c
.getName().isPresent()) {
1643 contact
.name
= c
.getName().get();
1645 if (c
.getColor().isPresent()) {
1646 contact
.color
= c
.getColor().get();
1648 if (c
.getProfileKey().isPresent()) {
1649 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1651 if (c
.getVerified().isPresent()) {
1652 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1653 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1655 if (c
.getExpirationTimer().isPresent()) {
1656 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1658 contact
.blocked
= c
.isBlocked();
1659 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1660 contact
.archived
= c
.isArchived();
1661 account
.getContactStore().updateContact(contact
);
1663 if (c
.getAvatar().isPresent()) {
1664 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1668 } catch (Exception e
) {
1669 e
.printStackTrace();
1671 if (tmpFile
!= null) {
1673 Files
.delete(tmpFile
.toPath());
1674 } catch (IOException e
) {
1675 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1680 if (syncMessage
.getVerified().isPresent()) {
1681 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1682 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1684 if (syncMessage
.getConfiguration().isPresent()) {
1691 private File
getContactAvatarFile(String number
) {
1692 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1695 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1696 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1697 if (attachment
.isPointer()) {
1698 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1699 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1701 SignalServiceAttachmentStream stream
= attachment
.asStream();
1702 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1706 private File
getGroupAvatarFile(byte[] groupId
) {
1707 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1710 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1711 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1712 if (attachment
.isPointer()) {
1713 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1714 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1716 SignalServiceAttachmentStream stream
= attachment
.asStream();
1717 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1721 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1722 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1725 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1726 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1727 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1730 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1731 if (storePreview
&& pointer
.getPreview().isPresent()) {
1732 File previewFile
= new File(outputFile
+ ".preview");
1733 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1734 byte[] preview
= pointer
.getPreview().get();
1735 output
.write(preview
, 0, preview
.length
);
1736 } catch (FileNotFoundException e
) {
1737 e
.printStackTrace();
1742 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1744 File tmpFile
= IOUtils
.createTempFile();
1745 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1746 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1747 byte[] buffer
= new byte[4096];
1750 while ((read
= input
.read(buffer
)) != -1) {
1751 output
.write(buffer
, 0, read
);
1753 } catch (FileNotFoundException e
) {
1754 e
.printStackTrace();
1759 Files
.delete(tmpFile
.toPath());
1760 } catch (IOException e
) {
1761 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1767 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1768 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1769 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1773 public boolean isRemote() {
1778 public String
getObjectPath() {
1782 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1783 File groupsFile
= IOUtils
.createTempFile();
1786 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1787 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1788 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1789 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1790 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1791 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1792 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1796 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1797 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1798 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1799 .withStream(groupsFileStream
)
1800 .withContentType("application/octet-stream")
1801 .withLength(groupsFile
.length())
1804 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1809 Files
.delete(groupsFile
.toPath());
1810 } catch (IOException e
) {
1811 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1816 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1817 File contactsFile
= IOUtils
.createTempFile();
1820 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1821 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1822 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1823 VerifiedMessage verifiedMessage
= null;
1824 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1825 if (currentIdentity
!= null) {
1826 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1829 ProfileKey profileKey
= null;
1831 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1832 } catch (InvalidInputException ignored
) {
1834 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1835 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1836 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1837 Optional
.of(record.messageExpirationTime
),
1838 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1841 if (account
.getProfileKey() != null) {
1842 // Send our own profile key as well
1843 out
.write(new DeviceContact(account
.getSelfAddress(),
1844 Optional
.absent(), Optional
.absent(),
1845 Optional
.absent(), Optional
.absent(),
1846 Optional
.of(account
.getProfileKey()),
1847 false, Optional
.absent(), Optional
.absent(), false));
1851 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1852 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1853 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1854 .withStream(contactsFileStream
)
1855 .withContentType("application/octet-stream")
1856 .withLength(contactsFile
.length())
1859 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1864 Files
.delete(contactsFile
.toPath());
1865 } catch (IOException e
) {
1866 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1871 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1872 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1873 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1874 if (record.blocked
) {
1875 addresses
.add(record.getAddress());
1878 List
<byte[]> groupIds
= new ArrayList
<>();
1879 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1880 if (record.blocked
) {
1881 groupIds
.add(record.groupId
);
1884 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1887 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1888 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1889 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1892 public List
<ContactInfo
> getContacts() {
1893 return account
.getContactStore().getContacts();
1896 public ContactInfo
getContact(String number
) {
1897 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1900 public GroupInfo
getGroup(byte[] groupId
) {
1901 return account
.getGroupStore().getGroup(groupId
);
1904 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1905 return account
.getSignalProtocolStore().getIdentities();
1908 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1909 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1913 * Trust this the identity with this fingerprint
1915 * @param name username of the identity
1916 * @param fingerprint Fingerprint
1918 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1919 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1920 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1924 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1925 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1929 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1931 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1932 } catch (IOException
| UntrustedIdentityException e
) {
1933 e
.printStackTrace();
1942 * Trust this the identity with this safety number
1944 * @param name username of the identity
1945 * @param safetyNumber Safety number
1947 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1948 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1949 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1953 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1954 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1958 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1960 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1961 } catch (IOException
| UntrustedIdentityException e
) {
1962 e
.printStackTrace();
1971 * Trust all keys of this identity without verification
1973 * @param name username of the identity
1975 public boolean trustIdentityAllKeys(String name
) {
1976 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1977 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1981 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1982 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1983 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1985 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1986 } catch (IOException
| UntrustedIdentityException e
) {
1987 e
.printStackTrace();
1995 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1996 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1999 void saveAccount() {
2003 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2004 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2005 return resolveSignalServiceAddress(canonicalizedNumber
);
2008 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2009 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2011 return resolveSignalServiceAddress(address
);
2014 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2015 if (address
.matches(account
.getSelfAddress())) {
2016 return account
.getSelfAddress();
2019 return account
.getRecipientStore().resolveServiceAddress(address
);
2023 public void close() throws IOException
{
2024 if (messagePipe
!= null) {
2025 messagePipe
.shutdown();
2029 if (unidentifiedMessagePipe
!= null) {
2030 unidentifiedMessagePipe
.shutdown();
2031 unidentifiedMessagePipe
= null;
2037 public interface ReceiveMessageHandler
{
2039 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);