2 Copyright (C) 2015-2020 AsamK and contributors
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org
.asamk
.signal
.manager
;
19 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
21 import org
.asamk
.signal
.storage
.SignalAccount
;
22 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
23 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
24 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
25 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
26 import org
.asamk
.signal
.util
.IOUtils
;
27 import org
.asamk
.signal
.util
.Util
;
28 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
29 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
30 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
31 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
38 import org
.signal
.libsignal
.metadata
.SelfSendException
;
39 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
40 import org
.signal
.zkgroup
.InvalidInputException
;
41 import org
.signal
.zkgroup
.VerificationFailedException
;
42 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
43 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
44 import org
.whispersystems
.libsignal
.IdentityKey
;
45 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
46 import org
.whispersystems
.libsignal
.InvalidKeyException
;
47 import org
.whispersystems
.libsignal
.InvalidMessageException
;
48 import org
.whispersystems
.libsignal
.InvalidVersionException
;
49 import org
.whispersystems
.libsignal
.ecc
.Curve
;
50 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
51 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
52 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
53 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
54 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
55 import org
.whispersystems
.libsignal
.util
.Medium
;
56 import org
.whispersystems
.libsignal
.util
.Pair
;
57 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
93 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
94 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
95 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
105 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
106 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
107 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
108 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
109 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
110 import org
.whispersystems
.util
.Base64
;
112 import java
.io
.Closeable
;
114 import java
.io
.FileInputStream
;
115 import java
.io
.FileNotFoundException
;
116 import java
.io
.FileOutputStream
;
117 import java
.io
.IOException
;
118 import java
.io
.InputStream
;
119 import java
.io
.OutputStream
;
121 import java
.net
.URISyntaxException
;
122 import java
.net
.URLEncoder
;
123 import java
.nio
.file
.Files
;
124 import java
.nio
.file
.Paths
;
125 import java
.nio
.file
.StandardCopyOption
;
126 import java
.util
.ArrayList
;
127 import java
.util
.Arrays
;
128 import java
.util
.Collection
;
129 import java
.util
.Collections
;
130 import java
.util
.Date
;
131 import java
.util
.HashSet
;
132 import java
.util
.LinkedList
;
133 import java
.util
.List
;
134 import java
.util
.Locale
;
135 import java
.util
.Objects
;
136 import java
.util
.Set
;
137 import java
.util
.UUID
;
138 import java
.util
.concurrent
.TimeUnit
;
139 import java
.util
.concurrent
.TimeoutException
;
140 import java
.util
.stream
.Collectors
;
141 import java
.util
.zip
.ZipEntry
;
142 import java
.util
.zip
.ZipFile
;
144 public class Manager
implements Closeable
{
146 private final SleepTimer timer
= new UptimeSleepTimer();
147 private final SignalServiceConfiguration serviceConfiguration
;
148 private final String userAgent
;
150 private final SignalAccount account
;
151 private final PathConfig pathConfig
;
152 private SignalServiceAccountManager accountManager
;
153 private SignalServiceMessagePipe messagePipe
= null;
154 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
156 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
157 this.account
= account
;
158 this.pathConfig
= pathConfig
;
159 this.serviceConfiguration
= serviceConfiguration
;
160 this.userAgent
= userAgent
;
161 this.accountManager
= createSignalServiceAccountManager();
163 this.account
.setResolver(this::resolveSignalServiceAddress
);
166 public String
getUsername() {
167 return account
.getUsername();
170 public SignalServiceAddress
getSelfAddress() {
171 return account
.getSelfAddress();
174 private SignalServiceAccountManager
createSignalServiceAccountManager() {
175 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
178 private IdentityKeyPair
getIdentityKeyPair() {
179 return account
.getSignalProtocolStore().getIdentityKeyPair();
182 public int getDeviceId() {
183 return account
.getDeviceId();
186 private String
getMessageCachePath() {
187 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
190 private String
getMessageCachePath(String sender
) {
191 if (sender
== null || sender
.isEmpty()) {
192 return getMessageCachePath();
195 return getMessageCachePath() + "/" + sender
.replace("/", "_");
198 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
199 String cachePath
= getMessageCachePath(sender
);
200 IOUtils
.createPrivateDirectories(cachePath
);
201 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
204 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
205 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
207 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
208 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
209 int registrationId
= KeyHelper
.generateRegistrationId(false);
211 ProfileKey profileKey
= KeyUtils
.createProfileKey();
212 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
215 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
218 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
220 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
222 m
.migrateLegacyConfigs();
227 private void migrateLegacyConfigs() {
228 // Copy group avatars that were previously stored in the attachments folder
229 // to the new avatar folder
230 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
231 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
232 File avatarFile
= getGroupAvatarFile(g
.groupId
);
233 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
234 if (!avatarFile
.exists() && attachmentFile
.exists()) {
236 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
237 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
238 } catch (Exception e
) {
243 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
246 if (account
.getProfileKey() == null) {
247 // Old config file, creating new profile key
248 account
.setProfileKey(KeyUtils
.createProfileKey());
253 public void checkAccountState() throws IOException
{
254 if (account
.isRegistered()) {
255 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
259 if (account
.getUuid() == null) {
260 account
.setUuid(accountManager
.getOwnUuid());
266 public boolean isRegistered() {
267 return account
.isRegistered();
270 public void register(boolean voiceVerification
) throws IOException
{
271 account
.setPassword(KeyUtils
.createPassword());
273 // Resetting UUID, because registering doesn't work otherwise
274 account
.setUuid(null);
275 accountManager
= createSignalServiceAccountManager();
277 if (voiceVerification
) {
278 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
280 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
283 account
.setRegistered(false);
287 public void updateAccountAttributes() throws IOException
{
288 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
291 public void setProfileName(String name
) throws IOException
{
292 accountManager
.setProfileName(account
.getProfileKey(), name
);
295 public void setProfileAvatar(File avatar
) throws IOException
{
296 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
297 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
298 streamDetails
.getStream().close();
301 public void removeProfileAvatar() throws IOException
{
302 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
305 public void unregister() throws IOException
{
306 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
307 // If this is the master device, other users can't send messages to this number anymore.
308 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
309 accountManager
.setGcmId(Optional
.absent());
311 account
.setRegistered(false);
315 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
316 List
<DeviceInfo
> devices
= accountManager
.getDevices();
317 account
.setMultiDevice(devices
.size() > 1);
322 public void removeLinkedDevices(int deviceId
) throws IOException
{
323 accountManager
.removeDevice(deviceId
);
324 List
<DeviceInfo
> devices
= accountManager
.getDevices();
325 account
.setMultiDevice(devices
.size() > 1);
329 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
330 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
332 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
335 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
336 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
337 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
339 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
340 account
.setMultiDevice(true);
344 private List
<PreKeyRecord
> generatePreKeys() {
345 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
347 final int offset
= account
.getPreKeyIdOffset();
348 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
349 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
350 ECKeyPair keyPair
= Curve
.generateKeyPair();
351 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
356 account
.addPreKeys(records
);
362 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
364 ECKeyPair keyPair
= Curve
.generateKeyPair();
365 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
366 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
368 account
.addSignedPreKey(record);
372 } catch (InvalidKeyException e
) {
373 throw new AssertionError(e
);
377 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
378 verificationCode
= verificationCode
.replace("-", "");
379 account
.setSignalingKey(KeyUtils
.createSignalingKey());
380 // TODO make unrestricted unidentified access configurable
381 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
383 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
384 // TODO response.isStorageCapable()
385 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
386 account
.setRegistered(true);
387 account
.setUuid(uuid
);
388 account
.setRegistrationLockPin(pin
);
389 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
395 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
396 if (pin
.isPresent()) {
397 account
.setRegistrationLockPin(pin
.get());
398 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
400 account
.setRegistrationLockPin(null);
401 accountManager
.removeRegistrationLockV1();
406 void refreshPreKeys() throws IOException
{
407 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
408 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
409 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
411 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
414 private SignalServiceMessageReceiver
getMessageReceiver() {
415 // TODO implement ZkGroup support
416 final ClientZkProfileOperations clientZkProfileOperations
= null;
417 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
420 private SignalServiceMessageSender
getMessageSender() {
421 // TODO implement ZkGroup support
422 final ClientZkProfileOperations clientZkProfileOperations
= null;
423 final boolean attachmentsV3
= false;
424 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
425 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
428 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
429 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
434 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
435 } catch (IOException ignored
) {
439 SignalServiceMessageReceiver receiver
= getMessageReceiver();
441 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
442 } catch (VerificationFailedException e
) {
443 throw new AssertionError(e
);
447 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
448 File file
= getGroupAvatarFile(groupId
);
449 if (!file
.exists()) {
450 return Optional
.absent();
453 return Optional
.of(Utils
.createAttachment(file
));
456 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
457 File file
= getContactAvatarFile(number
);
458 if (!file
.exists()) {
459 return Optional
.absent();
462 return Optional
.of(Utils
.createAttachment(file
));
465 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
466 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
468 throw new GroupNotFoundException(groupId
);
470 if (!g
.isMember(account
.getSelfAddress())) {
471 throw new NotAGroupMemberException(groupId
, g
.name
);
476 public List
<GroupInfo
> getGroups() {
477 return account
.getGroupStore().getGroups();
480 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
482 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
483 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
484 if (attachments
!= null) {
485 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
487 if (groupId
!= null) {
488 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
491 messageBuilder
.asGroupMessage(group
);
494 final GroupInfo g
= getGroupForSending(groupId
);
496 messageBuilder
.withExpiration(g
.messageExpirationTime
);
498 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
501 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
502 long targetSentTimestamp
, byte[] groupId
)
503 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
504 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
505 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
506 .withReaction(reaction
);
507 if (groupId
!= null) {
508 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
511 messageBuilder
.asGroupMessage(group
);
513 final GroupInfo g
= getGroupForSending(groupId
);
514 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
517 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
518 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
522 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
523 .asGroupMessage(group
);
525 final GroupInfo g
= getGroupForSending(groupId
);
526 g
.removeMember(account
.getSelfAddress());
527 account
.getGroupStore().updateGroup(g
);
529 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
532 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
534 if (groupId
== null) {
536 g
= new GroupInfo(KeyUtils
.createGroupId());
537 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
539 g
= getGroupForSending(groupId
);
546 if (members
!= null) {
547 final Set
<String
> newE164Members
= new HashSet
<>();
548 for (SignalServiceAddress member
: members
) {
549 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
552 newE164Members
.add(member
.getNumber().get());
555 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
556 if (contacts
.size() != newE164Members
.size()) {
557 // Some of the new members are not registered on Signal
558 for (ContactTokenDetails contact
: contacts
) {
559 newE164Members
.remove(contact
.getNumber());
561 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
562 System
.err
.println("Aborting…");
566 g
.addMembers(members
);
569 if (avatarFile
!= null) {
570 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
571 File aFile
= getGroupAvatarFile(g
.groupId
);
572 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
575 account
.getGroupStore().updateGroup(g
);
577 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
579 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
583 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
584 if (groupId
== null) {
587 GroupInfo g
= getGroupForSending(groupId
);
589 if (!g
.isMember(recipient
)) {
593 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
595 // Send group message only to the recipient who requested it
596 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
599 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
600 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
603 .withMembers(new ArrayList
<>(g
.getMembers()));
605 File aFile
= getGroupAvatarFile(g
.groupId
);
606 if (aFile
.exists()) {
608 group
.withAvatar(Utils
.createAttachment(aFile
));
609 } catch (IOException e
) {
610 throw new AttachmentInvalidException(aFile
.toString(), e
);
614 return SignalServiceDataMessage
.newBuilder()
615 .asGroupMessage(group
.build())
616 .withExpiration(g
.messageExpirationTime
);
619 private void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
620 if (groupId
== null) {
624 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
627 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
628 .asGroupMessage(group
.build());
630 // Send group info request message to the recipient who sent us a message with this groupId
631 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
634 private void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
635 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
636 Collections
.singletonList(messageId
),
637 System
.currentTimeMillis());
639 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
642 public long sendMessage(String messageText
, List
<String
> attachments
,
643 List
<String
> recipients
)
644 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
645 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
646 if (attachments
!= null) {
647 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
649 // Upload attachments here, so we only upload once even for multiple recipients
650 SignalServiceMessageSender messageSender
= getMessageSender();
651 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
652 for (SignalServiceAttachment attachment
: attachmentStreams
) {
653 if (attachment
.isStream()) {
654 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
655 } else if (attachment
.isPointer()) {
656 attachmentPointers
.add(attachment
.asPointer());
660 messageBuilder
.withAttachments(attachmentPointers
);
662 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
665 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
666 long targetSentTimestamp
, List
<String
> recipients
)
667 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
668 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
669 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
670 .withReaction(reaction
);
671 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
674 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
675 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
676 .asEndSessionMessage();
678 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
680 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
681 } catch (Exception e
) {
682 for (SignalServiceAddress address
: signalServiceAddresses
) {
683 handleEndSession(address
);
690 public String
getContactName(String number
) throws InvalidNumberException
{
691 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
692 if (contact
== null) {
699 public void setContactName(String number
, String name
) throws InvalidNumberException
{
700 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
701 ContactInfo contact
= account
.getContactStore().getContact(address
);
702 if (contact
== null) {
703 contact
= new ContactInfo(address
);
704 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
706 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
709 account
.getContactStore().updateContact(contact
);
713 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
714 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
717 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
718 ContactInfo contact
= account
.getContactStore().getContact(address
);
719 if (contact
== null) {
720 contact
= new ContactInfo(address
);
721 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
723 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
725 contact
.blocked
= blocked
;
726 account
.getContactStore().updateContact(contact
);
730 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
731 GroupInfo group
= getGroup(groupId
);
733 throw new GroupNotFoundException(groupId
);
735 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
736 group
.blocked
= blocked
;
737 account
.getGroupStore().updateGroup(group
);
742 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
743 if (groupId
.length
== 0) {
746 if (name
.isEmpty()) {
749 if (members
.size() == 0) {
752 if (avatar
.isEmpty()) {
755 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
759 * Change the expiration timer for a contact
761 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
762 ContactInfo contact
= account
.getContactStore().getContact(address
);
763 contact
.messageExpirationTime
= messageExpirationTimer
;
764 account
.getContactStore().updateContact(contact
);
765 sendExpirationTimerUpdate(address
);
769 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
770 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
771 .asExpirationUpdate();
772 sendMessage(messageBuilder
, Collections
.singleton(address
));
776 * Change the expiration timer for a contact
778 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
779 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
780 setExpirationTimer(address
, messageExpirationTimer
);
784 * Change the expiration timer for a group
786 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
787 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
788 g
.messageExpirationTime
= messageExpirationTimer
;
789 account
.getGroupStore().updateGroup(g
);
793 * Upload the sticker pack from path.
795 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
796 * @return if successful, returns the URL to install the sticker pack in the signal app
798 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
799 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
801 SignalServiceMessageSender messageSender
= getMessageSender();
803 byte[] packKey
= KeyUtils
.createStickerUploadKey();
804 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
807 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
809 } catch (URISyntaxException e
) {
810 throw new AssertionError(e
);
814 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
816 String rootPath
= null;
818 final File file
= new File(path
);
819 if (file
.getName().endsWith(".zip")) {
820 zip
= new ZipFile(file
);
821 } else if (file
.getName().equals("manifest.json")) {
822 rootPath
= file
.getParent();
824 throw new StickerPackInvalidException("Could not find manifest.json");
827 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
829 if (pack
.stickers
== null) {
830 throw new StickerPackInvalidException("Must set a 'stickers' field.");
833 if (pack
.stickers
.isEmpty()) {
834 throw new StickerPackInvalidException("Must include stickers.");
837 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
838 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
839 if (sticker
.file
== null) {
840 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
843 Pair
<InputStream
, Long
> data
;
845 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
846 } catch (IOException ignored
) {
847 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
850 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
851 stickers
.add(stickerInfo
);
854 StickerInfo cover
= null;
855 if (pack
.cover
!= null) {
856 if (pack
.cover
.file
== null) {
857 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
860 Pair
<InputStream
, Long
> data
;
862 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
863 } catch (IOException ignored
) {
864 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
867 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
870 return new SignalServiceStickerManifestUpload(
877 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
878 InputStream inputStream
;
880 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
882 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
884 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
887 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
889 final ZipEntry entry
= zip
.getEntry(subfile
);
890 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
892 final File file
= new File(rootPath
, subfile
);
893 return new Pair
<>(new FileInputStream(file
), file
.length());
897 void requestSyncGroups() throws IOException
{
898 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
899 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
901 sendSyncMessage(message
);
902 } catch (UntrustedIdentityException e
) {
907 void requestSyncContacts() throws IOException
{
908 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
909 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
911 sendSyncMessage(message
);
912 } catch (UntrustedIdentityException e
) {
917 void requestSyncBlocked() throws IOException
{
918 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
919 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
921 sendSyncMessage(message
);
922 } catch (UntrustedIdentityException e
) {
927 void requestSyncConfiguration() throws IOException
{
928 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
929 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
931 sendSyncMessage(message
);
932 } catch (UntrustedIdentityException e
) {
937 private byte[] getSenderCertificate() {
938 // TODO support UUID capable sender certificates
939 // byte[] certificate = accountManager.getSenderCertificate();
942 certificate
= accountManager
.getSenderCertificateLegacy();
943 } catch (IOException e
) {
944 System
.err
.println("Failed to get sender certificate: " + e
);
947 // TODO cache for a day
951 private byte[] getSelfUnidentifiedAccessKey() {
952 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
955 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
956 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
958 return new SignalProfile(
959 encryptedProfile
.getIdentityKey(),
960 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
961 encryptedProfile
.getAvatar(),
962 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
963 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
965 } catch (InvalidCiphertextException e
) {
970 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
971 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
972 if (contact
== null || contact
.profileKey
== null) {
975 ProfileKey theirProfileKey
;
977 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
978 } catch (InvalidInputException
| IOException e
) {
979 throw new AssertionError(e
);
981 SignalProfile targetProfile
;
983 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
984 } catch (IOException e
) {
985 System
.err
.println("Failed to get recipient profile: " + e
);
989 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
993 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
994 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
997 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1000 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1001 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1002 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1004 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1005 return Optional
.absent();
1009 return Optional
.of(new UnidentifiedAccessPair(
1010 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1011 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1013 } catch (InvalidCertificateException e
) {
1014 return Optional
.absent();
1018 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1019 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1020 for (SignalServiceAddress recipient
: recipients
) {
1021 result
.add(getAccessFor(recipient
));
1026 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1027 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1028 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1029 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1031 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1032 return Optional
.absent();
1036 return Optional
.of(new UnidentifiedAccessPair(
1037 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1038 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1040 } catch (InvalidCertificateException e
) {
1041 return Optional
.absent();
1045 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1046 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1048 if (unidentifiedAccess
.isPresent()) {
1049 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1052 return Optional
.absent();
1055 private void sendSyncMessage(SignalServiceSyncMessage message
)
1056 throws IOException
, UntrustedIdentityException
{
1057 SignalServiceMessageSender messageSender
= getMessageSender();
1059 messageSender
.sendMessage(message
, getAccessForSync());
1060 } catch (UntrustedIdentityException e
) {
1061 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1067 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1069 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1070 throws EncapsulatedExceptions
, IOException
{
1071 final long timestamp
= System
.currentTimeMillis();
1072 messageBuilder
.withTimestamp(timestamp
);
1073 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1075 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1076 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1077 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1079 for (SendMessageResult result
: results
) {
1080 if (result
.isUnregisteredFailure()) {
1081 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1082 } else if (result
.isNetworkFailure()) {
1083 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1084 } else if (result
.getIdentityFailure() != null) {
1085 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1088 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1089 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1094 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1095 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1097 for (String number
: numbers
) {
1098 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1100 return signalServiceAddresses
;
1103 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1104 throws IOException
{
1105 if (messagePipe
== null) {
1106 messagePipe
= getMessageReceiver().createMessagePipe();
1108 if (unidentifiedMessagePipe
== null) {
1109 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1111 SignalServiceDataMessage message
= null;
1113 SignalServiceMessageSender messageSender
= getMessageSender();
1115 message
= messageBuilder
.build();
1116 if (message
.getGroupContext().isPresent()) {
1118 final boolean isRecipientUpdate
= false;
1119 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1120 for (SendMessageResult r
: result
) {
1121 if (r
.getIdentityFailure() != null) {
1122 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1126 } catch (UntrustedIdentityException e
) {
1127 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1128 return Collections
.emptyList();
1130 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1131 SignalServiceAddress recipient
= account
.getSelfAddress();
1132 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1133 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1134 message
.getTimestamp(),
1136 message
.getExpiresInSeconds(),
1137 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1139 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1141 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1143 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1144 } catch (UntrustedIdentityException e
) {
1145 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1146 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1150 // Send to all individually, so sync messages are sent correctly
1151 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1152 for (SignalServiceAddress address
: recipients
) {
1153 ContactInfo contact
= account
.getContactStore().getContact(address
);
1154 if (contact
!= null) {
1155 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1156 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1158 messageBuilder
.withExpiration(0);
1159 messageBuilder
.withProfileKey(null);
1161 message
= messageBuilder
.build();
1163 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1164 results
.add(result
);
1165 } catch (UntrustedIdentityException e
) {
1166 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1167 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1173 if (message
!= null && message
.isEndSession()) {
1174 for (SignalServiceAddress recipient
: recipients
) {
1175 handleEndSession(recipient
);
1182 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1183 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1185 return cipher
.decrypt(envelope
);
1186 } catch (ProtocolUntrustedIdentityException e
) {
1187 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1188 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1189 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1190 throw identityException
;
1192 throw new AssertionError(e
);
1196 private void handleEndSession(SignalServiceAddress source
) {
1197 account
.getSignalProtocolStore().deleteAllSessions(source
);
1200 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1201 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1202 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1203 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1204 switch (groupInfo
.getType()) {
1206 if (group
== null) {
1207 group
= new GroupInfo(groupInfo
.getGroupId());
1210 if (groupInfo
.getAvatar().isPresent()) {
1211 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1212 if (avatar
.isPointer()) {
1214 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1215 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1216 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1221 if (groupInfo
.getName().isPresent()) {
1222 group
.name
= groupInfo
.getName().get();
1225 if (groupInfo
.getMembers().isPresent()) {
1226 group
.addMembers(groupInfo
.getMembers().get()
1228 .map(this::resolveSignalServiceAddress
)
1229 .collect(Collectors
.toSet()));
1232 account
.getGroupStore().updateGroup(group
);
1235 if (group
== null) {
1237 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1238 } catch (IOException
| EncapsulatedExceptions e
) {
1239 e
.printStackTrace();
1244 if (group
!= null) {
1245 group
.removeMember(source
);
1246 account
.getGroupStore().updateGroup(group
);
1250 if (group
!= null) {
1252 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1253 } catch (IOException
| EncapsulatedExceptions
| AttachmentInvalidException e
) {
1254 e
.printStackTrace();
1255 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
1256 // We have left this group, so don't send a group update message
1262 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1263 if (message
.isEndSession()) {
1264 handleEndSession(conversationPartnerAddress
);
1266 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1267 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1268 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1269 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1270 if (group
== null) {
1271 group
= new GroupInfo(groupInfo
.getGroupId());
1273 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1274 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1275 account
.getGroupStore().updateGroup(group
);
1278 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1279 if (contact
== null) {
1280 contact
= new ContactInfo(conversationPartnerAddress
);
1282 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1283 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1284 account
.getContactStore().updateContact(contact
);
1288 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1289 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1290 if (attachment
.isPointer()) {
1292 retrieveAttachment(attachment
.asPointer());
1293 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1294 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1299 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1300 if (source
.matches(account
.getSelfAddress())) {
1302 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1303 } catch (InvalidInputException ignored
) {
1305 ContactInfo contact
= account
.getContactStore().getContact(source
);
1306 if (contact
!= null) {
1307 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1308 account
.getContactStore().updateContact(contact
);
1311 ContactInfo contact
= account
.getContactStore().getContact(source
);
1312 if (contact
== null) {
1313 contact
= new ContactInfo(source
);
1315 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1316 account
.getContactStore().updateContact(contact
);
1319 if (message
.getPreviews().isPresent()) {
1320 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1321 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1322 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1323 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1325 retrieveAttachment(attachment
);
1326 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1327 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1334 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1335 final File cachePath
= new File(getMessageCachePath());
1336 if (!cachePath
.exists()) {
1339 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1340 if (!dir
.isDirectory()) {
1341 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1345 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1346 if (!fileEntry
.isFile()) {
1349 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1351 // Try to delete directory if empty
1356 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1357 SignalServiceEnvelope envelope
;
1359 envelope
= Utils
.loadEnvelope(fileEntry
);
1360 if (envelope
== null) {
1363 } catch (IOException e
) {
1364 e
.printStackTrace();
1367 SignalServiceContent content
= null;
1368 if (!envelope
.isReceipt()) {
1370 content
= decryptMessage(envelope
);
1371 } catch (Exception e
) {
1374 handleMessage(envelope
, content
, ignoreAttachments
);
1377 handler
.handleMessage(envelope
, content
, null);
1379 Files
.delete(fileEntry
.toPath());
1380 } catch (IOException e
) {
1381 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1385 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1386 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1387 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1389 if (messagePipe
== null) {
1390 messagePipe
= messageReceiver
.createMessagePipe();
1394 SignalServiceEnvelope envelope
;
1395 SignalServiceContent content
= null;
1396 Exception exception
= null;
1397 final long now
= new Date().getTime();
1399 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1400 // store message on disk, before acknowledging receipt to the server
1402 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1403 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1404 Utils
.storeEnvelope(envelope1
, cacheFile
);
1405 } catch (IOException e
) {
1406 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1409 } catch (TimeoutException e
) {
1410 if (returnOnTimeout
)
1413 } catch (InvalidVersionException e
) {
1414 System
.err
.println("Ignoring error: " + e
.getMessage());
1417 if (envelope
.hasSource()) {
1418 // Store uuid if we don't have it already
1419 SignalServiceAddress source
= envelope
.getSourceAddress();
1420 resolveSignalServiceAddress(source
);
1422 if (!envelope
.isReceipt()) {
1424 content
= decryptMessage(envelope
);
1425 } catch (Exception e
) {
1428 handleMessage(envelope
, content
, ignoreAttachments
);
1431 if (!isMessageBlocked(envelope
, content
)) {
1432 handler
.handleMessage(envelope
, content
, exception
);
1434 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1435 File cacheFile
= null;
1437 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1438 Files
.delete(cacheFile
.toPath());
1439 // Try to delete directory if empty
1440 new File(getMessageCachePath()).delete();
1441 } catch (IOException e
) {
1442 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1448 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1449 SignalServiceAddress source
;
1450 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1451 source
= envelope
.getSourceAddress();
1452 } else if (content
!= null) {
1453 source
= content
.getSender();
1457 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1458 if (sourceContact
!= null && sourceContact
.blocked
) {
1462 if (content
!= null && content
.getDataMessage().isPresent()) {
1463 SignalServiceDataMessage message
= content
.getDataMessage().get();
1464 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1465 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1466 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1467 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1475 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1476 if (content
!= null) {
1477 SignalServiceAddress sender
;
1478 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1479 sender
= envelope
.getSourceAddress();
1481 sender
= content
.getSender();
1483 // Store uuid if we don't have it already
1484 resolveSignalServiceAddress(sender
);
1486 if (content
.getDataMessage().isPresent()) {
1487 SignalServiceDataMessage message
= content
.getDataMessage().get();
1489 if (content
.isNeedsReceipt()) {
1491 sendReceipt(sender
, message
.getTimestamp());
1492 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1493 e
.printStackTrace();
1497 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1499 if (content
.getSyncMessage().isPresent()) {
1500 account
.setMultiDevice(true);
1501 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1502 if (syncMessage
.getSent().isPresent()) {
1503 SentTranscriptMessage message
= syncMessage
.getSent().get();
1504 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1506 if (syncMessage
.getRequest().isPresent()) {
1507 RequestMessage rm
= syncMessage
.getRequest().get();
1508 if (rm
.isContactsRequest()) {
1511 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1512 e
.printStackTrace();
1515 if (rm
.isGroupsRequest()) {
1518 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1519 e
.printStackTrace();
1522 if (rm
.isBlockedListRequest()) {
1525 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1526 e
.printStackTrace();
1529 // TODO Handle rm.isConfigurationRequest();
1531 if (syncMessage
.getGroups().isPresent()) {
1532 File tmpFile
= null;
1534 tmpFile
= IOUtils
.createTempFile();
1535 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1536 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1538 while ((g
= s
.read()) != null) {
1539 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1540 if (syncGroup
== null) {
1541 syncGroup
= new GroupInfo(g
.getId());
1543 if (g
.getName().isPresent()) {
1544 syncGroup
.name
= g
.getName().get();
1546 syncGroup
.addMembers(g
.getMembers()
1548 .map(this::resolveSignalServiceAddress
)
1549 .collect(Collectors
.toSet()));
1550 if (!g
.isActive()) {
1551 syncGroup
.removeMember(account
.getSelfAddress());
1553 // Add ourself to the member set as it's marked as active
1554 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1556 syncGroup
.blocked
= g
.isBlocked();
1557 if (g
.getColor().isPresent()) {
1558 syncGroup
.color
= g
.getColor().get();
1561 if (g
.getAvatar().isPresent()) {
1562 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1564 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1565 syncGroup
.archived
= g
.isArchived();
1566 account
.getGroupStore().updateGroup(syncGroup
);
1569 } catch (Exception e
) {
1570 e
.printStackTrace();
1572 if (tmpFile
!= null) {
1574 Files
.delete(tmpFile
.toPath());
1575 } catch (IOException e
) {
1576 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1581 if (syncMessage
.getBlockedList().isPresent()) {
1582 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1583 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1584 setContactBlocked(resolveSignalServiceAddress(address
), true);
1586 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1588 setGroupBlocked(groupId
, true);
1589 } catch (GroupNotFoundException e
) {
1590 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1594 if (syncMessage
.getContacts().isPresent()) {
1595 File tmpFile
= null;
1597 tmpFile
= IOUtils
.createTempFile();
1598 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1599 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1600 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1601 if (contactsMessage
.isComplete()) {
1602 account
.getContactStore().clear();
1605 while ((c
= s
.read()) != null) {
1606 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1607 account
.setProfileKey(c
.getProfileKey().get());
1609 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1610 ContactInfo contact
= account
.getContactStore().getContact(address
);
1611 if (contact
== null) {
1612 contact
= new ContactInfo(address
);
1614 if (c
.getName().isPresent()) {
1615 contact
.name
= c
.getName().get();
1617 if (c
.getColor().isPresent()) {
1618 contact
.color
= c
.getColor().get();
1620 if (c
.getProfileKey().isPresent()) {
1621 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1623 if (c
.getVerified().isPresent()) {
1624 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1625 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1627 if (c
.getExpirationTimer().isPresent()) {
1628 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1630 contact
.blocked
= c
.isBlocked();
1631 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1632 contact
.archived
= c
.isArchived();
1633 account
.getContactStore().updateContact(contact
);
1635 if (c
.getAvatar().isPresent()) {
1636 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1640 } catch (Exception e
) {
1641 e
.printStackTrace();
1643 if (tmpFile
!= null) {
1645 Files
.delete(tmpFile
.toPath());
1646 } catch (IOException e
) {
1647 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1652 if (syncMessage
.getVerified().isPresent()) {
1653 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1654 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1656 if (syncMessage
.getConfiguration().isPresent()) {
1663 private File
getContactAvatarFile(String number
) {
1664 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1667 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1668 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1669 if (attachment
.isPointer()) {
1670 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1671 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1673 SignalServiceAttachmentStream stream
= attachment
.asStream();
1674 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1678 private File
getGroupAvatarFile(byte[] groupId
) {
1679 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1682 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1683 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1684 if (attachment
.isPointer()) {
1685 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1686 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1688 SignalServiceAttachmentStream stream
= attachment
.asStream();
1689 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1693 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1694 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1697 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1698 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1699 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1702 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1703 if (storePreview
&& pointer
.getPreview().isPresent()) {
1704 File previewFile
= new File(outputFile
+ ".preview");
1705 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1706 byte[] preview
= pointer
.getPreview().get();
1707 output
.write(preview
, 0, preview
.length
);
1708 } catch (FileNotFoundException e
) {
1709 e
.printStackTrace();
1714 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1716 File tmpFile
= IOUtils
.createTempFile();
1717 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1718 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1719 byte[] buffer
= new byte[4096];
1722 while ((read
= input
.read(buffer
)) != -1) {
1723 output
.write(buffer
, 0, read
);
1725 } catch (FileNotFoundException e
) {
1726 e
.printStackTrace();
1731 Files
.delete(tmpFile
.toPath());
1732 } catch (IOException e
) {
1733 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1739 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1740 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1741 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1744 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1745 File groupsFile
= IOUtils
.createTempFile();
1748 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1749 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1750 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1751 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1752 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1753 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1754 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1758 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1759 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1760 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1761 .withStream(groupsFileStream
)
1762 .withContentType("application/octet-stream")
1763 .withLength(groupsFile
.length())
1766 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1771 Files
.delete(groupsFile
.toPath());
1772 } catch (IOException e
) {
1773 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1778 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1779 File contactsFile
= IOUtils
.createTempFile();
1782 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1783 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1784 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1785 VerifiedMessage verifiedMessage
= null;
1786 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1787 if (currentIdentity
!= null) {
1788 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1791 ProfileKey profileKey
= null;
1793 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1794 } catch (InvalidInputException ignored
) {
1796 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1797 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1798 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1799 Optional
.of(record.messageExpirationTime
),
1800 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1803 if (account
.getProfileKey() != null) {
1804 // Send our own profile key as well
1805 out
.write(new DeviceContact(account
.getSelfAddress(),
1806 Optional
.absent(), Optional
.absent(),
1807 Optional
.absent(), Optional
.absent(),
1808 Optional
.of(account
.getProfileKey()),
1809 false, Optional
.absent(), Optional
.absent(), false));
1813 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1814 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1815 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1816 .withStream(contactsFileStream
)
1817 .withContentType("application/octet-stream")
1818 .withLength(contactsFile
.length())
1821 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1826 Files
.delete(contactsFile
.toPath());
1827 } catch (IOException e
) {
1828 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1833 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1834 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1835 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1836 if (record.blocked
) {
1837 addresses
.add(record.getAddress());
1840 List
<byte[]> groupIds
= new ArrayList
<>();
1841 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1842 if (record.blocked
) {
1843 groupIds
.add(record.groupId
);
1846 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1849 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1850 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1851 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1854 public List
<ContactInfo
> getContacts() {
1855 return account
.getContactStore().getContacts();
1858 public ContactInfo
getContact(String number
) {
1859 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1862 public GroupInfo
getGroup(byte[] groupId
) {
1863 return account
.getGroupStore().getGroup(groupId
);
1866 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1867 return account
.getSignalProtocolStore().getIdentities();
1870 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1871 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1875 * Trust this the identity with this fingerprint
1877 * @param name username of the identity
1878 * @param fingerprint Fingerprint
1880 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1881 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1882 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1886 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1887 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1891 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1893 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1894 } catch (IOException
| UntrustedIdentityException e
) {
1895 e
.printStackTrace();
1904 * Trust this the identity with this safety number
1906 * @param name username of the identity
1907 * @param safetyNumber Safety number
1909 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1910 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1911 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1915 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1916 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1920 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1922 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1923 } catch (IOException
| UntrustedIdentityException e
) {
1924 e
.printStackTrace();
1933 * Trust all keys of this identity without verification
1935 * @param name username of the identity
1937 public boolean trustIdentityAllKeys(String name
) {
1938 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1939 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1943 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1944 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1945 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1947 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1948 } catch (IOException
| UntrustedIdentityException e
) {
1949 e
.printStackTrace();
1957 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1958 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1961 void saveAccount() {
1965 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
1966 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
1967 return resolveSignalServiceAddress(canonicalizedNumber
);
1970 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
1971 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
1973 return resolveSignalServiceAddress(address
);
1976 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
1977 if (address
.matches(account
.getSelfAddress())) {
1978 return account
.getSelfAddress();
1981 return account
.getRecipientStore().resolveServiceAddress(address
);
1985 public void close() throws IOException
{
1986 if (messagePipe
!= null) {
1987 messagePipe
.shutdown();
1991 if (unidentifiedMessagePipe
!= null) {
1992 unidentifiedMessagePipe
.shutdown();
1993 unidentifiedMessagePipe
= null;
1999 public interface ReceiveMessageHandler
{
2001 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);