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();
1473 queuedActions
.clear();
1474 queuedActions
= null;
1477 // Continue to wait another timeout for new messages
1480 } catch (TimeoutException e
) {
1481 if (returnOnTimeout
)
1484 } catch (InvalidVersionException e
) {
1485 System
.err
.println("Ignoring error: " + e
.getMessage());
1489 if (envelope
.hasSource()) {
1490 // Store uuid if we don't have it already
1491 SignalServiceAddress source
= envelope
.getSourceAddress();
1492 resolveSignalServiceAddress(source
);
1494 if (!envelope
.isReceipt()) {
1496 content
= decryptMessage(envelope
);
1497 } catch (Exception e
) {
1500 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1501 if (hasCaughtUpWithOldMessages
) {
1502 for (HandleAction action
: actions
) {
1504 action
.execute(this);
1505 } catch (Throwable e
) {
1506 e
.printStackTrace();
1510 if (queuedActions
== null) {
1511 queuedActions
= new HashSet
<>();
1513 queuedActions
.addAll(actions
);
1517 if (!isMessageBlocked(envelope
, content
)) {
1518 handler
.handleMessage(envelope
, content
, exception
);
1520 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1521 File cacheFile
= null;
1523 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1524 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1525 Files
.delete(cacheFile
.toPath());
1526 // Try to delete directory if empty
1527 new File(getMessageCachePath()).delete();
1528 } catch (IOException e
) {
1529 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1535 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1536 SignalServiceAddress source
;
1537 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1538 source
= envelope
.getSourceAddress();
1539 } else if (content
!= null) {
1540 source
= content
.getSender();
1544 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1545 if (sourceContact
!= null && sourceContact
.blocked
) {
1549 if (content
!= null && content
.getDataMessage().isPresent()) {
1550 SignalServiceDataMessage message
= content
.getDataMessage().get();
1551 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1552 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1553 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1554 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1562 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1563 List
<HandleAction
> actions
= new ArrayList
<>();
1564 if (content
!= null) {
1565 SignalServiceAddress sender
;
1566 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1567 sender
= envelope
.getSourceAddress();
1569 sender
= content
.getSender();
1571 // Store uuid if we don't have it already
1572 resolveSignalServiceAddress(sender
);
1574 if (content
.getDataMessage().isPresent()) {
1575 SignalServiceDataMessage message
= content
.getDataMessage().get();
1577 if (content
.isNeedsReceipt()) {
1578 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1581 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1583 if (content
.getSyncMessage().isPresent()) {
1584 account
.setMultiDevice(true);
1585 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1586 if (syncMessage
.getSent().isPresent()) {
1587 SentTranscriptMessage message
= syncMessage
.getSent().get();
1588 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1590 if (syncMessage
.getRequest().isPresent()) {
1591 RequestMessage rm
= syncMessage
.getRequest().get();
1592 if (rm
.isContactsRequest()) {
1593 actions
.add(SendSyncContactsAction
.create());
1595 if (rm
.isGroupsRequest()) {
1596 actions
.add(SendSyncGroupsAction
.create());
1598 if (rm
.isBlockedListRequest()) {
1599 actions
.add(SendSyncBlockedListAction
.create());
1601 // TODO Handle rm.isConfigurationRequest();
1603 if (syncMessage
.getGroups().isPresent()) {
1604 File tmpFile
= null;
1606 tmpFile
= IOUtils
.createTempFile();
1607 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1608 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1610 while ((g
= s
.read()) != null) {
1611 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1612 if (syncGroup
== null) {
1613 syncGroup
= new GroupInfo(g
.getId());
1615 if (g
.getName().isPresent()) {
1616 syncGroup
.name
= g
.getName().get();
1618 syncGroup
.addMembers(g
.getMembers()
1620 .map(this::resolveSignalServiceAddress
)
1621 .collect(Collectors
.toSet()));
1622 if (!g
.isActive()) {
1623 syncGroup
.removeMember(account
.getSelfAddress());
1625 // Add ourself to the member set as it's marked as active
1626 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1628 syncGroup
.blocked
= g
.isBlocked();
1629 if (g
.getColor().isPresent()) {
1630 syncGroup
.color
= g
.getColor().get();
1633 if (g
.getAvatar().isPresent()) {
1634 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1636 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1637 syncGroup
.archived
= g
.isArchived();
1638 account
.getGroupStore().updateGroup(syncGroup
);
1641 } catch (Exception e
) {
1642 e
.printStackTrace();
1644 if (tmpFile
!= null) {
1646 Files
.delete(tmpFile
.toPath());
1647 } catch (IOException e
) {
1648 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1653 if (syncMessage
.getBlockedList().isPresent()) {
1654 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1655 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1656 setContactBlocked(resolveSignalServiceAddress(address
), true);
1658 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1660 setGroupBlocked(groupId
, true);
1661 } catch (GroupNotFoundException e
) {
1662 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1666 if (syncMessage
.getContacts().isPresent()) {
1667 File tmpFile
= null;
1669 tmpFile
= IOUtils
.createTempFile();
1670 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1671 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1672 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1673 if (contactsMessage
.isComplete()) {
1674 account
.getContactStore().clear();
1677 while ((c
= s
.read()) != null) {
1678 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1679 account
.setProfileKey(c
.getProfileKey().get());
1681 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1682 ContactInfo contact
= account
.getContactStore().getContact(address
);
1683 if (contact
== null) {
1684 contact
= new ContactInfo(address
);
1686 if (c
.getName().isPresent()) {
1687 contact
.name
= c
.getName().get();
1689 if (c
.getColor().isPresent()) {
1690 contact
.color
= c
.getColor().get();
1692 if (c
.getProfileKey().isPresent()) {
1693 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1695 if (c
.getVerified().isPresent()) {
1696 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1697 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1699 if (c
.getExpirationTimer().isPresent()) {
1700 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1702 contact
.blocked
= c
.isBlocked();
1703 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1704 contact
.archived
= c
.isArchived();
1705 account
.getContactStore().updateContact(contact
);
1707 if (c
.getAvatar().isPresent()) {
1708 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1712 } catch (Exception e
) {
1713 e
.printStackTrace();
1715 if (tmpFile
!= null) {
1717 Files
.delete(tmpFile
.toPath());
1718 } catch (IOException e
) {
1719 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1724 if (syncMessage
.getVerified().isPresent()) {
1725 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1726 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1728 if (syncMessage
.getConfiguration().isPresent()) {
1736 private File
getContactAvatarFile(String number
) {
1737 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1740 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1741 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1742 if (attachment
.isPointer()) {
1743 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1744 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1746 SignalServiceAttachmentStream stream
= attachment
.asStream();
1747 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1751 private File
getGroupAvatarFile(byte[] groupId
) {
1752 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1755 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1756 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1757 if (attachment
.isPointer()) {
1758 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1759 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1761 SignalServiceAttachmentStream stream
= attachment
.asStream();
1762 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1766 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1767 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1770 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1771 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1772 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1773 File outputFile
= getProfileAvatarFile(address
);
1775 File tmpFile
= IOUtils
.createTempFile();
1776 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1777 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1778 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1781 Files
.delete(tmpFile
.toPath());
1782 } catch (IOException e
) {
1783 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1789 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1790 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1793 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1794 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1795 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1798 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1799 if (storePreview
&& pointer
.getPreview().isPresent()) {
1800 File previewFile
= new File(outputFile
+ ".preview");
1801 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1802 byte[] preview
= pointer
.getPreview().get();
1803 output
.write(preview
, 0, preview
.length
);
1804 } catch (FileNotFoundException e
) {
1805 e
.printStackTrace();
1810 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1812 File tmpFile
= IOUtils
.createTempFile();
1813 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1814 IOUtils
.copyStreamToFile(input
, outputFile
);
1817 Files
.delete(tmpFile
.toPath());
1818 } catch (IOException e
) {
1819 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1825 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1826 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1827 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1830 void sendGroups() throws IOException
, UntrustedIdentityException
{
1831 File groupsFile
= IOUtils
.createTempFile();
1834 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1835 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1836 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1837 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1838 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1839 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1840 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1844 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1845 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1846 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1847 .withStream(groupsFileStream
)
1848 .withContentType("application/octet-stream")
1849 .withLength(groupsFile
.length())
1852 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1857 Files
.delete(groupsFile
.toPath());
1858 } catch (IOException e
) {
1859 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1864 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1865 File contactsFile
= IOUtils
.createTempFile();
1868 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1869 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1870 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1871 VerifiedMessage verifiedMessage
= null;
1872 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1873 if (currentIdentity
!= null) {
1874 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1877 ProfileKey profileKey
= null;
1879 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1880 } catch (InvalidInputException ignored
) {
1882 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1883 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1884 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1885 Optional
.of(record.messageExpirationTime
),
1886 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1889 if (account
.getProfileKey() != null) {
1890 // Send our own profile key as well
1891 out
.write(new DeviceContact(account
.getSelfAddress(),
1892 Optional
.absent(), Optional
.absent(),
1893 Optional
.absent(), Optional
.absent(),
1894 Optional
.of(account
.getProfileKey()),
1895 false, Optional
.absent(), Optional
.absent(), false));
1899 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1900 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1901 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1902 .withStream(contactsFileStream
)
1903 .withContentType("application/octet-stream")
1904 .withLength(contactsFile
.length())
1907 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1912 Files
.delete(contactsFile
.toPath());
1913 } catch (IOException e
) {
1914 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1919 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1920 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1921 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1922 if (record.blocked
) {
1923 addresses
.add(record.getAddress());
1926 List
<byte[]> groupIds
= new ArrayList
<>();
1927 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1928 if (record.blocked
) {
1929 groupIds
.add(record.groupId
);
1932 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1935 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1936 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1937 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1940 public List
<ContactInfo
> getContacts() {
1941 return account
.getContactStore().getContacts();
1944 public ContactInfo
getContact(String number
) {
1945 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1948 public GroupInfo
getGroup(byte[] groupId
) {
1949 return account
.getGroupStore().getGroup(groupId
);
1952 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1953 return account
.getSignalProtocolStore().getIdentities();
1956 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1957 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1961 * Trust this the identity with this fingerprint
1963 * @param name username of the identity
1964 * @param fingerprint Fingerprint
1966 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1967 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1968 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1972 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1973 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1977 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1979 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1980 } catch (IOException
| UntrustedIdentityException e
) {
1981 e
.printStackTrace();
1990 * Trust this the identity with this safety number
1992 * @param name username of the identity
1993 * @param safetyNumber Safety number
1995 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1996 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1997 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2001 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2002 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2006 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2008 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2009 } catch (IOException
| UntrustedIdentityException e
) {
2010 e
.printStackTrace();
2019 * Trust all keys of this identity without verification
2021 * @param name username of the identity
2023 public boolean trustIdentityAllKeys(String name
) {
2024 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2025 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2029 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2030 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2031 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2033 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2034 } catch (IOException
| UntrustedIdentityException e
) {
2035 e
.printStackTrace();
2043 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2044 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2047 void saveAccount() {
2051 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2052 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2053 return resolveSignalServiceAddress(canonicalizedNumber
);
2056 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2057 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2059 return resolveSignalServiceAddress(address
);
2062 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2063 if (address
.matches(account
.getSelfAddress())) {
2064 return account
.getSelfAddress();
2067 return account
.getRecipientStore().resolveServiceAddress(address
);
2071 public void close() throws IOException
{
2072 if (messagePipe
!= null) {
2073 messagePipe
.shutdown();
2077 if (unidentifiedMessagePipe
!= null) {
2078 unidentifiedMessagePipe
.shutdown();
2079 unidentifiedMessagePipe
= null;
2085 public interface ReceiveMessageHandler
{
2087 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);