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
.profiles
.SignalProfile
;
26 import org
.asamk
.signal
.storage
.profiles
.SignalProfileEntry
;
27 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
28 import org
.asamk
.signal
.util
.IOUtils
;
29 import org
.asamk
.signal
.util
.Util
;
30 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
31 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
40 import org
.signal
.libsignal
.metadata
.SelfSendException
;
41 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
42 import org
.signal
.zkgroup
.InvalidInputException
;
43 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
44 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
45 import org
.whispersystems
.libsignal
.IdentityKey
;
46 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
47 import org
.whispersystems
.libsignal
.InvalidKeyException
;
48 import org
.whispersystems
.libsignal
.InvalidMessageException
;
49 import org
.whispersystems
.libsignal
.InvalidVersionException
;
50 import org
.whispersystems
.libsignal
.ecc
.Curve
;
51 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
52 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
53 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
54 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
55 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
56 import org
.whispersystems
.libsignal
.util
.Medium
;
57 import org
.whispersystems
.libsignal
.util
.Pair
;
58 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
61 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
62 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
67 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
68 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
69 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
70 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
96 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
97 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
98 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
99 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
100 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
101 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
102 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
103 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
104 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
105 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
106 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
107 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
108 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
109 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
110 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
111 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
112 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
113 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
114 import org
.whispersystems
.util
.Base64
;
116 import java
.io
.Closeable
;
118 import java
.io
.FileInputStream
;
119 import java
.io
.FileNotFoundException
;
120 import java
.io
.FileOutputStream
;
121 import java
.io
.IOException
;
122 import java
.io
.InputStream
;
123 import java
.io
.OutputStream
;
125 import java
.net
.URISyntaxException
;
126 import java
.net
.URLEncoder
;
127 import java
.nio
.file
.Files
;
128 import java
.nio
.file
.Paths
;
129 import java
.nio
.file
.StandardCopyOption
;
130 import java
.util
.ArrayList
;
131 import java
.util
.Arrays
;
132 import java
.util
.Collection
;
133 import java
.util
.Collections
;
134 import java
.util
.Date
;
135 import java
.util
.HashSet
;
136 import java
.util
.LinkedList
;
137 import java
.util
.List
;
138 import java
.util
.Locale
;
139 import java
.util
.Objects
;
140 import java
.util
.Set
;
141 import java
.util
.UUID
;
142 import java
.util
.concurrent
.ExecutionException
;
143 import java
.util
.concurrent
.ExecutorService
;
144 import java
.util
.concurrent
.TimeUnit
;
145 import java
.util
.concurrent
.TimeoutException
;
146 import java
.util
.stream
.Collectors
;
147 import java
.util
.zip
.ZipEntry
;
148 import java
.util
.zip
.ZipFile
;
150 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
152 public class Manager
implements Closeable
{
154 private final SleepTimer timer
= new UptimeSleepTimer();
155 private final SignalServiceConfiguration serviceConfiguration
;
156 private final String userAgent
;
158 private final SignalAccount account
;
159 private final PathConfig pathConfig
;
160 private SignalServiceAccountManager accountManager
;
161 private SignalServiceMessagePipe messagePipe
= null;
162 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
163 private boolean discoverableByPhoneNumber
= true;
165 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
166 this.account
= account
;
167 this.pathConfig
= pathConfig
;
168 this.serviceConfiguration
= serviceConfiguration
;
169 this.userAgent
= userAgent
;
170 this.accountManager
= createSignalServiceAccountManager();
172 this.account
.setResolver(this::resolveSignalServiceAddress
);
175 public String
getUsername() {
176 return account
.getUsername();
179 public SignalServiceAddress
getSelfAddress() {
180 return account
.getSelfAddress();
183 private SignalServiceAccountManager
createSignalServiceAccountManager() {
184 GroupsV2Operations groupsV2Operations
;
186 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceConfiguration
));
187 } catch (Throwable ignored
) {
188 groupsV2Operations
= null;
190 return new SignalServiceAccountManager(serviceConfiguration
,
191 new DynamicCredentialsProvider(account
.getUuid(), account
.getUsername(), account
.getPassword(), null, account
.getDeviceId()),
197 private IdentityKeyPair
getIdentityKeyPair() {
198 return account
.getSignalProtocolStore().getIdentityKeyPair();
201 public int getDeviceId() {
202 return account
.getDeviceId();
205 private String
getMessageCachePath() {
206 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
209 private String
getMessageCachePath(String sender
) {
210 if (sender
== null || sender
.isEmpty()) {
211 return getMessageCachePath();
214 return getMessageCachePath() + "/" + sender
.replace("/", "_");
217 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
218 String cachePath
= getMessageCachePath(sender
);
219 IOUtils
.createPrivateDirectories(cachePath
);
220 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
223 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
224 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
226 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
227 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
228 int registrationId
= KeyHelper
.generateRegistrationId(false);
230 ProfileKey profileKey
= KeyUtils
.createProfileKey();
231 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
234 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
237 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
239 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
241 m
.migrateLegacyConfigs();
246 private void migrateLegacyConfigs() {
247 // Copy group avatars that were previously stored in the attachments folder
248 // to the new avatar folder
249 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
250 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
251 File avatarFile
= getGroupAvatarFile(g
.groupId
);
252 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
253 if (!avatarFile
.exists() && attachmentFile
.exists()) {
255 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
256 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
257 } catch (Exception e
) {
262 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
265 if (account
.getProfileKey() == null) {
266 // Old config file, creating new profile key
267 account
.setProfileKey(KeyUtils
.createProfileKey());
272 public void checkAccountState() throws IOException
{
273 if (account
.isRegistered()) {
274 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
278 if (account
.getUuid() == null) {
279 account
.setUuid(accountManager
.getOwnUuid());
285 public boolean isRegistered() {
286 return account
.isRegistered();
289 public void register(boolean voiceVerification
) throws IOException
{
290 account
.setPassword(KeyUtils
.createPassword());
292 // Resetting UUID, because registering doesn't work otherwise
293 account
.setUuid(null);
294 accountManager
= createSignalServiceAccountManager();
296 if (voiceVerification
) {
297 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
299 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
302 account
.setRegistered(false);
306 public void updateAccountAttributes() throws IOException
{
307 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
310 public void setProfile(String name
, File avatar
) throws IOException
{
311 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
312 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
316 public void unregister() throws IOException
{
317 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
318 // If this is the master device, other users can't send messages to this number anymore.
319 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
320 accountManager
.setGcmId(Optional
.absent());
322 account
.setRegistered(false);
326 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
327 List
<DeviceInfo
> devices
= accountManager
.getDevices();
328 account
.setMultiDevice(devices
.size() > 1);
333 public void removeLinkedDevices(int deviceId
) throws IOException
{
334 accountManager
.removeDevice(deviceId
);
335 List
<DeviceInfo
> devices
= accountManager
.getDevices();
336 account
.setMultiDevice(devices
.size() > 1);
340 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
341 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
343 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
346 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
347 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
348 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
350 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
351 account
.setMultiDevice(true);
355 private List
<PreKeyRecord
> generatePreKeys() {
356 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
358 final int offset
= account
.getPreKeyIdOffset();
359 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
360 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
361 ECKeyPair keyPair
= Curve
.generateKeyPair();
362 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
367 account
.addPreKeys(records
);
373 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
375 ECKeyPair keyPair
= Curve
.generateKeyPair();
376 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
377 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
379 account
.addSignedPreKey(record);
383 } catch (InvalidKeyException e
) {
384 throw new AssertionError(e
);
388 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
389 verificationCode
= verificationCode
.replace("-", "");
390 account
.setSignalingKey(KeyUtils
.createSignalingKey());
391 // TODO make unrestricted unidentified access configurable
392 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
394 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
395 // TODO response.isStorageCapable()
396 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
397 account
.setRegistered(true);
398 account
.setUuid(uuid
);
399 account
.setRegistrationLockPin(pin
);
400 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
406 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
407 if (pin
.isPresent()) {
408 account
.setRegistrationLockPin(pin
.get());
409 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
411 account
.setRegistrationLockPin(null);
412 accountManager
.removeRegistrationLockV1();
417 void refreshPreKeys() throws IOException
{
418 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
419 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
420 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
422 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
425 private SignalServiceMessageReceiver
getMessageReceiver() {
426 // TODO implement ZkGroup support
427 final ClientZkProfileOperations clientZkProfileOperations
= null;
428 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
431 private SignalServiceMessageSender
getMessageSender() {
432 // TODO implement ZkGroup support
433 final ClientZkProfileOperations clientZkProfileOperations
= null;
434 final ExecutorService executor
= null;
435 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
436 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
439 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
440 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
445 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
446 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
450 SignalServiceMessageReceiver receiver
= getMessageReceiver();
452 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
453 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
454 throw new IOException("Failed to retrieve profile", e
);
458 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
459 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
460 long now
= new Date().getTime();
461 // Profiles are cache for 24h before retrieving them again
462 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
463 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
464 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
467 return profileEntry
.getProfile();
470 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
471 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
473 File avatarFile
= null;
475 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
476 } catch (Throwable e
) {
477 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
480 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
482 return new SignalProfile(
483 encryptedProfile
.getIdentityKey(),
484 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
486 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
487 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
488 encryptedProfile
.getCapabilities());
489 } catch (InvalidCiphertextException e
) {
494 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
495 File file
= getGroupAvatarFile(groupId
);
496 if (!file
.exists()) {
497 return Optional
.absent();
500 return Optional
.of(Utils
.createAttachment(file
));
503 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
504 File file
= getContactAvatarFile(number
);
505 if (!file
.exists()) {
506 return Optional
.absent();
509 return Optional
.of(Utils
.createAttachment(file
));
512 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
513 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
515 throw new GroupNotFoundException(groupId
);
517 if (!g
.isMember(account
.getSelfAddress())) {
518 throw new NotAGroupMemberException(groupId
, g
.name
);
523 public List
<GroupInfo
> getGroups() {
524 return account
.getGroupStore().getGroups();
527 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
529 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
530 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
531 if (attachments
!= null) {
532 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
534 if (groupId
!= null) {
535 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
538 messageBuilder
.asGroupMessage(group
);
541 final GroupInfo g
= getGroupForSending(groupId
);
543 messageBuilder
.withExpiration(g
.messageExpirationTime
);
545 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
548 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
549 long targetSentTimestamp
, byte[] groupId
)
550 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
551 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
552 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
553 .withReaction(reaction
);
554 if (groupId
!= null) {
555 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
558 messageBuilder
.asGroupMessage(group
);
560 final GroupInfo g
= getGroupForSending(groupId
);
561 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
564 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
565 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
569 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
570 .asGroupMessage(group
);
572 final GroupInfo g
= getGroupForSending(groupId
);
573 g
.removeMember(account
.getSelfAddress());
574 account
.getGroupStore().updateGroup(g
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
581 if (groupId
== null) {
583 g
= new GroupInfo(KeyUtils
.createGroupId());
584 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
586 g
= getGroupForSending(groupId
);
593 if (members
!= null) {
594 final Set
<String
> newE164Members
= new HashSet
<>();
595 for (SignalServiceAddress member
: members
) {
596 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
599 newE164Members
.add(member
.getNumber().get());
602 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
603 if (contacts
.size() != newE164Members
.size()) {
604 // Some of the new members are not registered on Signal
605 for (ContactTokenDetails contact
: contacts
) {
606 newE164Members
.remove(contact
.getNumber());
608 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
611 g
.addMembers(members
);
614 if (avatarFile
!= null) {
615 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
616 File aFile
= getGroupAvatarFile(g
.groupId
);
617 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
620 account
.getGroupStore().updateGroup(g
);
622 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
624 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
628 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
629 if (groupId
== null) {
632 GroupInfo g
= getGroupForSending(groupId
);
634 if (!g
.isMember(recipient
)) {
638 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
640 // Send group message only to the recipient who requested it
641 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
644 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
645 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
648 .withMembers(new ArrayList
<>(g
.getMembers()));
650 File aFile
= getGroupAvatarFile(g
.groupId
);
651 if (aFile
.exists()) {
653 group
.withAvatar(Utils
.createAttachment(aFile
));
654 } catch (IOException e
) {
655 throw new AttachmentInvalidException(aFile
.toString(), e
);
659 return SignalServiceDataMessage
.newBuilder()
660 .asGroupMessage(group
.build())
661 .withExpiration(g
.messageExpirationTime
);
664 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
665 if (groupId
== null) {
669 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
672 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
673 .asGroupMessage(group
.build());
675 // Send group info request message to the recipient who sent us a message with this groupId
676 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
679 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
680 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
681 Collections
.singletonList(messageId
),
682 System
.currentTimeMillis());
684 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
687 public long sendMessage(String messageText
, List
<String
> attachments
,
688 List
<String
> recipients
)
689 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
690 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
691 if (attachments
!= null) {
692 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
694 // Upload attachments here, so we only upload once even for multiple recipients
695 SignalServiceMessageSender messageSender
= getMessageSender();
696 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
697 for (SignalServiceAttachment attachment
: attachmentStreams
) {
698 if (attachment
.isStream()) {
699 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
700 } else if (attachment
.isPointer()) {
701 attachmentPointers
.add(attachment
.asPointer());
705 messageBuilder
.withAttachments(attachmentPointers
);
707 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
710 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
711 long targetSentTimestamp
, List
<String
> recipients
)
712 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
713 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
714 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
715 .withReaction(reaction
);
716 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
719 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
720 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
721 .asEndSessionMessage();
723 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
725 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
726 } catch (Exception e
) {
727 for (SignalServiceAddress address
: signalServiceAddresses
) {
728 handleEndSession(address
);
735 public String
getContactName(String number
) throws InvalidNumberException
{
736 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
737 if (contact
== null) {
744 public void setContactName(String number
, String name
) throws InvalidNumberException
{
745 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
746 ContactInfo contact
= account
.getContactStore().getContact(address
);
747 if (contact
== null) {
748 contact
= new ContactInfo(address
);
751 account
.getContactStore().updateContact(contact
);
755 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
756 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
759 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
760 ContactInfo contact
= account
.getContactStore().getContact(address
);
761 if (contact
== null) {
762 contact
= new ContactInfo(address
);
764 contact
.blocked
= blocked
;
765 account
.getContactStore().updateContact(contact
);
769 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
770 GroupInfo group
= getGroup(groupId
);
772 throw new GroupNotFoundException(groupId
);
775 group
.blocked
= blocked
;
776 account
.getGroupStore().updateGroup(group
);
780 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
781 if (groupId
.length
== 0) {
784 if (name
.isEmpty()) {
787 if (members
.isEmpty()) {
790 if (avatar
.isEmpty()) {
793 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
797 * Change the expiration timer for a contact
799 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
800 ContactInfo contact
= account
.getContactStore().getContact(address
);
801 contact
.messageExpirationTime
= messageExpirationTimer
;
802 account
.getContactStore().updateContact(contact
);
803 sendExpirationTimerUpdate(address
);
807 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
808 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
809 .asExpirationUpdate();
810 sendMessage(messageBuilder
, Collections
.singleton(address
));
814 * Change the expiration timer for a contact
816 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
817 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
818 setExpirationTimer(address
, messageExpirationTimer
);
822 * Change the expiration timer for a group
824 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
825 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
826 g
.messageExpirationTime
= messageExpirationTimer
;
827 account
.getGroupStore().updateGroup(g
);
831 * Upload the sticker pack from path.
833 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
834 * @return if successful, returns the URL to install the sticker pack in the signal app
836 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
837 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
839 SignalServiceMessageSender messageSender
= getMessageSender();
841 byte[] packKey
= KeyUtils
.createStickerUploadKey();
842 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
845 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
847 } catch (URISyntaxException e
) {
848 throw new AssertionError(e
);
852 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
854 String rootPath
= null;
856 final File file
= new File(path
);
857 if (file
.getName().endsWith(".zip")) {
858 zip
= new ZipFile(file
);
859 } else if (file
.getName().equals("manifest.json")) {
860 rootPath
= file
.getParent();
862 throw new StickerPackInvalidException("Could not find manifest.json");
865 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
867 if (pack
.stickers
== null) {
868 throw new StickerPackInvalidException("Must set a 'stickers' field.");
871 if (pack
.stickers
.isEmpty()) {
872 throw new StickerPackInvalidException("Must include stickers.");
875 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
876 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
877 if (sticker
.file
== null) {
878 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
881 Pair
<InputStream
, Long
> data
;
883 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
884 } catch (IOException ignored
) {
885 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
888 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
889 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
890 stickers
.add(stickerInfo
);
893 StickerInfo cover
= null;
894 if (pack
.cover
!= null) {
895 if (pack
.cover
.file
== null) {
896 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
899 Pair
<InputStream
, Long
> data
;
901 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
902 } catch (IOException ignored
) {
903 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
906 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
907 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
910 return new SignalServiceStickerManifestUpload(
917 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
918 InputStream inputStream
;
920 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
922 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
924 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
927 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
929 final ZipEntry entry
= zip
.getEntry(subfile
);
930 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
932 final File file
= new File(rootPath
, subfile
);
933 return new Pair
<>(new FileInputStream(file
), file
.length());
937 void requestSyncGroups() throws IOException
{
938 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
939 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
941 sendSyncMessage(message
);
942 } catch (UntrustedIdentityException e
) {
947 void requestSyncContacts() throws IOException
{
948 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
949 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
951 sendSyncMessage(message
);
952 } catch (UntrustedIdentityException e
) {
957 void requestSyncBlocked() throws IOException
{
958 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
959 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
961 sendSyncMessage(message
);
962 } catch (UntrustedIdentityException e
) {
967 void requestSyncConfiguration() throws IOException
{
968 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
969 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
971 sendSyncMessage(message
);
972 } catch (UntrustedIdentityException e
) {
977 private byte[] getSenderCertificate() {
978 // TODO support UUID capable sender certificates
979 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
982 certificate
= accountManager
.getSenderCertificate();
983 } catch (IOException e
) {
984 System
.err
.println("Failed to get sender certificate: " + e
);
987 // TODO cache for a day
991 private byte[] getSelfUnidentifiedAccessKey() {
992 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
995 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
996 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
997 if (contact
== null || contact
.profileKey
== null) {
1000 ProfileKey theirProfileKey
;
1002 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1003 } catch (InvalidInputException
| IOException e
) {
1004 throw new AssertionError(e
);
1006 SignalProfile targetProfile
;
1008 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1009 } catch (IOException e
) {
1010 System
.err
.println("Failed to get recipient profile: " + e
);
1014 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1018 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1019 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1022 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1025 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1026 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1027 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1029 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1030 return Optional
.absent();
1034 return Optional
.of(new UnidentifiedAccessPair(
1035 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1036 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1038 } catch (InvalidCertificateException e
) {
1039 return Optional
.absent();
1043 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1044 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1045 for (SignalServiceAddress recipient
: recipients
) {
1046 result
.add(getAccessFor(recipient
));
1051 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1052 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1053 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1054 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1056 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1057 return Optional
.absent();
1061 return Optional
.of(new UnidentifiedAccessPair(
1062 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1063 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1065 } catch (InvalidCertificateException e
) {
1066 return Optional
.absent();
1070 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1071 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1073 if (unidentifiedAccess
.isPresent()) {
1074 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1077 return Optional
.absent();
1080 private void sendSyncMessage(SignalServiceSyncMessage message
)
1081 throws IOException
, UntrustedIdentityException
{
1082 SignalServiceMessageSender messageSender
= getMessageSender();
1084 messageSender
.sendMessage(message
, getAccessForSync());
1085 } catch (UntrustedIdentityException e
) {
1086 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1092 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1094 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1095 throws EncapsulatedExceptions
, IOException
{
1096 final long timestamp
= System
.currentTimeMillis();
1097 messageBuilder
.withTimestamp(timestamp
);
1098 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1100 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1101 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1102 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1104 for (SendMessageResult result
: results
) {
1105 if (result
.isUnregisteredFailure()) {
1106 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1107 } else if (result
.isNetworkFailure()) {
1108 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1109 } else if (result
.getIdentityFailure() != null) {
1110 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1113 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1114 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1119 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1120 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1122 for (String number
: numbers
) {
1123 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1125 return signalServiceAddresses
;
1128 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1129 throws IOException
{
1130 if (messagePipe
== null) {
1131 messagePipe
= getMessageReceiver().createMessagePipe();
1133 if (unidentifiedMessagePipe
== null) {
1134 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1136 SignalServiceDataMessage message
= null;
1138 message
= messageBuilder
.build();
1139 if (message
.getGroupContext().isPresent()) {
1141 SignalServiceMessageSender messageSender
= getMessageSender();
1142 final boolean isRecipientUpdate
= false;
1143 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1144 for (SendMessageResult r
: result
) {
1145 if (r
.getIdentityFailure() != null) {
1146 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1150 } catch (UntrustedIdentityException e
) {
1151 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1152 return Collections
.emptyList();
1155 // Send to all individually, so sync messages are sent correctly
1156 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1157 for (SignalServiceAddress address
: recipients
) {
1158 ContactInfo contact
= account
.getContactStore().getContact(address
);
1159 if (contact
!= null) {
1160 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1161 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1163 messageBuilder
.withExpiration(0);
1164 messageBuilder
.withProfileKey(null);
1166 message
= messageBuilder
.build();
1167 if (address
.matches(account
.getSelfAddress())) {
1168 results
.add(sendSelfMessage(message
));
1170 results
.add(sendMessage(address
, message
));
1176 if (message
!= null && message
.isEndSession()) {
1177 for (SignalServiceAddress recipient
: recipients
) {
1178 handleEndSession(recipient
);
1185 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1186 SignalServiceMessageSender messageSender
= getMessageSender();
1188 SignalServiceAddress recipient
= account
.getSelfAddress();
1190 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1191 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1192 message
.getTimestamp(),
1194 message
.getExpiresInSeconds(),
1195 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1197 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1200 long startTime
= System
.currentTimeMillis();
1201 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1202 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1203 } catch (UntrustedIdentityException e
) {
1204 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1205 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1209 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1210 SignalServiceMessageSender messageSender
= getMessageSender();
1213 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1214 } catch (UntrustedIdentityException e
) {
1215 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1216 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1220 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1221 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1223 return cipher
.decrypt(envelope
);
1224 } catch (ProtocolUntrustedIdentityException e
) {
1225 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1226 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1227 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1228 throw identityException
;
1230 throw new AssertionError(e
);
1234 private void handleEndSession(SignalServiceAddress source
) {
1235 account
.getSignalProtocolStore().deleteAllSessions(source
);
1238 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1239 List
<HandleAction
> actions
= new ArrayList
<>();
1240 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1241 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1242 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1243 switch (groupInfo
.getType()) {
1245 if (group
== null) {
1246 group
= new GroupInfo(groupInfo
.getGroupId());
1249 if (groupInfo
.getAvatar().isPresent()) {
1250 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1251 if (avatar
.isPointer()) {
1253 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1254 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1255 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1260 if (groupInfo
.getName().isPresent()) {
1261 group
.name
= groupInfo
.getName().get();
1264 if (groupInfo
.getMembers().isPresent()) {
1265 group
.addMembers(groupInfo
.getMembers().get()
1267 .map(this::resolveSignalServiceAddress
)
1268 .collect(Collectors
.toSet()));
1271 account
.getGroupStore().updateGroup(group
);
1274 if (group
== null && !isSync
) {
1275 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1279 if (group
!= null) {
1280 group
.removeMember(source
);
1281 account
.getGroupStore().updateGroup(group
);
1285 if (group
!= null && !isSync
) {
1286 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1291 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1292 if (message
.isEndSession()) {
1293 handleEndSession(conversationPartnerAddress
);
1295 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1296 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1297 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1298 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1299 if (group
== null) {
1300 group
= new GroupInfo(groupInfo
.getGroupId());
1302 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1303 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1304 account
.getGroupStore().updateGroup(group
);
1307 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1308 if (contact
== null) {
1309 contact
= new ContactInfo(conversationPartnerAddress
);
1311 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1312 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1313 account
.getContactStore().updateContact(contact
);
1317 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1318 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1319 if (attachment
.isPointer()) {
1321 retrieveAttachment(attachment
.asPointer());
1322 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1323 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1328 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1329 if (source
.matches(account
.getSelfAddress())) {
1331 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1332 } catch (InvalidInputException ignored
) {
1334 ContactInfo contact
= account
.getContactStore().getContact(source
);
1335 if (contact
!= null) {
1336 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1337 account
.getContactStore().updateContact(contact
);
1340 ContactInfo contact
= account
.getContactStore().getContact(source
);
1341 if (contact
== null) {
1342 contact
= new ContactInfo(source
);
1344 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1345 account
.getContactStore().updateContact(contact
);
1348 if (message
.getPreviews().isPresent()) {
1349 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1350 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1351 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1352 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1354 retrieveAttachment(attachment
);
1355 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1356 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1364 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1365 final File cachePath
= new File(getMessageCachePath());
1366 if (!cachePath
.exists()) {
1369 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1370 if (!dir
.isDirectory()) {
1371 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1375 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1376 if (!fileEntry
.isFile()) {
1379 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1381 // Try to delete directory if empty
1386 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1387 SignalServiceEnvelope envelope
;
1389 envelope
= Utils
.loadEnvelope(fileEntry
);
1390 if (envelope
== null) {
1393 } catch (IOException e
) {
1394 e
.printStackTrace();
1397 SignalServiceContent content
= null;
1398 if (!envelope
.isReceipt()) {
1400 content
= decryptMessage(envelope
);
1401 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1403 } catch (Exception er
) {
1404 // All other errors are not recoverable, so delete the cached message
1406 Files
.delete(fileEntry
.toPath());
1407 } catch (IOException e
) {
1408 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1412 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1413 for (HandleAction action
: actions
) {
1415 action
.execute(this);
1416 } catch (Throwable e
) {
1417 e
.printStackTrace();
1422 handler
.handleMessage(envelope
, content
, null);
1424 Files
.delete(fileEntry
.toPath());
1425 } catch (IOException e
) {
1426 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1430 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1431 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1432 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1434 Set
<HandleAction
> queuedActions
= null;
1436 if (messagePipe
== null) {
1437 messagePipe
= messageReceiver
.createMessagePipe();
1440 boolean hasCaughtUpWithOldMessages
= false;
1443 SignalServiceEnvelope envelope
;
1444 SignalServiceContent content
= null;
1445 Exception exception
= null;
1446 final long now
= new Date().getTime();
1448 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1449 // store message on disk, before acknowledging receipt to the server
1451 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1452 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1453 Utils
.storeEnvelope(envelope1
, cacheFile
);
1454 } catch (IOException e
) {
1455 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1458 if (result
.isPresent()) {
1459 envelope
= result
.get();
1461 // Received indicator that server queue is empty
1462 hasCaughtUpWithOldMessages
= true;
1464 if (queuedActions
!= null) {
1465 for (HandleAction action
: queuedActions
) {
1467 action
.execute(this);
1468 } catch (Throwable e
) {
1469 e
.printStackTrace();
1472 queuedActions
.clear();
1473 queuedActions
= null;
1476 // Continue to wait another timeout for new messages
1479 } catch (TimeoutException e
) {
1480 if (returnOnTimeout
)
1483 } catch (InvalidVersionException e
) {
1484 System
.err
.println("Ignoring error: " + e
.getMessage());
1488 if (envelope
.hasSource()) {
1489 // Store uuid if we don't have it already
1490 SignalServiceAddress source
= envelope
.getSourceAddress();
1491 resolveSignalServiceAddress(source
);
1493 if (!envelope
.isReceipt()) {
1495 content
= decryptMessage(envelope
);
1496 } catch (Exception e
) {
1499 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1500 if (hasCaughtUpWithOldMessages
) {
1501 for (HandleAction action
: actions
) {
1503 action
.execute(this);
1504 } catch (Throwable e
) {
1505 e
.printStackTrace();
1509 if (queuedActions
== null) {
1510 queuedActions
= new HashSet
<>();
1512 queuedActions
.addAll(actions
);
1516 if (!isMessageBlocked(envelope
, content
)) {
1517 handler
.handleMessage(envelope
, content
, exception
);
1519 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1520 File cacheFile
= null;
1522 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1523 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1524 Files
.delete(cacheFile
.toPath());
1525 // Try to delete directory if empty
1526 new File(getMessageCachePath()).delete();
1527 } catch (IOException e
) {
1528 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1534 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1535 SignalServiceAddress source
;
1536 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1537 source
= envelope
.getSourceAddress();
1538 } else if (content
!= null) {
1539 source
= content
.getSender();
1543 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1544 if (sourceContact
!= null && sourceContact
.blocked
) {
1548 if (content
!= null && content
.getDataMessage().isPresent()) {
1549 SignalServiceDataMessage message
= content
.getDataMessage().get();
1550 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1551 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1552 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1553 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1561 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1562 List
<HandleAction
> actions
= new ArrayList
<>();
1563 if (content
!= null) {
1564 SignalServiceAddress sender
;
1565 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1566 sender
= envelope
.getSourceAddress();
1568 sender
= content
.getSender();
1570 // Store uuid if we don't have it already
1571 resolveSignalServiceAddress(sender
);
1573 if (content
.getDataMessage().isPresent()) {
1574 SignalServiceDataMessage message
= content
.getDataMessage().get();
1576 if (content
.isNeedsReceipt()) {
1577 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1580 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1582 if (content
.getSyncMessage().isPresent()) {
1583 account
.setMultiDevice(true);
1584 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1585 if (syncMessage
.getSent().isPresent()) {
1586 SentTranscriptMessage message
= syncMessage
.getSent().get();
1587 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1589 if (syncMessage
.getRequest().isPresent()) {
1590 RequestMessage rm
= syncMessage
.getRequest().get();
1591 if (rm
.isContactsRequest()) {
1592 actions
.add(SendSyncContactsAction
.create());
1594 if (rm
.isGroupsRequest()) {
1595 actions
.add(SendSyncGroupsAction
.create());
1597 if (rm
.isBlockedListRequest()) {
1598 actions
.add(SendSyncBlockedListAction
.create());
1600 // TODO Handle rm.isConfigurationRequest();
1602 if (syncMessage
.getGroups().isPresent()) {
1603 File tmpFile
= null;
1605 tmpFile
= IOUtils
.createTempFile();
1606 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1607 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1609 while ((g
= s
.read()) != null) {
1610 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1611 if (syncGroup
== null) {
1612 syncGroup
= new GroupInfo(g
.getId());
1614 if (g
.getName().isPresent()) {
1615 syncGroup
.name
= g
.getName().get();
1617 syncGroup
.addMembers(g
.getMembers()
1619 .map(this::resolveSignalServiceAddress
)
1620 .collect(Collectors
.toSet()));
1621 if (!g
.isActive()) {
1622 syncGroup
.removeMember(account
.getSelfAddress());
1624 // Add ourself to the member set as it's marked as active
1625 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1627 syncGroup
.blocked
= g
.isBlocked();
1628 if (g
.getColor().isPresent()) {
1629 syncGroup
.color
= g
.getColor().get();
1632 if (g
.getAvatar().isPresent()) {
1633 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1635 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1636 syncGroup
.archived
= g
.isArchived();
1637 account
.getGroupStore().updateGroup(syncGroup
);
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 groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1652 if (syncMessage
.getBlockedList().isPresent()) {
1653 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1654 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1655 setContactBlocked(resolveSignalServiceAddress(address
), true);
1657 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1659 setGroupBlocked(groupId
, true);
1660 } catch (GroupNotFoundException e
) {
1661 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1665 if (syncMessage
.getContacts().isPresent()) {
1666 File tmpFile
= null;
1668 tmpFile
= IOUtils
.createTempFile();
1669 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1670 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1671 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1672 if (contactsMessage
.isComplete()) {
1673 account
.getContactStore().clear();
1676 while ((c
= s
.read()) != null) {
1677 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1678 account
.setProfileKey(c
.getProfileKey().get());
1680 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1681 ContactInfo contact
= account
.getContactStore().getContact(address
);
1682 if (contact
== null) {
1683 contact
= new ContactInfo(address
);
1685 if (c
.getName().isPresent()) {
1686 contact
.name
= c
.getName().get();
1688 if (c
.getColor().isPresent()) {
1689 contact
.color
= c
.getColor().get();
1691 if (c
.getProfileKey().isPresent()) {
1692 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1694 if (c
.getVerified().isPresent()) {
1695 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1696 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1698 if (c
.getExpirationTimer().isPresent()) {
1699 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1701 contact
.blocked
= c
.isBlocked();
1702 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1703 contact
.archived
= c
.isArchived();
1704 account
.getContactStore().updateContact(contact
);
1706 if (c
.getAvatar().isPresent()) {
1707 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1711 } catch (Exception e
) {
1712 e
.printStackTrace();
1714 if (tmpFile
!= null) {
1716 Files
.delete(tmpFile
.toPath());
1717 } catch (IOException e
) {
1718 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1723 if (syncMessage
.getVerified().isPresent()) {
1724 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1725 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1727 if (syncMessage
.getConfiguration().isPresent()) {
1735 private File
getContactAvatarFile(String number
) {
1736 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1739 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1740 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1741 if (attachment
.isPointer()) {
1742 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1743 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1745 SignalServiceAttachmentStream stream
= attachment
.asStream();
1746 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1750 private File
getGroupAvatarFile(byte[] groupId
) {
1751 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1754 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1755 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1756 if (attachment
.isPointer()) {
1757 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1758 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1760 SignalServiceAttachmentStream stream
= attachment
.asStream();
1761 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1765 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1766 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1769 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1770 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1771 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1772 File outputFile
= getProfileAvatarFile(address
);
1774 File tmpFile
= IOUtils
.createTempFile();
1775 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1776 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1777 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1780 Files
.delete(tmpFile
.toPath());
1781 } catch (IOException e
) {
1782 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1788 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1789 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1792 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1793 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1794 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1797 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1798 if (storePreview
&& pointer
.getPreview().isPresent()) {
1799 File previewFile
= new File(outputFile
+ ".preview");
1800 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1801 byte[] preview
= pointer
.getPreview().get();
1802 output
.write(preview
, 0, preview
.length
);
1803 } catch (FileNotFoundException e
) {
1804 e
.printStackTrace();
1809 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1811 File tmpFile
= IOUtils
.createTempFile();
1812 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1813 IOUtils
.copyStreamToFile(input
, outputFile
);
1816 Files
.delete(tmpFile
.toPath());
1817 } catch (IOException e
) {
1818 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1824 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1825 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1826 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1829 void sendGroups() throws IOException
, UntrustedIdentityException
{
1830 File groupsFile
= IOUtils
.createTempFile();
1833 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1834 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1835 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1836 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1837 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1838 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1839 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1843 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1844 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1845 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1846 .withStream(groupsFileStream
)
1847 .withContentType("application/octet-stream")
1848 .withLength(groupsFile
.length())
1851 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1856 Files
.delete(groupsFile
.toPath());
1857 } catch (IOException e
) {
1858 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1863 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1864 File contactsFile
= IOUtils
.createTempFile();
1867 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1868 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1869 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1870 VerifiedMessage verifiedMessage
= null;
1871 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1872 if (currentIdentity
!= null) {
1873 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1876 ProfileKey profileKey
= null;
1878 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1879 } catch (InvalidInputException ignored
) {
1881 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1882 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1883 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1884 Optional
.of(record.messageExpirationTime
),
1885 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1888 if (account
.getProfileKey() != null) {
1889 // Send our own profile key as well
1890 out
.write(new DeviceContact(account
.getSelfAddress(),
1891 Optional
.absent(), Optional
.absent(),
1892 Optional
.absent(), Optional
.absent(),
1893 Optional
.of(account
.getProfileKey()),
1894 false, Optional
.absent(), Optional
.absent(), false));
1898 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1899 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1900 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1901 .withStream(contactsFileStream
)
1902 .withContentType("application/octet-stream")
1903 .withLength(contactsFile
.length())
1906 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1911 Files
.delete(contactsFile
.toPath());
1912 } catch (IOException e
) {
1913 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1918 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1919 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1920 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1921 if (record.blocked
) {
1922 addresses
.add(record.getAddress());
1925 List
<byte[]> groupIds
= new ArrayList
<>();
1926 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1927 if (record.blocked
) {
1928 groupIds
.add(record.groupId
);
1931 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1934 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1935 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1936 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1939 public List
<ContactInfo
> getContacts() {
1940 return account
.getContactStore().getContacts();
1943 public ContactInfo
getContact(String number
) {
1944 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1947 public GroupInfo
getGroup(byte[] groupId
) {
1948 return account
.getGroupStore().getGroup(groupId
);
1951 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1952 return account
.getSignalProtocolStore().getIdentities();
1955 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1956 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1960 * Trust this the identity with this fingerprint
1962 * @param name username of the identity
1963 * @param fingerprint Fingerprint
1965 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1966 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1967 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1971 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1972 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1976 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1978 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1979 } catch (IOException
| UntrustedIdentityException e
) {
1980 e
.printStackTrace();
1989 * Trust this the identity with this safety number
1991 * @param name username of the identity
1992 * @param safetyNumber Safety number
1994 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1995 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1996 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2000 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2001 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2005 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2007 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2008 } catch (IOException
| UntrustedIdentityException e
) {
2009 e
.printStackTrace();
2018 * Trust all keys of this identity without verification
2020 * @param name username of the identity
2022 public boolean trustIdentityAllKeys(String name
) {
2023 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2024 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2028 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2029 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2030 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2032 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2033 } catch (IOException
| UntrustedIdentityException e
) {
2034 e
.printStackTrace();
2042 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2043 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2046 void saveAccount() {
2050 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2051 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2052 return resolveSignalServiceAddress(canonicalizedNumber
);
2055 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2056 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2058 return resolveSignalServiceAddress(address
);
2061 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2062 if (address
.matches(account
.getSelfAddress())) {
2063 return account
.getSelfAddress();
2066 return account
.getRecipientStore().resolveServiceAddress(address
);
2070 public void close() throws IOException
{
2071 if (messagePipe
!= null) {
2072 messagePipe
.shutdown();
2076 if (unidentifiedMessagePipe
!= null) {
2077 unidentifiedMessagePipe
.shutdown();
2078 unidentifiedMessagePipe
= null;
2084 public interface ReceiveMessageHandler
{
2086 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);