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
.protocol
.JsonIdentityKeyStore
;
26 import org
.asamk
.signal
.util
.IOUtils
;
27 import org
.asamk
.signal
.util
.Util
;
28 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
29 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
30 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
31 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
32 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
33 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
34 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
35 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
36 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
37 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
38 import org
.signal
.libsignal
.metadata
.SelfSendException
;
39 import org
.signal
.libsignal
.metadata
.certificate
.InvalidCertificateException
;
40 import org
.signal
.zkgroup
.InvalidInputException
;
41 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
42 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
43 import org
.whispersystems
.libsignal
.IdentityKey
;
44 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
45 import org
.whispersystems
.libsignal
.InvalidKeyException
;
46 import org
.whispersystems
.libsignal
.InvalidMessageException
;
47 import org
.whispersystems
.libsignal
.InvalidVersionException
;
48 import org
.whispersystems
.libsignal
.ecc
.Curve
;
49 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
50 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
51 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
52 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
53 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
54 import org
.whispersystems
.libsignal
.util
.Medium
;
55 import org
.whispersystems
.libsignal
.util
.Pair
;
56 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
57 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
58 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
59 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
60 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
61 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
62 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
63 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
64 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
65 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
66 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
67 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
68 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
69 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
70 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
71 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
72 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
73 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
74 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
75 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
76 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
77 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
78 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
79 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
80 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
81 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
82 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
83 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
84 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
85 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
86 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
87 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
88 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
89 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
90 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
91 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
92 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
93 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
94 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
95 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.EncapsulatedExceptions
;
96 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
97 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NetworkFailureException
;
98 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
99 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
100 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
101 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
102 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
103 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
104 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
105 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
106 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
107 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
108 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
109 import org
.whispersystems
.util
.Base64
;
111 import java
.io
.Closeable
;
113 import java
.io
.FileInputStream
;
114 import java
.io
.FileNotFoundException
;
115 import java
.io
.FileOutputStream
;
116 import java
.io
.IOException
;
117 import java
.io
.InputStream
;
118 import java
.io
.OutputStream
;
120 import java
.net
.URISyntaxException
;
121 import java
.net
.URLEncoder
;
122 import java
.nio
.file
.Files
;
123 import java
.nio
.file
.Paths
;
124 import java
.nio
.file
.StandardCopyOption
;
125 import java
.util
.ArrayList
;
126 import java
.util
.Arrays
;
127 import java
.util
.Collection
;
128 import java
.util
.Collections
;
129 import java
.util
.Date
;
130 import java
.util
.HashSet
;
131 import java
.util
.LinkedList
;
132 import java
.util
.List
;
133 import java
.util
.Locale
;
134 import java
.util
.Objects
;
135 import java
.util
.Set
;
136 import java
.util
.UUID
;
137 import java
.util
.concurrent
.ExecutionException
;
138 import java
.util
.concurrent
.ExecutorService
;
139 import java
.util
.concurrent
.TimeUnit
;
140 import java
.util
.concurrent
.TimeoutException
;
141 import java
.util
.stream
.Collectors
;
142 import java
.util
.zip
.ZipEntry
;
143 import java
.util
.zip
.ZipFile
;
145 public class Manager
implements Closeable
{
147 private final SleepTimer timer
= new UptimeSleepTimer();
148 private final SignalServiceConfiguration serviceConfiguration
;
149 private final String userAgent
;
151 private final SignalAccount account
;
152 private final PathConfig pathConfig
;
153 private SignalServiceAccountManager accountManager
;
154 private SignalServiceMessagePipe messagePipe
= null;
155 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
157 public Manager(SignalAccount account
, PathConfig pathConfig
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) {
158 this.account
= account
;
159 this.pathConfig
= pathConfig
;
160 this.serviceConfiguration
= serviceConfiguration
;
161 this.userAgent
= userAgent
;
162 this.accountManager
= createSignalServiceAccountManager();
164 this.account
.setResolver(this::resolveSignalServiceAddress
);
167 public String
getUsername() {
168 return account
.getUsername();
171 public SignalServiceAddress
getSelfAddress() {
172 return account
.getSelfAddress();
175 private SignalServiceAccountManager
createSignalServiceAccountManager() {
176 return new SignalServiceAccountManager(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), userAgent
, timer
);
179 private IdentityKeyPair
getIdentityKeyPair() {
180 return account
.getSignalProtocolStore().getIdentityKeyPair();
183 public int getDeviceId() {
184 return account
.getDeviceId();
187 private String
getMessageCachePath() {
188 return pathConfig
.getDataPath() + "/" + account
.getUsername() + ".d/msg-cache";
191 private String
getMessageCachePath(String sender
) {
192 if (sender
== null || sender
.isEmpty()) {
193 return getMessageCachePath();
196 return getMessageCachePath() + "/" + sender
.replace("/", "_");
199 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
200 String cachePath
= getMessageCachePath(sender
);
201 IOUtils
.createPrivateDirectories(cachePath
);
202 return new File(cachePath
+ "/" + now
+ "_" + timestamp
);
205 public static Manager
init(String username
, String settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
) throws IOException
{
206 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
208 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
209 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
210 int registrationId
= KeyHelper
.generateRegistrationId(false);
212 ProfileKey profileKey
= KeyUtils
.createProfileKey();
213 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(), username
, identityKey
, registrationId
, profileKey
);
216 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
219 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
221 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
223 m
.migrateLegacyConfigs();
228 private void migrateLegacyConfigs() {
229 // Copy group avatars that were previously stored in the attachments folder
230 // to the new avatar folder
231 if (JsonGroupStore
.groupsWithLegacyAvatarId
.size() > 0) {
232 for (GroupInfo g
: JsonGroupStore
.groupsWithLegacyAvatarId
) {
233 File avatarFile
= getGroupAvatarFile(g
.groupId
);
234 File attachmentFile
= getAttachmentFile(new SignalServiceAttachmentRemoteId(g
.getAvatarId()));
235 if (!avatarFile
.exists() && attachmentFile
.exists()) {
237 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
238 Files
.copy(attachmentFile
.toPath(), avatarFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
239 } catch (Exception e
) {
244 JsonGroupStore
.groupsWithLegacyAvatarId
.clear();
247 if (account
.getProfileKey() == null) {
248 // Old config file, creating new profile key
249 account
.setProfileKey(KeyUtils
.createProfileKey());
254 public void checkAccountState() throws IOException
{
255 if (account
.isRegistered()) {
256 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
260 if (account
.getUuid() == null) {
261 account
.setUuid(accountManager
.getOwnUuid());
267 public boolean isRegistered() {
268 return account
.isRegistered();
271 public void register(boolean voiceVerification
) throws IOException
{
272 account
.setPassword(KeyUtils
.createPassword());
274 // Resetting UUID, because registering doesn't work otherwise
275 account
.setUuid(null);
276 accountManager
= createSignalServiceAccountManager();
278 if (voiceVerification
) {
279 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(), Optional
.absent(), Optional
.absent());
281 accountManager
.requestSmsVerificationCode(false, Optional
.absent(), Optional
.absent());
284 account
.setRegistered(false);
288 public void updateAccountAttributes() throws IOException
{
289 accountManager
.setAccountAttributes(account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, account
.getRegistrationLockPin(), account
.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
292 public void setProfile(String name
, File avatar
) throws IOException
{
293 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
294 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
298 public void unregister() throws IOException
{
299 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
300 // If this is the master device, other users can't send messages to this number anymore.
301 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
302 accountManager
.setGcmId(Optional
.absent());
304 account
.setRegistered(false);
308 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
309 List
<DeviceInfo
> devices
= accountManager
.getDevices();
310 account
.setMultiDevice(devices
.size() > 1);
315 public void removeLinkedDevices(int deviceId
) throws IOException
{
316 accountManager
.removeDevice(deviceId
);
317 List
<DeviceInfo
> devices
= accountManager
.getDevices();
318 account
.setMultiDevice(devices
.size() > 1);
322 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
323 Utils
.DeviceLinkInfo info
= Utils
.parseDeviceLinkUri(linkUri
);
325 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
328 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
329 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
330 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
332 accountManager
.addDevice(deviceIdentifier
, deviceKey
, identityKeyPair
, Optional
.of(account
.getProfileKey().serialize()), verificationCode
);
333 account
.setMultiDevice(true);
337 private List
<PreKeyRecord
> generatePreKeys() {
338 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
340 final int offset
= account
.getPreKeyIdOffset();
341 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
342 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
343 ECKeyPair keyPair
= Curve
.generateKeyPair();
344 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
349 account
.addPreKeys(records
);
355 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
357 ECKeyPair keyPair
= Curve
.generateKeyPair();
358 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(), keyPair
.getPublicKey().serialize());
359 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(), System
.currentTimeMillis(), keyPair
, signature
);
361 account
.addSignedPreKey(record);
365 } catch (InvalidKeyException e
) {
366 throw new AssertionError(e
);
370 public void verifyAccount(String verificationCode
, String pin
) throws IOException
{
371 verificationCode
= verificationCode
.replace("-", "");
372 account
.setSignalingKey(KeyUtils
.createSignalingKey());
373 // TODO make unrestricted unidentified access configurable
374 VerifyAccountResponse response
= accountManager
.verifyAccountWithCode(verificationCode
, account
.getSignalingKey(), account
.getSignalProtocolStore().getLocalRegistrationId(), true, pin
, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig
.capabilities
);
376 UUID uuid
= UuidUtil
.parseOrNull(response
.getUuid());
377 // TODO response.isStorageCapable()
378 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
379 account
.setRegistered(true);
380 account
.setUuid(uuid
);
381 account
.setRegistrationLockPin(pin
);
382 account
.getSignalProtocolStore().saveIdentity(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel
.TRUSTED_VERIFIED
);
388 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
{
389 if (pin
.isPresent()) {
390 account
.setRegistrationLockPin(pin
.get());
391 throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
393 account
.setRegistrationLockPin(null);
394 accountManager
.removeRegistrationLockV1();
399 void refreshPreKeys() throws IOException
{
400 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
401 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
402 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
404 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
407 private SignalServiceMessageReceiver
getMessageReceiver() {
408 // TODO implement ZkGroup support
409 final ClientZkProfileOperations clientZkProfileOperations
= null;
410 return new SignalServiceMessageReceiver(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(), account
.getDeviceId(), account
.getSignalingKey(), userAgent
, null, timer
, clientZkProfileOperations
);
413 private SignalServiceMessageSender
getMessageSender() {
414 // TODO implement ZkGroup support
415 final ClientZkProfileOperations clientZkProfileOperations
= null;
416 final boolean attachmentsV3
= false;
417 final ExecutorService executor
= null;
418 return new SignalServiceMessageSender(serviceConfiguration
, account
.getUuid(), account
.getUsername(), account
.getPassword(),
419 account
.getDeviceId(), account
.getSignalProtocolStore(), userAgent
, account
.isMultiDevice(), attachmentsV3
, Optional
.fromNullable(messagePipe
), Optional
.fromNullable(unidentifiedMessagePipe
), Optional
.absent(), clientZkProfileOperations
, executor
);
422 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
) throws IOException
{
423 SignalServiceMessagePipe pipe
= unidentifiedMessagePipe
!= null && unidentifiedAccess
.isPresent() ? unidentifiedMessagePipe
428 return pipe
.getProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
429 } catch (IOException
| InterruptedException
| ExecutionException
| TimeoutException ignored
) {
433 SignalServiceMessageReceiver receiver
= getMessageReceiver();
435 return receiver
.retrieveProfile(address
, Optional
.absent(), unidentifiedAccess
, SignalServiceProfile
.RequestType
.PROFILE
).get(10, TimeUnit
.SECONDS
).getProfile();
436 } catch (InterruptedException
| ExecutionException
| TimeoutException e
) {
437 throw new IOException("Failed to retrieve profile", e
);
441 private SignalProfile
getRecipientProfile(SignalServiceAddress address
, Optional
<UnidentifiedAccess
> unidentifiedAccess
, ProfileKey profileKey
) throws IOException
{
442 return decryptProfile(getEncryptedRecipientProfile(address
, unidentifiedAccess
), profileKey
);
445 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(byte[] groupId
) throws IOException
{
446 File file
= getGroupAvatarFile(groupId
);
447 if (!file
.exists()) {
448 return Optional
.absent();
451 return Optional
.of(Utils
.createAttachment(file
));
454 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
455 File file
= getContactAvatarFile(number
);
456 if (!file
.exists()) {
457 return Optional
.absent();
460 return Optional
.of(Utils
.createAttachment(file
));
463 private GroupInfo
getGroupForSending(byte[] groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
464 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
466 throw new GroupNotFoundException(groupId
);
468 if (!g
.isMember(account
.getSelfAddress())) {
469 throw new NotAGroupMemberException(groupId
, g
.name
);
474 public List
<GroupInfo
> getGroups() {
475 return account
.getGroupStore().getGroups();
478 public long sendGroupMessage(String messageText
, List
<String
> attachments
,
480 throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
481 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
482 if (attachments
!= null) {
483 messageBuilder
.withAttachments(Utils
.getSignalServiceAttachments(attachments
));
485 if (groupId
!= null) {
486 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
489 messageBuilder
.asGroupMessage(group
);
492 final GroupInfo g
= getGroupForSending(groupId
);
494 messageBuilder
.withExpiration(g
.messageExpirationTime
);
496 return sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
499 public void sendGroupMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
500 long targetSentTimestamp
, byte[] groupId
)
501 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
502 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
503 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
504 .withReaction(reaction
);
505 if (groupId
!= null) {
506 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.DELIVER
)
509 messageBuilder
.asGroupMessage(group
);
511 final GroupInfo g
= getGroupForSending(groupId
);
512 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
515 public void sendQuitGroupMessage(byte[] groupId
) throws GroupNotFoundException
, IOException
, EncapsulatedExceptions
, NotAGroupMemberException
{
516 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
520 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
521 .asGroupMessage(group
);
523 final GroupInfo g
= getGroupForSending(groupId
);
524 g
.removeMember(account
.getSelfAddress());
525 account
.getGroupStore().updateGroup(g
);
527 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
530 private byte[] sendUpdateGroupMessage(byte[] groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
532 if (groupId
== null) {
534 g
= new GroupInfo(KeyUtils
.createGroupId());
535 g
.addMembers(Collections
.singleton(account
.getSelfAddress()));
537 g
= getGroupForSending(groupId
);
544 if (members
!= null) {
545 final Set
<String
> newE164Members
= new HashSet
<>();
546 for (SignalServiceAddress member
: members
) {
547 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
550 newE164Members
.add(member
.getNumber().get());
553 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
554 if (contacts
.size() != newE164Members
.size()) {
555 // Some of the new members are not registered on Signal
556 for (ContactTokenDetails contact
: contacts
) {
557 newE164Members
.remove(contact
.getNumber());
559 System
.err
.println("Failed to add members " + Util
.join(", ", newE164Members
) + " to group: Not registered on Signal");
560 System
.err
.println("Aborting…");
564 g
.addMembers(members
);
567 if (avatarFile
!= null) {
568 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
569 File aFile
= getGroupAvatarFile(g
.groupId
);
570 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
573 account
.getGroupStore().updateGroup(g
);
575 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
577 sendMessageLegacy(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
581 void sendUpdateGroupMessage(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
582 if (groupId
== null) {
585 GroupInfo g
= getGroupForSending(groupId
);
587 if (!g
.isMember(recipient
)) {
591 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
593 // Send group message only to the recipient who requested it
594 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
597 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfo g
) throws AttachmentInvalidException
{
598 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
601 .withMembers(new ArrayList
<>(g
.getMembers()));
603 File aFile
= getGroupAvatarFile(g
.groupId
);
604 if (aFile
.exists()) {
606 group
.withAvatar(Utils
.createAttachment(aFile
));
607 } catch (IOException e
) {
608 throw new AttachmentInvalidException(aFile
.toString(), e
);
612 return SignalServiceDataMessage
.newBuilder()
613 .asGroupMessage(group
.build())
614 .withExpiration(g
.messageExpirationTime
);
617 void sendGroupInfoRequest(byte[] groupId
, SignalServiceAddress recipient
) throws IOException
, EncapsulatedExceptions
{
618 if (groupId
== null) {
622 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
625 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
626 .asGroupMessage(group
.build());
628 // Send group info request message to the recipient who sent us a message with this groupId
629 sendMessageLegacy(messageBuilder
, Collections
.singleton(recipient
));
632 void sendReceipt(SignalServiceAddress remoteAddress
, long messageId
) throws IOException
, UntrustedIdentityException
{
633 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
634 Collections
.singletonList(messageId
),
635 System
.currentTimeMillis());
637 getMessageSender().sendReceipt(remoteAddress
, getAccessFor(remoteAddress
), receiptMessage
);
640 public long sendMessage(String messageText
, List
<String
> attachments
,
641 List
<String
> recipients
)
642 throws IOException
, EncapsulatedExceptions
, AttachmentInvalidException
, InvalidNumberException
{
643 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().withBody(messageText
);
644 if (attachments
!= null) {
645 List
<SignalServiceAttachment
> attachmentStreams
= Utils
.getSignalServiceAttachments(attachments
);
647 // Upload attachments here, so we only upload once even for multiple recipients
648 SignalServiceMessageSender messageSender
= getMessageSender();
649 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
650 for (SignalServiceAttachment attachment
: attachmentStreams
) {
651 if (attachment
.isStream()) {
652 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
653 } else if (attachment
.isPointer()) {
654 attachmentPointers
.add(attachment
.asPointer());
658 messageBuilder
.withAttachments(attachmentPointers
);
660 return sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
663 public void sendMessageReaction(String emoji
, boolean remove
, String targetAuthor
,
664 long targetSentTimestamp
, List
<String
> recipients
)
665 throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
666 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
, remove
, canonicalizeAndResolveSignalServiceAddress(targetAuthor
), targetSentTimestamp
);
667 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
668 .withReaction(reaction
);
669 sendMessageLegacy(messageBuilder
, getSignalServiceAddresses(recipients
));
672 public void sendEndSessionMessage(List
<String
> recipients
) throws IOException
, EncapsulatedExceptions
, InvalidNumberException
{
673 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
674 .asEndSessionMessage();
676 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
678 sendMessageLegacy(messageBuilder
, signalServiceAddresses
);
679 } catch (Exception e
) {
680 for (SignalServiceAddress address
: signalServiceAddresses
) {
681 handleEndSession(address
);
688 public String
getContactName(String number
) throws InvalidNumberException
{
689 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
690 if (contact
== null) {
697 public void setContactName(String number
, String name
) throws InvalidNumberException
{
698 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
699 ContactInfo contact
= account
.getContactStore().getContact(address
);
700 if (contact
== null) {
701 contact
= new ContactInfo(address
);
702 System
.err
.println("Add contact " + contact
.number
+ " named " + name
);
704 System
.err
.println("Updating contact " + contact
.number
+ " name " + contact
.name
+ " -> " + name
);
707 account
.getContactStore().updateContact(contact
);
711 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
712 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
715 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
716 ContactInfo contact
= account
.getContactStore().getContact(address
);
717 if (contact
== null) {
718 contact
= new ContactInfo(address
);
719 System
.err
.println("Adding and " + (blocked ?
"blocking" : "unblocking") + " contact " + address
.getNumber().orNull());
721 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " contact " + address
.getNumber().orNull());
723 contact
.blocked
= blocked
;
724 account
.getContactStore().updateContact(contact
);
728 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) throws GroupNotFoundException
{
729 GroupInfo group
= getGroup(groupId
);
731 throw new GroupNotFoundException(groupId
);
733 System
.err
.println((blocked ?
"Blocking" : "Unblocking") + " group " + Base64
.encodeBytes(groupId
));
734 group
.blocked
= blocked
;
735 account
.getGroupStore().updateGroup(group
);
740 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) throws IOException
, EncapsulatedExceptions
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
741 if (groupId
.length
== 0) {
744 if (name
.isEmpty()) {
747 if (members
.isEmpty()) {
750 if (avatar
.isEmpty()) {
753 return sendUpdateGroupMessage(groupId
, name
, members
== null ?
null : getSignalServiceAddresses(members
), avatar
);
757 * Change the expiration timer for a contact
759 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
760 ContactInfo contact
= account
.getContactStore().getContact(address
);
761 contact
.messageExpirationTime
= messageExpirationTimer
;
762 account
.getContactStore().updateContact(contact
);
763 sendExpirationTimerUpdate(address
);
767 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
768 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
769 .asExpirationUpdate();
770 sendMessage(messageBuilder
, Collections
.singleton(address
));
774 * Change the expiration timer for a contact
776 public void setExpirationTimer(String number
, int messageExpirationTimer
) throws IOException
, InvalidNumberException
{
777 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
778 setExpirationTimer(address
, messageExpirationTimer
);
782 * Change the expiration timer for a group
784 public void setExpirationTimer(byte[] groupId
, int messageExpirationTimer
) {
785 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
786 g
.messageExpirationTime
= messageExpirationTimer
;
787 account
.getGroupStore().updateGroup(g
);
791 * Upload the sticker pack from path.
793 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
794 * @return if successful, returns the URL to install the sticker pack in the signal app
796 public String
uploadStickerPack(String path
) throws IOException
, StickerPackInvalidException
{
797 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
799 SignalServiceMessageSender messageSender
= getMessageSender();
801 byte[] packKey
= KeyUtils
.createStickerUploadKey();
802 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
805 return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder
.encode(packId
, "utf-8") + "&pack_key=" + URLEncoder
.encode(Hex
.toStringCondensed(packKey
), "utf-8"))
807 } catch (URISyntaxException e
) {
808 throw new AssertionError(e
);
812 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(final String path
) throws IOException
, StickerPackInvalidException
{
814 String rootPath
= null;
816 final File file
= new File(path
);
817 if (file
.getName().endsWith(".zip")) {
818 zip
= new ZipFile(file
);
819 } else if (file
.getName().equals("manifest.json")) {
820 rootPath
= file
.getParent();
822 throw new StickerPackInvalidException("Could not find manifest.json");
825 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
827 if (pack
.stickers
== null) {
828 throw new StickerPackInvalidException("Must set a 'stickers' field.");
831 if (pack
.stickers
.isEmpty()) {
832 throw new StickerPackInvalidException("Must include stickers.");
835 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
836 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
837 if (sticker
.file
== null) {
838 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
841 Pair
<InputStream
, Long
> data
;
843 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
844 } catch (IOException ignored
) {
845 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
848 StickerInfo stickerInfo
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(sticker
.emoji
).or(""));
849 stickers
.add(stickerInfo
);
852 StickerInfo cover
= null;
853 if (pack
.cover
!= null) {
854 if (pack
.cover
.file
== null) {
855 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
858 Pair
<InputStream
, Long
> data
;
860 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
861 } catch (IOException ignored
) {
862 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
865 cover
= new StickerInfo(data
.first(), data
.second(), Optional
.fromNullable(pack
.cover
.emoji
).or(""));
868 return new SignalServiceStickerManifestUpload(
875 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
876 InputStream inputStream
;
878 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
880 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
882 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
885 private static Pair
<InputStream
, Long
> getInputStreamAndLength(final String rootPath
, final ZipFile zip
, final String subfile
) throws IOException
{
887 final ZipEntry entry
= zip
.getEntry(subfile
);
888 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
890 final File file
= new File(rootPath
, subfile
);
891 return new Pair
<>(new FileInputStream(file
), file
.length());
895 void requestSyncGroups() throws IOException
{
896 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
).build();
897 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
899 sendSyncMessage(message
);
900 } catch (UntrustedIdentityException e
) {
905 void requestSyncContacts() throws IOException
{
906 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
).build();
907 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
909 sendSyncMessage(message
);
910 } catch (UntrustedIdentityException e
) {
915 void requestSyncBlocked() throws IOException
{
916 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
).build();
917 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
919 sendSyncMessage(message
);
920 } catch (UntrustedIdentityException e
) {
925 void requestSyncConfiguration() throws IOException
{
926 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder().setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
).build();
927 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
929 sendSyncMessage(message
);
930 } catch (UntrustedIdentityException e
) {
935 private byte[] getSenderCertificate() {
936 // TODO support UUID capable sender certificates
937 // byte[] certificate = accountManager.getSenderCertificate();
940 certificate
= accountManager
.getSenderCertificateLegacy();
941 } catch (IOException e
) {
942 System
.err
.println("Failed to get sender certificate: " + e
);
945 // TODO cache for a day
949 private byte[] getSelfUnidentifiedAccessKey() {
950 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
953 private static SignalProfile
decryptProfile(SignalServiceProfile encryptedProfile
, ProfileKey profileKey
) throws IOException
{
954 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
956 return new SignalProfile(
957 encryptedProfile
.getIdentityKey(),
958 encryptedProfile
.getName() == null ?
null : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName()))),
959 encryptedProfile
.getAvatar(),
960 encryptedProfile
.getUnidentifiedAccess() == null || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess())) ?
null : encryptedProfile
.getUnidentifiedAccess(),
961 encryptedProfile
.isUnrestrictedUnidentifiedAccess()
963 } catch (InvalidCiphertextException e
) {
968 private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient
) {
969 ContactInfo contact
= account
.getContactStore().getContact(recipient
);
970 if (contact
== null || contact
.profileKey
== null) {
973 ProfileKey theirProfileKey
;
975 theirProfileKey
= new ProfileKey(Base64
.decode(contact
.profileKey
));
976 } catch (InvalidInputException
| IOException e
) {
977 throw new AssertionError(e
);
979 SignalProfile targetProfile
;
981 targetProfile
= getRecipientProfile(recipient
, Optional
.absent(), theirProfileKey
);
982 } catch (IOException e
) {
983 System
.err
.println("Failed to get recipient profile: " + e
);
987 if (targetProfile
== null || targetProfile
.getUnidentifiedAccess() == null) {
991 if (targetProfile
.isUnrestrictedUnidentifiedAccess()) {
992 return KeyUtils
.createUnrestrictedUnidentifiedAccess();
995 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
998 private Optional
<UnidentifiedAccessPair
> getAccessForSync() {
999 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1000 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1002 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1003 return Optional
.absent();
1007 return Optional
.of(new UnidentifiedAccessPair(
1008 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1009 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1011 } catch (InvalidCertificateException e
) {
1012 return Optional
.absent();
1016 private List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(Collection
<SignalServiceAddress
> recipients
) {
1017 List
<Optional
<UnidentifiedAccessPair
>> result
= new ArrayList
<>(recipients
.size());
1018 for (SignalServiceAddress recipient
: recipients
) {
1019 result
.add(getAccessFor(recipient
));
1024 private Optional
<UnidentifiedAccessPair
> getAccessFor(SignalServiceAddress recipient
) {
1025 byte[] recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
);
1026 byte[] selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey();
1027 byte[] selfUnidentifiedAccessCertificate
= getSenderCertificate();
1029 if (recipientUnidentifiedAccessKey
== null || selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
1030 return Optional
.absent();
1034 return Optional
.of(new UnidentifiedAccessPair(
1035 new UnidentifiedAccess(recipientUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
),
1036 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)
1038 } catch (InvalidCertificateException e
) {
1039 return Optional
.absent();
1043 private Optional
<UnidentifiedAccess
> getUnidentifiedAccess(SignalServiceAddress recipient
) {
1044 Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1046 if (unidentifiedAccess
.isPresent()) {
1047 return unidentifiedAccess
.get().getTargetUnidentifiedAccess();
1050 return Optional
.absent();
1053 private void sendSyncMessage(SignalServiceSyncMessage message
)
1054 throws IOException
, UntrustedIdentityException
{
1055 SignalServiceMessageSender messageSender
= getMessageSender();
1057 messageSender
.sendMessage(message
, getAccessForSync());
1058 } catch (UntrustedIdentityException e
) {
1059 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1065 * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
1067 private long sendMessageLegacy(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1068 throws EncapsulatedExceptions
, IOException
{
1069 final long timestamp
= System
.currentTimeMillis();
1070 messageBuilder
.withTimestamp(timestamp
);
1071 List
<SendMessageResult
> results
= sendMessage(messageBuilder
, recipients
);
1073 List
<UntrustedIdentityException
> untrustedIdentities
= new LinkedList
<>();
1074 List
<UnregisteredUserException
> unregisteredUsers
= new LinkedList
<>();
1075 List
<NetworkFailureException
> networkExceptions
= new LinkedList
<>();
1077 for (SendMessageResult result
: results
) {
1078 if (result
.isUnregisteredFailure()) {
1079 unregisteredUsers
.add(new UnregisteredUserException(result
.getAddress().getLegacyIdentifier(), null));
1080 } else if (result
.isNetworkFailure()) {
1081 networkExceptions
.add(new NetworkFailureException(result
.getAddress().getLegacyIdentifier(), null));
1082 } else if (result
.getIdentityFailure() != null) {
1083 untrustedIdentities
.add(new UntrustedIdentityException("Untrusted", result
.getAddress().getLegacyIdentifier(), result
.getIdentityFailure().getIdentityKey()));
1086 if (!untrustedIdentities
.isEmpty() || !unregisteredUsers
.isEmpty() || !networkExceptions
.isEmpty()) {
1087 throw new EncapsulatedExceptions(untrustedIdentities
, unregisteredUsers
, networkExceptions
);
1092 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1093 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1095 for (String number
: numbers
) {
1096 signalServiceAddresses
.add(canonicalizeAndResolveSignalServiceAddress(number
));
1098 return signalServiceAddresses
;
1101 private List
<SendMessageResult
> sendMessage(SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
)
1102 throws IOException
{
1103 if (messagePipe
== null) {
1104 messagePipe
= getMessageReceiver().createMessagePipe();
1106 if (unidentifiedMessagePipe
== null) {
1107 unidentifiedMessagePipe
= getMessageReceiver().createUnidentifiedMessagePipe();
1109 SignalServiceDataMessage message
= null;
1111 message
= messageBuilder
.build();
1112 if (message
.getGroupContext().isPresent()) {
1114 SignalServiceMessageSender messageSender
= getMessageSender();
1115 final boolean isRecipientUpdate
= false;
1116 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
), getAccessFor(recipients
), isRecipientUpdate
, message
);
1117 for (SendMessageResult r
: result
) {
1118 if (r
.getIdentityFailure() != null) {
1119 account
.getSignalProtocolStore().saveIdentity(r
.getAddress(), r
.getIdentityFailure().getIdentityKey(), TrustLevel
.UNTRUSTED
);
1123 } catch (UntrustedIdentityException e
) {
1124 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1125 return Collections
.emptyList();
1128 // Send to all individually, so sync messages are sent correctly
1129 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1130 for (SignalServiceAddress address
: recipients
) {
1131 ContactInfo contact
= account
.getContactStore().getContact(address
);
1132 if (contact
!= null) {
1133 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1134 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1136 messageBuilder
.withExpiration(0);
1137 messageBuilder
.withProfileKey(null);
1139 message
= messageBuilder
.build();
1140 if (address
.matches(account
.getSelfAddress())) {
1141 results
.add(sendSelfMessage(message
));
1143 results
.add(sendMessage(address
, message
));
1149 if (message
!= null && message
.isEndSession()) {
1150 for (SignalServiceAddress recipient
: recipients
) {
1151 handleEndSession(recipient
);
1158 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1159 SignalServiceMessageSender messageSender
= getMessageSender();
1161 SignalServiceAddress recipient
= account
.getSelfAddress();
1163 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= getAccessFor(recipient
);
1164 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1165 message
.getTimestamp(),
1167 message
.getExpiresInSeconds(),
1168 Collections
.singletonMap(recipient
, unidentifiedAccess
.isPresent()),
1170 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1173 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1174 return SendMessageResult
.success(recipient
, unidentifiedAccess
.isPresent(), false);
1175 } catch (UntrustedIdentityException e
) {
1176 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1177 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1181 private SendMessageResult
sendMessage(SignalServiceAddress address
, SignalServiceDataMessage message
) throws IOException
{
1182 SignalServiceMessageSender messageSender
= getMessageSender();
1185 return messageSender
.sendMessage(address
, getAccessFor(address
), message
);
1186 } catch (UntrustedIdentityException e
) {
1187 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()), e
.getIdentityKey(), TrustLevel
.UNTRUSTED
);
1188 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1192 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1193 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(), account
.getSignalProtocolStore(), Utils
.getCertificateValidator());
1195 return cipher
.decrypt(envelope
);
1196 } catch (ProtocolUntrustedIdentityException e
) {
1197 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1198 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
.getCause();
1199 account
.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(identityException
.getName()), identityException
.getUntrustedIdentity(), TrustLevel
.UNTRUSTED
);
1200 throw identityException
;
1202 throw new AssertionError(e
);
1206 private void handleEndSession(SignalServiceAddress source
) {
1207 account
.getSignalProtocolStore().deleteAllSessions(source
);
1210 private List
<HandleAction
> handleSignalServiceDataMessage(SignalServiceDataMessage message
, boolean isSync
, SignalServiceAddress source
, SignalServiceAddress destination
, boolean ignoreAttachments
) {
1211 List
<HandleAction
> actions
= new ArrayList
<>();
1212 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1213 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1214 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1215 switch (groupInfo
.getType()) {
1217 if (group
== null) {
1218 group
= new GroupInfo(groupInfo
.getGroupId());
1221 if (groupInfo
.getAvatar().isPresent()) {
1222 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1223 if (avatar
.isPointer()) {
1225 retrieveGroupAvatarAttachment(avatar
.asPointer(), group
.groupId
);
1226 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1227 System
.err
.println("Failed to retrieve group avatar (" + avatar
.asPointer().getRemoteId() + "): " + e
.getMessage());
1232 if (groupInfo
.getName().isPresent()) {
1233 group
.name
= groupInfo
.getName().get();
1236 if (groupInfo
.getMembers().isPresent()) {
1237 group
.addMembers(groupInfo
.getMembers().get()
1239 .map(this::resolveSignalServiceAddress
)
1240 .collect(Collectors
.toSet()));
1243 account
.getGroupStore().updateGroup(group
);
1246 if (group
== null && !isSync
) {
1247 actions
.add(new SendGroupInfoRequestAction(source
, groupInfo
.getGroupId()));
1251 if (group
!= null) {
1252 group
.removeMember(source
);
1253 account
.getGroupStore().updateGroup(group
);
1257 if (group
!= null && !isSync
) {
1258 actions
.add(new SendGroupUpdateAction(source
, group
.groupId
));
1263 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1264 if (message
.isEndSession()) {
1265 handleEndSession(conversationPartnerAddress
);
1267 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1268 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1269 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1270 GroupInfo group
= account
.getGroupStore().getGroup(groupInfo
.getGroupId());
1271 if (group
== null) {
1272 group
= new GroupInfo(groupInfo
.getGroupId());
1274 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1275 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1276 account
.getGroupStore().updateGroup(group
);
1279 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1280 if (contact
== null) {
1281 contact
= new ContactInfo(conversationPartnerAddress
);
1283 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1284 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1285 account
.getContactStore().updateContact(contact
);
1289 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1290 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1291 if (attachment
.isPointer()) {
1293 retrieveAttachment(attachment
.asPointer());
1294 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1295 System
.err
.println("Failed to retrieve attachment (" + attachment
.asPointer().getRemoteId() + "): " + e
.getMessage());
1300 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1301 if (source
.matches(account
.getSelfAddress())) {
1303 this.account
.setProfileKey(new ProfileKey(message
.getProfileKey().get()));
1304 } catch (InvalidInputException ignored
) {
1306 ContactInfo contact
= account
.getContactStore().getContact(source
);
1307 if (contact
!= null) {
1308 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1309 account
.getContactStore().updateContact(contact
);
1312 ContactInfo contact
= account
.getContactStore().getContact(source
);
1313 if (contact
== null) {
1314 contact
= new ContactInfo(source
);
1316 contact
.profileKey
= Base64
.encodeBytes(message
.getProfileKey().get());
1317 account
.getContactStore().updateContact(contact
);
1320 if (message
.getPreviews().isPresent()) {
1321 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1322 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1323 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1324 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1326 retrieveAttachment(attachment
);
1327 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1328 System
.err
.println("Failed to retrieve attachment (" + attachment
.getRemoteId() + "): " + e
.getMessage());
1336 private void retryFailedReceivedMessages(ReceiveMessageHandler handler
, boolean ignoreAttachments
) {
1337 final File cachePath
= new File(getMessageCachePath());
1338 if (!cachePath
.exists()) {
1341 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1342 if (!dir
.isDirectory()) {
1343 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1347 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1348 if (!fileEntry
.isFile()) {
1351 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1353 // Try to delete directory if empty
1358 private void retryFailedReceivedMessage(final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
) {
1359 SignalServiceEnvelope envelope
;
1361 envelope
= Utils
.loadEnvelope(fileEntry
);
1362 if (envelope
== null) {
1365 } catch (IOException e
) {
1366 e
.printStackTrace();
1369 SignalServiceContent content
= null;
1370 if (!envelope
.isReceipt()) {
1372 content
= decryptMessage(envelope
);
1373 } catch (Exception e
) {
1376 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1377 for (HandleAction action
: actions
) {
1379 action
.execute(this);
1380 } catch (Throwable e
) {
1381 e
.printStackTrace();
1386 handler
.handleMessage(envelope
, content
, null);
1388 Files
.delete(fileEntry
.toPath());
1389 } catch (IOException e
) {
1390 System
.err
.println("Failed to delete cached message file “" + fileEntry
+ "”: " + e
.getMessage());
1394 public void receiveMessages(long timeout
, TimeUnit unit
, boolean returnOnTimeout
, boolean ignoreAttachments
, ReceiveMessageHandler handler
) throws IOException
{
1395 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1396 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1398 Set
<HandleAction
> queuedActions
= null;
1400 if (messagePipe
== null) {
1401 messagePipe
= messageReceiver
.createMessagePipe();
1404 boolean hasCaughtUpWithOldMessages
= false;
1407 SignalServiceEnvelope envelope
;
1408 SignalServiceContent content
= null;
1409 Exception exception
= null;
1410 final long now
= new Date().getTime();
1412 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1413 // store message on disk, before acknowledging receipt to the server
1415 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1416 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1417 Utils
.storeEnvelope(envelope1
, cacheFile
);
1418 } catch (IOException e
) {
1419 System
.err
.println("Failed to store encrypted message in disk cache, ignoring: " + e
.getMessage());
1422 if (result
.isPresent()) {
1423 envelope
= result
.get();
1425 // Received indicator that server queue is empty
1426 hasCaughtUpWithOldMessages
= true;
1428 if (queuedActions
!= null) {
1429 for (HandleAction action
: queuedActions
) {
1431 action
.execute(this);
1432 } catch (Throwable e
) {
1433 e
.printStackTrace();
1436 queuedActions
.clear();
1437 queuedActions
= null;
1440 // Continue to wait another timeout for new messages
1443 } catch (TimeoutException e
) {
1444 if (returnOnTimeout
)
1447 } catch (InvalidVersionException e
) {
1448 System
.err
.println("Ignoring error: " + e
.getMessage());
1451 if (envelope
.hasSource()) {
1452 // Store uuid if we don't have it already
1453 SignalServiceAddress source
= envelope
.getSourceAddress();
1454 resolveSignalServiceAddress(source
);
1456 if (!envelope
.isReceipt()) {
1458 content
= decryptMessage(envelope
);
1459 } catch (Exception e
) {
1462 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1463 if (hasCaughtUpWithOldMessages
) {
1464 for (HandleAction action
: actions
) {
1466 action
.execute(this);
1467 } catch (Throwable e
) {
1468 e
.printStackTrace();
1472 if (queuedActions
== null) {
1473 queuedActions
= new HashSet
<>();
1475 queuedActions
.addAll(actions
);
1479 if (!isMessageBlocked(envelope
, content
)) {
1480 handler
.handleMessage(envelope
, content
, exception
);
1482 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
1483 File cacheFile
= null;
1485 cacheFile
= getMessageCacheFile(envelope
.getSourceE164().get(), now
, envelope
.getTimestamp());
1486 Files
.delete(cacheFile
.toPath());
1487 // Try to delete directory if empty
1488 new File(getMessageCachePath()).delete();
1489 } catch (IOException e
) {
1490 System
.err
.println("Failed to delete cached message file “" + cacheFile
+ "”: " + e
.getMessage());
1496 private boolean isMessageBlocked(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
1497 SignalServiceAddress source
;
1498 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1499 source
= envelope
.getSourceAddress();
1500 } else if (content
!= null) {
1501 source
= content
.getSender();
1505 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
1506 if (sourceContact
!= null && sourceContact
.blocked
) {
1510 if (content
!= null && content
.getDataMessage().isPresent()) {
1511 SignalServiceDataMessage message
= content
.getDataMessage().get();
1512 if (message
.getGroupContext().isPresent() && message
.getGroupContext().get().getGroupV1().isPresent()) {
1513 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1514 GroupInfo group
= getGroup(groupInfo
.getGroupId());
1515 if (groupInfo
.getType() == SignalServiceGroup
.Type
.DELIVER
&& group
!= null && group
.blocked
) {
1523 private List
<HandleAction
> handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
) {
1524 List
<HandleAction
> actions
= new ArrayList
<>();
1525 if (content
!= null) {
1526 SignalServiceAddress sender
;
1527 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
1528 sender
= envelope
.getSourceAddress();
1530 sender
= content
.getSender();
1532 // Store uuid if we don't have it already
1533 resolveSignalServiceAddress(sender
);
1535 if (content
.getDataMessage().isPresent()) {
1536 SignalServiceDataMessage message
= content
.getDataMessage().get();
1538 if (content
.isNeedsReceipt()) {
1539 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
1542 actions
.addAll(handleSignalServiceDataMessage(message
, false, sender
, account
.getSelfAddress(), ignoreAttachments
));
1544 if (content
.getSyncMessage().isPresent()) {
1545 account
.setMultiDevice(true);
1546 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
1547 if (syncMessage
.getSent().isPresent()) {
1548 SentTranscriptMessage message
= syncMessage
.getSent().get();
1549 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(), true, sender
, message
.getDestination().orNull(), ignoreAttachments
));
1551 if (syncMessage
.getRequest().isPresent()) {
1552 RequestMessage rm
= syncMessage
.getRequest().get();
1553 if (rm
.isContactsRequest()) {
1554 actions
.add(SendSyncContactsAction
.create());
1556 if (rm
.isGroupsRequest()) {
1557 actions
.add(SendSyncGroupsAction
.create());
1559 if (rm
.isBlockedListRequest()) {
1560 actions
.add(SendSyncBlockedListAction
.create());
1562 // TODO Handle rm.isConfigurationRequest();
1564 if (syncMessage
.getGroups().isPresent()) {
1565 File tmpFile
= null;
1567 tmpFile
= IOUtils
.createTempFile();
1568 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups().get().asPointer(), tmpFile
)) {
1569 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
1571 while ((g
= s
.read()) != null) {
1572 GroupInfo syncGroup
= account
.getGroupStore().getGroup(g
.getId());
1573 if (syncGroup
== null) {
1574 syncGroup
= new GroupInfo(g
.getId());
1576 if (g
.getName().isPresent()) {
1577 syncGroup
.name
= g
.getName().get();
1579 syncGroup
.addMembers(g
.getMembers()
1581 .map(this::resolveSignalServiceAddress
)
1582 .collect(Collectors
.toSet()));
1583 if (!g
.isActive()) {
1584 syncGroup
.removeMember(account
.getSelfAddress());
1586 // Add ourself to the member set as it's marked as active
1587 syncGroup
.addMembers(Collections
.singleton(account
.getSelfAddress()));
1589 syncGroup
.blocked
= g
.isBlocked();
1590 if (g
.getColor().isPresent()) {
1591 syncGroup
.color
= g
.getColor().get();
1594 if (g
.getAvatar().isPresent()) {
1595 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.groupId
);
1597 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
1598 syncGroup
.archived
= g
.isArchived();
1599 account
.getGroupStore().updateGroup(syncGroup
);
1602 } catch (Exception e
) {
1603 e
.printStackTrace();
1605 if (tmpFile
!= null) {
1607 Files
.delete(tmpFile
.toPath());
1608 } catch (IOException e
) {
1609 System
.err
.println("Failed to delete received groups temp file “" + tmpFile
+ "”: " + e
.getMessage());
1614 if (syncMessage
.getBlockedList().isPresent()) {
1615 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
1616 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
1617 setContactBlocked(resolveSignalServiceAddress(address
), true);
1619 for (byte[] groupId
: blockedListMessage
.getGroupIds()) {
1621 setGroupBlocked(groupId
, true);
1622 } catch (GroupNotFoundException e
) {
1623 System
.err
.println("BlockedListMessage contained groupID that was not found in GroupStore: " + Base64
.encodeBytes(groupId
));
1627 if (syncMessage
.getContacts().isPresent()) {
1628 File tmpFile
= null;
1630 tmpFile
= IOUtils
.createTempFile();
1631 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
1632 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream().asPointer(), tmpFile
)) {
1633 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
1634 if (contactsMessage
.isComplete()) {
1635 account
.getContactStore().clear();
1638 while ((c
= s
.read()) != null) {
1639 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
1640 account
.setProfileKey(c
.getProfileKey().get());
1642 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
1643 ContactInfo contact
= account
.getContactStore().getContact(address
);
1644 if (contact
== null) {
1645 contact
= new ContactInfo(address
);
1647 if (c
.getName().isPresent()) {
1648 contact
.name
= c
.getName().get();
1650 if (c
.getColor().isPresent()) {
1651 contact
.color
= c
.getColor().get();
1653 if (c
.getProfileKey().isPresent()) {
1654 contact
.profileKey
= Base64
.encodeBytes(c
.getProfileKey().get().serialize());
1656 if (c
.getVerified().isPresent()) {
1657 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
1658 account
.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage
.getDestination(), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1660 if (c
.getExpirationTimer().isPresent()) {
1661 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
1663 contact
.blocked
= c
.isBlocked();
1664 contact
.inboxPosition
= c
.getInboxPosition().orNull();
1665 contact
.archived
= c
.isArchived();
1666 account
.getContactStore().updateContact(contact
);
1668 if (c
.getAvatar().isPresent()) {
1669 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
1673 } catch (Exception e
) {
1674 e
.printStackTrace();
1676 if (tmpFile
!= null) {
1678 Files
.delete(tmpFile
.toPath());
1679 } catch (IOException e
) {
1680 System
.err
.println("Failed to delete received contacts temp file “" + tmpFile
+ "”: " + e
.getMessage());
1685 if (syncMessage
.getVerified().isPresent()) {
1686 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
1687 account
.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()), verifiedMessage
.getIdentityKey(), TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
1689 if (syncMessage
.getConfiguration().isPresent()) {
1697 private File
getContactAvatarFile(String number
) {
1698 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
1701 private File
retrieveContactAvatarAttachment(SignalServiceAttachment attachment
, String number
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1702 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1703 if (attachment
.isPointer()) {
1704 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1705 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
1707 SignalServiceAttachmentStream stream
= attachment
.asStream();
1708 return Utils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
1712 private File
getGroupAvatarFile(byte[] groupId
) {
1713 return new File(pathConfig
.getAvatarsPath(), "group-" + Base64
.encodeBytes(groupId
).replace("/", "_"));
1716 private File
retrieveGroupAvatarAttachment(SignalServiceAttachment attachment
, byte[] groupId
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1717 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1718 if (attachment
.isPointer()) {
1719 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
1720 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
1722 SignalServiceAttachmentStream stream
= attachment
.asStream();
1723 return Utils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
1727 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
1728 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
1731 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1732 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
1733 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
1736 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1737 if (storePreview
&& pointer
.getPreview().isPresent()) {
1738 File previewFile
= new File(outputFile
+ ".preview");
1739 try (OutputStream output
= new FileOutputStream(previewFile
)) {
1740 byte[] preview
= pointer
.getPreview().get();
1741 output
.write(preview
, 0, preview
.length
);
1742 } catch (FileNotFoundException e
) {
1743 e
.printStackTrace();
1748 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1750 File tmpFile
= IOUtils
.createTempFile();
1751 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
1752 try (OutputStream output
= new FileOutputStream(outputFile
)) {
1753 byte[] buffer
= new byte[4096];
1756 while ((read
= input
.read(buffer
)) != -1) {
1757 output
.write(buffer
, 0, read
);
1759 } catch (FileNotFoundException e
) {
1760 e
.printStackTrace();
1765 Files
.delete(tmpFile
.toPath());
1766 } catch (IOException e
) {
1767 System
.err
.println("Failed to delete received attachment temp file “" + tmpFile
+ "”: " + e
.getMessage());
1773 private InputStream
retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer
, File tmpFile
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
1774 final SignalServiceMessageReceiver messageReceiver
= getMessageReceiver();
1775 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
1778 void sendGroups() throws IOException
, UntrustedIdentityException
{
1779 File groupsFile
= IOUtils
.createTempFile();
1782 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
1783 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
1784 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1785 out
.write(new DeviceGroup(record.groupId
, Optional
.fromNullable(record.name
),
1786 new ArrayList
<>(record.getMembers()), createGroupAvatarAttachment(record.groupId
),
1787 record.isMember(account
.getSelfAddress()), Optional
.of(record.messageExpirationTime
),
1788 Optional
.fromNullable(record.color
), record.blocked
, Optional
.fromNullable(record.inboxPosition
), record.archived
));
1792 if (groupsFile
.exists() && groupsFile
.length() > 0) {
1793 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
1794 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1795 .withStream(groupsFileStream
)
1796 .withContentType("application/octet-stream")
1797 .withLength(groupsFile
.length())
1800 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
1805 Files
.delete(groupsFile
.toPath());
1806 } catch (IOException e
) {
1807 System
.err
.println("Failed to delete groups temp file “" + groupsFile
+ "”: " + e
.getMessage());
1812 public void sendContacts() throws IOException
, UntrustedIdentityException
{
1813 File contactsFile
= IOUtils
.createTempFile();
1816 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
1817 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
1818 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1819 VerifiedMessage verifiedMessage
= null;
1820 JsonIdentityKeyStore
.Identity currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
1821 if (currentIdentity
!= null) {
1822 verifiedMessage
= new VerifiedMessage(record.getAddress(), currentIdentity
.getIdentityKey(), currentIdentity
.getTrustLevel().toVerifiedState(), currentIdentity
.getDateAdded().getTime());
1825 ProfileKey profileKey
= null;
1827 profileKey
= record.profileKey
== null ?
null : new ProfileKey(Base64
.decode(record.profileKey
));
1828 } catch (InvalidInputException ignored
) {
1830 out
.write(new DeviceContact(record.getAddress(), Optional
.fromNullable(record.name
),
1831 createContactAvatarAttachment(record.number
), Optional
.fromNullable(record.color
),
1832 Optional
.fromNullable(verifiedMessage
), Optional
.fromNullable(profileKey
), record.blocked
,
1833 Optional
.of(record.messageExpirationTime
),
1834 Optional
.fromNullable(record.inboxPosition
), record.archived
));
1837 if (account
.getProfileKey() != null) {
1838 // Send our own profile key as well
1839 out
.write(new DeviceContact(account
.getSelfAddress(),
1840 Optional
.absent(), Optional
.absent(),
1841 Optional
.absent(), Optional
.absent(),
1842 Optional
.of(account
.getProfileKey()),
1843 false, Optional
.absent(), Optional
.absent(), false));
1847 if (contactsFile
.exists() && contactsFile
.length() > 0) {
1848 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
1849 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
1850 .withStream(contactsFileStream
)
1851 .withContentType("application/octet-stream")
1852 .withLength(contactsFile
.length())
1855 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
1860 Files
.delete(contactsFile
.toPath());
1861 } catch (IOException e
) {
1862 System
.err
.println("Failed to delete contacts temp file “" + contactsFile
+ "”: " + e
.getMessage());
1867 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
1868 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
1869 for (ContactInfo
record : account
.getContactStore().getContacts()) {
1870 if (record.blocked
) {
1871 addresses
.add(record.getAddress());
1874 List
<byte[]> groupIds
= new ArrayList
<>();
1875 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
1876 if (record.blocked
) {
1877 groupIds
.add(record.groupId
);
1880 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
1883 private void sendVerifiedMessage(SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
) throws IOException
, UntrustedIdentityException
{
1884 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
, identityKey
, trustLevel
.toVerifiedState(), System
.currentTimeMillis());
1885 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
1888 public List
<ContactInfo
> getContacts() {
1889 return account
.getContactStore().getContacts();
1892 public ContactInfo
getContact(String number
) {
1893 return account
.getContactStore().getContact(Util
.getSignalServiceAddressFromIdentifier(number
));
1896 public GroupInfo
getGroup(byte[] groupId
) {
1897 return account
.getGroupStore().getGroup(groupId
);
1900 public List
<JsonIdentityKeyStore
.Identity
> getIdentities() {
1901 return account
.getSignalProtocolStore().getIdentities();
1904 public List
<JsonIdentityKeyStore
.Identity
> getIdentities(String number
) throws InvalidNumberException
{
1905 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
1909 * Trust this the identity with this fingerprint
1911 * @param name username of the identity
1912 * @param fingerprint Fingerprint
1914 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
1915 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1916 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1920 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1921 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
1925 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1927 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1928 } catch (IOException
| UntrustedIdentityException e
) {
1929 e
.printStackTrace();
1938 * Trust this the identity with this safety number
1940 * @param name username of the identity
1941 * @param safetyNumber Safety number
1943 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
1944 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
1945 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1949 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1950 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
1954 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1956 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
1957 } catch (IOException
| UntrustedIdentityException e
) {
1958 e
.printStackTrace();
1967 * Trust all keys of this identity without verification
1969 * @param name username of the identity
1971 public boolean trustIdentityAllKeys(String name
) {
1972 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
1973 List
<JsonIdentityKeyStore
.Identity
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
1977 for (JsonIdentityKeyStore
.Identity id
: ids
) {
1978 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
1979 account
.getSignalProtocolStore().setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1981 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
1982 } catch (IOException
| UntrustedIdentityException e
) {
1983 e
.printStackTrace();
1991 public String
computeSafetyNumber(SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
) {
1992 return Utils
.computeSafetyNumber(account
.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress
, theirIdentityKey
);
1995 void saveAccount() {
1999 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2000 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
) ? identifier
: Util
.canonicalizeNumber(identifier
, account
.getUsername());
2001 return resolveSignalServiceAddress(canonicalizedNumber
);
2004 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2005 SignalServiceAddress address
= Util
.getSignalServiceAddressFromIdentifier(identifier
);
2007 return resolveSignalServiceAddress(address
);
2010 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2011 if (address
.matches(account
.getSelfAddress())) {
2012 return account
.getSelfAddress();
2015 return account
.getRecipientStore().resolveServiceAddress(address
);
2019 public void close() throws IOException
{
2020 if (messagePipe
!= null) {
2021 messagePipe
.shutdown();
2025 if (unidentifiedMessagePipe
!= null) {
2026 unidentifiedMessagePipe
.shutdown();
2027 unidentifiedMessagePipe
= null;
2033 public interface ReceiveMessageHandler
{
2035 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);