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());
270 // Store profile keys only in profile store
271 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
272 String profileKeyString
= contact
.profileKey
;
273 if (profileKeyString
== null) {
276 final ProfileKey profileKey
;
278 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
279 } catch (InvalidInputException
| IOException e
) {
282 contact
.profileKey
= null;
283 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
287 public void checkAccountState() throws IOException
{
288 if (account
.isRegistered()) {
289 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
293 if (account
.getUuid() == null) {
294 account
.setUuid(accountManager
.getOwnUuid());
300 public boolean isRegistered() {
301 return account
.isRegistered();
304 public void register(boolean voiceVerification
) throws IOException
{
305 account
.setPassword(KeyUtils
.createPassword());
307 // Resetting UUID, because registering doesn't work otherwise
308 account
.setUuid(null);
309 accountManager
= createSignalServiceAccountManager();
311 if (voiceVerification
) {
312 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
314 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
317 account
.setRegistered(false);
321 public void updateAccountAttributes() throws IOException
{
322 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
325 public void setProfile(String name
, File avatar
) throws IOException
{
326 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
327 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
331 public void unregister() throws IOException
{
332 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
333 // If this is the master device, other users can't send messages to this number anymore.
334 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
335 accountManager
.setGcmId(Optional
.absent());
337 account
.setRegistered(false);
341 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
342 List
<DeviceInfo
> devices
= accountManager
.getDevices();
343 account
.setMultiDevice(devices
.size() > 1);
348 public void removeLinkedDevices(int deviceId
) throws IOException
{
349 accountManager
.removeDevice(deviceId
);
350 List
<DeviceInfo
> devices
= accountManager
.getDevices();
351 account
.setMultiDevice(devices
.size() > 1);
355 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
356 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
358 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
361 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
362 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
363 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
365 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
366 account
.setMultiDevice(true);
370 private List
<PreKeyRecord
> generatePreKeys() {
371 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
373 final int offset
= account
.getPreKeyIdOffset();
374 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
375 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
376 ECKeyPair keyPair
= Curve
.generateKeyPair();
377 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
382 account
.addPreKeys(records
);
388 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
390 ECKeyPair keyPair
= Curve
.generateKeyPair();
391 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
392 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
394 account
.addSignedPreKey(record);
398 } catch (InvalidKeyException e
) {
399 throw new AssertionError(e
);
403 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
404 verificationCode
= verificationCode
.replace("-", "");
405 account
.setSignalingKey(KeyUtils
.createSignalingKey());
406 // TODO make unrestricted unidentified access configurable
407 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
409 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
410 // TODO response.isStorageCapable()
411 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
412 account
.setRegistered(true);
413 account
.setUuid(uuid
);
414 account
.setRegistrationLockPin(pin
);
415 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
421 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
422 if (pin
.isPresent()) {
423 account
.setRegistrationLockPin(pin
.get());
424 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
426 account
.setRegistrationLockPin(null);
427 accountManager
.removeRegistrationLockV1();
432 void refreshPreKeys() throws IOException
{
433 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
434 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
435 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
437 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
440 private SignalServiceMessageReceiver
getMessageReceiver() {
441 // TODO implement ZkGroup support
442 final ClientZkProfileOperations clientZkProfileOperations
= null;
443 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
446 private SignalServiceMessageSender
getMessageSender() {
447 // TODO implement ZkGroup support
448 final ClientZkProfileOperations clientZkProfileOperations
= null;
449 final ExecutorService executor
= null;
450 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
451 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
454 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
455 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
460 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
461 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
465 SignalServiceMessageReceiver receiver
= getMessageReceiver();
467 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
468 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
469 throw new IOException("Failed to retrieve profile", e
);
473 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
474 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
475 long now
= new Date().getTime();
476 // Profiles are cache for 24h before retrieving them again
477 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
478 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
479 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
482 return profileEntry
.getProfile();
485 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
486 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
488 File avatarFile
= null;
490 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
491 } catch (Throwable e
) {
492 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
495 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
497 return new SignalProfile(
498 encryptedProfile
.getIdentityKey(),
499 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
501 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
502 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
503 encryptedProfile
.getCapabilities());
504 } catch (InvalidCiphertextException e
) {
509 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
510 File file
= getGroupAvatarFile(groupId
);
511 if (!file
.exists()) {
512 return Optional
.absent();
515 return Optional
.of(Utils
.createAttachment(file
));
518 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
519 File file
= getContactAvatarFile(number
);
520 if (!file
.exists()) {
521 return Optional
.absent();
524 return Optional
.of(Utils
.createAttachment(file
));
527 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
528 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
530 throw new GroupNotFoundException(groupId
);
532 if (!g
.isMember(account
.getSelfAddress())) {
533 throw new NotAGroupMemberException(groupId
, g
.name
);
538 public List
<GroupInfo
> getGroups() {
539 return account
.getGroupStore().getGroups();
542 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
544 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
545 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
546 if (attachments
!= null) {
547 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
549 if (groupId
!= null) {
550 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
553 messageBuilder
.asGroupMessage(group
);
556 final GroupInfo g
= getGroupForSending(groupId
);
558 messageBuilder
.withExpiration(g
.messageExpirationTime
);
560 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
563 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
564 long targetSentTimestamp
, byte[] groupId
)
565 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
566 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
567 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
568 .withReaction(reaction
);
569 if (groupId
!= null) {
570 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
573 messageBuilder
.asGroupMessage(group
);
575 final GroupInfo g
= getGroupForSending(groupId
);
576 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
580 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
584 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
585 .asGroupMessage(group
);
587 final GroupInfo g
= getGroupForSending(groupId
);
588 g
.removeMember(account
.getSelfAddress());
589 account
.getGroupStore().updateGroup(g
);
591 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
594 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
596 if (groupId
== null) {
598 g
= new GroupInfo(KeyUtils
.createGroupId());
599 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
601 g
= getGroupForSending(groupId
);
608 if (members
!= null) {
609 final Set
<String
> newE164Members
= new HashSet
<>();
610 for (SignalServiceAddress member
: members
) {
611 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
614 newE164Members
.add(member
.getNumber().get());
617 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
618 if (contacts
.size() != newE164Members
.size()) {
619 // Some of the new members are not registered on Signal
620 for (ContactTokenDetails contact
: contacts
) {
621 newE164Members
.remove(contact
.getNumber());
623 throw new IOException("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
626 g
.addMembers(members
);
629 if (avatarFile
!= null) {
630 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
631 File aFile
= getGroupAvatarFile(g
.groupId
);
632 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
635 account
.getGroupStore().updateGroup(g
);
637 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
639 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
643 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
644 if (groupId
== null) {
647 GroupInfo g
= getGroupForSending(groupId
);
649 if (!g
.isMember(recipient
)) {
653 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
655 // Send group message only to the recipient who requested it
656 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
659 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
660 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
663 .withMembers(new ArrayList
<>(g
.getMembers()));
665 File aFile
= getGroupAvatarFile(g
.groupId
);
666 if (aFile
.exists()) {
668 group
.withAvatar(Utils
.createAttachment(aFile
));
669 } catch (IOException e
) {
670 throw new AttachmentInvalidException(aFile
.toString(), e
);
674 return SignalServiceDataMessage
.newBuilder()
675 .asGroupMessage(group
.build())
676 .withExpiration(g
.messageExpirationTime
);
679 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
680 if (groupId
== null) {
684 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
687 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
688 .asGroupMessage(group
.build());
690 // Send group info request message to the recipient who sent us a message with this groupId
691 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
694 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
695 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
696 Collections
.singletonList(messageId
),
697 System
.currentTimeMillis());
699 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
702 public long sendMessage(String messageText
, List
<String
> attachments
,
703 List
<String
> recipients
)
704 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
705 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
706 if (attachments
!= null) {
707 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
709 // Upload attachments here, so we only upload once even for multiple recipients
710 SignalServiceMessageSender messageSender
= getMessageSender();
711 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
712 for (SignalServiceAttachment attachment
: attachmentStreams
) {
713 if (attachment
.isStream()) {
714 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
715 } else if (attachment
.isPointer()) {
716 attachmentPointers
.add(attachment
.asPointer());
720 messageBuilder
.withAttachments(attachmentPointers
);
722 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
725 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
726 long targetSentTimestamp
, List
<String
> recipients
)
727 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
728 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
729 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
730 .withReaction(reaction
);
731 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
734 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
735 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
736 .asEndSessionMessage();
738 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
740 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
741 } catch (Exception e
) {
742 for (SignalServiceAddress address
: signalServiceAddresses
) {
743 handleEndSession(address
);
750 public String
getContactName(String number
) throws InvalidNumberException
{
751 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
752 if (contact
== null) {
759 public void setContactName(String number
, String name
) throws InvalidNumberException
{
760 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
761 ContactInfo contact
= account
.getContactStore().getContact(address
);
762 if (contact
== null) {
763 contact
= new ContactInfo(address
);
766 account
.getContactStore().updateContact(contact
);
770 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
771 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
774 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
775 ContactInfo contact
= account
.getContactStore().getContact(address
);
776 if (contact
== null) {
777 contact
= new ContactInfo(address
);
779 contact
.blocked
= blocked
;
780 account
.getContactStore().updateContact(contact
);
784 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
785 GroupInfo group
= getGroup(groupId
);
787 throw new GroupNotFoundException(groupId
);
790 group
.blocked
= blocked
;
791 account
.getGroupStore().updateGroup(group
);
795 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
796 if (groupId
.length
== 0) {
799 if (name
.isEmpty()) {
802 if (members
.isEmpty()) {
805 if (avatar
.isEmpty()) {
808 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
812 * Change the expiration timer for a contact
814 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
815 ContactInfo contact
= account
.getContactStore().getContact(address
);
816 contact
.messageExpirationTime
= messageExpirationTimer
;
817 account
.getContactStore().updateContact(contact
);
818 sendExpirationTimerUpdate(address
);
822 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
823 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
824 .asExpirationUpdate();
825 sendMessage(messageBuilder
, Collections
.singleton(address
));
829 * Change the expiration timer for a contact
831 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
832 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
833 setExpirationTimer(address
, messageExpirationTimer
);
837 * Change the expiration timer for a group
839 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
840 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
841 g
.messageExpirationTime
= messageExpirationTimer
;
842 account
.getGroupStore().updateGroup(g
);
846 * Upload the sticker pack from path.
848 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
849 * @return if successful, returns the URL to install the sticker pack in the signal app
851 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
852 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
854 SignalServiceMessageSender messageSender
= getMessageSender();
856 byte[] packKey
= KeyUtils
.createStickerUploadKey();
857 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
860 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
862 } catch (URISyntaxException e
) {
863 throw new AssertionError(e
);
867 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
869 String rootPath
= null;
871 final File file
= new File(path
);
872 if (file
.getName().endsWith(".zip")) {
873 zip
= new ZipFile(file
);
874 } else if (file
.getName().equals("manifest.json")) {
875 rootPath
= file
.getParent();
877 throw new StickerPackInvalidException("Could not find manifest.json");
880 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
882 if (pack
.stickers
== null) {
883 throw new StickerPackInvalidException("Must set a 'stickers' field.");
886 if (pack
.stickers
.isEmpty()) {
887 throw new StickerPackInvalidException("Must include stickers.");
890 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
891 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
892 if (sticker
.file
== null) {
893 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
896 Pair
<InputStream
, Long
> data
;
898 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
899 } catch (IOException ignored
) {
900 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
903 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
904 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
905 stickers
.add(stickerInfo
);
908 StickerInfo cover
= null;
909 if (pack
.cover
!= null) {
910 if (pack
.cover
.file
== null) {
911 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
914 Pair
<InputStream
, Long
> data
;
916 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
917 } catch (IOException ignored
) {
918 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
921 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
922 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
925 return new SignalServiceStickerManifestUpload(
932 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
933 InputStream inputStream
;
935 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
937 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
939 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
942 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
944 final ZipEntry entry
= zip
.getEntry(subfile
);
945 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
947 final File file
= new File(rootPath
, subfile
);
948 return new Pair
<>(new FileInputStream(file
), file
.length());
952 void requestSyncGroups() throws IOException
{
953 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
954 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
956 sendSyncMessage(message
);
957 } catch (UntrustedIdentityException e
) {
962 void requestSyncContacts() throws IOException
{
963 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
964 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
966 sendSyncMessage(message
);
967 } catch (UntrustedIdentityException e
) {
972 void requestSyncBlocked() throws IOException
{
973 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
974 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
976 sendSyncMessage(message
);
977 } catch (UntrustedIdentityException e
) {
982 void requestSyncConfiguration() throws IOException
{
983 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
984 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
986 sendSyncMessage(message
);
987 } catch (UntrustedIdentityException e
) {
992 private byte[] getSenderCertificate() {
993 // TODO support UUID capable sender certificates
994 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
997 certificate
= accountManager
.getSenderCertificate();
998 } catch (IOException e
) {
999 System
.err
.println("Failed to get sender certificate: " + e
);
1002 // TODO cache for a day
1006 private byte[] getSelfUnidentifiedAccessKey() {
1007 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1010 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1011 ProfileKey theirProfileKey
= account
.getProfileStore().getProfileKey(recipient
);
1012 if (theirProfileKey
== null) {
1015 SignalProfile targetProfile
;
1017 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1018 } catch (IOException e
) {
1019 System
.err
.println("Failed to get recipient profile: " + e
);
1023 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1027 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1028 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1031 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1034 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1035 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1036 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1038 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1039 return Optional
.absent();
1043 return Optional
.of(new UnidentifiedAccessPair(
1044 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1045 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1047 } catch (InvalidCertificateException e
) {
1048 return Optional
.absent();
1052 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1053 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1054 for (SignalServiceAddress recipient
: recipients
) {
1055 result
.add(getAccessFor(recipient
));
1060 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1061 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1062 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1063 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1065 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1066 return Optional
.absent();
1070 return Optional
.of(new UnidentifiedAccessPair(
1071 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1072 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1074 } catch (InvalidCertificateException e
) {
1075 return Optional
.absent();
1079 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1080 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1082 if (unidentifiedAccess
.isPresent()) {
1083 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1086 return Optional
.absent();
1089 private void sendSyncMessage(SignalServiceSyncMessage message
)
1090 throws IOException
, UntrustedIdentityException
{
1091 SignalServiceMessageSender messageSender
= getMessageSender();
1093 messageSender
.sendMessage(message
, getAccessForSync());
1094 } catch (UntrustedIdentityException e
) {
1095 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1101 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1103 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1104 throws EncapsulatedExceptions
, IOException
{
1105 final long timestamp
= System
.currentTimeMillis();
1106 messageBuilder
.withTimestamp(timestamp
);
1107 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1109 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1110 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1111 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1113 for (SendMessageResult result
: results
) {
1114 if (result
.isUnregisteredFailure()) {
1115 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1116 } else if (result
.isNetworkFailure()) {
1117 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1118 } else if (result
.getIdentityFailure() != null) {
1119 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1122 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1123 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1128 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1129 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1131 for (String number
: numbers
) {
1132 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1134 return signalServiceAddresses
;
1137 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1138 throws IOException
{
1139 if (messagePipe
== null) {
1140 messagePipe
= getMessageReceiver().createMessagePipe();
1142 if (unidentifiedMessagePipe
== null) {
1143 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1145 SignalServiceDataMessage message
= null;
1147 message
= messageBuilder
.build();
1148 if (message
.getGroupContext().isPresent()) {
1150 SignalServiceMessageSender messageSender
= getMessageSender();
1151 final boolean isRecipientUpdate
= false;
1152 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1153 for (SendMessageResult r
: result
) {
1154 if (r
.getIdentityFailure() != null) {
1155 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1159 } catch (UntrustedIdentityException e
) {
1160 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1161 return Collections
.emptyList();
1164 // Send to all individually, so sync messages are sent correctly
1165 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1166 for (SignalServiceAddress address
: recipients
) {
1167 ContactInfo contact
= account
.getContactStore().getContact(address
);
1168 if (contact
!= null) {
1169 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1170 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1172 messageBuilder
.withExpiration(0);
1173 messageBuilder
.withProfileKey(null);
1175 message
= messageBuilder
.build();
1176 if (address
.matches(account
.getSelfAddress())) {
1177 results
.add(sendSelfMessage(message
));
1179 results
.add(sendMessage(address
, message
));
1185 if (message
!= null && message
.isEndSession()) {
1186 for (SignalServiceAddress recipient
: recipients
) {
1187 handleEndSession(recipient
);
1194 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1195 SignalServiceMessageSender messageSender
= getMessageSender();
1197 SignalServiceAddress recipient
= account
.getSelfAddress();
1199 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1200 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1201 message
.getTimestamp(),
1203 message
.getExpiresInSeconds(),
1204 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1206 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1209 long startTime
= System
.currentTimeMillis();
1210 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1211 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1212 } catch (UntrustedIdentityException e
) {
1213 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1214 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1218 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1219 SignalServiceMessageSender messageSender
= getMessageSender();
1222 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1223 } catch (UntrustedIdentityException e
) {
1224 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1225 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1229 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1230 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1232 return cipher
.decrypt(envelope
);
1233 } catch (ProtocolUntrustedIdentityException e
) {
1234 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1235 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1236 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1237 throw identityException
;
1239 throw new AssertionError(e
);
1243 private void handleEndSession(SignalServiceAddress source
) {
1244 account
.getSignalProtocolStore().deleteAllSessions(source
);
1247 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1248 List
<HandleAction
> actions
= new ArrayList
<>();
1249 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1250 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1251 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1252 switch (groupInfo
.getType()) {
1254 if (group
== null) {
1255 group
= new GroupInfo(groupInfo
.getGroupId());
1258 if (groupInfo
.getAvatar().isPresent()) {
1259 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1260 if (avatar
.isPointer()) {
1262 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1263 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1264 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1269 if (groupInfo
.getName().isPresent()) {
1270 group
.name
= groupInfo
.getName().get();
1273 if (groupInfo
.getMembers().isPresent()) {
1274 group
.addMembers(groupInfo
.getMembers().get()
1276 .map(this::resolveSignalServiceAddress
)
1277 .collect(Collectors
.toSet()));
1280 account
.getGroupStore().updateGroup(group
);
1283 if (group
== null && !isSync
) {
1284 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1288 if (group
!= null) {
1289 group
.removeMember(source
);
1290 account
.getGroupStore().updateGroup(group
);
1294 if (group
!= null && !isSync
) {
1295 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1300 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1301 if (message
.isEndSession()) {
1302 handleEndSession(conversationPartnerAddress
);
1304 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1305 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1306 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1307 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1308 if (group
== null) {
1309 group
= new GroupInfo(groupInfo
.getGroupId());
1311 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1312 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1313 account
.getGroupStore().updateGroup(group
);
1316 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1317 if (contact
== null) {
1318 contact
= new ContactInfo(conversationPartnerAddress
);
1320 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1321 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1322 account
.getContactStore().updateContact(contact
);
1326 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1327 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1328 if (attachment
.isPointer()) {
1330 retrieveAttachment(attachment
.asPointer());
1331 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1332 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1337 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1338 final ProfileKey profileKey
;
1340 profileKey
= new ProfileKey(message
.getProfileKey().get());
1341 } catch (InvalidInputException e
) {
1342 throw new AssertionError(e
);
1344 if (source
.matches(account
.getSelfAddress())) {
1345 this.account
.setProfileKey(profileKey
);
1347 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1349 if (message
.getPreviews().isPresent()) {
1350 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1351 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1352 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1353 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1355 retrieveAttachment(attachment
);
1356 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1357 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1365 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1366 final File cachePath
= new File(getMessageCachePath());
1367 if (!cachePath
.exists()) {
1370 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1371 if (!dir
.isDirectory()) {
1372 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1376 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1377 if (!fileEntry
.isFile()) {
1380 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1382 // Try to delete directory if empty
1387 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1388 SignalServiceEnvelope envelope
;
1390 envelope
= Utils
.loadEnvelope(fileEntry
);
1391 if (envelope
== null) {
1394 } catch (IOException e
) {
1395 e
.printStackTrace();
1398 SignalServiceContent content
= null;
1399 if (!envelope
.isReceipt()) {
1401 content
= decryptMessage(envelope
);
1402 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1404 } catch (Exception er
) {
1405 // All other errors are not recoverable, so delete the cached message
1407 Files
.delete(fileEntry
.toPath());
1408 } catch (IOException e
) {
1409 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1413 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1414 for (HandleAction action
: actions
) {
1416 action
.execute(this);
1417 } catch (Throwable e
) {
1418 e
.printStackTrace();
1423 handler
.handleMessage(envelope
, content
, null);
1425 Files
.delete(fileEntry
.toPath());
1426 } catch (IOException e
) {
1427 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1431 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1432 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1433 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1435 Set
<HandleAction
> queuedActions
= null;
1437 if (messagePipe
== null) {
1438 messagePipe
= messageReceiver
.createMessagePipe();
1441 boolean hasCaughtUpWithOldMessages
= false;
1444 SignalServiceEnvelope envelope
;
1445 SignalServiceContent content
= null;
1446 Exception exception
= null;
1447 final long now
= new Date().getTime();
1449 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1450 // store message on disk, before acknowledging receipt to the server
1452 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1453 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1454 Utils
.storeEnvelope(envelope1
, cacheFile
);
1455 } catch (IOException e
) {
1456 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1459 if (result
.isPresent()) {
1460 envelope
= result
.get();
1462 // Received indicator that server queue is empty
1463 hasCaughtUpWithOldMessages
= true;
1465 if (queuedActions
!= null) {
1466 for (HandleAction action
: queuedActions
) {
1468 action
.execute(this);
1469 } catch (Throwable e
) {
1470 e
.printStackTrace();
1474 queuedActions
.clear();
1475 queuedActions
= null;
1478 // Continue to wait another timeout for new messages
1481 } catch (TimeoutException e
) {
1482 if (returnOnTimeout
)
1485 } catch (InvalidVersionException e
) {
1486 System
.err
.println("Ignoring error: " + e
.getMessage());
1490 if (envelope
.hasSource()) {
1491 // Store uuid if we don't have it already
1492 SignalServiceAddress source
= envelope
.getSourceAddress();
1493 resolveSignalServiceAddress(source
);
1495 if (!envelope
.isReceipt()) {
1497 content
= decryptMessage(envelope
);
1498 } catch (Exception e
) {
1501 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1502 if (hasCaughtUpWithOldMessages
) {
1503 for (HandleAction action
: actions
) {
1505 action
.execute(this);
1506 } catch (Throwable e
) {
1507 e
.printStackTrace();
1511 if (queuedActions
== null) {
1512 queuedActions
= new HashSet
<>();
1514 queuedActions
.addAll(actions
);
1518 if (!isMessageBlocked(envelope
, content
)) {
1519 handler
.handleMessage(envelope
, content
, exception
);
1521 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1522 File cacheFile
= null;
1524 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1525 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1526 Files
.delete(cacheFile
.toPath());
1527 // Try to delete directory if empty
1528 new File(getMessageCachePath()).delete();
1529 } catch (IOException e
) {
1530 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1536 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1537 SignalServiceAddress source
;
1538 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1539 source
= envelope
.getSourceAddress();
1540 } else if (content
!= null) {
1541 source
= content
.getSender();
1545 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1546 if (sourceContact
!= null && sourceContact
.blocked
) {
1550 if (content
!= null && content
.getDataMessage().isPresent()) {
1551 SignalServiceDataMessage message
= content
.getDataMessage().get();
1552 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1553 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1554 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1555 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1563 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1564 List
<HandleAction
> actions
= new ArrayList
<>();
1565 if (content
!= null) {
1566 SignalServiceAddress sender
;
1567 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1568 sender
= envelope
.getSourceAddress();
1570 sender
= content
.getSender();
1572 // Store uuid if we don't have it already
1573 resolveSignalServiceAddress(sender
);
1575 if (content
.getDataMessage().isPresent()) {
1576 SignalServiceDataMessage message
= content
.getDataMessage().get();
1578 if (content
.isNeedsReceipt()) {
1579 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1582 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1584 if (content
.getSyncMessage().isPresent()) {
1585 account
.setMultiDevice(true);
1586 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1587 if (syncMessage
.getSent().isPresent()) {
1588 SentTranscriptMessage message
= syncMessage
.getSent().get();
1589 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1591 if (syncMessage
.getRequest().isPresent()) {
1592 RequestMessage rm
= syncMessage
.getRequest().get();
1593 if (rm
.isContactsRequest()) {
1594 actions
.add(SendSyncContactsAction
.create());
1596 if (rm
.isGroupsRequest()) {
1597 actions
.add(SendSyncGroupsAction
.create());
1599 if (rm
.isBlockedListRequest()) {
1600 actions
.add(SendSyncBlockedListAction
.create());
1602 // TODO Handle rm.isConfigurationRequest();
1604 if (syncMessage
.getGroups().isPresent()) {
1605 File tmpFile
= null;
1607 tmpFile
= IOUtils
.createTempFile();
1608 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1609 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1611 while ((g
= s
.read()) != null) {
1612 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1613 if (syncGroup
== null) {
1614 syncGroup
= new GroupInfo(g
.getId());
1616 if (g
.getName().isPresent()) {
1617 syncGroup
.name
= g
.getName().get();
1619 syncGroup
.addMembers(g
.getMembers()
1621 .map(this::resolveSignalServiceAddress
)
1622 .collect(Collectors
.toSet()));
1623 if (!g
.isActive()) {
1624 syncGroup
.removeMember(account
.getSelfAddress());
1626 // Add ourself to the member set as it's marked as active
1627 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1629 syncGroup
.blocked
= g
.isBlocked();
1630 if (g
.getColor().isPresent()) {
1631 syncGroup
.color
= g
.getColor().get();
1634 if (g
.getAvatar().isPresent()) {
1635 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1637 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1638 syncGroup
.archived
= g
.isArchived();
1639 account
.getGroupStore().updateGroup(syncGroup
);
1642 } catch (Exception e
) {
1643 e
.printStackTrace();
1645 if (tmpFile
!= null) {
1647 Files
.delete(tmpFile
.toPath());
1648 } catch (IOException e
) {
1649 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1654 if (syncMessage
.getBlockedList().isPresent()) {
1655 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1656 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1657 setContactBlocked(resolveSignalServiceAddress(address
), true);
1659 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1661 setGroupBlocked(groupId
, true);
1662 } catch (GroupNotFoundException e
) {
1663 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1667 if (syncMessage
.getContacts().isPresent()) {
1668 File tmpFile
= null;
1670 tmpFile
= IOUtils
.createTempFile();
1671 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1672 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1673 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1674 if (contactsMessage
.isComplete()) {
1675 account
.getContactStore().clear();
1678 while ((c
= s
.read()) != null) {
1679 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1680 account
.setProfileKey(c
.getProfileKey().get());
1682 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1683 ContactInfo contact
= account
.getContactStore().getContact(address
);
1684 if (contact
== null) {
1685 contact
= new ContactInfo(address
);
1687 if (c
.getName().isPresent()) {
1688 contact
.name
= c
.getName().get();
1690 if (c
.getColor().isPresent()) {
1691 contact
.color
= c
.getColor().get();
1693 if (c
.getProfileKey().isPresent()) {
1694 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
1696 if (c
.getVerified().isPresent()) {
1697 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1698 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1700 if (c
.getExpirationTimer().isPresent()) {
1701 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1703 contact
.blocked
= c
.isBlocked();
1704 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1705 contact
.archived
= c
.isArchived();
1706 account
.getContactStore().updateContact(contact
);
1708 if (c
.getAvatar().isPresent()) {
1709 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1713 } catch (Exception e
) {
1714 e
.printStackTrace();
1716 if (tmpFile
!= null) {
1718 Files
.delete(tmpFile
.toPath());
1719 } catch (IOException e
) {
1720 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1725 if (syncMessage
.getVerified().isPresent()) {
1726 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1727 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1729 if (syncMessage
.getConfiguration().isPresent()) {
1737 private File
getContactAvatarFile(String number
) {
1738 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1741 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1742 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1743 if (attachment
.isPointer()) {
1744 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1745 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1747 SignalServiceAttachmentStream stream
= attachment
.asStream();
1748 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1752 private File
getGroupAvatarFile(byte[] groupId
) {
1753 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1756 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1757 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1758 if (attachment
.isPointer()) {
1759 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1760 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1762 SignalServiceAttachmentStream stream
= attachment
.asStream();
1763 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1767 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1768 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1771 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1772 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1773 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1774 File outputFile
= getProfileAvatarFile(address
);
1776 File tmpFile
= IOUtils
.createTempFile();
1777 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1778 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1779 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1782 Files
.delete(tmpFile
.toPath());
1783 } catch (IOException e
) {
1784 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1790 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1791 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1794 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1795 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1796 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1799 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1800 if (storePreview
&& pointer
.getPreview().isPresent()) {
1801 File previewFile
= new File(outputFile
+ ".preview");
1802 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1803 byte[] preview
= pointer
.getPreview().get();
1804 output
.write(preview
, 0, preview
.length
);
1805 } catch (FileNotFoundException e
) {
1806 e
.printStackTrace();
1811 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1813 File tmpFile
= IOUtils
.createTempFile();
1814 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1815 IOUtils
.copyStreamToFile(input
, outputFile
);
1818 Files
.delete(tmpFile
.toPath());
1819 } catch (IOException e
) {
1820 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1826 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1827 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1828 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1831 void sendGroups() throws IOException
, UntrustedIdentityException
{
1832 File groupsFile
= IOUtils
.createTempFile();
1835 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1836 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1837 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1838 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1839 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1840 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1841 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1845 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1846 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1847 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1848 .withStream(groupsFileStream
)
1849 .withContentType("application/octet-stream")
1850 .withLength(groupsFile
.length())
1853 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1858 Files
.delete(groupsFile
.toPath());
1859 } catch (IOException e
) {
1860 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1865 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1866 File contactsFile
= IOUtils
.createTempFile();
1869 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1870 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1871 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1872 VerifiedMessage verifiedMessage
= null;
1873 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1874 if (currentIdentity
!= null) {
1875 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1878 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
1879 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1880 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1881 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1882 Optional
.of(record.messageExpirationTime
),
1883 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1886 if (account
.getProfileKey() != null) {
1887 // Send our own profile key as well
1888 out
.write(new DeviceContact(account
.getSelfAddress(),
1889 Optional
.absent(), Optional
.absent(),
1890 Optional
.absent(), Optional
.absent(),
1891 Optional
.of(account
.getProfileKey()),
1892 false, Optional
.absent(), Optional
.absent(), false));
1896 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1897 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1898 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1899 .withStream(contactsFileStream
)
1900 .withContentType("application/octet-stream")
1901 .withLength(contactsFile
.length())
1904 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1909 Files
.delete(contactsFile
.toPath());
1910 } catch (IOException e
) {
1911 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1916 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1917 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1918 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1919 if (record.blocked
) {
1920 addresses
.add(record.getAddress());
1923 List
<byte[]> groupIds
= new ArrayList
<>();
1924 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1925 if (record.blocked
) {
1926 groupIds
.add(record.groupId
);
1929 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1932 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1933 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1934 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1937 public List
<ContactInfo
> getContacts() {
1938 return account
.getContactStore().getContacts();
1941 public ContactInfo
getContact(String number
) {
1942 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1945 public GroupInfo
getGroup(byte[] groupId
) {
1946 return account
.getGroupStore().getGroup(groupId
);
1949 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1950 return account
.getSignalProtocolStore().getIdentities();
1953 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1954 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1958 * Trust this the identity with this fingerprint
1960 * @param name username of the identity
1961 * @param fingerprint Fingerprint
1963 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1964 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1965 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1969 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1970 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1974 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1976 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1977 } catch (IOException
| UntrustedIdentityException e
) {
1978 e
.printStackTrace();
1987 * Trust this the identity with this safety number
1989 * @param name username of the identity
1990 * @param safetyNumber Safety number
1992 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1993 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1994 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1998 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1999 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2003 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2005 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2006 } catch (IOException
| UntrustedIdentityException e
) {
2007 e
.printStackTrace();
2016 * Trust all keys of this identity without verification
2018 * @param name username of the identity
2020 public boolean trustIdentityAllKeys(String name
) {
2021 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2022 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2026 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2027 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2028 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2030 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2031 } catch (IOException
| UntrustedIdentityException e
) {
2032 e
.printStackTrace();
2040 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2041 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2044 void saveAccount() {
2048 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2049 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2050 return resolveSignalServiceAddress(canonicalizedNumber
);
2053 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2054 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2056 return resolveSignalServiceAddress(address
);
2059 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2060 if (address
.matches(account
.getSelfAddress())) {
2061 return account
.getSelfAddress();
2064 return account
.getRecipientStore().resolveServiceAddress(address
);
2068 public void close() throws IOException
{
2069 if (messagePipe
!= null) {
2070 messagePipe
.shutdown();
2074 if (unidentifiedMessagePipe
!= null) {
2075 unidentifiedMessagePipe
.shutdown();
2076 unidentifiedMessagePipe
= null;
2082 public interface ReceiveMessageHandler
{
2084 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);