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
.storage
.SignalAccount
;
23 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
24 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
25 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
26 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
27 import org
.asamk
.signal
.util
.IOUtils
;
28 import org
.asamk
.signal
.util
.Util
;
29 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
30 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
31 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
39 import org
.signal
.libsignal
.metadata
.SelfSendException
;
40 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
41 import org
.signal
.zkgroup
.InvalidInputException
;
42 import org
.signal
.zkgroup
.VerificationFailedException
;
43 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
44 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
45 import org
.whispersystems
.libsignal
.IdentityKey
;
46 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
47 import org
.whispersystems
.libsignal
.InvalidKeyException
;
48 import org
.whispersystems
.libsignal
.InvalidMessageException
;
49 import org
.whispersystems
.libsignal
.InvalidVersionException
;
50 import org
.whispersystems
.libsignal
.ecc
.Curve
;
51 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
52 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
53 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
54 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
55 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
56 import org
.whispersystems
.libsignal
.util
.Medium
;
57 import org
.whispersystems
.libsignal
.util
.Pair
;
58 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
94 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
95 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
96 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
100 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
101 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
102 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
103 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
104 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
105 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
106 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
107 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
108 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
109 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
110 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
111 import org
.whispersystems
.util
.Base64
;
113 import java
.io
.Closeable
;
115 import java
.io
.FileInputStream
;
116 import java
.io
.FileNotFoundException
;
117 import java
.io
.FileOutputStream
;
118 import java
.io
.IOException
;
119 import java
.io
.InputStream
;
120 import java
.io
.OutputStream
;
122 import java
.net
.URISyntaxException
;
123 import java
.net
.URLEncoder
;
124 import java
.nio
.file
.Files
;
125 import java
.nio
.file
.Paths
;
126 import java
.nio
.file
.StandardCopyOption
;
127 import java
.util
.ArrayList
;
128 import java
.util
.Arrays
;
129 import java
.util
.Collection
;
130 import java
.util
.Collections
;
131 import java
.util
.Date
;
132 import java
.util
.HashSet
;
133 import java
.util
.LinkedList
;
134 import java
.util
.List
;
135 import java
.util
.Locale
;
136 import java
.util
.Objects
;
137 import java
.util
.Set
;
138 import java
.util
.UUID
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.stream
.Collectors
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Signal
, Closeable
{
147 private final SleepTimer timer
= new UptimeSleepTimer();
148 private final SignalServiceConfiguration serviceConfiguration
;
149 private final String userAgent
;
151 private final SignalAccount account
;
152 private final PathConfig pathConfig
;
153 private SignalServiceAccountManager accountManager
;
154 private SignalServiceMessagePipe messagePipe
= null;
155 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
157 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
158 this.account
= account
;
159 this.pathConfig
= pathConfig
;
160 this.serviceConfiguration
= serviceConfiguration
;
161 this.userAgent
= userAgent
;
162 this.accountManager
= createSignalServiceAccountManager();
164 this.account
.setResolver(this::resolveSignalServiceAddress
);
167 public String
getUsername() {
168 return account
.getUsername();
171 public SignalServiceAddress
getSelfAddress() {
172 return account
.getSelfAddress();
175 private SignalServiceAccountManager
createSignalServiceAccountManager() {
176 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
179 private IdentityKeyPair
getIdentityKeyPair() {
180 return account
.getSignalProtocolStore().getIdentityKeyPair();
183 public int getDeviceId() {
184 return account
.getDeviceId();
187 private String
getMessageCachePath() {
188 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
191 private String
getMessageCachePath(String sender
) {
192 if (sender
== null || sender
.isEmpty()) {
193 return getMessageCachePath();
196 return getMessageCachePath() + "/" + sender
.replace("/", "_");
199 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
200 String cachePath
= getMessageCachePath(sender
);
201 IOUtils
.createPrivateDirectories(cachePath
);
202 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
205 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
206 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
208 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
209 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
210 int registrationId
= KeyHelper
.generateRegistrationId(false);
212 ProfileKey profileKey
= KeyUtils
.createProfileKey();
213 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
216 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
219 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
221 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
223 m
.migrateLegacyConfigs();
228 private void migrateLegacyConfigs() {
229 // Copy group avatars that were previously stored in the attachments folder
230 // to the new avatar folder
231 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
232 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
233 File avatarFile
= getGroupAvatarFile(g
.groupId
);
234 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
235 if (!avatarFile
.exists() && attachmentFile
.exists()) {
237 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
238 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
239 } catch (Exception e
) {
244 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
247 if (account
.getProfileKey() == null) {
248 // Old config file, creating new profile key
249 account
.setProfileKey(KeyUtils
.createProfileKey());
254 public void checkAccountState() throws IOException
{
255 if (account
.isRegistered()) {
256 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
260 if (account
.getUuid() == null) {
261 account
.setUuid(accountManager
.getOwnUuid());
267 public boolean isRegistered() {
268 return account
.isRegistered();
271 public void register(boolean voiceVerification
) throws IOException
{
272 account
.setPassword(KeyUtils
.createPassword());
274 // Resetting UUID, because registering doesn't work otherwise
275 account
.setUuid(null);
276 accountManager
= createSignalServiceAccountManager();
278 if (voiceVerification
) {
279 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
281 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
284 account
.setRegistered(false);
288 public void updateAccountAttributes() throws IOException
{
289 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
292 public void setProfileName(String name
) throws IOException
{
293 accountManager
.setProfileName(account
.getProfileKey(), name
);
296 public void setProfileAvatar(File avatar
) throws IOException
{
297 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
298 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
299 streamDetails
.getStream().close();
302 public void removeProfileAvatar() throws IOException
{
303 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
306 public void unregister() throws IOException
{
307 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
308 // If this is the master device, other users can't send messages to this number anymore.
309 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
310 accountManager
.setGcmId(Optional
.absent());
312 account
.setRegistered(false);
316 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
317 List
<DeviceInfo
> devices
= accountManager
.getDevices();
318 account
.setMultiDevice(devices
.size() > 1);
323 public void removeLinkedDevices(int deviceId
) throws IOException
{
324 accountManager
.removeDevice(deviceId
);
325 List
<DeviceInfo
> devices
= accountManager
.getDevices();
326 account
.setMultiDevice(devices
.size() > 1);
330 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
331 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
333 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
336 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
337 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
338 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
340 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
341 account
.setMultiDevice(true);
345 private List
<PreKeyRecord
> generatePreKeys() {
346 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
348 final int offset
= account
.getPreKeyIdOffset();
349 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
350 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
351 ECKeyPair keyPair
= Curve
.generateKeyPair();
352 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
357 account
.addPreKeys(records
);
363 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
365 ECKeyPair keyPair
= Curve
.generateKeyPair();
366 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
367 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
369 account
.addSignedPreKey(record);
373 } catch (InvalidKeyException e
) {
374 throw new AssertionError(e
);
378 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
379 verificationCode
= verificationCode
.replace("-", "");
380 account
.setSignalingKey(KeyUtils
.createSignalingKey());
381 // TODO make unrestricted unidentified access configurable
382 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
384 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
385 // TODO response.isStorageCapable()
386 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
387 account
.setRegistered(true);
388 account
.setUuid(uuid
);
389 account
.setRegistrationLockPin(pin
);
390 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
396 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
397 if (pin
.isPresent()) {
398 account
.setRegistrationLockPin(pin
.get());
399 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
401 account
.setRegistrationLockPin(null);
402 accountManager
.removeRegistrationLockV1();
407 void refreshPreKeys() throws IOException
{
408 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
409 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
410 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
412 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
415 private SignalServiceMessageReceiver
getMessageReceiver() {
416 // TODO implement ZkGroup support
417 final ClientZkProfileOperations clientZkProfileOperations
= null;
418 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
421 private SignalServiceMessageSender
getMessageSender() {
422 // TODO implement ZkGroup support
423 final ClientZkProfileOperations clientZkProfileOperations
= null;
424 final boolean attachmentsV3
= false;
425 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
426 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
429 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
430 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
435 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
436 } catch (IOException ignored
) {
440 SignalServiceMessageReceiver receiver
= getMessageReceiver();
442 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
443 } catch (VerificationFailedException e
) {
444 throw new AssertionError(e
);
448 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
449 File file
= getGroupAvatarFile(groupId
);
450 if (!file
.exists()) {
451 return Optional
.absent();
454 return Optional
.of(Utils
.createAttachment(file
));
457 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
458 File file
= getContactAvatarFile(number
);
459 if (!file
.exists()) {
460 return Optional
.absent();
463 return Optional
.of(Utils
.createAttachment(file
));
466 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
467 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
469 throw new GroupNotFoundException(groupId
);
471 if (!g
.isMember(account
.getSelfAddress())) {
472 throw new NotAGroupMemberException(groupId
, g
.name
);
477 public List
<GroupInfo
> getGroups() {
478 return account
.getGroupStore().getGroups();
482 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
484 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
485 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
486 if (attachments
!= null) {
487 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
489 if (groupId
!= null) {
490 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
493 messageBuilder
.asGroupMessage(group
);
496 final GroupInfo g
= getGroupForSending(groupId
);
498 messageBuilder
.withExpiration(g
.messageExpirationTime
);
500 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
503 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
504 long targetSentTimestamp
, byte[] groupId
)
505 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
506 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
507 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
508 .withReaction(reaction
);
509 if (groupId
!= null) {
510 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
513 messageBuilder
.asGroupMessage(group
);
515 final GroupInfo g
= getGroupForSending(groupId
);
516 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
519 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
520 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
524 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
525 .asGroupMessage(group
);
527 final GroupInfo g
= getGroupForSending(groupId
);
528 g
.removeMember(account
.getSelfAddress());
529 account
.getGroupStore().updateGroup(g
);
531 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
534 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
536 if (groupId
== null) {
538 g
= new GroupInfo(KeyUtils
.createGroupId());
539 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
541 g
= getGroupForSending(groupId
);
548 if (members
!= null) {
549 final Set
<String
> newE164Members
= new HashSet
<>();
550 for (SignalServiceAddress member
: members
) {
551 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
554 newE164Members
.add(member
.getNumber().get());
557 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
558 if (contacts
.size() != newE164Members
.size()) {
559 // Some of the new members are not registered on Signal
560 for (ContactTokenDetails contact
: contacts
) {
561 newE164Members
.remove(contact
.getNumber());
563 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
564 System
.err
.println("Aborting…");
568 g
.addMembers(members
);
571 if (avatarFile
!= null) {
572 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
573 File aFile
= getGroupAvatarFile(g
.groupId
);
574 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
577 account
.getGroupStore().updateGroup(g
);
579 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
581 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
585 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
586 if (groupId
== null) {
589 GroupInfo g
= getGroupForSending(groupId
);
591 if (!g
.isMember(recipient
)) {
595 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
597 // Send group message only to the recipient who requested it
598 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
601 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
602 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
605 .withMembers(new ArrayList
<>(g
.getMembers()));
607 File aFile
= getGroupAvatarFile(g
.groupId
);
608 if (aFile
.exists()) {
610 group
.withAvatar(Utils
.createAttachment(aFile
));
611 } catch (IOException e
) {
612 throw new AttachmentInvalidException(aFile
.toString(), e
);
616 return SignalServiceDataMessage
.newBuilder()
617 .asGroupMessage(group
.build())
618 .withExpiration(g
.messageExpirationTime
);
621 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
622 if (groupId
== null) {
626 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
629 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
630 .asGroupMessage(group
.build());
632 // Send group info request message to the recipient who sent us a message with this groupId
633 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
636 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
637 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
638 Collections
.singletonList(messageId
),
639 System
.currentTimeMillis());
641 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
645 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
646 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
647 List
<String
> recipients
= new ArrayList
<>(1);
648 recipients
.add(recipient
);
649 return sendMessage(message
, attachments
, recipients
);
653 public long sendMessage(String messageText
, List
<String
> attachments
,
654 List
<String
> recipients
)
655 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
656 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
657 if (attachments
!= null) {
658 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
660 // Upload attachments here, so we only upload once even for multiple recipients
661 SignalServiceMessageSender messageSender
= getMessageSender();
662 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
663 for (SignalServiceAttachment attachment
: attachmentStreams
) {
664 if (attachment
.isStream()) {
665 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
666 } else if (attachment
.isPointer()) {
667 attachmentPointers
.add(attachment
.asPointer());
671 messageBuilder
.withAttachments(attachmentPointers
);
673 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
676 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
677 long targetSentTimestamp
, List
<String
> recipients
)
678 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
679 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
680 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
681 .withReaction(reaction
);
682 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
686 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
687 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
688 .asEndSessionMessage();
690 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
692 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
693 } catch (Exception e
) {
694 for (SignalServiceAddress address
: signalServiceAddresses
) {
695 handleEndSession(address
);
703 public String
getContactName(String number
) throws InvalidNumberException
{
704 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
705 if (contact
== null) {
713 public void setContactName(String number
, String name
) throws InvalidNumberException
{
714 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
715 ContactInfo contact
= account
.getContactStore().getContact(address
);
716 if (contact
== null) {
717 contact
= new ContactInfo(address
);
718 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
720 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
723 account
.getContactStore().updateContact(contact
);
728 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
729 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
732 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
733 ContactInfo contact
= account
.getContactStore().getContact(address
);
734 if (contact
== null) {
735 contact
= new ContactInfo(address
);
736 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
738 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
740 contact
.blocked
= blocked
;
741 account
.getContactStore().updateContact(contact
);
746 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
747 GroupInfo group
= getGroup(groupId
);
749 throw new GroupNotFoundException(groupId
);
751 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
752 group
.blocked
= blocked
;
753 account
.getGroupStore().updateGroup(group
);
759 public List
<byte[]> getGroupIds() {
760 List
<GroupInfo
> groups
= getGroups();
761 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
762 for (GroupInfo group
: groups
) {
763 ids
.add(group
.groupId
);
769 public String
getGroupName(byte[] groupId
) {
770 GroupInfo group
= getGroup(groupId
);
779 public List
<String
> getGroupMembers(byte[] groupId
) {
780 GroupInfo group
= getGroup(groupId
);
782 return Collections
.emptyList();
784 return new ArrayList
<>(group
.getMembersE164());
789 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
790 if (groupId
.length
== 0) {
793 if (name
.isEmpty()) {
796 if (members
.size() == 0) {
799 if (avatar
.isEmpty()) {
802 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
806 * Change the expiration timer for a contact
808 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
809 ContactInfo c
= account
.getContactStore().getContact(address
);
810 c
.messageExpirationTime
= messageExpirationTimer
;
811 account
.getContactStore().updateContact(c
);
815 * Change the expiration timer for a group
817 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
818 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
819 g
.messageExpirationTime
= messageExpirationTimer
;
820 account
.getGroupStore().updateGroup(g
);
824 * Upload the sticker pack from path.
826 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
827 * @return if successful, returns the URL to install the sticker pack in the signal app
829 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
830 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
832 SignalServiceMessageSender messageSender
= getMessageSender();
834 byte[] packKey
= KeyUtils
.createStickerUploadKey();
835 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
838 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
840 } catch (URISyntaxException e
) {
841 throw new AssertionError(e
);
845 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
847 String rootPath
= null;
849 final File file
= new File(path
);
850 if (file
.getName().endsWith(".zip")) {
851 zip
= new ZipFile(file
);
852 } else if (file
.getName().equals("manifest.json")) {
853 rootPath
= file
.getParent();
855 throw new StickerPackInvalidException("Could not find manifest.json");
858 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
860 if (pack
.stickers
== null) {
861 throw new StickerPackInvalidException("Must set a 'stickers' field.");
864 if (pack
.stickers
.isEmpty()) {
865 throw new StickerPackInvalidException("Must include stickers.");
868 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
869 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
870 if (sticker
.file
== null) {
871 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
874 Pair
<InputStream
, Long
> data
;
876 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
877 } catch (IOException ignored
) {
878 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
881 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
882 stickers
.add(stickerInfo
);
885 StickerInfo cover
= null;
886 if (pack
.cover
!= null) {
887 if (pack
.cover
.file
== null) {
888 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
891 Pair
<InputStream
, Long
> data
;
893 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
894 } catch (IOException ignored
) {
895 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
898 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
901 return new SignalServiceStickerManifestUpload(
908 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
909 InputStream inputStream
;
911 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
913 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
915 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
918 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
920 final ZipEntry entry
= zip
.getEntry(subfile
);
921 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
923 final File file
= new File(rootPath
, subfile
);
924 return new Pair
<>(new FileInputStream(file
), file
.length());
928 void requestSyncGroups() throws IOException
{
929 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
930 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
932 sendSyncMessage(message
);
933 } catch (UntrustedIdentityException e
) {
938 void requestSyncContacts() throws IOException
{
939 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
940 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
942 sendSyncMessage(message
);
943 } catch (UntrustedIdentityException e
) {
948 void requestSyncBlocked() throws IOException
{
949 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
950 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
952 sendSyncMessage(message
);
953 } catch (UntrustedIdentityException e
) {
958 void requestSyncConfiguration() throws IOException
{
959 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
960 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
962 sendSyncMessage(message
);
963 } catch (UntrustedIdentityException e
) {
968 private byte[] getSenderCertificate() {
969 // TODO support UUID capable sender certificates
970 // byte[] certificate = accountManager.getSenderCertificate();
973 certificate
= accountManager
.getSenderCertificateLegacy();
974 } catch (IOException e
) {
975 System
.err
.println("Failed to get sender certificate: " + e
);
978 // TODO cache for a day
982 private byte[] getSelfUnidentifiedAccessKey() {
983 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
986 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
987 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
989 return new SignalProfile(
990 encryptedProfile
.getIdentityKey(),
991 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
992 encryptedProfile
.getAvatar(),
993 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
994 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
996 } catch (InvalidCiphertextException e
) {
1001 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1002 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1003 if (contact
== null || contact
.profileKey
== null) {
1006 ProfileKey theirProfileKey
;
1008 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1009 } catch (InvalidInputException
| IOException e
) {
1010 throw new AssertionError(e
);
1012 SignalProfile targetProfile
;
1014 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1015 } catch (IOException e
) {
1016 System
.err
.println("Failed to get recipient profile: " + e
);
1020 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1024 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1025 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1028 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1031 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1032 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1033 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1035 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1036 return Optional
.absent();
1040 return Optional
.of(new UnidentifiedAccessPair(
1041 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1042 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1044 } catch (InvalidCertificateException e
) {
1045 return Optional
.absent();
1049 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1050 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1051 for (SignalServiceAddress recipient
: recipients
) {
1052 result
.add(getAccessFor(recipient
));
1057 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1058 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1059 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1060 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1062 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1063 return Optional
.absent();
1067 return Optional
.of(new UnidentifiedAccessPair(
1068 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1069 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1071 } catch (InvalidCertificateException e
) {
1072 return Optional
.absent();
1076 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1077 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1079 if (unidentifiedAccess
.isPresent()) {
1080 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1083 return Optional
.absent();
1086 private void sendSyncMessage(SignalServiceSyncMessage message
)
1087 throws IOException
, UntrustedIdentityException
{
1088 SignalServiceMessageSender messageSender
= getMessageSender();
1090 messageSender
.sendMessage(message
, getAccessForSync());
1091 } catch (UntrustedIdentityException e
) {
1092 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1098 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1100 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1101 throws EncapsulatedExceptions
, IOException
{
1102 final long timestamp
= System
.currentTimeMillis();
1103 messageBuilder
.withTimestamp(timestamp
);
1104 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1106 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1107 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1108 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1110 for (SendMessageResult result
: results
) {
1111 if (result
.isUnregisteredFailure()) {
1112 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1113 } else if (result
.isNetworkFailure()) {
1114 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1115 } else if (result
.getIdentityFailure() != null) {
1116 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1119 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1120 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1125 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1126 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1128 for (String number
: numbers
) {
1129 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1131 return signalServiceAddresses
;
1134 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1135 throws IOException
{
1136 if (messagePipe
== null) {
1137 messagePipe
= getMessageReceiver().createMessagePipe();
1139 if (unidentifiedMessagePipe
== null) {
1140 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1142 SignalServiceDataMessage message
= null;
1144 SignalServiceMessageSender messageSender
= getMessageSender();
1146 message
= messageBuilder
.build();
1147 if (message
.getGroupContext().isPresent()) {
1149 final boolean isRecipientUpdate
= false;
1150 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1151 for (SendMessageResult r
: result
) {
1152 if (r
.getIdentityFailure() != null) {
1153 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1157 } catch (UntrustedIdentityException e
) {
1158 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1159 return Collections
.emptyList();
1161 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1162 SignalServiceAddress recipient
= account
.getSelfAddress();
1163 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1164 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1165 message
.getTimestamp(),
1167 message
.getExpiresInSeconds(),
1168 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1170 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1172 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1174 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1175 } catch (UntrustedIdentityException e
) {
1176 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1177 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1181 // Send to all individually, so sync messages are sent correctly
1182 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1183 for (SignalServiceAddress address
: recipients
) {
1184 ContactInfo contact
= account
.getContactStore().getContact(address
);
1185 if (contact
!= null) {
1186 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1187 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1189 messageBuilder
.withExpiration(0);
1190 messageBuilder
.withProfileKey(null);
1192 message
= messageBuilder
.build();
1194 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1195 results
.add(result
);
1196 } catch (UntrustedIdentityException e
) {
1197 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1198 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1204 if (message
!= null && message
.isEndSession()) {
1205 for (SignalServiceAddress recipient
: recipients
) {
1206 handleEndSession(recipient
);
1213 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1214 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1216 return cipher
.decrypt(envelope
);
1217 } catch (ProtocolUntrustedIdentityException e
) {
1218 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1219 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1220 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1221 throw identityException
;
1223 throw new AssertionError(e
);
1227 private void handleEndSession(SignalServiceAddress source
) {
1228 account
.getSignalProtocolStore().deleteAllSessions(source
);
1231 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1232 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1233 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1234 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1235 switch (groupInfo
.getType()) {
1237 if (group
== null) {
1238 group
= new GroupInfo(groupInfo
.getGroupId());
1241 if (groupInfo
.getAvatar().isPresent()) {
1242 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1243 if (avatar
.isPointer()) {
1245 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1246 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1247 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1252 if (groupInfo
.getName().isPresent()) {
1253 group
.name
= groupInfo
.getName().get();
1256 if (groupInfo
.getMembers().isPresent()) {
1257 group
.addMembers(groupInfo
.getMembers().get()
1259 .map(this::resolveSignalServiceAddress
)
1260 .collect(Collectors
.toSet()));
1263 account
.getGroupStore().updateGroup(group
);
1266 if (group
== null) {
1268 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1269 } catch (IOException
| EncapsulatedExceptions e
) {
1270 e
.printStackTrace();
1275 if (group
!= null) {
1276 group
.removeMember(source
);
1277 account
.getGroupStore().updateGroup(group
);
1281 if (group
!= null) {
1283 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1284 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1285 e
.printStackTrace();
1286 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1287 // We have left this group, so don't send a group update message
1293 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1294 if (message
.isEndSession()) {
1295 handleEndSession(conversationPartnerAddress
);
1297 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1298 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1299 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1300 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1301 if (group
== null) {
1302 group
= new GroupInfo(groupInfo
.getGroupId());
1304 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1305 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1306 account
.getGroupStore().updateGroup(group
);
1309 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1310 if (contact
== null) {
1311 contact
= new ContactInfo(conversationPartnerAddress
);
1313 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1314 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1315 account
.getContactStore().updateContact(contact
);
1319 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1320 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1321 if (attachment
.isPointer()) {
1323 retrieveAttachment(attachment
.asPointer());
1324 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1325 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1330 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1331 if (source
.matches(account
.getSelfAddress())) {
1333 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1334 } catch (InvalidInputException ignored
) {
1336 ContactInfo contact
= account
.getContactStore().getContact(source
);
1337 if (contact
!= null) {
1338 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1339 account
.getContactStore().updateContact(contact
);
1342 ContactInfo contact
= account
.getContactStore().getContact(source
);
1343 if (contact
== null) {
1344 contact
= new ContactInfo(source
);
1346 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1347 account
.getContactStore().updateContact(contact
);
1350 if (message
.getPreviews().isPresent()) {
1351 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1352 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1353 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1354 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1356 retrieveAttachment(attachment
);
1357 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1358 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1365 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1366 final File cachePath
= new File(getMessageCachePath());
1367 if (!cachePath
.exists()) {
1370 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1371 if (!dir
.isDirectory()) {
1372 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1376 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1377 if (!fileEntry
.isFile()) {
1380 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1382 // Try to delete directory if empty
1387 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1388 SignalServiceEnvelope envelope
;
1390 envelope
= Utils
.loadEnvelope(fileEntry
);
1391 if (envelope
== null) {
1394 } catch (IOException e
) {
1395 e
.printStackTrace();
1398 SignalServiceContent content
= null;
1399 if (!envelope
.isReceipt()) {
1401 content
= decryptMessage(envelope
);
1402 } catch (Exception e
) {
1405 handleMessage(envelope
, content
, ignoreAttachments
);
1408 handler
.handleMessage(envelope
, content
, null);
1410 Files
.delete(fileEntry
.toPath());
1411 } catch (IOException e
) {
1412 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1416 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1417 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1418 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1420 if (messagePipe
== null) {
1421 messagePipe
= messageReceiver
.createMessagePipe();
1425 SignalServiceEnvelope envelope
;
1426 SignalServiceContent content
= null;
1427 Exception exception
= null;
1428 final long now
= new Date().getTime();
1430 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1431 // store message on disk, before acknowledging receipt to the server
1433 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1434 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1435 Utils
.storeEnvelope(envelope1
, cacheFile
);
1436 } catch (IOException e
) {
1437 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1440 } catch (TimeoutException e
) {
1441 if (returnOnTimeout
)
1444 } catch (InvalidVersionException e
) {
1445 System
.err
.println("Ignoring error: " + e
.getMessage());
1448 if (!envelope
.isReceipt()) {
1450 content
= decryptMessage(envelope
);
1451 } catch (Exception e
) {
1454 handleMessage(envelope
, content
, ignoreAttachments
);
1457 if (!isMessageBlocked(envelope
, content
)) {
1458 handler
.handleMessage(envelope
, content
, exception
);
1460 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1461 File cacheFile
= null;
1463 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1464 Files
.delete(cacheFile
.toPath());
1465 // Try to delete directory if empty
1466 new File(getMessageCachePath()).delete();
1467 } catch (IOException e
) {
1468 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1474 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1475 SignalServiceAddress source
;
1476 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1477 source
= envelope
.getSourceAddress();
1478 } else if (content
!= null) {
1479 source
= content
.getSender();
1483 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1484 if (sourceContact
!= null && sourceContact
.blocked
) {
1488 if (content
!= null && content
.getDataMessage().isPresent()) {
1489 SignalServiceDataMessage message
= content
.getDataMessage().get();
1490 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1491 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1492 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1493 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1501 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1502 if (content
!= null) {
1503 SignalServiceAddress sender
;
1504 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1505 sender
= envelope
.getSourceAddress();
1507 sender
= content
.getSender();
1509 if (content
.getDataMessage().isPresent()) {
1510 SignalServiceDataMessage message
= content
.getDataMessage().get();
1512 if (content
.isNeedsReceipt()) {
1514 sendReceipt(sender
, message
.getTimestamp());
1515 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1516 e
.printStackTrace();
1520 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1522 if (content
.getSyncMessage().isPresent()) {
1523 account
.setMultiDevice(true);
1524 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1525 if (syncMessage
.getSent().isPresent()) {
1526 SentTranscriptMessage message
= syncMessage
.getSent().get();
1527 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1529 if (syncMessage
.getRequest().isPresent()) {
1530 RequestMessage rm
= syncMessage
.getRequest().get();
1531 if (rm
.isContactsRequest()) {
1534 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1535 e
.printStackTrace();
1538 if (rm
.isGroupsRequest()) {
1541 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1542 e
.printStackTrace();
1545 if (rm
.isBlockedListRequest()) {
1548 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1549 e
.printStackTrace();
1552 // TODO Handle rm.isConfigurationRequest();
1554 if (syncMessage
.getGroups().isPresent()) {
1555 File tmpFile
= null;
1557 tmpFile
= IOUtils
.createTempFile();
1558 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1559 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1561 while ((g
= s
.read()) != null) {
1562 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1563 if (syncGroup
== null) {
1564 syncGroup
= new GroupInfo(g
.getId());
1566 if (g
.getName().isPresent()) {
1567 syncGroup
.name
= g
.getName().get();
1569 syncGroup
.addMembers(g
.getMembers()
1571 .map(this::resolveSignalServiceAddress
)
1572 .collect(Collectors
.toSet()));
1573 if (!g
.isActive()) {
1574 syncGroup
.removeMember(account
.getSelfAddress());
1576 // Add ourself to the member set as it's marked as active
1577 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1579 syncGroup
.blocked
= g
.isBlocked();
1580 if (g
.getColor().isPresent()) {
1581 syncGroup
.color
= g
.getColor().get();
1584 if (g
.getAvatar().isPresent()) {
1585 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1587 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1588 syncGroup
.archived
= g
.isArchived();
1589 account
.getGroupStore().updateGroup(syncGroup
);
1592 } catch (Exception e
) {
1593 e
.printStackTrace();
1595 if (tmpFile
!= null) {
1597 Files
.delete(tmpFile
.toPath());
1598 } catch (IOException e
) {
1599 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1604 if (syncMessage
.getBlockedList().isPresent()) {
1605 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1606 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1607 setContactBlocked(resolveSignalServiceAddress(address
), true);
1609 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1611 setGroupBlocked(groupId
, true);
1612 } catch (GroupNotFoundException e
) {
1613 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1617 if (syncMessage
.getContacts().isPresent()) {
1618 File tmpFile
= null;
1620 tmpFile
= IOUtils
.createTempFile();
1621 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1622 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1623 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1624 if (contactsMessage
.isComplete()) {
1625 account
.getContactStore().clear();
1628 while ((c
= s
.read()) != null) {
1629 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1630 account
.setProfileKey(c
.getProfileKey().get());
1632 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1633 ContactInfo contact
= account
.getContactStore().getContact(address
);
1634 if (contact
== null) {
1635 contact
= new ContactInfo(address
);
1637 if (c
.getName().isPresent()) {
1638 contact
.name
= c
.getName().get();
1640 if (c
.getColor().isPresent()) {
1641 contact
.color
= c
.getColor().get();
1643 if (c
.getProfileKey().isPresent()) {
1644 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1646 if (c
.getVerified().isPresent()) {
1647 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1648 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1650 if (c
.getExpirationTimer().isPresent()) {
1651 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1653 contact
.blocked
= c
.isBlocked();
1654 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1655 contact
.archived
= c
.isArchived();
1656 account
.getContactStore().updateContact(contact
);
1658 if (c
.getAvatar().isPresent()) {
1659 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1663 } catch (Exception e
) {
1664 e
.printStackTrace();
1666 if (tmpFile
!= null) {
1668 Files
.delete(tmpFile
.toPath());
1669 } catch (IOException e
) {
1670 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1675 if (syncMessage
.getVerified().isPresent()) {
1676 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1677 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1679 if (syncMessage
.getConfiguration().isPresent()) {
1686 private File
getContactAvatarFile(String number
) {
1687 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1690 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1691 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1692 if (attachment
.isPointer()) {
1693 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1694 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1696 SignalServiceAttachmentStream stream
= attachment
.asStream();
1697 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1701 private File
getGroupAvatarFile(byte[] groupId
) {
1702 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1705 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1706 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1707 if (attachment
.isPointer()) {
1708 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1709 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1711 SignalServiceAttachmentStream stream
= attachment
.asStream();
1712 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1716 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1717 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1720 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1721 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1722 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1725 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1726 if (storePreview
&& pointer
.getPreview().isPresent()) {
1727 File previewFile
= new File(outputFile
+ ".preview");
1728 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1729 byte[] preview
= pointer
.getPreview().get();
1730 output
.write(preview
, 0, preview
.length
);
1731 } catch (FileNotFoundException e
) {
1732 e
.printStackTrace();
1737 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1739 File tmpFile
= IOUtils
.createTempFile();
1740 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1741 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1742 byte[] buffer
= new byte[4096];
1745 while ((read
= input
.read(buffer
)) != -1) {
1746 output
.write(buffer
, 0, read
);
1748 } catch (FileNotFoundException e
) {
1749 e
.printStackTrace();
1754 Files
.delete(tmpFile
.toPath());
1755 } catch (IOException e
) {
1756 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1762 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1763 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1764 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1768 public boolean isRemote() {
1773 public String
getObjectPath() {
1777 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1778 File groupsFile
= IOUtils
.createTempFile();
1781 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1782 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1783 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1784 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1785 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1786 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1787 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1791 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1792 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1793 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1794 .withStream(groupsFileStream
)
1795 .withContentType("application/octet-stream")
1796 .withLength(groupsFile
.length())
1799 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1804 Files
.delete(groupsFile
.toPath());
1805 } catch (IOException e
) {
1806 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1811 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1812 File contactsFile
= IOUtils
.createTempFile();
1815 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1816 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1817 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1818 VerifiedMessage verifiedMessage
= null;
1819 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1820 if (currentIdentity
!= null) {
1821 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1824 ProfileKey profileKey
= null;
1826 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1827 } catch (InvalidInputException ignored
) {
1829 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1830 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1831 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1832 Optional
.of(record.messageExpirationTime
),
1833 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1836 if (account
.getProfileKey() != null) {
1837 // Send our own profile key as well
1838 out
.write(new DeviceContact(account
.getSelfAddress(),
1839 Optional
.absent(), Optional
.absent(),
1840 Optional
.absent(), Optional
.absent(),
1841 Optional
.of(account
.getProfileKey()),
1842 false, Optional
.absent(), Optional
.absent(), false));
1846 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1847 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1848 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1849 .withStream(contactsFileStream
)
1850 .withContentType("application/octet-stream")
1851 .withLength(contactsFile
.length())
1854 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1859 Files
.delete(contactsFile
.toPath());
1860 } catch (IOException e
) {
1861 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1866 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1867 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1868 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1869 if (record.blocked
) {
1870 addresses
.add(record.getAddress());
1873 List
<byte[]> groupIds
= new ArrayList
<>();
1874 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1875 if (record.blocked
) {
1876 groupIds
.add(record.groupId
);
1879 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1882 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1883 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1884 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1887 public List
<ContactInfo
> getContacts() {
1888 return account
.getContactStore().getContacts();
1891 public ContactInfo
getContact(String number
) {
1892 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1895 public GroupInfo
getGroup(byte[] groupId
) {
1896 return account
.getGroupStore().getGroup(groupId
);
1899 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1900 return account
.getSignalProtocolStore().getIdentities();
1903 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1904 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1908 * Trust this the identity with this fingerprint
1910 * @param name username of the identity
1911 * @param fingerprint Fingerprint
1913 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1914 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1915 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1919 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1920 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1924 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1926 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1927 } catch (IOException
| UntrustedIdentityException e
) {
1928 e
.printStackTrace();
1937 * Trust this the identity with this safety number
1939 * @param name username of the identity
1940 * @param safetyNumber Safety number
1942 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1943 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1944 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1948 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1949 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1953 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1955 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1956 } catch (IOException
| UntrustedIdentityException e
) {
1957 e
.printStackTrace();
1966 * Trust all keys of this identity without verification
1968 * @param name username of the identity
1970 public boolean trustIdentityAllKeys(String name
) {
1971 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1972 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1976 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1977 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1978 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1980 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1981 } catch (IOException
| UntrustedIdentityException e
) {
1982 e
.printStackTrace();
1990 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1991 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1994 void saveAccount() {
1998 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1999 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2000 return resolveSignalServiceAddress(canonicalizedNumber
);
2003 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2004 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2006 return resolveSignalServiceAddress(address
);
2009 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2010 if (address
.matches(account
.getSelfAddress())) {
2011 return account
.getSelfAddress();
2014 return account
.getRecipientStore().resolveServiceAddress(address
);
2018 public void close() throws IOException
{
2019 if (messagePipe
!= null) {
2020 messagePipe
.shutdown();
2024 if (unidentifiedMessagePipe
!= null) {
2025 unidentifiedMessagePipe
.shutdown();
2026 unidentifiedMessagePipe
= null;
2032 public interface ReceiveMessageHandler
{
2034 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);