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
;
22 import org
.asamk
.signal
.AttachmentInvalidException
;
23 import org
.asamk
.signal
.GroupNotFoundException
;
24 import org
.asamk
.signal
.NotAGroupMemberException
;
25 import org
.asamk
.signal
.StickerPackInvalidException
;
26 import org
.asamk
.signal
.TrustLevel
;
27 import org
.asamk
.signal
.UserAlreadyExists
;
28 import org
.asamk
.signal
.storage
.SignalAccount
;
29 import org
.asamk
.signal
.storage
.contacts
.ContactInfo
;
30 import org
.asamk
.signal
.storage
.groups
.GroupInfo
;
31 import org
.asamk
.signal
.storage
.groups
.JsonGroupStore
;
32 import org
.asamk
.signal
.storage
.protocol
.JsonIdentityKeyStore
;
33 import org
.asamk
.signal
.util
.IOUtils
;
34 import org
.asamk
.signal
.util
.Util
;
35 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
36 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
38 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
39 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
40 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
41 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
42 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
43 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
44 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
45 import org
.signal
.libsignal
.metadata
.SelfSendException
;
46 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
47 import org
.signal
.zkgroup
.InvalidInputException
;
48 import org
.signal
.zkgroup
.VerificationFailedException
;
49 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
50 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
51 import org
.whispersystems
.libsignal
.IdentityKey
;
52 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
53 import org
.whispersystems
.libsignal
.InvalidKeyException
;
54 import org
.whispersystems
.libsignal
.InvalidMessageException
;
55 import org
.whispersystems
.libsignal
.InvalidVersionException
;
56 import org
.whispersystems
.libsignal
.ecc
.Curve
;
57 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
58 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
59 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
60 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
61 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
62 import org
.whispersystems
.libsignal
.util
.Medium
;
63 import org
.whispersystems
.libsignal
.util
.Pair
;
64 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
65 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
66 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
67 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
68 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
69 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
70 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
71 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
72 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
73 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
74 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
79 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
80 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
81 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
82 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
83 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
84 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
85 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
86 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
92 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
93 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
94 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
95 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
96 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
97 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
98 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
99 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
100 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
101 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
102 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
103 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
104 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
105 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
106 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
107 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
108 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
109 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
110 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
111 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
112 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
113 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
114 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
115 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
116 import org
.whispersystems
.util
.Base64
;
119 import java
.io
.FileInputStream
;
120 import java
.io
.FileNotFoundException
;
121 import java
.io
.FileOutputStream
;
122 import java
.io
.IOException
;
123 import java
.io
.InputStream
;
124 import java
.io
.OutputStream
;
126 import java
.net
.URISyntaxException
;
127 import java
.net
.URLEncoder
;
128 import java
.nio
.file
.Files
;
129 import java
.nio
.file
.Paths
;
130 import java
.nio
.file
.StandardCopyOption
;
131 import java
.util
.ArrayDeque
;
132 import java
.util
.ArrayList
;
133 import java
.util
.Arrays
;
134 import java
.util
.Collection
;
135 import java
.util
.Collections
;
136 import java
.util
.Date
;
137 import java
.util
.Deque
;
138 import java
.util
.HashSet
;
139 import java
.util
.LinkedList
;
140 import java
.util
.List
;
141 import java
.util
.Locale
;
142 import java
.util
.Objects
;
143 import java
.util
.Set
;
144 import java
.util
.UUID
;
145 import java
.util
.concurrent
.TimeUnit
;
146 import java
.util
.concurrent
.TimeoutException
;
147 import java
.util
.stream
.Collectors
;
148 import java
.util
.zip
.ZipEntry
;
149 import java
.util
.zip
.ZipFile
;
151 public class Manager
implements Signal
{
153 private final String settingsPath
;
154 private final String dataPath
;
155 private final String attachmentsPath
;
156 private final String avatarsPath
;
157 private final SleepTimer timer
= new UptimeSleepTimer();
159 private SignalAccount account
;
160 private String username
;
161 private SignalServiceAccountManager accountManager
;
162 private SignalServiceMessagePipe messagePipe
= null;
163 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
165 public Manager(String username
, String settingsPath
) {
166 this.username
= username
;
167 this.settingsPath
= settingsPath
;
168 this.dataPath
= this.settingsPath
+ "/data";
169 this.attachmentsPath
= this.settingsPath
+ "/attachments";
170 this.avatarsPath
= this.settingsPath
+ "/avatars";
174 public String
getUsername() {
178 public SignalServiceAddress
getSelfAddress() {
179 return account
.getSelfAddress();
182 private SignalServiceAccountManager
getSignalServiceAccountManager() {
183 return new SignalServiceAccountManager(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), BaseConfig
.USER_AGENT
, timer
);
186 private IdentityKey
getIdentity() {
187 return account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
190 public int getDeviceId() {
191 return account
.getDeviceId();
194 private String
getMessageCachePath() {
195 return this.dataPath
+ "/" + username
+ ".d/msg-cache";
198 private String
getMessageCachePath(String sender
) {
199 if (sender
== null || sender
.isEmpty()) {
200 return getMessageCachePath();
203 return getMessageCachePath() + "/" + sender
.replace("/", "_");
206 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
207 String cachePath
= getMessageCachePath(sender
);
208 IOUtils
.createPrivateDirectories(cachePath
);
209 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
212 public boolean userHasKeys() {
213 return account
!= null && account
.getSignalProtocolStore() != null;
216 public void init() throws IOException
{
217 if (!SignalAccount
.userExists(dataPath
, username
)) {
220 account
= SignalAccount
.load(dataPath
, username
);
221 account
.setResolver(this::resolveSignalServiceAddress
);
223 migrateLegacyConfigs();
225 accountManager
= getSignalServiceAccountManager();
226 if (account
.isRegistered()) {
227 if (accountManager
.getPreKeysCount() < BaseConfig
.PREKEY_MINIMUM_COUNT
) {
231 if (account
.getUuid() == null) {
232 account
.setUuid(accountManager
.getOwnUuid());
238 private void migrateLegacyConfigs() {
239 // Copy group avatars that were previously stored in the attachments folder
240 // to the new avatar folder
241 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
242 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
243 File avatarFile
= getGroupAvatarFile(g
.groupId
);
244 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
245 if (!avatarFile
.exists() && attachmentFile
.exists()) {
247 IOUtils
.createPrivateDirectories(avatarsPath
);
248 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
249 } catch (Exception e
) {
254 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
257 if (account
.getProfileKey() == null) {
258 // Old config file, creating new profile key
259 account
.setProfileKey(KeyUtils
.createProfileKey());
264 private void createNewIdentity() throws IOException
{
265 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
266 int registrationId
= KeyHelper
.generateRegistrationId(false);
267 if (username
== null) {
268 account
= SignalAccount
.createTemporaryAccount(identityKey
, registrationId
);
269 account
.setResolver(this::resolveSignalServiceAddress
);
271 ProfileKey profileKey
= KeyUtils
.createProfileKey();
272 account
= SignalAccount
.create(dataPath
, username
, identityKey
, registrationId
, profileKey
);
273 account
.setResolver(this::resolveSignalServiceAddress
);
278 public boolean isRegistered() {
279 return account
!= null && account
.isRegistered();
282 public void register(boolean voiceVerification
) throws IOException
{
283 if (account
== null) {
286 account
.setPassword(KeyUtils
.createPassword());
287 account
.setUuid(null);
288 accountManager
= getSignalServiceAccountManager();
290 if (voiceVerification
) {
291 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
293 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
296 account
.setRegistered(false);
300 public void updateAccountAttributes() throws IOException
{
301 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
304 public void setProfileName(String name
) throws IOException
{
305 accountManager
.setProfileName(account
.getProfileKey(), name
);
308 public void setProfileAvatar(File avatar
) throws IOException
{
309 final StreamDetails streamDetails
= Utils
.createStreamDetailsFromFile(avatar
);
310 accountManager
.setProfileAvatar(account
.getProfileKey(), streamDetails
);
311 streamDetails
.getStream().close();
314 public void removeProfileAvatar() throws IOException
{
315 accountManager
.setProfileAvatar(account
.getProfileKey(), null);
318 public void unregister() throws IOException
{
319 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
320 // If this is the master device, other users can't send messages to this number anymore.
321 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
322 accountManager
.setGcmId(Optional
.absent());
324 account
.setRegistered(false);
328 public String
getDeviceLinkUri() throws TimeoutException
, IOException
{
329 if (account
== null) {
332 account
.setPassword(KeyUtils
.createPassword());
333 accountManager
= getSignalServiceAccountManager();
334 String uuid
= accountManager
.getNewDeviceUuid();
336 return Utils
.createDeviceLinkUri(new Utils
.DeviceLinkInfo(uuid
, getIdentity().getPublicKey()));
339 public void finishDeviceLink(String deviceName
) throws IOException
, InvalidKeyException
, TimeoutException
, UserAlreadyExists
{
340 account
.setSignalingKey(KeyUtils
.createSignalingKey());
341 SignalServiceAccountManager
.NewDeviceRegistrationReturn ret
= accountManager
.finishNewDeviceRegistration(account
.getSignalProtocolStore().getIdentityKeyPair(), account
.getSignalingKey(), false, true, account
.getSignalProtocolStore().getLocalRegistrationId(), deviceName
);
343 username
= ret
.getNumber();
344 // TODO do this check before actually registering
345 if (SignalAccount
.userExists(dataPath
, username
)) {
346 throw new UserAlreadyExists(username
, SignalAccount
.getFileName(dataPath
, username
));
349 // Create new account with the synced identity
350 byte[] profileKeyBytes
= ret
.getProfileKey();
351 ProfileKey profileKey
;
352 if (profileKeyBytes
== null) {
353 profileKey
= KeyUtils
.createProfileKey();
356 profileKey
= new ProfileKey(profileKeyBytes
);
357 } catch (InvalidInputException e
) {
358 throw new IOException("Received invalid profileKey", e
);
361 account
= SignalAccount
.createLinkedAccount(dataPath
, username
, ret
.getUuid(), account
.getPassword(), ret
.getDeviceId(), ret
.getIdentity(), account
.getSignalProtocolStore().getLocalRegistrationId(), account
.getSignalingKey(), profileKey
);
362 account
.setResolver(this::resolveSignalServiceAddress
);
367 requestSyncContacts();
368 requestSyncBlocked();
369 requestSyncConfiguration();
374 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
375 List
<DeviceInfo
> devices
= accountManager
.getDevices();
376 account
.setMultiDevice(devices
.size() > 1);
381 public void removeLinkedDevices(int deviceId
) throws IOException
{
382 accountManager
.removeDevice(deviceId
);
383 List
<DeviceInfo
> devices
= accountManager
.getDevices();
384 account
.setMultiDevice(devices
.size() > 1);
388 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
389 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
391 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
394 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
395 IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
396 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
398 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
399 account
.setMultiDevice(true);
403 private List
<PreKeyRecord
> generatePreKeys() {
404 List
<PreKeyRecord
> records
= new ArrayList
<>(BaseConfig
.PREKEY_BATCH_SIZE
);
406 final int offset
= account
.getPreKeyIdOffset();
407 for (int i
= 0; i
< BaseConfig
.PREKEY_BATCH_SIZE
; i
++) {
408 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
409 ECKeyPair keyPair
= Curve
.generateKeyPair();
410 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
415 account
.addPreKeys(records
);
421 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
423 ECKeyPair keyPair
= Curve
.generateKeyPair();
424 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
425 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
427 account
.addSignedPreKey(record);
431 } catch (InvalidKeyException e
) {
432 throw new AssertionError(e
);
436 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
437 verificationCode
= verificationCode
.replace("-", "");
438 account
.setSignalingKey(KeyUtils
.createSignalingKey());
439 // TODO make unrestricted unidentified access configurable
440 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, BaseConfig
.capabilities
);
442 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
443 // TODO response.isStorageCapable()
444 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
445 account
.setRegistered(true);
446 account
.setUuid(uuid
);
447 account
.setRegistrationLockPin(pin
);
448 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), account
.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
454 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
455 if (pin
.isPresent()) {
456 account
.setRegistrationLockPin(pin
.get());
457 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
459 account
.setRegistrationLockPin(null);
460 accountManager
.removeRegistrationLockV1();
465 private void refreshPreKeys() throws IOException
{
466 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
467 final IdentityKeyPair identityKeyPair
= account
.getSignalProtocolStore().getIdentityKeyPair();
468 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
470 accountManager
.setPreKeys(getIdentity(), signedPreKeyRecord
, oneTimePreKeys
);
473 private SignalServiceMessageReceiver
getMessageReceiver() {
474 // TODO implement ZkGroup support
475 final ClientZkProfileOperations clientZkProfileOperations
= null;
476 return new SignalServiceMessageReceiver(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), BaseConfig
.USER_AGENT
, null, timer
, clientZkProfileOperations
);
479 private SignalServiceMessageSender
getMessageSender() {
480 // TODO implement ZkGroup support
481 final ClientZkProfileOperations clientZkProfileOperations
= null;
482 final boolean attachmentsV3
= false;
483 return new SignalServiceMessageSender(BaseConfig
.serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
484 account
.getDeviceId(), account
.getSignalProtocolStore(), BaseConfig
.USER_AGENT
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
);
487 private SignalServiceProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
488 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
493 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
494 } catch (IOException ignored
) {
498 SignalServiceMessageReceiver receiver
= getMessageReceiver();
500 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
501 } catch (VerificationFailedException e
) {
502 throw new AssertionError(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();
540 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
542 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
543 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
544 if (attachments
!= null) {
545 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
547 if (groupId
!= null) {
548 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
551 messageBuilder
.asGroupMessage(group
);
554 final GroupInfo g
= getGroupForSending(groupId
);
556 messageBuilder
.withExpiration(g
.messageExpirationTime
);
558 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
561 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
562 long targetSentTimestamp
, byte[] groupId
)
563 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
564 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
565 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
566 .withReaction(reaction
);
567 if (groupId
!= null) {
568 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
571 messageBuilder
.asGroupMessage(group
);
573 final GroupInfo g
= getGroupForSending(groupId
);
574 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
577 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
{
578 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
582 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
583 .asGroupMessage(group
);
585 final GroupInfo g
= getGroupForSending(groupId
);
586 g
.removeMember(account
.getSelfAddress());
587 account
.getGroupStore().updateGroup(g
);
589 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
592 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
{
594 if (groupId
== null) {
596 g
= new GroupInfo(KeyUtils
.createGroupId());
597 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
599 g
= getGroupForSending(groupId
);
606 if (members
!= null) {
607 final Set
<String
> newE164Members
= new HashSet
<>();
608 for (SignalServiceAddress member
: members
) {
609 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
612 newE164Members
.add(member
.getNumber().get());
615 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
616 if (contacts
.size() != newE164Members
.size()) {
617 // Some of the new members are not registered on Signal
618 for (ContactTokenDetails contact
: contacts
) {
619 newE164Members
.remove(contact
.getNumber());
621 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
622 System
.err
.println("Aborting…");
626 g
.addMembers(members
);
629 if (avatarFile
!= null) {
630 IOUtils
.createPrivateDirectories(avatarsPath
);
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 private void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
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
) {
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 private 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 private 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
);
703 public long sendMessage(String message
, List
<String
> attachments
, String recipient
)
704 throws EncapsulatedExceptions
, AttachmentInvalidException
, IOException
, InvalidNumberException
{
705 List
<String
> recipients
= new ArrayList
<>(1);
706 recipients
.add(recipient
);
707 return sendMessage(message
, attachments
, recipients
);
711 public long sendMessage(String messageText
, List
<String
> attachments
,
712 List
<String
> recipients
)
713 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
714 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
715 if (attachments
!= null) {
716 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
718 // Upload attachments here, so we only upload once even for multiple recipients
719 SignalServiceMessageSender messageSender
= getMessageSender();
720 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
721 for (SignalServiceAttachment attachment
: attachmentStreams
) {
722 if (attachment
.isStream()) {
723 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
724 } else if (attachment
.isPointer()) {
725 attachmentPointers
.add(attachment
.asPointer());
729 messageBuilder
.withAttachments(attachmentPointers
);
731 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
734 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
735 long targetSentTimestamp
, List
<String
> recipients
)
736 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
737 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
738 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
739 .withReaction(reaction
);
740 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
744 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
745 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
746 .asEndSessionMessage();
748 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
750 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
751 } catch (Exception e
) {
752 for (SignalServiceAddress address
: signalServiceAddresses
) {
753 handleEndSession(address
);
760 public String
getContactName(String number
) throws InvalidNumberException
{
761 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
762 if (contact
== null) {
770 public void setContactName(String number
, String name
) throws InvalidNumberException
{
771 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
772 ContactInfo contact
= account
.getContactStore().getContact(address
);
773 if (contact
== null) {
774 contact
= new ContactInfo(address
);
775 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
777 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
780 account
.getContactStore().updateContact(contact
);
785 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
786 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
789 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
790 ContactInfo contact
= account
.getContactStore().getContact(address
);
791 if (contact
== null) {
792 contact
= new ContactInfo(address
);
793 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
795 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
797 contact
.blocked
= blocked
;
798 account
.getContactStore().updateContact(contact
);
803 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
804 GroupInfo group
= getGroup(groupId
);
806 throw new GroupNotFoundException(groupId
);
808 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
809 group
.blocked
= blocked
;
810 account
.getGroupStore().updateGroup(group
);
816 public List
<byte[]> getGroupIds() {
817 List
<GroupInfo
> groups
= getGroups();
818 List
<byte[]> ids
= new ArrayList
<>(groups
.size());
819 for (GroupInfo group
: groups
) {
820 ids
.add(group
.groupId
);
826 public String
getGroupName(byte[] groupId
) {
827 GroupInfo group
= getGroup(groupId
);
836 public List
<String
> getGroupMembers(byte[] groupId
) {
837 GroupInfo group
= getGroup(groupId
);
839 return Collections
.emptyList();
841 return new ArrayList
<>(group
.getMembersE164());
846 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
{
847 if (groupId
.length
== 0) {
850 if (name
.isEmpty()) {
853 if (members
.size() == 0) {
856 if (avatar
.isEmpty()) {
859 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
863 * Change the expiration timer for a contact
865 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) {
866 ContactInfo c
= account
.getContactStore().getContact(address
);
867 c
.messageExpirationTime
= messageExpirationTimer
;
868 account
.getContactStore().updateContact(c
);
872 * Change the expiration timer for a group
874 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
875 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
876 g
.messageExpirationTime
= messageExpirationTimer
;
877 account
.getGroupStore().updateGroup(g
);
881 * Upload the sticker pack from path.
883 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
884 * @return if successful, returns the URL to install the sticker pack in the signal app
886 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
887 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
889 SignalServiceMessageSender messageSender
= getMessageSender();
891 byte[] packKey
= KeyUtils
.createStickerUploadKey();
892 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
895 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
897 } catch (URISyntaxException e
) {
898 throw new AssertionError(e
);
902 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
904 String rootPath
= null;
906 final File file
= new File(path
);
907 if (file
.getName().endsWith(".zip")) {
908 zip
= new ZipFile(file
);
909 } else if (file
.getName().equals("manifest.json")) {
910 rootPath
= file
.getParent();
912 throw new StickerPackInvalidException("Could not find manifest.json");
915 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
917 if (pack
.stickers
== null) {
918 throw new StickerPackInvalidException("Must set a 'stickers' field.");
921 if (pack
.stickers
.isEmpty()) {
922 throw new StickerPackInvalidException("Must include stickers.");
925 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
926 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
927 if (sticker
.file
== null) {
928 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
931 Pair
<InputStream
, Long
> data
;
933 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
934 } catch (IOException ignored
) {
935 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
938 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
939 stickers
.add(stickerInfo
);
942 StickerInfo cover
= null;
943 if (pack
.cover
!= null) {
944 if (pack
.cover
.file
== null) {
945 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
948 Pair
<InputStream
, Long
> data
;
950 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
951 } catch (IOException ignored
) {
952 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
955 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
958 return new SignalServiceStickerManifestUpload(
965 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
966 InputStream inputStream
;
968 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
970 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
972 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
975 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
977 final ZipEntry entry
= zip
.getEntry(subfile
);
978 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
980 final File file
= new File(rootPath
, subfile
);
981 return new Pair
<>(new FileInputStream(file
), file
.length());
985 private void requestSyncGroups() throws IOException
{
986 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
987 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
989 sendSyncMessage(message
);
990 } catch (UntrustedIdentityException e
) {
995 private void requestSyncContacts() throws IOException
{
996 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
997 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
999 sendSyncMessage(message
);
1000 } catch (UntrustedIdentityException e
) {
1001 e
.printStackTrace();
1005 private void requestSyncBlocked() throws IOException
{
1006 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
1007 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1009 sendSyncMessage(message
);
1010 } catch (UntrustedIdentityException e
) {
1011 e
.printStackTrace();
1015 private void requestSyncConfiguration() throws IOException
{
1016 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
1017 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1019 sendSyncMessage(message
);
1020 } catch (UntrustedIdentityException e
) {
1021 e
.printStackTrace();
1025 private byte[] getSenderCertificate() {
1026 // TODO support UUID capable sender certificates
1027 // byte[] certificate = accountManager.getSenderCertificate();
1030 certificate
= accountManager
.getSenderCertificateLegacy();
1031 } catch (IOException e
) {
1032 System
.err
.println("Failed to get sender certificate: " + e
);
1035 // TODO cache for a day
1039 private byte[] getSelfUnidentifiedAccessKey() {
1040 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
1043 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
1044 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
1046 return new SignalProfile(
1047 encryptedProfile
.getIdentityKey(),
1048 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
1049 encryptedProfile
.getAvatar(),
1050 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
1051 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
1053 } catch (InvalidCiphertextException e
) {
1058 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
1059 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
1060 if (contact
== null || contact
.profileKey
== null) {
1063 ProfileKey theirProfileKey
;
1065 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
1066 } catch (InvalidInputException
| IOException e
) {
1067 throw new AssertionError(e
);
1069 SignalProfile targetProfile
;
1071 targetProfile
= decryptProfile(getRecipientProfile(recipient
, Optional
.absent()), theirProfileKey
);
1072 } catch (IOException e
) {
1073 System
.err
.println("Failed to get recipient profile: " + e
);
1077 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
1081 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
1082 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
1085 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
1088 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
1089 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1090 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1092 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1093 return Optional
.absent();
1097 return Optional
.of(new UnidentifiedAccessPair(
1098 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1099 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1101 } catch (InvalidCertificateException e
) {
1102 return Optional
.absent();
1106 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1107 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1108 for (SignalServiceAddress recipient
: recipients
) {
1109 result
.add(getAccessFor(recipient
));
1114 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1115 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1116 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1117 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1119 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1120 return Optional
.absent();
1124 return Optional
.of(new UnidentifiedAccessPair(
1125 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1126 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1128 } catch (InvalidCertificateException e
) {
1129 return Optional
.absent();
1133 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1134 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1136 if (unidentifiedAccess
.isPresent()) {
1137 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1140 return Optional
.absent();
1143 private void sendSyncMessage(SignalServiceSyncMessage message
)
1144 throws IOException
, UntrustedIdentityException
{
1145 SignalServiceMessageSender messageSender
= getMessageSender();
1147 messageSender
.sendMessage(message
, getAccessForSync());
1148 } catch (UntrustedIdentityException e
) {
1149 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1155 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1157 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1158 throws EncapsulatedExceptions
, IOException
{
1159 final long timestamp
= System
.currentTimeMillis();
1160 messageBuilder
.withTimestamp(timestamp
);
1161 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1163 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1164 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1165 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1167 for (SendMessageResult result
: results
) {
1168 if (result
.isUnregisteredFailure()) {
1169 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1170 } else if (result
.isNetworkFailure()) {
1171 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1172 } else if (result
.getIdentityFailure() != null) {
1173 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1176 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1177 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1182 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1183 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1185 for (String number
: numbers
) {
1186 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1188 return signalServiceAddresses
;
1191 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1192 throws IOException
{
1193 if (messagePipe
== null) {
1194 messagePipe
= getMessageReceiver().createMessagePipe();
1196 if (unidentifiedMessagePipe
== null) {
1197 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1199 SignalServiceDataMessage message
= null;
1201 SignalServiceMessageSender messageSender
= getMessageSender();
1203 message
= messageBuilder
.build();
1204 if (message
.getGroupContext().isPresent()) {
1206 final boolean isRecipientUpdate
= false;
1207 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1208 for (SendMessageResult r
: result
) {
1209 if (r
.getIdentityFailure() != null) {
1210 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1214 } catch (UntrustedIdentityException e
) {
1215 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1216 return Collections
.emptyList();
1218 } else if (recipients
.size() == 1 && recipients
.contains(account
.getSelfAddress())) {
1219 SignalServiceAddress recipient
= account
.getSelfAddress();
1220 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1221 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1222 message
.getTimestamp(),
1224 message
.getExpiresInSeconds(),
1225 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1227 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1229 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1231 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1232 } catch (UntrustedIdentityException e
) {
1233 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1234 results
.add(SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey()));
1238 // Send to all individually, so sync messages are sent correctly
1239 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1240 for (SignalServiceAddress address
: recipients
) {
1241 ContactInfo contact
= account
.getContactStore().getContact(address
);
1242 if (contact
!= null) {
1243 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1244 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1246 messageBuilder
.withExpiration(0);
1247 messageBuilder
.withProfileKey(null);
1249 message
= messageBuilder
.build();
1251 SendMessageResult result
= messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1252 results
.add(result
);
1253 } catch (UntrustedIdentityException e
) {
1254 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1255 results
.add(SendMessageResult
.identityFailure(address
, e
.getIdentityKey()));
1261 if (message
!= null && message
.isEndSession()) {
1262 for (SignalServiceAddress recipient
: recipients
) {
1263 handleEndSession(recipient
);
1270 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1271 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1273 return cipher
.decrypt(envelope
);
1274 } catch (ProtocolUntrustedIdentityException e
) {
1275 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1276 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1277 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1278 throw identityException
;
1280 throw new AssertionError(e
);
1284 private void handleEndSession(SignalServiceAddress source
) {
1285 account
.getSignalProtocolStore().deleteAllSessions(source
);
1288 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1289 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1290 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1291 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1292 switch (groupInfo
.getType()) {
1294 if (group
== null) {
1295 group
= new GroupInfo(groupInfo
.getGroupId());
1298 if (groupInfo
.getAvatar().isPresent()) {
1299 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1300 if (avatar
.isPointer()) {
1302 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1303 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1304 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1309 if (groupInfo
.getName().isPresent()) {
1310 group
.name
= groupInfo
.getName().get();
1313 if (groupInfo
.getMembers().isPresent()) {
1314 group
.addMembers(groupInfo
.getMembers().get()
1316 .map(this::resolveSignalServiceAddress
)
1317 .collect(Collectors
.toSet()));
1320 account
.getGroupStore().updateGroup(group
);
1323 if (group
== null) {
1325 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1326 } catch (IOException
| EncapsulatedExceptions e
) {
1327 e
.printStackTrace();
1332 if (group
== null) {
1334 sendGroupInfoRequest(groupInfo
.getGroupId(), source
);
1335 } catch (IOException
| EncapsulatedExceptions e
) {
1336 e
.printStackTrace();
1339 group
.removeMember(source
);
1340 account
.getGroupStore().updateGroup(group
);
1344 if (group
!= null) {
1346 sendUpdateGroupMessage(groupInfo
.getGroupId(), source
);
1347 } catch (IOException
| EncapsulatedExceptions e
) {
1348 e
.printStackTrace();
1349 } catch (NotAGroupMemberException e
) {
1350 // We have left this group, so don't send a group update message
1356 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1357 if (message
.isEndSession()) {
1358 handleEndSession(conversationPartnerAddress
);
1360 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1361 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1362 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1363 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1364 if (group
== null) {
1365 group
= new GroupInfo(groupInfo
.getGroupId());
1367 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1368 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1369 account
.getGroupStore().updateGroup(group
);
1372 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1373 if (contact
== null) {
1374 contact
= new ContactInfo(conversationPartnerAddress
);
1376 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1377 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1378 account
.getContactStore().updateContact(contact
);
1382 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1383 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1384 if (attachment
.isPointer()) {
1386 retrieveAttachment(attachment
.asPointer());
1387 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1388 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1393 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1394 if (source
.matches(account
.getSelfAddress())) {
1396 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1397 } catch (InvalidInputException ignored
) {
1399 ContactInfo contact
= account
.getContactStore().getContact(source
);
1400 if (contact
!= null) {
1401 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1402 account
.getContactStore().updateContact(contact
);
1405 ContactInfo contact
= account
.getContactStore().getContact(source
);
1406 if (contact
== null) {
1407 contact
= new ContactInfo(source
);
1409 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1410 account
.getContactStore().updateContact(contact
);
1413 if (message
.getPreviews().isPresent()) {
1414 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1415 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1416 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1417 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1419 retrieveAttachment(attachment
);
1420 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1421 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1428 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1429 final File cachePath
= new File(getMessageCachePath());
1430 if (!cachePath
.exists()) {
1433 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1434 if (!dir
.isDirectory()) {
1435 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1439 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1440 if (!fileEntry
.isFile()) {
1443 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1445 // Try to delete directory if empty
1450 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1451 SignalServiceEnvelope envelope
;
1453 envelope
= Utils
.loadEnvelope(fileEntry
);
1454 if (envelope
== null) {
1457 } catch (IOException e
) {
1458 e
.printStackTrace();
1461 SignalServiceContent content
= null;
1462 if (!envelope
.isReceipt()) {
1464 content
= decryptMessage(envelope
);
1465 } catch (Exception e
) {
1468 handleMessage(envelope
, content
, ignoreAttachments
);
1471 handler
.handleMessage(envelope
, content
, null);
1473 Files
.delete(fileEntry
.toPath());
1474 } catch (IOException e
) {
1475 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1479 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1480 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1481 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1484 if (messagePipe
== null) {
1485 messagePipe
= messageReceiver
.createMessagePipe();
1489 SignalServiceEnvelope envelope
;
1490 SignalServiceContent content
= null;
1491 Exception exception
= null;
1492 final long now
= new Date().getTime();
1494 envelope
= messagePipe
.read(timeout
, unit
, envelope1
-> {
1495 // store message on disk, before acknowledging receipt to the server
1497 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1498 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1499 Utils
.storeEnvelope(envelope1
, cacheFile
);
1500 } catch (IOException e
) {
1501 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1504 } catch (TimeoutException e
) {
1505 if (returnOnTimeout
)
1508 } catch (InvalidVersionException e
) {
1509 System
.err
.println("Ignoring error: " + e
.getMessage());
1512 if (!envelope
.isReceipt()) {
1514 content
= decryptMessage(envelope
);
1515 } catch (Exception e
) {
1518 handleMessage(envelope
, content
, ignoreAttachments
);
1521 if (!isMessageBlocked(envelope
, content
)) {
1522 handler
.handleMessage(envelope
, content
, exception
);
1524 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1525 File cacheFile
= null;
1527 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1528 Files
.delete(cacheFile
.toPath());
1529 // Try to delete directory if empty
1530 new File(getMessageCachePath()).delete();
1531 } catch (IOException e
) {
1532 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1537 if (messagePipe
!= null) {
1538 messagePipe
.shutdown();
1544 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1545 SignalServiceAddress source
;
1546 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1547 source
= envelope
.getSourceAddress();
1548 } else if (content
!= null) {
1549 source
= content
.getSender();
1553 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1554 if (sourceContact
!= null && sourceContact
.blocked
) {
1558 if (content
!= null && content
.getDataMessage().isPresent()) {
1559 SignalServiceDataMessage message
= content
.getDataMessage().get();
1560 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1561 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1562 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1563 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1571 private void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1572 if (content
!= null) {
1573 SignalServiceAddress sender
;
1574 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1575 sender
= envelope
.getSourceAddress();
1577 sender
= content
.getSender();
1579 if (content
.getDataMessage().isPresent()) {
1580 SignalServiceDataMessage message
= content
.getDataMessage().get();
1582 if (content
.isNeedsReceipt()) {
1584 sendReceipt(sender
, message
.getTimestamp());
1585 } catch (IOException
| UntrustedIdentityException
| IllegalArgumentException e
) {
1586 e
.printStackTrace();
1590 handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
);
1592 if (content
.getSyncMessage().isPresent()) {
1593 account
.setMultiDevice(true);
1594 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1595 if (syncMessage
.getSent().isPresent()) {
1596 SentTranscriptMessage message
= syncMessage
.getSent().get();
1597 handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
);
1599 if (syncMessage
.getRequest().isPresent()) {
1600 RequestMessage rm
= syncMessage
.getRequest().get();
1601 if (rm
.isContactsRequest()) {
1604 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1605 e
.printStackTrace();
1608 if (rm
.isGroupsRequest()) {
1611 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1612 e
.printStackTrace();
1615 if (rm
.isBlockedListRequest()) {
1618 } catch (UntrustedIdentityException
| IOException
| IllegalArgumentException e
) {
1619 e
.printStackTrace();
1622 // TODO Handle rm.isConfigurationRequest();
1624 if (syncMessage
.getGroups().isPresent()) {
1625 File tmpFile
= null;
1627 tmpFile
= IOUtils
.createTempFile();
1628 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1629 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1631 while ((g
= s
.read()) != null) {
1632 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1633 if (syncGroup
== null) {
1634 syncGroup
= new GroupInfo(g
.getId());
1636 if (g
.getName().isPresent()) {
1637 syncGroup
.name
= g
.getName().get();
1639 syncGroup
.addMembers(g
.getMembers()
1641 .map(this::resolveSignalServiceAddress
)
1642 .collect(Collectors
.toSet()));
1643 if (!g
.isActive()) {
1644 syncGroup
.removeMember(account
.getSelfAddress());
1646 // Add ourself to the member set as it's marked as active
1647 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1649 syncGroup
.blocked
= g
.isBlocked();
1650 if (g
.getColor().isPresent()) {
1651 syncGroup
.color
= g
.getColor().get();
1654 if (g
.getAvatar().isPresent()) {
1655 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1657 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1658 syncGroup
.archived
= g
.isArchived();
1659 account
.getGroupStore().updateGroup(syncGroup
);
1662 } catch (Exception e
) {
1663 e
.printStackTrace();
1665 if (tmpFile
!= null) {
1667 Files
.delete(tmpFile
.toPath());
1668 } catch (IOException e
) {
1669 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1674 if (syncMessage
.getBlockedList().isPresent()) {
1675 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1676 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1677 setContactBlocked(resolveSignalServiceAddress(address
), true);
1679 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1681 setGroupBlocked(groupId
, true);
1682 } catch (GroupNotFoundException e
) {
1683 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1687 if (syncMessage
.getContacts().isPresent()) {
1688 File tmpFile
= null;
1690 tmpFile
= IOUtils
.createTempFile();
1691 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1692 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1693 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1694 if (contactsMessage
.isComplete()) {
1695 account
.getContactStore().clear();
1698 while ((c
= s
.read()) != null) {
1699 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1700 account
.setProfileKey(c
.getProfileKey().get());
1702 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1703 ContactInfo contact
= account
.getContactStore().getContact(address
);
1704 if (contact
== null) {
1705 contact
= new ContactInfo(address
);
1707 if (c
.getName().isPresent()) {
1708 contact
.name
= c
.getName().get();
1710 if (c
.getColor().isPresent()) {
1711 contact
.color
= c
.getColor().get();
1713 if (c
.getProfileKey().isPresent()) {
1714 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1716 if (c
.getVerified().isPresent()) {
1717 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1718 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1720 if (c
.getExpirationTimer().isPresent()) {
1721 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1723 contact
.blocked
= c
.isBlocked();
1724 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1725 contact
.archived
= c
.isArchived();
1726 account
.getContactStore().updateContact(contact
);
1728 if (c
.getAvatar().isPresent()) {
1729 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1733 } catch (Exception e
) {
1734 e
.printStackTrace();
1736 if (tmpFile
!= null) {
1738 Files
.delete(tmpFile
.toPath());
1739 } catch (IOException e
) {
1740 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1745 if (syncMessage
.getVerified().isPresent()) {
1746 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1747 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1749 if (syncMessage
.getConfiguration().isPresent()) {
1756 private File
getContactAvatarFile(String number
) {
1757 return new File(avatarsPath
, "contact-" + number
);
1760 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1761 IOUtils
.createPrivateDirectories(avatarsPath
);
1762 if (attachment
.isPointer()) {
1763 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1764 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1766 SignalServiceAttachmentStream stream
= attachment
.asStream();
1767 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1771 private File
getGroupAvatarFile(byte[] groupId
) {
1772 return new File(avatarsPath
, "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1775 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1776 IOUtils
.createPrivateDirectories(avatarsPath
);
1777 if (attachment
.isPointer()) {
1778 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1779 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1781 SignalServiceAttachmentStream stream
= attachment
.asStream();
1782 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1786 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1787 return new File(attachmentsPath
, attachmentId
.toString());
1790 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1791 IOUtils
.createPrivateDirectories(attachmentsPath
);
1792 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1795 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1796 if (storePreview
&& pointer
.getPreview().isPresent()) {
1797 File previewFile
= new File(outputFile
+ ".preview");
1798 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1799 byte[] preview
= pointer
.getPreview().get();
1800 output
.write(preview
, 0, preview
.length
);
1801 } catch (FileNotFoundException e
) {
1802 e
.printStackTrace();
1807 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1809 File tmpFile
= IOUtils
.createTempFile();
1810 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
)) {
1811 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1812 byte[] buffer
= new byte[4096];
1815 while ((read
= input
.read(buffer
)) != -1) {
1816 output
.write(buffer
, 0, read
);
1818 } catch (FileNotFoundException e
) {
1819 e
.printStackTrace();
1824 Files
.delete(tmpFile
.toPath());
1825 } catch (IOException e
) {
1826 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1832 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1833 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1834 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, BaseConfig
.MAX_ATTACHMENT_SIZE
);
1838 public boolean isRemote() {
1843 public String
getObjectPath() {
1847 private void sendGroups() throws IOException
, UntrustedIdentityException
{
1848 File groupsFile
= IOUtils
.createTempFile();
1851 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1852 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1853 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1854 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1855 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1856 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1857 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1861 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1862 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1863 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1864 .withStream(groupsFileStream
)
1865 .withContentType("application/octet-stream")
1866 .withLength(groupsFile
.length())
1869 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1874 Files
.delete(groupsFile
.toPath());
1875 } catch (IOException e
) {
1876 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1881 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1882 File contactsFile
= IOUtils
.createTempFile();
1885 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1886 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1887 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1888 VerifiedMessage verifiedMessage
= null;
1889 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1890 if (currentIdentity
!= null) {
1891 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1894 ProfileKey profileKey
= null;
1896 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1897 } catch (InvalidInputException ignored
) {
1899 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1900 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1901 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1902 Optional
.of(record.messageExpirationTime
),
1903 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1906 if (account
.getProfileKey() != null) {
1907 // Send our own profile key as well
1908 out
.write(new DeviceContact(account
.getSelfAddress(),
1909 Optional
.absent(), Optional
.absent(),
1910 Optional
.absent(), Optional
.absent(),
1911 Optional
.of(account
.getProfileKey()),
1912 false, Optional
.absent(), Optional
.absent(), false));
1916 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1917 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1918 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1919 .withStream(contactsFileStream
)
1920 .withContentType("application/octet-stream")
1921 .withLength(contactsFile
.length())
1924 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1929 Files
.delete(contactsFile
.toPath());
1930 } catch (IOException e
) {
1931 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1936 private void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1937 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1938 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1939 if (record.blocked
) {
1940 addresses
.add(record.getAddress());
1943 List
<byte[]> groupIds
= new ArrayList
<>();
1944 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1945 if (record.blocked
) {
1946 groupIds
.add(record.groupId
);
1949 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1952 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1953 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1954 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1957 public List
<ContactInfo
> getContacts() {
1958 return account
.getContactStore().getContacts();
1961 public ContactInfo
getContact(String number
) {
1962 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1965 public GroupInfo
getGroup(byte[] groupId
) {
1966 return account
.getGroupStore().getGroup(groupId
);
1969 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1970 return account
.getSignalProtocolStore().getIdentities();
1973 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1974 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1978 * Trust this the identity with this fingerprint
1980 * @param name username of the identity
1981 * @param fingerprint Fingerprint
1983 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1984 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1985 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1989 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1990 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1994 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1996 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1997 } catch (IOException
| UntrustedIdentityException e
) {
1998 e
.printStackTrace();
2007 * Trust this the identity with this safety number
2009 * @param name username of the identity
2010 * @param safetyNumber Safety number
2012 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2013 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2014 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2018 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2019 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2023 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2025 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2026 } catch (IOException
| UntrustedIdentityException e
) {
2027 e
.printStackTrace();
2036 * Trust all keys of this identity without verification
2038 * @param name username of the identity
2040 public boolean trustIdentityAllKeys(String name
) {
2041 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2042 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2046 for (JsonIdentityKeyStore
.Identity id
: ids
) {
2047 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2048 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2050 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2051 } catch (IOException
| UntrustedIdentityException e
) {
2052 e
.printStackTrace();
2060 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
2061 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentity(), theirAddress
, theirIdentityKey
);
2064 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2065 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2066 return resolveSignalServiceAddress(canonicalizedNumber
);
2069 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2070 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2072 return resolveSignalServiceAddress(address
);
2075 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2076 if (address
.matches(account
.getSelfAddress())) {
2077 return account
.getSelfAddress();
2080 return account
.getRecipientStore().resolveServiceAddress(address
);
2083 public interface ReceiveMessageHandler
{
2085 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);