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
.profiles
.ClientZkProfileOperations
;
42 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
43 import org
.whispersystems
.libsignal
.IdentityKey
;
44 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
45 import org
.whispersystems
.libsignal
.InvalidKeyException
;
46 import org
.whispersystems
.libsignal
.InvalidMessageException
;
47 import org
.whispersystems
.libsignal
.InvalidVersionException
;
48 import org
.whispersystems
.libsignal
.ecc
.Curve
;
49 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
50 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
51 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
52 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
53 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
54 import org
.whispersystems
.libsignal
.util
.Medium
;
55 import org
.whispersystems
.libsignal
.util
.Pair
;
56 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
57 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
61 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
67 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
79 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
92 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
93 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
94 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
95 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
99 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
100 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
101 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
102 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
103 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
104 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
105 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
106 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
107 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
108 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
109 import org
.whispersystems
.util
.Base64
;
111 import java
.io
.Closeable
;
113 import java
.io
.FileInputStream
;
114 import java
.io
.FileNotFoundException
;
115 import java
.io
.FileOutputStream
;
116 import java
.io
.IOException
;
117 import java
.io
.InputStream
;
118 import java
.io
.OutputStream
;
120 import java
.net
.URISyntaxException
;
121 import java
.net
.URLEncoder
;
122 import java
.nio
.file
.Files
;
123 import java
.nio
.file
.Paths
;
124 import java
.nio
.file
.StandardCopyOption
;
125 import java
.util
.ArrayList
;
126 import java
.util
.Arrays
;
127 import java
.util
.Collection
;
128 import java
.util
.Collections
;
129 import java
.util
.Date
;
130 import java
.util
.HashSet
;
131 import java
.util
.LinkedList
;
132 import java
.util
.List
;
133 import java
.util
.Locale
;
134 import java
.util
.Objects
;
135 import java
.util
.Set
;
136 import java
.util
.UUID
;
137 import java
.util
.concurrent
.ExecutionException
;
138 import java
.util
.concurrent
.ExecutorService
;
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 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
147 public class Manager
implements Closeable
{
149 private final SleepTimer timer
= new UptimeSleepTimer();
150 private final SignalServiceConfiguration serviceConfiguration
;
151 private final String userAgent
;
153 private final SignalAccount account
;
154 private final PathConfig pathConfig
;
155 private SignalServiceAccountManager accountManager
;
156 private SignalServiceMessagePipe messagePipe
= null;
157 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
158 private boolean discoverableByPhoneNumber
= true;
160 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
161 this.account
= account
;
162 this.pathConfig
= pathConfig
;
163 this.serviceConfiguration
= serviceConfiguration
;
164 this.userAgent
= userAgent
;
165 this.accountManager
= createSignalServiceAccountManager();
167 this.account
.setResolver(this::resolveSignalServiceAddress
);
170 public String
getUsername() {
171 return account
.getUsername();
174 public SignalServiceAddress
getSelfAddress() {
175 return account
.getSelfAddress();
178 private SignalServiceAccountManager
createSignalServiceAccountManager() {
179 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
182 private IdentityKeyPair
getIdentityKeyPair() {
183 return account
.getSignalProtocolStore().getIdentityKeyPair();
186 public int getDeviceId() {
187 return account
.getDeviceId();
190 private String
getMessageCachePath() {
191 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
194 private String
getMessageCachePath(String sender
) {
195 if (sender
== null || sender
.isEmpty()) {
196 return getMessageCachePath();
199 return getMessageCachePath() + "/" + sender
.replace("/", "_");
202 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
203 String cachePath
= getMessageCachePath(sender
);
204 IOUtils
.createPrivateDirectories(cachePath
);
205 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
208 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
209 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
211 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
212 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
213 int registrationId
= KeyHelper
.generateRegistrationId(false);
215 ProfileKey profileKey
= KeyUtils
.createProfileKey();
216 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
219 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
222 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
224 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
226 m
.migrateLegacyConfigs();
231 private void migrateLegacyConfigs() {
232 // Copy group avatars that were previously stored in the attachments folder
233 // to the new avatar folder
234 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
235 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
236 File avatarFile
= getGroupAvatarFile(g
.groupId
);
237 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
238 if (!avatarFile
.exists() && attachmentFile
.exists()) {
240 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
241 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
242 } catch (Exception e
) {
247 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
250 if (account
.getProfileKey() == null) {
251 // Old config file, creating new profile key
252 account
.setProfileKey(KeyUtils
.createProfileKey());
257 public void checkAccountState() throws IOException
{
258 if (account
.isRegistered()) {
259 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
263 if (account
.getUuid() == null) {
264 account
.setUuid(accountManager
.getOwnUuid());
270 public boolean isRegistered() {
271 return account
.isRegistered();
274 public void register(boolean voiceVerification
) throws IOException
{
275 account
.setPassword(KeyUtils
.createPassword());
277 // Resetting UUID, because registering doesn't work otherwise
278 account
.setUuid(null);
279 accountManager
= createSignalServiceAccountManager();
281 if (voiceVerification
) {
282 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
284 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
287 account
.setRegistered(false);
291 public void updateAccountAttributes() throws IOException
{
292 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
295 public void setProfile(String name
, File avatar
) throws IOException
{
296 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
297 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
301 public void unregister() throws IOException
{
302 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
303 // If this is the master device, other users can't send messages to this number anymore.
304 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
305 accountManager
.setGcmId(Optional
.absent());
307 account
.setRegistered(false);
311 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
312 List
<DeviceInfo
> devices
= accountManager
.getDevices();
313 account
.setMultiDevice(devices
.size() > 1);
318 public void removeLinkedDevices(int deviceId
) throws IOException
{
319 accountManager
.removeDevice(deviceId
);
320 List
<DeviceInfo
> devices
= accountManager
.getDevices();
321 account
.setMultiDevice(devices
.size() > 1);
325 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
326 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
328 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
331 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
332 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
333 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
335 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
336 account
.setMultiDevice(true);
340 private List
<PreKeyRecord
> generatePreKeys() {
341 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
343 final int offset
= account
.getPreKeyIdOffset();
344 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
345 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
346 ECKeyPair keyPair
= Curve
.generateKeyPair();
347 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
352 account
.addPreKeys(records
);
358 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
360 ECKeyPair keyPair
= Curve
.generateKeyPair();
361 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
362 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
364 account
.addSignedPreKey(record);
368 } catch (InvalidKeyException e
) {
369 throw new AssertionError(e
);
373 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
374 verificationCode
= verificationCode
.replace("-", "");
375 account
.setSignalingKey(KeyUtils
.createSignalingKey());
376 // TODO make unrestricted unidentified access configurable
377 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
379 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
380 // TODO response.isStorageCapable()
381 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
382 account
.setRegistered(true);
383 account
.setUuid(uuid
);
384 account
.setRegistrationLockPin(pin
);
385 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
391 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
392 if (pin
.isPresent()) {
393 account
.setRegistrationLockPin(pin
.get());
394 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
396 account
.setRegistrationLockPin(null);
397 accountManager
.removeRegistrationLockV1();
402 void refreshPreKeys() throws IOException
{
403 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
404 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
405 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
407 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
410 private SignalServiceMessageReceiver
getMessageReceiver() {
411 // TODO implement ZkGroup support
412 final ClientZkProfileOperations clientZkProfileOperations
= null;
413 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
416 private SignalServiceMessageSender
getMessageSender() {
417 // TODO implement ZkGroup support
418 final ClientZkProfileOperations clientZkProfileOperations
= null;
419 final boolean attachmentsV3
= false;
420 final ExecutorService executor
= null;
421 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
422 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
);
425 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
426 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
431 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
432 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
436 SignalServiceMessageReceiver receiver
= getMessageReceiver();
438 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
439 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
440 throw new IOException("Failed to retrieve profile", e
);
444 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
445 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
447 File avatarFile
= null;
449 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
450 } catch (AssertionError e
) {
451 System
.err
.println("Failed to retrieve profile avatar: " + e
.getMessage());
454 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
456 return new SignalProfile(
457 encryptedProfile
.getIdentityKey(),
458 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
460 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
461 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
462 encryptedProfile
.getCapabilities());
463 } catch (InvalidCiphertextException e
) {
468 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
469 File file
= getGroupAvatarFile(groupId
);
470 if (!file
.exists()) {
471 return Optional
.absent();
474 return Optional
.of(Utils
.createAttachment(file
));
477 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
478 File file
= getContactAvatarFile(number
);
479 if (!file
.exists()) {
480 return Optional
.absent();
483 return Optional
.of(Utils
.createAttachment(file
));
486 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
487 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
489 throw new GroupNotFoundException(groupId
);
491 if (!g
.isMember(account
.getSelfAddress())) {
492 throw new NotAGroupMemberException(groupId
, g
.name
);
497 public List
<GroupInfo
> getGroups() {
498 return account
.getGroupStore().getGroups();
501 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
503 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
504 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
505 if (attachments
!= null) {
506 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
508 if (groupId
!= null) {
509 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
512 messageBuilder
.asGroupMessage(group
);
515 final GroupInfo g
= getGroupForSending(groupId
);
517 messageBuilder
.withExpiration(g
.messageExpirationTime
);
519 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
522 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
523 long targetSentTimestamp
, byte[] groupId
)
524 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
525 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
526 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
527 .withReaction(reaction
);
528 if (groupId
!= null) {
529 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
532 messageBuilder
.asGroupMessage(group
);
534 final GroupInfo g
= getGroupForSending(groupId
);
535 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
538 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
539 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
543 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
544 .asGroupMessage(group
);
546 final GroupInfo g
= getGroupForSending(groupId
);
547 g
.removeMember(account
.getSelfAddress());
548 account
.getGroupStore().updateGroup(g
);
550 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
553 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
555 if (groupId
== null) {
557 g
= new GroupInfo(KeyUtils
.createGroupId());
558 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
560 g
= getGroupForSending(groupId
);
567 if (members
!= null) {
568 final Set
<String
> newE164Members
= new HashSet
<>();
569 for (SignalServiceAddress member
: members
) {
570 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
573 newE164Members
.add(member
.getNumber().get());
576 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
577 if (contacts
.size() != newE164Members
.size()) {
578 // Some of the new members are not registered on Signal
579 for (ContactTokenDetails contact
: contacts
) {
580 newE164Members
.remove(contact
.getNumber());
582 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
585 g
.addMembers(members
);
588 if (avatarFile
!= null) {
589 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
590 File aFile
= getGroupAvatarFile(g
.groupId
);
591 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
594 account
.getGroupStore().updateGroup(g
);
596 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
598 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
602 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
603 if (groupId
== null) {
606 GroupInfo g
= getGroupForSending(groupId
);
608 if (!g
.isMember(recipient
)) {
612 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
614 // Send group message only to the recipient who requested it
615 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
618 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
619 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
622 .withMembers(new ArrayList
<>(g
.getMembers()));
624 File aFile
= getGroupAvatarFile(g
.groupId
);
625 if (aFile
.exists()) {
627 group
.withAvatar(Utils
.createAttachment(aFile
));
628 } catch (IOException e
) {
629 throw new AttachmentInvalidException(aFile
.toString(), e
);
633 return SignalServiceDataMessage
.newBuilder()
634 .asGroupMessage(group
.build())
635 .withExpiration(g
.messageExpirationTime
);
638 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
639 if (groupId
== null) {
643 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
646 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
647 .asGroupMessage(group
.build());
649 // Send group info request message to the recipient who sent us a message with this groupId
650 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
653 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
654 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
655 Collections
.singletonList(messageId
),
656 System
.currentTimeMillis());
658 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
661 public long sendMessage(String messageText
, List
<String
> attachments
,
662 List
<String
> recipients
)
663 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
664 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
665 if (attachments
!= null) {
666 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
668 // Upload attachments here, so we only upload once even for multiple recipients
669 SignalServiceMessageSender messageSender
= getMessageSender();
670 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
671 for (SignalServiceAttachment attachment
: attachmentStreams
) {
672 if (attachment
.isStream()) {
673 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
674 } else if (attachment
.isPointer()) {
675 attachmentPointers
.add(attachment
.asPointer());
679 messageBuilder
.withAttachments(attachmentPointers
);
681 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
684 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
685 long targetSentTimestamp
, List
<String
> recipients
)
686 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
687 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
688 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
689 .withReaction(reaction
);
690 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
693 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
694 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
695 .asEndSessionMessage();
697 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
699 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
700 } catch (Exception e
) {
701 for (SignalServiceAddress address
: signalServiceAddresses
) {
702 handleEndSession(address
);
709 public String
getContactName(String number
) throws InvalidNumberException
{
710 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
711 if (contact
== null) {
718 public void setContactName(String number
, String name
) throws InvalidNumberException
{
719 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
720 ContactInfo contact
= account
.getContactStore().getContact(address
);
721 if (contact
== null) {
722 contact
= new ContactInfo(address
);
725 account
.getContactStore().updateContact(contact
);
729 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
730 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
733 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
734 ContactInfo contact
= account
.getContactStore().getContact(address
);
735 if (contact
== null) {
736 contact
= new ContactInfo(address
);
738 contact
.blocked
= blocked
;
739 account
.getContactStore().updateContact(contact
);
743 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
744 GroupInfo group
= getGroup(groupId
);
746 throw new GroupNotFoundException(groupId
);
749 group
.blocked
= blocked
;
750 account
.getGroupStore().updateGroup(group
);
754 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
755 if (groupId
.length
== 0) {
758 if (name
.isEmpty()) {
761 if (members
.isEmpty()) {
764 if (avatar
.isEmpty()) {
767 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
771 * Change the expiration timer for a contact
773 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
774 ContactInfo contact
= account
.getContactStore().getContact(address
);
775 contact
.messageExpirationTime
= messageExpirationTimer
;
776 account
.getContactStore().updateContact(contact
);
777 sendExpirationTimerUpdate(address
);
781 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
782 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
783 .asExpirationUpdate();
784 sendMessage(messageBuilder
, Collections
.singleton(address
));
788 * Change the expiration timer for a contact
790 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
791 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
792 setExpirationTimer(address
, messageExpirationTimer
);
796 * Change the expiration timer for a group
798 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
799 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
800 g
.messageExpirationTime
= messageExpirationTimer
;
801 account
.getGroupStore().updateGroup(g
);
805 * Upload the sticker pack from path.
807 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
808 * @return if successful, returns the URL to install the sticker pack in the signal app
810 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
811 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
813 SignalServiceMessageSender messageSender
= getMessageSender();
815 byte[] packKey
= KeyUtils
.createStickerUploadKey();
816 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
819 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
821 } catch (URISyntaxException e
) {
822 throw new AssertionError(e
);
826 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
828 String rootPath
= null;
830 final File file
= new File(path
);
831 if (file
.getName().endsWith(".zip")) {
832 zip
= new ZipFile(file
);
833 } else if (file
.getName().equals("manifest.json")) {
834 rootPath
= file
.getParent();
836 throw new StickerPackInvalidException("Could not find manifest.json");
839 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
841 if (pack
.stickers
== null) {
842 throw new StickerPackInvalidException("Must set a 'stickers' field.");
845 if (pack
.stickers
.isEmpty()) {
846 throw new StickerPackInvalidException("Must include stickers.");
849 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
850 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
851 if (sticker
.file
== null) {
852 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
855 Pair
<InputStream
, Long
> data
;
857 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
858 } catch (IOException ignored
) {
859 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
862 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
863 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
864 stickers
.add(stickerInfo
);
867 StickerInfo cover
= null;
868 if (pack
.cover
!= null) {
869 if (pack
.cover
.file
== null) {
870 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
873 Pair
<InputStream
, Long
> data
;
875 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
876 } catch (IOException ignored
) {
877 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
880 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
881 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
884 return new SignalServiceStickerManifestUpload(
891 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
892 InputStream inputStream
;
894 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
896 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
898 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
901 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
903 final ZipEntry entry
= zip
.getEntry(subfile
);
904 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
906 final File file
= new File(rootPath
, subfile
);
907 return new Pair
<>(new FileInputStream(file
), file
.length());
911 void requestSyncGroups() throws IOException
{
912 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
913 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
915 sendSyncMessage(message
);
916 } catch (UntrustedIdentityException e
) {
921 void requestSyncContacts() throws IOException
{
922 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
923 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
925 sendSyncMessage(message
);
926 } catch (UntrustedIdentityException e
) {
931 void requestSyncBlocked() throws IOException
{
932 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
933 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
935 sendSyncMessage(message
);
936 } catch (UntrustedIdentityException e
) {
941 void requestSyncConfiguration() throws IOException
{
942 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
943 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
945 sendSyncMessage(message
);
946 } catch (UntrustedIdentityException e
) {
951 private byte[] getSenderCertificate() {
952 // TODO support UUID capable sender certificates
953 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
956 certificate
= accountManager
.getSenderCertificate();
957 } catch (IOException e
) {
958 System
.err
.println("Failed to get sender certificate: " + e
);
961 // TODO cache for a day
965 private byte[] getSelfUnidentifiedAccessKey() {
966 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
969 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
970 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
971 if (contact
== null || contact
.profileKey
== null) {
974 ProfileKey theirProfileKey
;
976 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
977 } catch (InvalidInputException
| IOException e
) {
978 throw new AssertionError(e
);
980 SignalProfile targetProfile
;
982 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
983 } catch (IOException e
) {
984 System
.err
.println("Failed to get recipient profile: " + e
);
988 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
992 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
993 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
996 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
999 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1000 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1001 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1003 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1004 return Optional
.absent();
1008 return Optional
.of(new UnidentifiedAccessPair(
1009 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1010 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1012 } catch (InvalidCertificateException e
) {
1013 return Optional
.absent();
1017 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1018 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1019 for (SignalServiceAddress recipient
: recipients
) {
1020 result
.add(getAccessFor(recipient
));
1025 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1026 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1027 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1028 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1030 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1031 return Optional
.absent();
1035 return Optional
.of(new UnidentifiedAccessPair(
1036 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1037 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1039 } catch (InvalidCertificateException e
) {
1040 return Optional
.absent();
1044 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1045 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1047 if (unidentifiedAccess
.isPresent()) {
1048 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1051 return Optional
.absent();
1054 private void sendSyncMessage(SignalServiceSyncMessage message
)
1055 throws IOException
, UntrustedIdentityException
{
1056 SignalServiceMessageSender messageSender
= getMessageSender();
1058 messageSender
.sendMessage(message
, getAccessForSync());
1059 } catch (UntrustedIdentityException e
) {
1060 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1066 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1068 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1069 throws EncapsulatedExceptions
, IOException
{
1070 final long timestamp
= System
.currentTimeMillis();
1071 messageBuilder
.withTimestamp(timestamp
);
1072 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1074 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1075 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1076 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1078 for (SendMessageResult result
: results
) {
1079 if (result
.isUnregisteredFailure()) {
1080 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1081 } else if (result
.isNetworkFailure()) {
1082 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1083 } else if (result
.getIdentityFailure() != null) {
1084 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1087 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1088 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1093 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1094 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1096 for (String number
: numbers
) {
1097 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1099 return signalServiceAddresses
;
1102 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1103 throws IOException
{
1104 if (messagePipe
== null) {
1105 messagePipe
= getMessageReceiver().createMessagePipe();
1107 if (unidentifiedMessagePipe
== null) {
1108 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1110 SignalServiceDataMessage message
= null;
1112 message
= messageBuilder
.build();
1113 if (message
.getGroupContext().isPresent()) {
1115 SignalServiceMessageSender messageSender
= getMessageSender();
1116 final boolean isRecipientUpdate
= false;
1117 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1118 for (SendMessageResult r
: result
) {
1119 if (r
.getIdentityFailure() != null) {
1120 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1124 } catch (UntrustedIdentityException e
) {
1125 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1126 return Collections
.emptyList();
1129 // Send to all individually, so sync messages are sent correctly
1130 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1131 for (SignalServiceAddress address
: recipients
) {
1132 ContactInfo contact
= account
.getContactStore().getContact(address
);
1133 if (contact
!= null) {
1134 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1135 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1137 messageBuilder
.withExpiration(0);
1138 messageBuilder
.withProfileKey(null);
1140 message
= messageBuilder
.build();
1141 if (address
.matches(account
.getSelfAddress())) {
1142 results
.add(sendSelfMessage(message
));
1144 results
.add(sendMessage(address
, message
));
1150 if (message
!= null && message
.isEndSession()) {
1151 for (SignalServiceAddress recipient
: recipients
) {
1152 handleEndSession(recipient
);
1159 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1160 SignalServiceMessageSender messageSender
= getMessageSender();
1162 SignalServiceAddress recipient
= account
.getSelfAddress();
1164 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1165 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1166 message
.getTimestamp(),
1168 message
.getExpiresInSeconds(),
1169 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1171 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1174 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1175 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1176 } catch (UntrustedIdentityException e
) {
1177 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1178 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1182 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1183 SignalServiceMessageSender messageSender
= getMessageSender();
1186 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1187 } catch (UntrustedIdentityException e
) {
1188 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1189 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1193 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1194 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1196 return cipher
.decrypt(envelope
);
1197 } catch (ProtocolUntrustedIdentityException e
) {
1198 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1199 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1200 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1201 throw identityException
;
1203 throw new AssertionError(e
);
1207 private void handleEndSession(SignalServiceAddress source
) {
1208 account
.getSignalProtocolStore().deleteAllSessions(source
);
1211 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1212 List
<HandleAction
> actions
= new ArrayList
<>();
1213 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1214 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1215 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1216 switch (groupInfo
.getType()) {
1218 if (group
== null) {
1219 group
= new GroupInfo(groupInfo
.getGroupId());
1222 if (groupInfo
.getAvatar().isPresent()) {
1223 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1224 if (avatar
.isPointer()) {
1226 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1227 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1228 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1233 if (groupInfo
.getName().isPresent()) {
1234 group
.name
= groupInfo
.getName().get();
1237 if (groupInfo
.getMembers().isPresent()) {
1238 group
.addMembers(groupInfo
.getMembers().get()
1240 .map(this::resolveSignalServiceAddress
)
1241 .collect(Collectors
.toSet()));
1244 account
.getGroupStore().updateGroup(group
);
1247 if (group
== null && !isSync
) {
1248 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1252 if (group
!= null) {
1253 group
.removeMember(source
);
1254 account
.getGroupStore().updateGroup(group
);
1258 if (group
!= null && !isSync
) {
1259 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1264 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1265 if (message
.isEndSession()) {
1266 handleEndSession(conversationPartnerAddress
);
1268 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1269 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1270 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1271 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1272 if (group
== null) {
1273 group
= new GroupInfo(groupInfo
.getGroupId());
1275 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1276 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1277 account
.getGroupStore().updateGroup(group
);
1280 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1281 if (contact
== null) {
1282 contact
= new ContactInfo(conversationPartnerAddress
);
1284 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1285 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1286 account
.getContactStore().updateContact(contact
);
1290 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1291 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1292 if (attachment
.isPointer()) {
1294 retrieveAttachment(attachment
.asPointer());
1295 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1296 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1301 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1302 if (source
.matches(account
.getSelfAddress())) {
1304 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1305 } catch (InvalidInputException ignored
) {
1307 ContactInfo contact
= account
.getContactStore().getContact(source
);
1308 if (contact
!= null) {
1309 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1310 account
.getContactStore().updateContact(contact
);
1313 ContactInfo contact
= account
.getContactStore().getContact(source
);
1314 if (contact
== null) {
1315 contact
= new ContactInfo(source
);
1317 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1318 account
.getContactStore().updateContact(contact
);
1321 if (message
.getPreviews().isPresent()) {
1322 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1323 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1324 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1325 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1327 retrieveAttachment(attachment
);
1328 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1329 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1337 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1338 final File cachePath
= new File(getMessageCachePath());
1339 if (!cachePath
.exists()) {
1342 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1343 if (!dir
.isDirectory()) {
1344 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1348 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1349 if (!fileEntry
.isFile()) {
1352 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1354 // Try to delete directory if empty
1359 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1360 SignalServiceEnvelope envelope
;
1362 envelope
= Utils
.loadEnvelope(fileEntry
);
1363 if (envelope
== null) {
1366 } catch (IOException e
) {
1367 e
.printStackTrace();
1370 SignalServiceContent content
= null;
1371 if (!envelope
.isReceipt()) {
1373 content
= decryptMessage(envelope
);
1374 } catch (Exception e
) {
1377 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1378 for (HandleAction action
: actions
) {
1380 action
.execute(this);
1381 } catch (Throwable e
) {
1382 e
.printStackTrace();
1387 handler
.handleMessage(envelope
, content
, null);
1389 Files
.delete(fileEntry
.toPath());
1390 } catch (IOException e
) {
1391 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1395 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1396 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1397 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1399 Set
<HandleAction
> queuedActions
= null;
1401 if (messagePipe
== null) {
1402 messagePipe
= messageReceiver
.createMessagePipe();
1405 boolean hasCaughtUpWithOldMessages
= false;
1408 SignalServiceEnvelope envelope
;
1409 SignalServiceContent content
= null;
1410 Exception exception
= null;
1411 final long now
= new Date().getTime();
1413 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1414 // store message on disk, before acknowledging receipt to the server
1416 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1417 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1418 Utils
.storeEnvelope(envelope1
, cacheFile
);
1419 } catch (IOException e
) {
1420 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1423 if (result
.isPresent()) {
1424 envelope
= result
.get();
1426 // Received indicator that server queue is empty
1427 hasCaughtUpWithOldMessages
= true;
1429 if (queuedActions
!= null) {
1430 for (HandleAction action
: queuedActions
) {
1432 action
.execute(this);
1433 } catch (Throwable e
) {
1434 e
.printStackTrace();
1437 queuedActions
.clear();
1438 queuedActions
= null;
1441 // Continue to wait another timeout for new messages
1444 } catch (TimeoutException e
) {
1445 if (returnOnTimeout
)
1448 } catch (InvalidVersionException e
) {
1449 System
.err
.println("Ignoring error: " + e
.getMessage());
1452 if (envelope
.hasSource()) {
1453 // Store uuid if we don't have it already
1454 SignalServiceAddress source
= envelope
.getSourceAddress();
1455 resolveSignalServiceAddress(source
);
1457 if (!envelope
.isReceipt()) {
1459 content
= decryptMessage(envelope
);
1460 } catch (Exception e
) {
1463 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1464 if (hasCaughtUpWithOldMessages
) {
1465 for (HandleAction action
: actions
) {
1467 action
.execute(this);
1468 } catch (Throwable e
) {
1469 e
.printStackTrace();
1473 if (queuedActions
== null) {
1474 queuedActions
= new HashSet
<>();
1476 queuedActions
.addAll(actions
);
1480 if (!isMessageBlocked(envelope
, content
)) {
1481 handler
.handleMessage(envelope
, content
, exception
);
1483 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1484 File cacheFile
= null;
1486 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1487 Files
.delete(cacheFile
.toPath());
1488 // Try to delete directory if empty
1489 new File(getMessageCachePath()).delete();
1490 } catch (IOException e
) {
1491 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1497 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1498 SignalServiceAddress source
;
1499 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1500 source
= envelope
.getSourceAddress();
1501 } else if (content
!= null) {
1502 source
= content
.getSender();
1506 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1507 if (sourceContact
!= null && sourceContact
.blocked
) {
1511 if (content
!= null && content
.getDataMessage().isPresent()) {
1512 SignalServiceDataMessage message
= content
.getDataMessage().get();
1513 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1514 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1515 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1516 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1524 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1525 List
<HandleAction
> actions
= new ArrayList
<>();
1526 if (content
!= null) {
1527 SignalServiceAddress sender
;
1528 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1529 sender
= envelope
.getSourceAddress();
1531 sender
= content
.getSender();
1533 // Store uuid if we don't have it already
1534 resolveSignalServiceAddress(sender
);
1536 if (content
.getDataMessage().isPresent()) {
1537 SignalServiceDataMessage message
= content
.getDataMessage().get();
1539 if (content
.isNeedsReceipt()) {
1540 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1543 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1545 if (content
.getSyncMessage().isPresent()) {
1546 account
.setMultiDevice(true);
1547 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1548 if (syncMessage
.getSent().isPresent()) {
1549 SentTranscriptMessage message
= syncMessage
.getSent().get();
1550 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1552 if (syncMessage
.getRequest().isPresent()) {
1553 RequestMessage rm
= syncMessage
.getRequest().get();
1554 if (rm
.isContactsRequest()) {
1555 actions
.add(SendSyncContactsAction
.create());
1557 if (rm
.isGroupsRequest()) {
1558 actions
.add(SendSyncGroupsAction
.create());
1560 if (rm
.isBlockedListRequest()) {
1561 actions
.add(SendSyncBlockedListAction
.create());
1563 // TODO Handle rm.isConfigurationRequest();
1565 if (syncMessage
.getGroups().isPresent()) {
1566 File tmpFile
= null;
1568 tmpFile
= IOUtils
.createTempFile();
1569 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1570 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1572 while ((g
= s
.read()) != null) {
1573 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1574 if (syncGroup
== null) {
1575 syncGroup
= new GroupInfo(g
.getId());
1577 if (g
.getName().isPresent()) {
1578 syncGroup
.name
= g
.getName().get();
1580 syncGroup
.addMembers(g
.getMembers()
1582 .map(this::resolveSignalServiceAddress
)
1583 .collect(Collectors
.toSet()));
1584 if (!g
.isActive()) {
1585 syncGroup
.removeMember(account
.getSelfAddress());
1587 // Add ourself to the member set as it's marked as active
1588 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1590 syncGroup
.blocked
= g
.isBlocked();
1591 if (g
.getColor().isPresent()) {
1592 syncGroup
.color
= g
.getColor().get();
1595 if (g
.getAvatar().isPresent()) {
1596 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1598 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1599 syncGroup
.archived
= g
.isArchived();
1600 account
.getGroupStore().updateGroup(syncGroup
);
1603 } catch (Exception e
) {
1604 e
.printStackTrace();
1606 if (tmpFile
!= null) {
1608 Files
.delete(tmpFile
.toPath());
1609 } catch (IOException e
) {
1610 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1615 if (syncMessage
.getBlockedList().isPresent()) {
1616 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1617 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1618 setContactBlocked(resolveSignalServiceAddress(address
), true);
1620 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1622 setGroupBlocked(groupId
, true);
1623 } catch (GroupNotFoundException e
) {
1624 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1628 if (syncMessage
.getContacts().isPresent()) {
1629 File tmpFile
= null;
1631 tmpFile
= IOUtils
.createTempFile();
1632 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1633 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1634 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1635 if (contactsMessage
.isComplete()) {
1636 account
.getContactStore().clear();
1639 while ((c
= s
.read()) != null) {
1640 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1641 account
.setProfileKey(c
.getProfileKey().get());
1643 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1644 ContactInfo contact
= account
.getContactStore().getContact(address
);
1645 if (contact
== null) {
1646 contact
= new ContactInfo(address
);
1648 if (c
.getName().isPresent()) {
1649 contact
.name
= c
.getName().get();
1651 if (c
.getColor().isPresent()) {
1652 contact
.color
= c
.getColor().get();
1654 if (c
.getProfileKey().isPresent()) {
1655 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1657 if (c
.getVerified().isPresent()) {
1658 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1659 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1661 if (c
.getExpirationTimer().isPresent()) {
1662 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1664 contact
.blocked
= c
.isBlocked();
1665 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1666 contact
.archived
= c
.isArchived();
1667 account
.getContactStore().updateContact(contact
);
1669 if (c
.getAvatar().isPresent()) {
1670 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1674 } catch (Exception e
) {
1675 e
.printStackTrace();
1677 if (tmpFile
!= null) {
1679 Files
.delete(tmpFile
.toPath());
1680 } catch (IOException e
) {
1681 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1686 if (syncMessage
.getVerified().isPresent()) {
1687 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1688 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1690 if (syncMessage
.getConfiguration().isPresent()) {
1698 private File
getContactAvatarFile(String number
) {
1699 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1702 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1703 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1704 if (attachment
.isPointer()) {
1705 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1706 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1708 SignalServiceAttachmentStream stream
= attachment
.asStream();
1709 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1713 private File
getGroupAvatarFile(byte[] groupId
) {
1714 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1717 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1718 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1719 if (attachment
.isPointer()) {
1720 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1721 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1723 SignalServiceAttachmentStream stream
= attachment
.asStream();
1724 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1728 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1729 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1732 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1733 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1734 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1735 File outputFile
= getProfileAvatarFile(address
);
1737 File tmpFile
= IOUtils
.createTempFile();
1738 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1739 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1740 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1743 Files
.delete(tmpFile
.toPath());
1744 } catch (IOException e
) {
1745 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1751 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1752 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1755 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1756 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1757 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1760 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1761 if (storePreview
&& pointer
.getPreview().isPresent()) {
1762 File previewFile
= new File(outputFile
+ ".preview");
1763 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1764 byte[] preview
= pointer
.getPreview().get();
1765 output
.write(preview
, 0, preview
.length
);
1766 } catch (FileNotFoundException e
) {
1767 e
.printStackTrace();
1772 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1774 File tmpFile
= IOUtils
.createTempFile();
1775 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1776 IOUtils
.copyStreamToFile(input
, outputFile
);
1779 Files
.delete(tmpFile
.toPath());
1780 } catch (IOException e
) {
1781 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1787 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1788 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1789 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1792 void sendGroups() throws IOException
, UntrustedIdentityException
{
1793 File groupsFile
= IOUtils
.createTempFile();
1796 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1797 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1798 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1799 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1800 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1801 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1802 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1806 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1807 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1808 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1809 .withStream(groupsFileStream
)
1810 .withContentType("application/octet-stream")
1811 .withLength(groupsFile
.length())
1814 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1819 Files
.delete(groupsFile
.toPath());
1820 } catch (IOException e
) {
1821 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1826 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1827 File contactsFile
= IOUtils
.createTempFile();
1830 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1831 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1832 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1833 VerifiedMessage verifiedMessage
= null;
1834 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1835 if (currentIdentity
!= null) {
1836 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1839 ProfileKey profileKey
= null;
1841 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1842 } catch (InvalidInputException ignored
) {
1844 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1845 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1846 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1847 Optional
.of(record.messageExpirationTime
),
1848 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1851 if (account
.getProfileKey() != null) {
1852 // Send our own profile key as well
1853 out
.write(new DeviceContact(account
.getSelfAddress(),
1854 Optional
.absent(), Optional
.absent(),
1855 Optional
.absent(), Optional
.absent(),
1856 Optional
.of(account
.getProfileKey()),
1857 false, Optional
.absent(), Optional
.absent(), false));
1861 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1862 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1863 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1864 .withStream(contactsFileStream
)
1865 .withContentType("application/octet-stream")
1866 .withLength(contactsFile
.length())
1869 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1874 Files
.delete(contactsFile
.toPath());
1875 } catch (IOException e
) {
1876 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1881 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1882 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1883 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1884 if (record.blocked
) {
1885 addresses
.add(record.getAddress());
1888 List
<byte[]> groupIds
= new ArrayList
<>();
1889 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1890 if (record.blocked
) {
1891 groupIds
.add(record.groupId
);
1894 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1897 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1898 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1899 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1902 public List
<ContactInfo
> getContacts() {
1903 return account
.getContactStore().getContacts();
1906 public ContactInfo
getContact(String number
) {
1907 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1910 public GroupInfo
getGroup(byte[] groupId
) {
1911 return account
.getGroupStore().getGroup(groupId
);
1914 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1915 return account
.getSignalProtocolStore().getIdentities();
1918 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1919 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1923 * Trust this the identity with this fingerprint
1925 * @param name username of the identity
1926 * @param fingerprint Fingerprint
1928 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1929 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1930 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1934 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1935 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1939 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1941 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1942 } catch (IOException
| UntrustedIdentityException e
) {
1943 e
.printStackTrace();
1952 * Trust this the identity with this safety number
1954 * @param name username of the identity
1955 * @param safetyNumber Safety number
1957 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1958 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1959 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1963 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1964 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1968 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1971 } catch (IOException
| UntrustedIdentityException e
) {
1972 e
.printStackTrace();
1981 * Trust all keys of this identity without verification
1983 * @param name username of the identity
1985 public boolean trustIdentityAllKeys(String name
) {
1986 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1987 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1991 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1992 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1993 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1995 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1996 } catch (IOException
| UntrustedIdentityException e
) {
1997 e
.printStackTrace();
2005 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2006 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2009 void saveAccount() {
2013 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2014 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2015 return resolveSignalServiceAddress(canonicalizedNumber
);
2018 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2019 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2021 return resolveSignalServiceAddress(address
);
2024 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2025 if (address
.matches(account
.getSelfAddress())) {
2026 return account
.getSelfAddress();
2029 return account
.getRecipientStore().resolveServiceAddress(address
);
2033 public void close() throws IOException
{
2034 if (messagePipe
!= null) {
2035 messagePipe
.shutdown();
2039 if (unidentifiedMessagePipe
!= null) {
2040 unidentifiedMessagePipe
.shutdown();
2041 unidentifiedMessagePipe
= null;
2047 public interface ReceiveMessageHandler
{
2049 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);