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
.MissingConfigurationException
;
100 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
101 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
102 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
103 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
104 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
105 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
106 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
107 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
108 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
109 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
110 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
111 import org
.whispersystems
.util
.Base64
;
113 import java
.io
.Closeable
;
115 import java
.io
.FileInputStream
;
116 import java
.io
.FileNotFoundException
;
117 import java
.io
.FileOutputStream
;
118 import java
.io
.IOException
;
119 import java
.io
.InputStream
;
120 import java
.io
.OutputStream
;
122 import java
.net
.URISyntaxException
;
123 import java
.net
.URLEncoder
;
124 import java
.nio
.charset
.StandardCharsets
;
125 import java
.nio
.file
.Files
;
126 import java
.nio
.file
.Paths
;
127 import java
.nio
.file
.StandardCopyOption
;
128 import java
.util
.ArrayList
;
129 import java
.util
.Arrays
;
130 import java
.util
.Collection
;
131 import java
.util
.Collections
;
132 import java
.util
.Date
;
133 import java
.util
.HashSet
;
134 import java
.util
.List
;
135 import java
.util
.Locale
;
136 import java
.util
.Objects
;
137 import java
.util
.Set
;
138 import java
.util
.UUID
;
139 import java
.util
.concurrent
.ExecutionException
;
140 import java
.util
.concurrent
.ExecutorService
;
141 import java
.util
.concurrent
.TimeUnit
;
142 import java
.util
.concurrent
.TimeoutException
;
143 import java
.util
.stream
.Collectors
;
144 import java
.util
.zip
.ZipEntry
;
145 import java
.util
.zip
.ZipFile
;
147 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
149 public class Manager
implements Closeable
{
151 private final SleepTimer timer
= new UptimeSleepTimer();
152 private final SignalServiceConfiguration serviceConfiguration
;
153 private final String userAgent
;
155 private final SignalAccount account
;
156 private final PathConfig pathConfig
;
157 private SignalServiceAccountManager accountManager
;
158 private SignalServiceMessagePipe messagePipe
= null;
159 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
160 private final boolean discoverableByPhoneNumber
= true;
162 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
163 this.account
= account
;
164 this.pathConfig
= pathConfig
;
165 this.serviceConfiguration
= serviceConfiguration
;
166 this.userAgent
= userAgent
;
167 this.accountManager
= createSignalServiceAccountManager();
169 this.account
.setResolver(this::resolveSignalServiceAddress
);
172 public String
getUsername() {
173 return account
.getUsername();
176 public SignalServiceAddress
getSelfAddress() {
177 return account
.getSelfAddress();
180 private SignalServiceAccountManager
createSignalServiceAccountManager() {
181 GroupsV2Operations groupsV2Operations
;
183 groupsV2Operations
= new GroupsV2Operations(ClientZkOperations
.create(serviceConfiguration
));
184 } catch (Throwable ignored
) {
185 groupsV2Operations
= null;
187 return new SignalServiceAccountManager(serviceConfiguration
,
188 new DynamicCredentialsProvider(account
.getUuid(), account
.getUsername(), account
.getPassword(), null, account
.getDeviceId()),
194 private IdentityKeyPair
getIdentityKeyPair() {
195 return account
.getSignalProtocolStore().getIdentityKeyPair();
198 public int getDeviceId() {
199 return account
.getDeviceId();
202 private String
getMessageCachePath() {
203 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
206 private String
getMessageCachePath(String sender
) {
207 if (sender
== null || sender
.isEmpty()) {
208 return getMessageCachePath();
211 return getMessageCachePath() + "/" + sender
.replace("/", "_");
214 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
215 String cachePath
= getMessageCachePath(sender
);
216 IOUtils
.createPrivateDirectories(cachePath
);
217 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
220 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
221 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
223 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
224 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
225 int registrationId
= KeyHelper
.generateRegistrationId(false);
227 ProfileKey profileKey
= KeyUtils
.createProfileKey();
228 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
231 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
234 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
236 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
238 m
.migrateLegacyConfigs();
243 private void migrateLegacyConfigs() {
244 // Copy group avatars that were previously stored in the attachments folder
245 // to the new avatar folder
246 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
247 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
248 File avatarFile
= getGroupAvatarFile(g
.groupId
);
249 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
250 if (!avatarFile
.exists() && attachmentFile
.exists()) {
252 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
253 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
254 } catch (Exception e
) {
259 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
262 if (account
.getProfileKey() == null) {
263 // Old config file, creating new profile key
264 account
.setProfileKey(KeyUtils
.createProfileKey());
267 // Store profile keys only in profile store
268 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
269 String profileKeyString
= contact
.profileKey
;
270 if (profileKeyString
== null) {
273 final ProfileKey profileKey
;
275 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
276 } catch (InvalidInputException
| IOException e
) {
279 contact
.profileKey
= null;
280 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
284 public void checkAccountState() throws IOException
{
285 if (account
.isRegistered()) {
286 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
290 if (account
.getUuid() == null) {
291 account
.setUuid(accountManager
.getOwnUuid());
297 public boolean isRegistered() {
298 return account
.isRegistered();
301 public void register(boolean voiceVerification
) throws IOException
{
302 account
.setPassword(KeyUtils
.createPassword());
304 // Resetting UUID, because registering doesn't work otherwise
305 account
.setUuid(null);
306 accountManager
= createSignalServiceAccountManager();
308 if (voiceVerification
) {
309 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
311 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
314 account
.setRegistered(false);
318 public void updateAccountAttributes() throws IOException
{
319 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
322 public void setProfile(String name
, File avatar
) throws IOException
{
323 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
324 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
328 public void unregister() throws IOException
{
329 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
330 // If this is the master device, other users can't send messages to this number anymore.
331 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
332 accountManager
.setGcmId(Optional
.absent());
334 account
.setRegistered(false);
338 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
339 List
<DeviceInfo
> devices
= accountManager
.getDevices();
340 account
.setMultiDevice(devices
.size() > 1);
345 public void removeLinkedDevices(int deviceId
) throws IOException
{
346 accountManager
.removeDevice(deviceId
);
347 List
<DeviceInfo
> devices
= accountManager
.getDevices();
348 account
.setMultiDevice(devices
.size() > 1);
352 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
353 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
355 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
358 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
359 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
360 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
362 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
363 account
.setMultiDevice(true);
367 private List
<PreKeyRecord
> generatePreKeys() {
368 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
370 final int offset
= account
.getPreKeyIdOffset();
371 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
372 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
373 ECKeyPair keyPair
= Curve
.generateKeyPair();
374 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
379 account
.addPreKeys(records
);
385 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
387 ECKeyPair keyPair
= Curve
.generateKeyPair();
388 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
389 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
391 account
.addSignedPreKey(record);
395 } catch (InvalidKeyException e
) {
396 throw new AssertionError(e
);
400 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
401 verificationCode
= verificationCode
.replace("-", "");
402 account
.setSignalingKey(KeyUtils
.createSignalingKey());
403 // TODO make unrestricted unidentified access configurable
404 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, capabilities
, discoverableByPhoneNumber
);
406 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
407 // TODO response.isStorageCapable()
408 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
409 account
.setRegistered(true);
410 account
.setUuid(uuid
);
411 account
.setRegistrationLockPin(pin
);
412 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
418 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
419 if (pin
.isPresent()) {
420 account
.setRegistrationLockPin(pin
.get());
421 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
423 account
.setRegistrationLockPin(null);
424 accountManager
.removeRegistrationLockV1();
429 void refreshPreKeys() throws IOException
{
430 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
431 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
432 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
434 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
437 private SignalServiceMessageReceiver
getMessageReceiver() {
438 // TODO implement ZkGroup support
439 final ClientZkProfileOperations clientZkProfileOperations
= null;
440 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
443 private SignalServiceMessageSender
getMessageSender() {
444 // TODO implement ZkGroup support
445 final ClientZkProfileOperations clientZkProfileOperations
= null;
446 final ExecutorService executor
= null;
447 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
448 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
, ServiceConfig
.MAX_ENVELOPE_SIZE
);
451 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
452 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
457 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
458 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
462 SignalServiceMessageReceiver receiver
= getMessageReceiver();
464 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
465 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
466 throw new IOException("Failed to retrieve profile", e
);
470 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
471 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfile(address
);
472 long now
= new Date().getTime();
473 // Profiles are cache for 24h before retrieving them again
474 if (profileEntry
== null || profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
475 SignalProfile profile
= retrieveRecipientProfile(address
, unidentifiedAccess
, profileKey
);
476 account
.getProfileStore().updateProfile(address
, profileKey
, now
, profile
);
479 return profileEntry
.getProfile();
482 private SignalProfile
retrieveRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
483 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
, unidentifiedAccess
);
485 File avatarFile
= null;
487 avatarFile
= encryptedProfile
.getAvatar() == null ?
null : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
488 } catch (Throwable e
) {
489 System
.err
.println("Failed to retrieve profile avatar, ignoring: " + e
.getMessage());
492 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
494 return new SignalProfile(
495 encryptedProfile
.getIdentityKey(),
496 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
498 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
499 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
500 encryptedProfile
.getCapabilities());
501 } catch (InvalidCiphertextException e
) {
506 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
507 File file
= getGroupAvatarFile(groupId
);
508 if (!file
.exists()) {
509 return Optional
.absent();
512 return Optional
.of(Utils
.createAttachment(file
));
515 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
516 File file
= getContactAvatarFile(number
);
517 if (!file
.exists()) {
518 return Optional
.absent();
521 return Optional
.of(Utils
.createAttachment(file
));
524 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
525 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
527 throw new GroupNotFoundException(groupId
);
529 if (!g
.isMember(account
.getSelfAddress())) {
530 throw new NotAGroupMemberException(groupId
, g
.name
);
535 public List
<GroupInfo
> getGroups() {
536 return account
.getGroupStore().getGroups();
539 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
541 List
<String
> attachments
,
544 throws IOException
, 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 sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
563 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
564 long targetSentTimestamp
, byte[] groupId
)
565 throws IOException
, 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 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
579 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, 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 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
594 private Pair
<byte[], List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, 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 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
640 return new Pair
<>(g
.groupId
, result
.second());
643 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
644 GroupInfo g
= getGroupForSending(groupId
);
646 if (!g
.isMember(recipient
)) {
647 throw new NotAGroupMemberException(groupId
, g
.name
);
650 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
652 // Send group message only to the recipient who requested it
653 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
656 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
657 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
660 .withMembers(new ArrayList
<>(g
.getMembers()));
662 File aFile
= getGroupAvatarFile(g
.groupId
);
663 if (aFile
.exists()) {
665 group
.withAvatar(Utils
.createAttachment(aFile
));
666 } catch (IOException e
) {
667 throw new AttachmentInvalidException(aFile
.toString(), e
);
671 return SignalServiceDataMessage
.newBuilder()
672 .asGroupMessage(group
.build())
673 .withExpiration(g
.messageExpirationTime
);
676 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
{
677 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
680 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
681 .asGroupMessage(group
.build());
683 // Send group info request message to the recipient who sent us a message with this groupId
684 return sendMessage(messageBuilder
, Collections
.singleton(recipient
));
687 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
688 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
689 Collections
.singletonList(messageId
),
690 System
.currentTimeMillis());
692 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
695 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(String messageText
, List
<String
> attachments
,
696 List
<String
> recipients
)
697 throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
698 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
699 if (attachments
!= null) {
700 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
702 // Upload attachments here, so we only upload once even for multiple recipients
703 SignalServiceMessageSender messageSender
= getMessageSender();
704 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
705 for (SignalServiceAttachment attachment
: attachmentStreams
) {
706 if (attachment
.isStream()) {
707 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
708 } else if (attachment
.isPointer()) {
709 attachmentPointers
.add(attachment
.asPointer());
713 messageBuilder
.withAttachments(attachmentPointers
);
715 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
718 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
719 long targetSentTimestamp
, List
<String
> recipients
)
720 throws IOException
, InvalidNumberException
{
721 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
722 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
723 .withReaction(reaction
);
724 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
727 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
728 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
729 .asEndSessionMessage();
731 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
733 return sendMessage(messageBuilder
, signalServiceAddresses
);
734 } catch (Exception e
) {
735 for (SignalServiceAddress address
: signalServiceAddresses
) {
736 handleEndSession(address
);
743 public String
getContactName(String number
) throws InvalidNumberException
{
744 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
745 if (contact
== null) {
752 public void setContactName(String number
, String name
) throws InvalidNumberException
{
753 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
754 ContactInfo contact
= account
.getContactStore().getContact(address
);
755 if (contact
== null) {
756 contact
= new ContactInfo(address
);
759 account
.getContactStore().updateContact(contact
);
763 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
764 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
767 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
768 ContactInfo contact
= account
.getContactStore().getContact(address
);
769 if (contact
== null) {
770 contact
= new ContactInfo(address
);
772 contact
.blocked
= blocked
;
773 account
.getContactStore().updateContact(contact
);
777 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
778 GroupInfo group
= getGroup(groupId
);
780 throw new GroupNotFoundException(groupId
);
783 group
.blocked
= blocked
;
784 account
.getGroupStore().updateGroup(group
);
788 public Pair
<byte[], List
<SendMessageResult
>> updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
789 if (groupId
.length
== 0) {
792 if (name
.isEmpty()) {
795 if (members
.isEmpty()) {
798 if (avatar
.isEmpty()) {
801 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
805 * Change the expiration timer for a contact
807 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
808 ContactInfo contact
= account
.getContactStore().getContact(address
);
809 contact
.messageExpirationTime
= messageExpirationTimer
;
810 account
.getContactStore().updateContact(contact
);
811 sendExpirationTimerUpdate(address
);
815 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
816 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
817 .asExpirationUpdate();
818 sendMessage(messageBuilder
, Collections
.singleton(address
));
822 * Change the expiration timer for a contact
824 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
825 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
826 setExpirationTimer(address
, messageExpirationTimer
);
830 * Change the expiration timer for a group
832 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
833 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
834 g
.messageExpirationTime
= messageExpirationTimer
;
835 account
.getGroupStore().updateGroup(g
);
839 * Upload the sticker pack from path.
841 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
842 * @return if successful, returns the URL to install the sticker pack in the signal app
844 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
845 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
847 SignalServiceMessageSender messageSender
= getMessageSender();
849 byte[] packKey
= KeyUtils
.createStickerUploadKey();
850 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
853 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), StandardCharsets
.UTF_8
))
855 } catch (URISyntaxException e
) {
856 throw new AssertionError(e
);
860 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
862 String rootPath
= null;
864 final File file
= new File(path
);
865 if (file
.getName().endsWith(".zip")) {
866 zip
= new ZipFile(file
);
867 } else if (file
.getName().equals("manifest.json")) {
868 rootPath
= file
.getParent();
870 throw new StickerPackInvalidException("Could not find manifest.json");
873 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
875 if (pack
.stickers
== null) {
876 throw new StickerPackInvalidException("Must set a 'stickers' field.");
879 if (pack
.stickers
.isEmpty()) {
880 throw new StickerPackInvalidException("Must include stickers.");
883 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
884 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
885 if (sticker
.file
== null) {
886 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
889 Pair
<InputStream
, Long
> data
;
891 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
892 } catch (IOException ignored
) {
893 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
896 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
897 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""), contentType
);
898 stickers
.add(stickerInfo
);
901 StickerInfo cover
= null;
902 if (pack
.cover
!= null) {
903 if (pack
.cover
.file
== null) {
904 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
907 Pair
<InputStream
, Long
> data
;
909 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
910 } catch (IOException ignored
) {
911 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
914 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
915 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""), contentType
);
918 return new SignalServiceStickerManifestUpload(
925 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
926 InputStream inputStream
;
928 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
930 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
932 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
935 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
937 final ZipEntry entry
= zip
.getEntry(subfile
);
938 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
940 final File file
= new File(rootPath
, subfile
);
941 return new Pair
<>(new FileInputStream(file
), file
.length());
945 void requestSyncGroups() throws IOException
{
946 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
947 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
949 sendSyncMessage(message
);
950 } catch (UntrustedIdentityException e
) {
955 void requestSyncContacts() throws IOException
{
956 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
957 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
959 sendSyncMessage(message
);
960 } catch (UntrustedIdentityException e
) {
965 void requestSyncBlocked() throws IOException
{
966 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
967 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
969 sendSyncMessage(message
);
970 } catch (UntrustedIdentityException e
) {
975 void requestSyncConfiguration() throws IOException
{
976 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
977 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
979 sendSyncMessage(message
);
980 } catch (UntrustedIdentityException e
) {
985 private byte[] getSenderCertificate() {
986 // TODO support UUID capable sender certificates
987 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
990 certificate
= accountManager
.getSenderCertificate();
991 } catch (IOException e
) {
992 System
.err
.println("Failed to get sender certificate: " + e
);
995 // TODO cache for a day
999 private byte[] getSelfUnidentifiedAccessKey() {
1000 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1003 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1004 ProfileKey theirProfileKey
= account
.getProfileStore().getProfileKey(recipient
);
1005 if (theirProfileKey
== null) {
1008 SignalProfile targetProfile
;
1010 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
1011 } catch (IOException e
) {
1012 System
.err
.println("Failed to get recipient profile: " + e
);
1016 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1020 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1021 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1024 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1027 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1028 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1029 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1031 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1032 return Optional
.absent();
1036 return Optional
.of(new UnidentifiedAccessPair(
1037 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1038 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1040 } catch (InvalidCertificateException e
) {
1041 return Optional
.absent();
1045 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1046 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1047 for (SignalServiceAddress recipient
: recipients
) {
1048 result
.add(getAccessFor(recipient
));
1053 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1054 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1055 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1056 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1058 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1059 return Optional
.absent();
1063 return Optional
.of(new UnidentifiedAccessPair(
1064 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1065 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1067 } catch (InvalidCertificateException e
) {
1068 return Optional
.absent();
1072 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1073 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1075 if (unidentifiedAccess
.isPresent()) {
1076 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1079 return Optional
.absent();
1082 private void sendSyncMessage(SignalServiceSyncMessage message
)
1083 throws IOException
, UntrustedIdentityException
{
1084 SignalServiceMessageSender messageSender
= getMessageSender();
1086 messageSender
.sendMessage(message
, getAccessForSync());
1087 } catch (UntrustedIdentityException e
) {
1088 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1093 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1094 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1096 for (String number
: numbers
) {
1097 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1099 return signalServiceAddresses
;
1102 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1103 throws IOException
{
1104 final long timestamp
= System
.currentTimeMillis();
1105 messageBuilder
.withTimestamp(timestamp
);
1106 if (messagePipe
== null) {
1107 messagePipe
= getMessageReceiver().createMessagePipe();
1109 if (unidentifiedMessagePipe
== null) {
1110 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1112 SignalServiceDataMessage message
= null;
1114 message
= messageBuilder
.build();
1115 if (message
.getGroupContext().isPresent()) {
1117 SignalServiceMessageSender messageSender
= getMessageSender();
1118 final boolean isRecipientUpdate
= false;
1119 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1120 for (SendMessageResult r
: result
) {
1121 if (r
.getIdentityFailure() != null) {
1122 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1125 return new Pair
<>(timestamp
, result
);
1126 } catch (UntrustedIdentityException e
) {
1127 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1128 return new Pair
<>(timestamp
, Collections
.emptyList());
1131 // Send to all individually, so sync messages are sent correctly
1132 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1133 for (SignalServiceAddress address
: recipients
) {
1134 ContactInfo contact
= account
.getContactStore().getContact(address
);
1135 if (contact
!= null) {
1136 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1137 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1139 messageBuilder
.withExpiration(0);
1140 messageBuilder
.withProfileKey(null);
1142 message
= messageBuilder
.build();
1143 if (address
.matches(account
.getSelfAddress())) {
1144 results
.add(sendSelfMessage(message
));
1146 results
.add(sendMessage(address
, message
));
1149 return new Pair
<>(timestamp
, results
);
1152 if (message
!= null && message
.isEndSession()) {
1153 for (SignalServiceAddress recipient
: recipients
) {
1154 handleEndSession(recipient
);
1161 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1162 SignalServiceMessageSender messageSender
= getMessageSender();
1164 SignalServiceAddress recipient
= account
.getSelfAddress();
1166 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1167 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1168 message
.getTimestamp(),
1170 message
.getExpiresInSeconds(),
1171 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1173 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1176 long startTime
= System
.currentTimeMillis();
1177 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1178 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false, System
.currentTimeMillis() - startTime
);
1179 } catch (UntrustedIdentityException e
) {
1180 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1181 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1185 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1186 SignalServiceMessageSender messageSender
= getMessageSender();
1189 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1190 } catch (UntrustedIdentityException e
) {
1191 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1192 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1196 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1197 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1199 return cipher
.decrypt(envelope
);
1200 } catch (ProtocolUntrustedIdentityException e
) {
1201 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1202 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1203 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1204 throw identityException
;
1206 throw new AssertionError(e
);
1210 private void handleEndSession(SignalServiceAddress source
) {
1211 account
.getSignalProtocolStore().deleteAllSessions(source
);
1214 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1215 List
<HandleAction
> actions
= new ArrayList
<>();
1216 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1217 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1218 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1219 switch (groupInfo
.getType()) {
1221 if (group
== null) {
1222 group
= new GroupInfo(groupInfo
.getGroupId());
1225 if (groupInfo
.getAvatar().isPresent()) {
1226 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1227 if (avatar
.isPointer()) {
1229 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1230 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1231 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1236 if (groupInfo
.getName().isPresent()) {
1237 group
.name
= groupInfo
.getName().get();
1240 if (groupInfo
.getMembers().isPresent()) {
1241 group
.addMembers(groupInfo
.getMembers().get()
1243 .map(this::resolveSignalServiceAddress
)
1244 .collect(Collectors
.toSet()));
1247 account
.getGroupStore().updateGroup(group
);
1250 if (group
== null && !isSync
) {
1251 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1255 if (group
!= null) {
1256 group
.removeMember(source
);
1257 account
.getGroupStore().updateGroup(group
);
1261 if (group
!= null && !isSync
) {
1262 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1267 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1268 if (message
.isEndSession()) {
1269 handleEndSession(conversationPartnerAddress
);
1271 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1272 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1273 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1274 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1275 if (group
== null) {
1276 group
= new GroupInfo(groupInfo
.getGroupId());
1278 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1279 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1280 account
.getGroupStore().updateGroup(group
);
1283 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1284 if (contact
== null) {
1285 contact
= new ContactInfo(conversationPartnerAddress
);
1287 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1288 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1289 account
.getContactStore().updateContact(contact
);
1293 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1294 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1295 if (attachment
.isPointer()) {
1297 retrieveAttachment(attachment
.asPointer());
1298 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1299 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1304 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1305 final ProfileKey profileKey
;
1307 profileKey
= new ProfileKey(message
.getProfileKey().get());
1308 } catch (InvalidInputException e
) {
1309 throw new AssertionError(e
);
1311 if (source
.matches(account
.getSelfAddress())) {
1312 this.account
.setProfileKey(profileKey
);
1314 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1316 if (message
.getPreviews().isPresent()) {
1317 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1318 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1319 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1320 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1322 retrieveAttachment(attachment
);
1323 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1324 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1332 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1333 final File cachePath
= new File(getMessageCachePath());
1334 if (!cachePath
.exists()) {
1337 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1338 if (!dir
.isDirectory()) {
1339 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1343 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1344 if (!fileEntry
.isFile()) {
1347 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1349 // Try to delete directory if empty
1354 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1355 SignalServiceEnvelope envelope
;
1357 envelope
= Utils
.loadEnvelope(fileEntry
);
1358 if (envelope
== null) {
1361 } catch (IOException e
) {
1362 e
.printStackTrace();
1365 SignalServiceContent content
= null;
1366 if (!envelope
.isReceipt()) {
1368 content
= decryptMessage(envelope
);
1369 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1371 } catch (Exception er
) {
1372 // All other errors are not recoverable, so delete the cached message
1374 Files
.delete(fileEntry
.toPath());
1375 } catch (IOException e
) {
1376 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1380 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1381 for (HandleAction action
: actions
) {
1383 action
.execute(this);
1384 } catch (Throwable e
) {
1385 e
.printStackTrace();
1390 handler
.handleMessage(envelope
, content
, null);
1392 Files
.delete(fileEntry
.toPath());
1393 } catch (IOException e
) {
1394 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1398 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1399 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1400 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1402 Set
<HandleAction
> queuedActions
= null;
1404 if (messagePipe
== null) {
1405 messagePipe
= messageReceiver
.createMessagePipe();
1408 boolean hasCaughtUpWithOldMessages
= false;
1411 SignalServiceEnvelope envelope
;
1412 SignalServiceContent content
= null;
1413 Exception exception
= null;
1414 final long now
= new Date().getTime();
1416 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1417 // store message on disk, before acknowledging receipt to the server
1419 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1420 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1421 Utils
.storeEnvelope(envelope1
, cacheFile
);
1422 } catch (IOException e
) {
1423 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1426 if (result
.isPresent()) {
1427 envelope
= result
.get();
1429 // Received indicator that server queue is empty
1430 hasCaughtUpWithOldMessages
= true;
1432 if (queuedActions
!= null) {
1433 for (HandleAction action
: queuedActions
) {
1435 action
.execute(this);
1436 } catch (Throwable e
) {
1437 e
.printStackTrace();
1441 queuedActions
.clear();
1442 queuedActions
= null;
1445 // Continue to wait another timeout for new messages
1448 } catch (TimeoutException e
) {
1449 if (returnOnTimeout
)
1452 } catch (InvalidVersionException e
) {
1453 System
.err
.println("Ignoring error: " + e
.getMessage());
1457 if (envelope
.hasSource()) {
1458 // Store uuid if we don't have it already
1459 SignalServiceAddress source
= envelope
.getSourceAddress();
1460 resolveSignalServiceAddress(source
);
1462 if (!envelope
.isReceipt()) {
1464 content
= decryptMessage(envelope
);
1465 } catch (Exception e
) {
1468 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1469 if (hasCaughtUpWithOldMessages
) {
1470 for (HandleAction action
: actions
) {
1472 action
.execute(this);
1473 } catch (Throwable e
) {
1474 e
.printStackTrace();
1478 if (queuedActions
== null) {
1479 queuedActions
= new HashSet
<>();
1481 queuedActions
.addAll(actions
);
1485 if (!isMessageBlocked(envelope
, content
)) {
1486 handler
.handleMessage(envelope
, content
, exception
);
1488 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1489 File cacheFile
= null;
1491 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
1492 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
1493 Files
.delete(cacheFile
.toPath());
1494 // Try to delete directory if empty
1495 new File(getMessageCachePath()).delete();
1496 } catch (IOException e
) {
1497 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1503 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1504 SignalServiceAddress source
;
1505 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1506 source
= envelope
.getSourceAddress();
1507 } else if (content
!= null) {
1508 source
= content
.getSender();
1512 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1513 if (sourceContact
!= null && sourceContact
.blocked
) {
1517 if (content
!= null && content
.getDataMessage().isPresent()) {
1518 SignalServiceDataMessage message
= content
.getDataMessage().get();
1519 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1520 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1521 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1522 return groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
;
1528 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1529 List
<HandleAction
> actions
= new ArrayList
<>();
1530 if (content
!= null) {
1531 SignalServiceAddress sender
;
1532 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1533 sender
= envelope
.getSourceAddress();
1535 sender
= content
.getSender();
1537 // Store uuid if we don't have it already
1538 resolveSignalServiceAddress(sender
);
1540 if (content
.getDataMessage().isPresent()) {
1541 SignalServiceDataMessage message
= content
.getDataMessage().get();
1543 if (content
.isNeedsReceipt()) {
1544 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1547 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1549 if (content
.getSyncMessage().isPresent()) {
1550 account
.setMultiDevice(true);
1551 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1552 if (syncMessage
.getSent().isPresent()) {
1553 SentTranscriptMessage message
= syncMessage
.getSent().get();
1554 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1556 if (syncMessage
.getRequest().isPresent()) {
1557 RequestMessage rm
= syncMessage
.getRequest().get();
1558 if (rm
.isContactsRequest()) {
1559 actions
.add(SendSyncContactsAction
.create());
1561 if (rm
.isGroupsRequest()) {
1562 actions
.add(SendSyncGroupsAction
.create());
1564 if (rm
.isBlockedListRequest()) {
1565 actions
.add(SendSyncBlockedListAction
.create());
1567 // TODO Handle rm.isConfigurationRequest();
1569 if (syncMessage
.getGroups().isPresent()) {
1570 File tmpFile
= null;
1572 tmpFile
= IOUtils
.createTempFile();
1573 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1574 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1576 while ((g
= s
.read()) != null) {
1577 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1578 if (syncGroup
== null) {
1579 syncGroup
= new GroupInfo(g
.getId());
1581 if (g
.getName().isPresent()) {
1582 syncGroup
.name
= g
.getName().get();
1584 syncGroup
.addMembers(g
.getMembers()
1586 .map(this::resolveSignalServiceAddress
)
1587 .collect(Collectors
.toSet()));
1588 if (!g
.isActive()) {
1589 syncGroup
.removeMember(account
.getSelfAddress());
1591 // Add ourself to the member set as it's marked as active
1592 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1594 syncGroup
.blocked
= g
.isBlocked();
1595 if (g
.getColor().isPresent()) {
1596 syncGroup
.color
= g
.getColor().get();
1599 if (g
.getAvatar().isPresent()) {
1600 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1602 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1603 syncGroup
.archived
= g
.isArchived();
1604 account
.getGroupStore().updateGroup(syncGroup
);
1607 } catch (Exception e
) {
1608 e
.printStackTrace();
1610 if (tmpFile
!= null) {
1612 Files
.delete(tmpFile
.toPath());
1613 } catch (IOException e
) {
1614 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1619 if (syncMessage
.getBlockedList().isPresent()) {
1620 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1621 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1622 setContactBlocked(resolveSignalServiceAddress(address
), true);
1624 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1626 setGroupBlocked(groupId
, true);
1627 } catch (GroupNotFoundException e
) {
1628 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1632 if (syncMessage
.getContacts().isPresent()) {
1633 File tmpFile
= null;
1635 tmpFile
= IOUtils
.createTempFile();
1636 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1637 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1638 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1639 if (contactsMessage
.isComplete()) {
1640 account
.getContactStore().clear();
1643 while ((c
= s
.read()) != null) {
1644 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1645 account
.setProfileKey(c
.getProfileKey().get());
1647 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1648 ContactInfo contact
= account
.getContactStore().getContact(address
);
1649 if (contact
== null) {
1650 contact
= new ContactInfo(address
);
1652 if (c
.getName().isPresent()) {
1653 contact
.name
= c
.getName().get();
1655 if (c
.getColor().isPresent()) {
1656 contact
.color
= c
.getColor().get();
1658 if (c
.getProfileKey().isPresent()) {
1659 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
1661 if (c
.getVerified().isPresent()) {
1662 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1663 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1665 if (c
.getExpirationTimer().isPresent()) {
1666 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1668 contact
.blocked
= c
.isBlocked();
1669 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1670 contact
.archived
= c
.isArchived();
1671 account
.getContactStore().updateContact(contact
);
1673 if (c
.getAvatar().isPresent()) {
1674 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1678 } catch (Exception e
) {
1679 e
.printStackTrace();
1681 if (tmpFile
!= null) {
1683 Files
.delete(tmpFile
.toPath());
1684 } catch (IOException e
) {
1685 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1690 if (syncMessage
.getVerified().isPresent()) {
1691 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1692 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1694 if (syncMessage
.getConfiguration().isPresent()) {
1702 private File
getContactAvatarFile(String number
) {
1703 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1706 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1707 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1708 if (attachment
.isPointer()) {
1709 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1710 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1712 SignalServiceAttachmentStream stream
= attachment
.asStream();
1713 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1717 private File
getGroupAvatarFile(byte[] groupId
) {
1718 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1721 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1722 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1723 if (attachment
.isPointer()) {
1724 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1725 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1727 SignalServiceAttachmentStream stream
= attachment
.asStream();
1728 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1732 private File
getProfileAvatarFile(SignalServiceAddress address
) {
1733 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
1736 private File
retrieveProfileAvatar(SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
) throws IOException
{
1737 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1738 SignalServiceMessageReceiver receiver
= getMessageReceiver();
1739 File outputFile
= getProfileAvatarFile(address
);
1741 File tmpFile
= IOUtils
.createTempFile();
1742 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
, tmpFile
, profileKey
, ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
1743 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
1744 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
1747 Files
.delete(tmpFile
.toPath());
1748 } catch (IOException e
) {
1749 System
.err
.println("Failed to delete received avatar temp file “" + tmpFile
+ "”: " + e
.getMessage());
1755 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1756 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1759 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1760 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1761 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1764 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1765 if (storePreview
&& pointer
.getPreview().isPresent()) {
1766 File previewFile
= new File(outputFile
+ ".preview");
1767 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1768 byte[] preview
= pointer
.getPreview().get();
1769 output
.write(preview
, 0, preview
.length
);
1770 } catch (FileNotFoundException e
) {
1771 e
.printStackTrace();
1776 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1778 File tmpFile
= IOUtils
.createTempFile();
1779 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1780 IOUtils
.copyStreamToFile(input
, outputFile
);
1783 Files
.delete(tmpFile
.toPath());
1784 } catch (IOException e
) {
1785 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1791 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1792 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1793 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1796 void sendGroups() throws IOException
, UntrustedIdentityException
{
1797 File groupsFile
= IOUtils
.createTempFile();
1800 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1801 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1802 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1803 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1804 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1805 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1806 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1810 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1811 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1812 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1813 .withStream(groupsFileStream
)
1814 .withContentType("application/octet-stream")
1815 .withLength(groupsFile
.length())
1818 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1823 Files
.delete(groupsFile
.toPath());
1824 } catch (IOException e
) {
1825 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1830 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1831 File contactsFile
= IOUtils
.createTempFile();
1834 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1835 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1836 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1837 VerifiedMessage verifiedMessage
= null;
1838 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1839 if (currentIdentity
!= null) {
1840 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1843 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
1844 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1845 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1846 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1847 Optional
.of(record.messageExpirationTime
),
1848 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1851 if (account
.getProfileKey() != null) {
1852 // Send our own profile key as well
1853 out
.write(new DeviceContact(account
.getSelfAddress(),
1854 Optional
.absent(), Optional
.absent(),
1855 Optional
.absent(), Optional
.absent(),
1856 Optional
.of(account
.getProfileKey()),
1857 false, Optional
.absent(), Optional
.absent(), false));
1861 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1862 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1863 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1864 .withStream(contactsFileStream
)
1865 .withContentType("application/octet-stream")
1866 .withLength(contactsFile
.length())
1869 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1874 Files
.delete(contactsFile
.toPath());
1875 } catch (IOException e
) {
1876 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1881 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1882 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1883 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1884 if (record.blocked
) {
1885 addresses
.add(record.getAddress());
1888 List
<byte[]> groupIds
= new ArrayList
<>();
1889 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1890 if (record.blocked
) {
1891 groupIds
.add(record.groupId
);
1894 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1897 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1898 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1899 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1902 public List
<ContactInfo
> getContacts() {
1903 return account
.getContactStore().getContacts();
1906 public ContactInfo
getContact(String number
) {
1907 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1910 public GroupInfo
getGroup(byte[] groupId
) {
1911 return account
.getGroupStore().getGroup(groupId
);
1914 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1915 return account
.getSignalProtocolStore().getIdentities();
1918 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1919 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1923 * Trust this the identity with this fingerprint
1925 * @param name username of the identity
1926 * @param fingerprint Fingerprint
1928 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1929 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1930 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1934 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1935 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1939 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1941 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1942 } catch (IOException
| UntrustedIdentityException e
) {
1943 e
.printStackTrace();
1952 * Trust this the identity with this safety number
1954 * @param name username of the identity
1955 * @param safetyNumber Safety number
1957 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1958 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1959 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1963 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1964 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1968 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1970 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1971 } catch (IOException
| UntrustedIdentityException e
) {
1972 e
.printStackTrace();
1981 * Trust all keys of this identity without verification
1983 * @param name username of the identity
1985 public boolean trustIdentityAllKeys(String name
) {
1986 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1987 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1991 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1992 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1993 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1995 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1996 } catch (IOException
| UntrustedIdentityException e
) {
1997 e
.printStackTrace();
2005 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2006 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
2009 void saveAccount() {
2013 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2014 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2015 return resolveSignalServiceAddress(canonicalizedNumber
);
2018 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2019 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2021 return resolveSignalServiceAddress(address
);
2024 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2025 if (address
.matches(account
.getSelfAddress())) {
2026 return account
.getSelfAddress();
2029 return account
.getRecipientStore().resolveServiceAddress(address
);
2033 public void close() throws IOException
{
2034 if (messagePipe
!= null) {
2035 messagePipe
.shutdown();
2039 if (unidentifiedMessagePipe
!= null) {
2040 unidentifiedMessagePipe
.shutdown();
2041 unidentifiedMessagePipe
= null;
2047 public interface ReceiveMessageHandler
{
2049 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);