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
.manager
.groups
.GroupId
;
22 import org
.asamk
.signal
.manager
.groups
.GroupIdV1
;
23 import org
.asamk
.signal
.manager
.groups
.GroupIdV2
;
24 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
25 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
26 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
27 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
28 import org
.asamk
.signal
.manager
.helper
.GroupHelper
;
29 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
30 import org
.asamk
.signal
.manager
.helper
.ProfileHelper
;
31 import org
.asamk
.signal
.manager
.helper
.UnidentifiedAccessHelper
;
32 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
33 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactInfo
;
34 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfo
;
35 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
36 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
37 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfile
;
38 import org
.asamk
.signal
.manager
.storage
.profiles
.SignalProfileEntry
;
39 import org
.asamk
.signal
.manager
.storage
.protocol
.IdentityInfo
;
40 import org
.asamk
.signal
.manager
.storage
.stickers
.Sticker
;
41 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
42 import org
.asamk
.signal
.manager
.util
.IOUtils
;
43 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
44 import org
.asamk
.signal
.manager
.util
.MessageCacheUtils
;
45 import org
.asamk
.signal
.manager
.util
.Utils
;
46 import org
.signal
.libsignal
.metadata
.InvalidMetadataMessageException
;
47 import org
.signal
.libsignal
.metadata
.InvalidMetadataVersionException
;
48 import org
.signal
.libsignal
.metadata
.ProtocolDuplicateMessageException
;
49 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyException
;
50 import org
.signal
.libsignal
.metadata
.ProtocolInvalidKeyIdException
;
51 import org
.signal
.libsignal
.metadata
.ProtocolInvalidMessageException
;
52 import org
.signal
.libsignal
.metadata
.ProtocolInvalidVersionException
;
53 import org
.signal
.libsignal
.metadata
.ProtocolLegacyMessageException
;
54 import org
.signal
.libsignal
.metadata
.ProtocolNoSessionException
;
55 import org
.signal
.libsignal
.metadata
.ProtocolUntrustedIdentityException
;
56 import org
.signal
.libsignal
.metadata
.SelfSendException
;
57 import org
.signal
.libsignal
.metadata
.certificate
.CertificateValidator
;
58 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
59 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
60 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
61 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedMember
;
62 import org
.signal
.zkgroup
.InvalidInputException
;
63 import org
.signal
.zkgroup
.VerificationFailedException
;
64 import org
.signal
.zkgroup
.auth
.AuthCredentialResponse
;
65 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
66 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
67 import org
.signal
.zkgroup
.profiles
.ClientZkProfileOperations
;
68 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
69 import org
.signal
.zkgroup
.profiles
.ProfileKeyCredential
;
70 import org
.slf4j
.Logger
;
71 import org
.slf4j
.LoggerFactory
;
72 import org
.whispersystems
.libsignal
.IdentityKey
;
73 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
74 import org
.whispersystems
.libsignal
.InvalidKeyException
;
75 import org
.whispersystems
.libsignal
.InvalidMessageException
;
76 import org
.whispersystems
.libsignal
.InvalidVersionException
;
77 import org
.whispersystems
.libsignal
.ecc
.Curve
;
78 import org
.whispersystems
.libsignal
.ecc
.ECKeyPair
;
79 import org
.whispersystems
.libsignal
.ecc
.ECPublicKey
;
80 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
81 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
82 import org
.whispersystems
.libsignal
.util
.KeyHelper
;
83 import org
.whispersystems
.libsignal
.util
.Medium
;
84 import org
.whispersystems
.libsignal
.util
.Pair
;
85 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
86 import org
.whispersystems
.signalservice
.api
.KbsPinData
;
87 import org
.whispersystems
.signalservice
.api
.KeyBackupService
;
88 import org
.whispersystems
.signalservice
.api
.KeyBackupServicePinException
;
89 import org
.whispersystems
.signalservice
.api
.KeyBackupSystemNoDataException
;
90 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
91 import org
.whispersystems
.signalservice
.api
.SignalServiceMessagePipe
;
92 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageReceiver
;
93 import org
.whispersystems
.signalservice
.api
.SignalServiceMessageSender
;
94 import org
.whispersystems
.signalservice
.api
.crypto
.InvalidCiphertextException
;
95 import org
.whispersystems
.signalservice
.api
.crypto
.ProfileCipher
;
96 import org
.whispersystems
.signalservice
.api
.crypto
.SignalServiceCipher
;
97 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccessPair
;
98 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
99 import org
.whispersystems
.signalservice
.api
.groupsv2
.ClientZkOperations
;
100 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
101 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
102 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
103 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
104 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
105 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
106 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
107 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
108 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
109 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
110 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
111 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
112 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
113 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
114 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupV2
;
115 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
116 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
;
117 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStickerManifestUpload
.StickerInfo
;
118 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
119 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
120 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContact
;
121 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsInputStream
;
122 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceContactsOutputStream
;
123 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroup
;
124 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsInputStream
;
125 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceGroupsOutputStream
;
126 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
127 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.RequestMessage
;
128 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
129 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
130 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.StickerPackOperationMessage
;
131 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.VerifiedMessage
;
132 import org
.whispersystems
.signalservice
.api
.profiles
.ProfileAndCredential
;
133 import org
.whispersystems
.signalservice
.api
.profiles
.SignalServiceProfile
;
134 import org
.whispersystems
.signalservice
.api
.push
.ContactTokenDetails
;
135 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
136 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
137 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
138 import org
.whispersystems
.signalservice
.api
.util
.PhoneNumberFormatter
;
139 import org
.whispersystems
.signalservice
.api
.util
.SleepTimer
;
140 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
141 import org
.whispersystems
.signalservice
.api
.util
.UptimeSleepTimer
;
142 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
143 import org
.whispersystems
.signalservice
.internal
.configuration
.SignalServiceConfiguration
;
144 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
145 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
146 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
147 import org
.whispersystems
.signalservice
.internal
.push
.LockedException
;
148 import org
.whispersystems
.signalservice
.internal
.push
.SignalServiceProtos
;
149 import org
.whispersystems
.signalservice
.internal
.push
.UnsupportedDataMessageException
;
150 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
151 import org
.whispersystems
.signalservice
.internal
.util
.DynamicCredentialsProvider
;
152 import org
.whispersystems
.signalservice
.internal
.util
.Hex
;
153 import org
.whispersystems
.util
.Base64
;
155 import java
.io
.Closeable
;
157 import java
.io
.FileInputStream
;
158 import java
.io
.FileNotFoundException
;
159 import java
.io
.FileOutputStream
;
160 import java
.io
.IOException
;
161 import java
.io
.InputStream
;
162 import java
.io
.OutputStream
;
164 import java
.net
.URISyntaxException
;
165 import java
.net
.URLEncoder
;
166 import java
.nio
.charset
.StandardCharsets
;
167 import java
.nio
.file
.Files
;
168 import java
.nio
.file
.Paths
;
169 import java
.nio
.file
.StandardCopyOption
;
170 import java
.security
.KeyStore
;
171 import java
.security
.SignatureException
;
172 import java
.util
.ArrayList
;
173 import java
.util
.Arrays
;
174 import java
.util
.Collection
;
175 import java
.util
.Date
;
176 import java
.util
.HashMap
;
177 import java
.util
.HashSet
;
178 import java
.util
.List
;
179 import java
.util
.Locale
;
180 import java
.util
.Map
;
181 import java
.util
.Objects
;
182 import java
.util
.Set
;
183 import java
.util
.UUID
;
184 import java
.util
.concurrent
.ExecutorService
;
185 import java
.util
.concurrent
.TimeUnit
;
186 import java
.util
.concurrent
.TimeoutException
;
187 import java
.util
.stream
.Collectors
;
188 import java
.util
.zip
.ZipEntry
;
189 import java
.util
.zip
.ZipFile
;
191 import static org
.asamk
.signal
.manager
.ServiceConfig
.CDS_MRENCLAVE
;
192 import static org
.asamk
.signal
.manager
.ServiceConfig
.capabilities
;
193 import static org
.asamk
.signal
.manager
.ServiceConfig
.getIasKeyStore
;
195 public class Manager
implements Closeable
{
197 final static Logger logger
= LoggerFactory
.getLogger(Manager
.class);
199 private final SleepTimer timer
= new UptimeSleepTimer();
200 private final CertificateValidator certificateValidator
= new CertificateValidator(ServiceConfig
.getUnidentifiedSenderTrustRoot());
202 private final SignalServiceConfiguration serviceConfiguration
;
203 private final String userAgent
;
205 // TODO make configurable
206 private final boolean discoverableByPhoneNumber
= true;
207 private final boolean unrestrictedUnidentifiedAccess
= false;
209 private final SignalAccount account
;
210 private final PathConfig pathConfig
;
211 private SignalServiceAccountManager accountManager
;
212 private GroupsV2Api groupsV2Api
;
213 private final GroupsV2Operations groupsV2Operations
;
215 private SignalServiceMessageReceiver messageReceiver
= null;
216 private SignalServiceMessagePipe messagePipe
= null;
217 private SignalServiceMessagePipe unidentifiedMessagePipe
= null;
219 private final UnidentifiedAccessHelper unidentifiedAccessHelper
;
220 private final ProfileHelper profileHelper
;
221 private final GroupHelper groupHelper
;
222 private PinHelper pinHelper
;
225 SignalAccount account
,
226 PathConfig pathConfig
,
227 SignalServiceConfiguration serviceConfiguration
,
230 this.account
= account
;
231 this.pathConfig
= pathConfig
;
232 this.serviceConfiguration
= serviceConfiguration
;
233 this.userAgent
= userAgent
;
234 this.groupsV2Operations
= capabilities
.isGv2() ?
new GroupsV2Operations(ClientZkOperations
.create(
235 serviceConfiguration
)) : null;
236 createSignalServiceAccountManager();
238 this.account
.setResolver(this::resolveSignalServiceAddress
);
240 this.unidentifiedAccessHelper
= new UnidentifiedAccessHelper(account
::getProfileKey
,
241 account
.getProfileStore()::getProfileKey
,
242 this::getRecipientProfile
,
243 this::getSenderCertificate
);
244 this.profileHelper
= new ProfileHelper(account
.getProfileStore()::getProfileKey
,
245 unidentifiedAccessHelper
::getAccessFor
,
246 unidentified
-> unidentified ?
getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
247 this::getOrCreateMessageReceiver
);
248 this.groupHelper
= new GroupHelper(this::getRecipientProfileKeyCredential
,
249 this::getRecipientProfile
,
250 account
::getSelfAddress
,
253 this::getGroupAuthForToday
);
256 public String
getUsername() {
257 return account
.getUsername();
260 public SignalServiceAddress
getSelfAddress() {
261 return account
.getSelfAddress();
264 private void createSignalServiceAccountManager() {
265 this.accountManager
= new SignalServiceAccountManager(serviceConfiguration
,
266 new DynamicCredentialsProvider(account
.getUuid(),
267 account
.getUsername(),
268 account
.getPassword(),
270 account
.getDeviceId()),
274 this.groupsV2Api
= accountManager
.getGroupsV2Api();
275 this.pinHelper
= new PinHelper(createKeyBackupService());
278 private KeyBackupService
createKeyBackupService() {
279 KeyStore keyStore
= ServiceConfig
.getIasKeyStore();
281 return accountManager
.getKeyBackupService(keyStore
,
282 ServiceConfig
.KEY_BACKUP_ENCLAVE_NAME
,
283 ServiceConfig
.KEY_BACKUP_SERVICE_ID
,
284 ServiceConfig
.KEY_BACKUP_MRENCLAVE
,
288 private IdentityKeyPair
getIdentityKeyPair() {
289 return account
.getSignalProtocolStore().getIdentityKeyPair();
292 public int getDeviceId() {
293 return account
.getDeviceId();
296 private File
getMessageCachePath() {
297 return SignalAccount
.getMessageCachePath(pathConfig
.getDataPath(), account
.getUsername());
300 private File
getMessageCachePath(String sender
) {
301 if (sender
== null || sender
.isEmpty()) {
302 return getMessageCachePath();
305 return new File(getMessageCachePath(), sender
.replace("/", "_"));
308 private File
getMessageCacheFile(String sender
, long now
, long timestamp
) throws IOException
{
309 File cachePath
= getMessageCachePath(sender
);
310 IOUtils
.createPrivateDirectories(cachePath
);
311 return new File(cachePath
, now
+ "_" + timestamp
);
314 public static Manager
init(
315 String username
, File settingsPath
, SignalServiceConfiguration serviceConfiguration
, String userAgent
316 ) throws IOException
{
317 PathConfig pathConfig
= PathConfig
.createDefault(settingsPath
);
319 if (!SignalAccount
.userExists(pathConfig
.getDataPath(), username
)) {
320 IdentityKeyPair identityKey
= KeyHelper
.generateIdentityKeyPair();
321 int registrationId
= KeyHelper
.generateRegistrationId(false);
323 ProfileKey profileKey
= KeyUtils
.createProfileKey();
324 SignalAccount account
= SignalAccount
.create(pathConfig
.getDataPath(),
331 return new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
334 SignalAccount account
= SignalAccount
.load(pathConfig
.getDataPath(), username
);
336 Manager m
= new Manager(account
, pathConfig
, serviceConfiguration
, userAgent
);
338 m
.migrateLegacyConfigs();
343 private void migrateLegacyConfigs() {
344 if (account
.getProfileKey() == null && isRegistered()) {
345 // Old config file, creating new profile key
346 account
.setProfileKey(KeyUtils
.createProfileKey());
349 // Store profile keys only in profile store
350 for (ContactInfo contact
: account
.getContactStore().getContacts()) {
351 String profileKeyString
= contact
.profileKey
;
352 if (profileKeyString
== null) {
355 final ProfileKey profileKey
;
357 profileKey
= new ProfileKey(Base64
.decode(profileKeyString
));
358 } catch (InvalidInputException
| IOException e
) {
361 contact
.profileKey
= null;
362 account
.getProfileStore().storeProfileKey(contact
.getAddress(), profileKey
);
364 // Ensure our profile key is stored in profile store
365 account
.getProfileStore().storeProfileKey(getSelfAddress(), account
.getProfileKey());
368 public void checkAccountState() throws IOException
{
369 if (account
.isRegistered()) {
370 if (accountManager
.getPreKeysCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
374 if (account
.getUuid() == null) {
375 account
.setUuid(accountManager
.getOwnUuid());
378 updateAccountAttributes();
382 public boolean isRegistered() {
383 return account
.isRegistered();
386 public void register(boolean voiceVerification
, String captcha
) throws IOException
{
387 account
.setPassword(KeyUtils
.createPassword());
389 // Resetting UUID, because registering doesn't work otherwise
390 account
.setUuid(null);
391 createSignalServiceAccountManager();
393 if (voiceVerification
) {
394 accountManager
.requestVoiceVerificationCode(Locale
.getDefault(),
395 Optional
.fromNullable(captcha
),
398 accountManager
.requestSmsVerificationCode(false, Optional
.fromNullable(captcha
), Optional
.absent());
401 account
.setRegistered(false);
405 public void updateAccountAttributes() throws IOException
{
406 accountManager
.setAccountAttributes(account
.getSignalingKey(),
407 account
.getSignalProtocolStore().getLocalRegistrationId(),
409 // set legacy pin only if no KBS master key is set
410 account
.getPinMasterKey() == null ? account
.getRegistrationLockPin() : null,
411 account
.getPinMasterKey() == null ?
null : account
.getPinMasterKey().deriveRegistrationLock(),
412 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
413 unrestrictedUnidentifiedAccess
,
415 discoverableByPhoneNumber
);
418 public void setProfile(String name
, File avatar
) throws IOException
{
419 try (final StreamDetails streamDetails
= avatar
== null ?
null : Utils
.createStreamDetailsFromFile(avatar
)) {
420 accountManager
.setVersionedProfile(account
.getUuid(), account
.getProfileKey(), name
, streamDetails
);
424 public void unregister() throws IOException
{
425 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
426 // If this is the master device, other users can't send messages to this number anymore.
427 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
428 accountManager
.setGcmId(Optional
.absent());
430 account
.setRegistered(false);
434 public List
<DeviceInfo
> getLinkedDevices() throws IOException
{
435 List
<DeviceInfo
> devices
= accountManager
.getDevices();
436 account
.setMultiDevice(devices
.size() > 1);
441 public void removeLinkedDevices(int deviceId
) throws IOException
{
442 accountManager
.removeDevice(deviceId
);
443 List
<DeviceInfo
> devices
= accountManager
.getDevices();
444 account
.setMultiDevice(devices
.size() > 1);
448 public void addDeviceLink(URI linkUri
) throws IOException
, InvalidKeyException
{
449 DeviceLinkInfo info
= DeviceLinkInfo
.parseDeviceLinkUri(linkUri
);
451 addDevice(info
.deviceIdentifier
, info
.deviceKey
);
454 private void addDevice(String deviceIdentifier
, ECPublicKey deviceKey
) throws IOException
, InvalidKeyException
{
455 IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
456 String verificationCode
= accountManager
.getNewDeviceVerificationCode();
458 accountManager
.addDevice(deviceIdentifier
,
461 Optional
.of(account
.getProfileKey().serialize()),
463 account
.setMultiDevice(true);
467 private List
<PreKeyRecord
> generatePreKeys() {
468 List
<PreKeyRecord
> records
= new ArrayList
<>(ServiceConfig
.PREKEY_BATCH_SIZE
);
470 final int offset
= account
.getPreKeyIdOffset();
471 for (int i
= 0; i
< ServiceConfig
.PREKEY_BATCH_SIZE
; i
++) {
472 int preKeyId
= (offset
+ i
) % Medium
.MAX_VALUE
;
473 ECKeyPair keyPair
= Curve
.generateKeyPair();
474 PreKeyRecord
record = new PreKeyRecord(preKeyId
, keyPair
);
479 account
.addPreKeys(records
);
485 private SignedPreKeyRecord
generateSignedPreKey(IdentityKeyPair identityKeyPair
) {
487 ECKeyPair keyPair
= Curve
.generateKeyPair();
488 byte[] signature
= Curve
.calculateSignature(identityKeyPair
.getPrivateKey(),
489 keyPair
.getPublicKey().serialize());
490 SignedPreKeyRecord
record = new SignedPreKeyRecord(account
.getNextSignedPreKeyId(),
491 System
.currentTimeMillis(),
495 account
.addSignedPreKey(record);
499 } catch (InvalidKeyException e
) {
500 throw new AssertionError(e
);
504 public void verifyAccount(
505 String verificationCode
,
507 ) throws IOException
, KeyBackupSystemNoDataException
, KeyBackupServicePinException
{
508 verificationCode
= verificationCode
.replace("-", "");
509 account
.setSignalingKey(KeyUtils
.createSignalingKey());
510 VerifyAccountResponse response
;
512 response
= verifyAccountWithCode(verificationCode
, pin
, null);
513 } catch (LockedException e
) {
518 KbsPinData registrationLockData
= pinHelper
.getRegistrationLockData(pin
, e
);
519 if (registrationLockData
== null) {
523 String registrationLock
= registrationLockData
.getMasterKey().deriveRegistrationLock();
525 response
= verifyAccountWithCode(verificationCode
, null, registrationLock
);
526 } catch (LockedException _e
) {
527 throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
529 account
.setPinMasterKey(registrationLockData
.getMasterKey());
532 // TODO response.isStorageCapable()
533 //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
535 account
.setRegistered(true);
536 account
.setUuid(UuidUtil
.parseOrNull(response
.getUuid()));
537 account
.setRegistrationLockPin(pin
);
538 account
.getSignalProtocolStore()
539 .saveIdentity(account
.getSelfAddress(),
540 getIdentityKeyPair().getPublicKey(),
541 TrustLevel
.TRUSTED_VERIFIED
);
547 private VerifyAccountResponse
verifyAccountWithCode(
548 final String verificationCode
, final String legacyPin
, final String registrationLock
549 ) throws IOException
{
550 return accountManager
.verifyAccountWithCode(verificationCode
,
551 account
.getSignalingKey(),
552 account
.getSignalProtocolStore().getLocalRegistrationId(),
556 unidentifiedAccessHelper
.getSelfUnidentifiedAccessKey(),
557 unrestrictedUnidentifiedAccess
,
559 discoverableByPhoneNumber
);
562 public void setRegistrationLockPin(Optional
<String
> pin
) throws IOException
, UnauthenticatedResponseException
{
563 if (pin
.isPresent()) {
564 final MasterKey masterKey
= account
.getPinMasterKey() != null
565 ? account
.getPinMasterKey()
566 : KeyUtils
.createMasterKey();
568 pinHelper
.setRegistrationLockPin(pin
.get(), masterKey
);
570 account
.setRegistrationLockPin(pin
.get());
571 account
.setPinMasterKey(masterKey
);
573 // Remove legacy registration lock
574 accountManager
.removeRegistrationLockV1();
577 pinHelper
.removeRegistrationLockPin();
579 account
.setRegistrationLockPin(null);
580 account
.setPinMasterKey(null);
585 void refreshPreKeys() throws IOException
{
586 List
<PreKeyRecord
> oneTimePreKeys
= generatePreKeys();
587 final IdentityKeyPair identityKeyPair
= getIdentityKeyPair();
588 SignedPreKeyRecord signedPreKeyRecord
= generateSignedPreKey(identityKeyPair
);
590 accountManager
.setPreKeys(identityKeyPair
.getPublicKey(), signedPreKeyRecord
, oneTimePreKeys
);
593 private SignalServiceMessageReceiver
createMessageReceiver() {
594 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
595 serviceConfiguration
).getProfileOperations() : null;
596 return new SignalServiceMessageReceiver(serviceConfiguration
,
598 account
.getUsername(),
599 account
.getPassword(),
600 account
.getDeviceId(),
601 account
.getSignalingKey(),
605 clientZkProfileOperations
);
608 private SignalServiceMessageReceiver
getOrCreateMessageReceiver() {
609 if (messageReceiver
== null) {
610 messageReceiver
= createMessageReceiver();
612 return messageReceiver
;
615 private SignalServiceMessagePipe
getOrCreateMessagePipe() {
616 if (messagePipe
== null) {
617 messagePipe
= getOrCreateMessageReceiver().createMessagePipe();
622 private SignalServiceMessagePipe
getOrCreateUnidentifiedMessagePipe() {
623 if (unidentifiedMessagePipe
== null) {
624 unidentifiedMessagePipe
= getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
626 return unidentifiedMessagePipe
;
629 private SignalServiceMessageSender
createMessageSender() {
630 final ClientZkProfileOperations clientZkProfileOperations
= capabilities
.isGv2() ? ClientZkOperations
.create(
631 serviceConfiguration
).getProfileOperations() : null;
632 final ExecutorService executor
= null;
633 return new SignalServiceMessageSender(serviceConfiguration
,
635 account
.getUsername(),
636 account
.getPassword(),
637 account
.getDeviceId(),
638 account
.getSignalProtocolStore(),
640 account
.isMultiDevice(),
641 Optional
.fromNullable(messagePipe
),
642 Optional
.fromNullable(unidentifiedMessagePipe
),
644 clientZkProfileOperations
,
646 ServiceConfig
.MAX_ENVELOPE_SIZE
);
649 private SignalServiceProfile
getEncryptedRecipientProfile(SignalServiceAddress address
) throws IOException
{
650 return profileHelper
.retrieveProfileSync(address
, SignalServiceProfile
.RequestType
.PROFILE
).getProfile();
653 private SignalProfile
getRecipientProfile(
654 SignalServiceAddress address
656 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
657 if (profileEntry
== null) {
660 long now
= new Date().getTime();
661 // Profiles are cache for 24h before retrieving them again
662 if (!profileEntry
.isRequestPending() && (
663 profileEntry
.getProfile() == null || now
- profileEntry
.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
665 ProfileKey profileKey
= profileEntry
.getProfileKey();
666 profileEntry
.setRequestPending(true);
667 SignalProfile profile
;
669 profile
= retrieveRecipientProfile(address
, profileKey
);
670 } catch (IOException e
) {
671 logger
.warn("Failed to retrieve profile, ignoring: {}", e
.getMessage());
672 profileEntry
.setRequestPending(false);
675 profileEntry
.setRequestPending(false);
676 account
.getProfileStore()
677 .updateProfile(address
, profileKey
, now
, profile
, profileEntry
.getProfileKeyCredential());
680 return profileEntry
.getProfile();
683 private ProfileKeyCredential
getRecipientProfileKeyCredential(SignalServiceAddress address
) {
684 SignalProfileEntry profileEntry
= account
.getProfileStore().getProfileEntry(address
);
685 if (profileEntry
== null) {
688 if (profileEntry
.getProfileKeyCredential() == null) {
689 ProfileAndCredential profileAndCredential
;
691 profileAndCredential
= profileHelper
.retrieveProfileSync(address
,
692 SignalServiceProfile
.RequestType
.PROFILE_AND_CREDENTIAL
);
693 } catch (IOException e
) {
694 logger
.warn("Failed to retrieve profile key credential, ignoring: {}", e
.getMessage());
698 long now
= new Date().getTime();
699 final ProfileKeyCredential profileKeyCredential
= profileAndCredential
.getProfileKeyCredential().orNull();
700 final SignalProfile profile
= decryptProfile(address
,
701 profileEntry
.getProfileKey(),
702 profileAndCredential
.getProfile());
703 account
.getProfileStore()
704 .updateProfile(address
, profileEntry
.getProfileKey(), now
, profile
, profileKeyCredential
);
705 return profileKeyCredential
;
707 return profileEntry
.getProfileKeyCredential();
710 private SignalProfile
retrieveRecipientProfile(
711 SignalServiceAddress address
, ProfileKey profileKey
712 ) throws IOException
{
713 final SignalServiceProfile encryptedProfile
= getEncryptedRecipientProfile(address
);
715 return decryptProfile(address
, profileKey
, encryptedProfile
);
718 private SignalProfile
decryptProfile(
719 final SignalServiceAddress address
, final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
721 File avatarFile
= null;
723 avatarFile
= encryptedProfile
.getAvatar() == null
725 : retrieveProfileAvatar(address
, encryptedProfile
.getAvatar(), profileKey
);
726 } catch (Throwable e
) {
727 logger
.warn("Failed to retrieve profile avatar, ignoring: {}", e
.getMessage());
730 ProfileCipher profileCipher
= new ProfileCipher(profileKey
);
734 name
= encryptedProfile
.getName() == null
736 : new String(profileCipher
.decryptName(Base64
.decode(encryptedProfile
.getName())));
737 } catch (IOException e
) {
740 String unidentifiedAccess
;
742 unidentifiedAccess
= encryptedProfile
.getUnidentifiedAccess() == null
743 || !profileCipher
.verifyUnidentifiedAccess(Base64
.decode(encryptedProfile
.getUnidentifiedAccess()))
745 : encryptedProfile
.getUnidentifiedAccess();
746 } catch (IOException e
) {
747 unidentifiedAccess
= null;
749 return new SignalProfile(encryptedProfile
.getIdentityKey(),
753 encryptedProfile
.isUnrestrictedUnidentifiedAccess(),
754 encryptedProfile
.getCapabilities());
755 } catch (InvalidCiphertextException e
) {
760 private Optional
<SignalServiceAttachmentStream
> createGroupAvatarAttachment(GroupId groupId
) throws IOException
{
761 File file
= getGroupAvatarFile(groupId
);
762 if (!file
.exists()) {
763 return Optional
.absent();
766 return Optional
.of(AttachmentUtils
.createAttachment(file
));
769 private Optional
<SignalServiceAttachmentStream
> createContactAvatarAttachment(String number
) throws IOException
{
770 File file
= getContactAvatarFile(number
);
771 if (!file
.exists()) {
772 return Optional
.absent();
775 return Optional
.of(AttachmentUtils
.createAttachment(file
));
778 private GroupInfo
getGroupForSending(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
779 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
781 throw new GroupNotFoundException(groupId
);
783 if (!g
.isMember(account
.getSelfAddress())) {
784 throw new NotAGroupMemberException(groupId
, g
.getTitle());
789 private GroupInfo
getGroupForUpdating(GroupId groupId
) throws GroupNotFoundException
, NotAGroupMemberException
{
790 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
792 throw new GroupNotFoundException(groupId
);
794 if (!g
.isMember(account
.getSelfAddress()) && !g
.isPendingMember(account
.getSelfAddress())) {
795 throw new NotAGroupMemberException(groupId
, g
.getTitle());
800 public List
<GroupInfo
> getGroups() {
801 return account
.getGroupStore().getGroups();
804 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
805 SignalServiceDataMessage
.Builder messageBuilder
, GroupId groupId
806 ) throws IOException
, GroupNotFoundException
, NotAGroupMemberException
{
807 final GroupInfo g
= getGroupForSending(groupId
);
809 GroupUtils
.setGroupContext(messageBuilder
, g
);
810 messageBuilder
.withExpiration(g
.getMessageExpirationTime());
812 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
815 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessage(
816 String messageText
, List
<String
> attachments
, GroupId groupId
817 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
818 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
819 .withBody(messageText
);
820 if (attachments
!= null) {
821 messageBuilder
.withAttachments(AttachmentUtils
.getSignalServiceAttachments(attachments
));
824 return sendGroupMessage(messageBuilder
, groupId
);
827 public Pair
<Long
, List
<SendMessageResult
>> sendGroupMessageReaction(
828 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, GroupId groupId
829 ) throws IOException
, InvalidNumberException
, NotAGroupMemberException
, GroupNotFoundException
{
830 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
832 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
833 targetSentTimestamp
);
834 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
835 .withReaction(reaction
);
837 return sendGroupMessage(messageBuilder
, groupId
);
840 public Pair
<Long
, List
<SendMessageResult
>> sendQuitGroupMessage(GroupId groupId
) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
{
842 SignalServiceDataMessage
.Builder messageBuilder
;
844 final GroupInfo g
= getGroupForUpdating(groupId
);
845 if (g
instanceof GroupInfoV1
) {
846 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
847 SignalServiceGroup group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.QUIT
)
848 .withId(groupId
.serialize())
850 messageBuilder
= SignalServiceDataMessage
.newBuilder().asGroupMessage(group
);
851 groupInfoV1
.removeMember(account
.getSelfAddress());
852 account
.getGroupStore().updateGroup(groupInfoV1
);
854 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) g
;
855 final Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.leaveGroup(groupInfoV2
);
856 groupInfoV2
.setGroup(groupGroupChangePair
.first());
857 messageBuilder
= getGroupUpdateMessageBuilder(groupInfoV2
, groupGroupChangePair
.second().toByteArray());
858 account
.getGroupStore().updateGroup(groupInfoV2
);
861 return sendMessage(messageBuilder
, g
.getMembersWithout(account
.getSelfAddress()));
864 private Pair
<GroupId
, List
<SendMessageResult
>> sendUpdateGroupMessage(
865 GroupId groupId
, String name
, Collection
<SignalServiceAddress
> members
, String avatarFile
866 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
{
868 SignalServiceDataMessage
.Builder messageBuilder
;
869 if (groupId
== null) {
871 GroupInfoV2 gv2
= groupHelper
.createGroupV2(name
, members
, avatarFile
);
873 GroupInfoV1 gv1
= new GroupInfoV1(GroupIdV1
.createRandom());
874 gv1
.addMembers(List
.of(account
.getSelfAddress()));
875 updateGroupV1(gv1
, name
, members
, avatarFile
);
876 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
879 messageBuilder
= getGroupUpdateMessageBuilder(gv2
, null);
883 GroupInfo group
= getGroupForUpdating(groupId
);
884 if (group
instanceof GroupInfoV2
) {
885 final GroupInfoV2 groupInfoV2
= (GroupInfoV2
) group
;
887 Pair
<Long
, List
<SendMessageResult
>> result
= null;
888 if (groupInfoV2
.isPendingMember(getSelfAddress())) {
889 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.acceptInvite(groupInfoV2
);
890 result
= sendUpdateGroupMessage(groupInfoV2
,
891 groupGroupChangePair
.first(),
892 groupGroupChangePair
.second());
895 if (members
!= null) {
896 final Set
<SignalServiceAddress
> newMembers
= new HashSet
<>(members
);
897 newMembers
.removeAll(group
.getMembers()
899 .map(this::resolveSignalServiceAddress
)
900 .collect(Collectors
.toSet()));
901 if (newMembers
.size() > 0) {
902 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
904 result
= sendUpdateGroupMessage(groupInfoV2
,
905 groupGroupChangePair
.first(),
906 groupGroupChangePair
.second());
909 if (result
== null || name
!= null || avatarFile
!= null) {
910 Pair
<DecryptedGroup
, GroupChange
> groupGroupChangePair
= groupHelper
.updateGroupV2(groupInfoV2
,
913 result
= sendUpdateGroupMessage(groupInfoV2
,
914 groupGroupChangePair
.first(),
915 groupGroupChangePair
.second());
918 return new Pair
<>(group
.getGroupId(), result
.second());
920 GroupInfoV1 gv1
= (GroupInfoV1
) group
;
921 updateGroupV1(gv1
, name
, members
, avatarFile
);
922 messageBuilder
= getGroupUpdateMessageBuilder(gv1
);
927 account
.getGroupStore().updateGroup(g
);
929 final Pair
<Long
, List
<SendMessageResult
>> result
= sendMessage(messageBuilder
,
930 g
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
931 return new Pair
<>(g
.getGroupId(), result
.second());
934 public Pair
<GroupId
, List
<SendMessageResult
>> joinGroup(
935 GroupInviteLinkUrl inviteLinkUrl
936 ) throws IOException
, GroupLinkNotActiveException
{
937 return sendJoinGroupMessage(inviteLinkUrl
);
940 private Pair
<GroupId
, List
<SendMessageResult
>> sendJoinGroupMessage(
941 GroupInviteLinkUrl inviteLinkUrl
942 ) throws IOException
, GroupLinkNotActiveException
{
943 final DecryptedGroupJoinInfo groupJoinInfo
= groupHelper
.getDecryptedGroupJoinInfo(inviteLinkUrl
.getGroupMasterKey(),
944 inviteLinkUrl
.getPassword());
945 final GroupChange groupChange
= groupHelper
.joinGroup(inviteLinkUrl
.getGroupMasterKey(),
946 inviteLinkUrl
.getPassword(),
948 final GroupInfoV2 group
= getOrMigrateGroup(inviteLinkUrl
.getGroupMasterKey(),
949 groupJoinInfo
.getRevision() + 1,
950 groupChange
.toByteArray());
952 if (group
.getGroup() == null) {
953 // Only requested member, can't send update to group members
954 return new Pair
<>(group
.getGroupId(), List
.of());
957 final Pair
<Long
, List
<SendMessageResult
>> result
= sendUpdateGroupMessage(group
, group
.getGroup(), groupChange
);
959 return new Pair
<>(group
.getGroupId(), result
.second());
962 private Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
963 GroupInfoV2 group
, DecryptedGroup newDecryptedGroup
, GroupChange groupChange
964 ) throws IOException
{
965 group
.setGroup(newDecryptedGroup
);
966 final SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(group
,
967 groupChange
.toByteArray());
968 account
.getGroupStore().updateGroup(group
);
969 return sendMessage(messageBuilder
, group
.getMembersIncludingPendingWithout(account
.getSelfAddress()));
972 private void updateGroupV1(
975 final Collection
<SignalServiceAddress
> members
,
976 final String avatarFile
977 ) throws IOException
{
982 if (members
!= null) {
983 final Set
<String
> newE164Members
= new HashSet
<>();
984 for (SignalServiceAddress member
: members
) {
985 if (g
.isMember(member
) || !member
.getNumber().isPresent()) {
988 newE164Members
.add(member
.getNumber().get());
991 final List
<ContactTokenDetails
> contacts
= accountManager
.getContacts(newE164Members
);
992 if (contacts
.size() != newE164Members
.size()) {
993 // Some of the new members are not registered on Signal
994 for (ContactTokenDetails contact
: contacts
) {
995 newE164Members
.remove(contact
.getNumber());
997 throw new IOException("Failed to add members "
998 + String
.join(", ", newE164Members
)
999 + " to group: Not registered on Signal");
1002 g
.addMembers(members
);
1005 if (avatarFile
!= null) {
1006 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
1007 File aFile
= getGroupAvatarFile(g
.getGroupId());
1008 Files
.copy(Paths
.get(avatarFile
), aFile
.toPath(), StandardCopyOption
.REPLACE_EXISTING
);
1012 Pair
<Long
, List
<SendMessageResult
>> sendUpdateGroupMessage(
1013 GroupIdV1 groupId
, SignalServiceAddress recipient
1014 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, AttachmentInvalidException
{
1016 GroupInfo group
= getGroupForSending(groupId
);
1017 if (!(group
instanceof GroupInfoV1
)) {
1018 throw new RuntimeException("Received an invalid group request for a v2 group!");
1020 g
= (GroupInfoV1
) group
;
1022 if (!g
.isMember(recipient
)) {
1023 throw new NotAGroupMemberException(groupId
, g
.name
);
1026 SignalServiceDataMessage
.Builder messageBuilder
= getGroupUpdateMessageBuilder(g
);
1028 // Send group message only to the recipient who requested it
1029 return sendMessage(messageBuilder
, List
.of(recipient
));
1032 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV1 g
) throws AttachmentInvalidException
{
1033 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.UPDATE
)
1034 .withId(g
.getGroupId().serialize())
1036 .withMembers(new ArrayList
<>(g
.getMembers()));
1038 File aFile
= getGroupAvatarFile(g
.getGroupId());
1039 if (aFile
.exists()) {
1041 group
.withAvatar(AttachmentUtils
.createAttachment(aFile
));
1042 } catch (IOException e
) {
1043 throw new AttachmentInvalidException(aFile
.toString(), e
);
1047 return SignalServiceDataMessage
.newBuilder()
1048 .asGroupMessage(group
.build())
1049 .withExpiration(g
.getMessageExpirationTime());
1052 private SignalServiceDataMessage
.Builder
getGroupUpdateMessageBuilder(GroupInfoV2 g
, byte[] signedGroupChange
) {
1053 SignalServiceGroupV2
.Builder group
= SignalServiceGroupV2
.newBuilder(g
.getMasterKey())
1054 .withRevision(g
.getGroup().getRevision())
1055 .withSignedGroupChange(signedGroupChange
);
1056 return SignalServiceDataMessage
.newBuilder()
1057 .asGroupMessage(group
.build())
1058 .withExpiration(g
.getMessageExpirationTime());
1061 Pair
<Long
, List
<SendMessageResult
>> sendGroupInfoRequest(
1062 GroupIdV1 groupId
, SignalServiceAddress recipient
1063 ) throws IOException
{
1064 SignalServiceGroup
.Builder group
= SignalServiceGroup
.newBuilder(SignalServiceGroup
.Type
.REQUEST_INFO
)
1065 .withId(groupId
.serialize());
1067 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1068 .asGroupMessage(group
.build());
1070 // Send group info request message to the recipient who sent us a message with this groupId
1071 return sendMessage(messageBuilder
, List
.of(recipient
));
1075 SignalServiceAddress remoteAddress
, long messageId
1076 ) throws IOException
, UntrustedIdentityException
{
1077 SignalServiceReceiptMessage receiptMessage
= new SignalServiceReceiptMessage(SignalServiceReceiptMessage
.Type
.DELIVERY
,
1079 System
.currentTimeMillis());
1081 createMessageSender().sendReceipt(remoteAddress
,
1082 unidentifiedAccessHelper
.getAccessFor(remoteAddress
),
1086 public Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1087 String messageText
, List
<String
> attachments
, List
<String
> recipients
1088 ) throws IOException
, AttachmentInvalidException
, InvalidNumberException
{
1089 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1090 .withBody(messageText
);
1091 if (attachments
!= null) {
1092 List
<SignalServiceAttachment
> attachmentStreams
= AttachmentUtils
.getSignalServiceAttachments(attachments
);
1094 // Upload attachments here, so we only upload once even for multiple recipients
1095 SignalServiceMessageSender messageSender
= createMessageSender();
1096 List
<SignalServiceAttachment
> attachmentPointers
= new ArrayList
<>(attachmentStreams
.size());
1097 for (SignalServiceAttachment attachment
: attachmentStreams
) {
1098 if (attachment
.isStream()) {
1099 attachmentPointers
.add(messageSender
.uploadAttachment(attachment
.asStream()));
1100 } else if (attachment
.isPointer()) {
1101 attachmentPointers
.add(attachment
.asPointer());
1105 messageBuilder
.withAttachments(attachmentPointers
);
1107 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1110 public Pair
<Long
, List
<SendMessageResult
>> sendMessageReaction(
1111 String emoji
, boolean remove
, String targetAuthor
, long targetSentTimestamp
, List
<String
> recipients
1112 ) throws IOException
, InvalidNumberException
{
1113 SignalServiceDataMessage
.Reaction reaction
= new SignalServiceDataMessage
.Reaction(emoji
,
1115 canonicalizeAndResolveSignalServiceAddress(targetAuthor
),
1116 targetSentTimestamp
);
1117 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1118 .withReaction(reaction
);
1119 return sendMessage(messageBuilder
, getSignalServiceAddresses(recipients
));
1122 public Pair
<Long
, List
<SendMessageResult
>> sendEndSessionMessage(List
<String
> recipients
) throws IOException
, InvalidNumberException
{
1123 SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder().asEndSessionMessage();
1125 final Collection
<SignalServiceAddress
> signalServiceAddresses
= getSignalServiceAddresses(recipients
);
1127 return sendMessage(messageBuilder
, signalServiceAddresses
);
1128 } catch (Exception e
) {
1129 for (SignalServiceAddress address
: signalServiceAddresses
) {
1130 handleEndSession(address
);
1137 public String
getContactName(String number
) throws InvalidNumberException
{
1138 ContactInfo contact
= account
.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number
));
1139 if (contact
== null) {
1142 return contact
.name
;
1146 public void setContactName(String number
, String name
) throws InvalidNumberException
{
1147 final SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1148 ContactInfo contact
= account
.getContactStore().getContact(address
);
1149 if (contact
== null) {
1150 contact
= new ContactInfo(address
);
1152 contact
.name
= name
;
1153 account
.getContactStore().updateContact(contact
);
1157 public void setContactBlocked(String number
, boolean blocked
) throws InvalidNumberException
{
1158 setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number
), blocked
);
1161 private void setContactBlocked(SignalServiceAddress address
, boolean blocked
) {
1162 ContactInfo contact
= account
.getContactStore().getContact(address
);
1163 if (contact
== null) {
1164 contact
= new ContactInfo(address
);
1166 contact
.blocked
= blocked
;
1167 account
.getContactStore().updateContact(contact
);
1171 public void setGroupBlocked(final GroupId groupId
, final boolean blocked
) throws GroupNotFoundException
{
1172 GroupInfo group
= getGroup(groupId
);
1173 if (group
== null) {
1174 throw new GroupNotFoundException(groupId
);
1177 group
.setBlocked(blocked
);
1178 account
.getGroupStore().updateGroup(group
);
1182 public Pair
<GroupId
, List
<SendMessageResult
>> updateGroup(
1183 GroupId groupId
, String name
, List
<String
> members
, String avatar
1184 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, InvalidNumberException
, NotAGroupMemberException
{
1185 return sendUpdateGroupMessage(groupId
,
1187 members
== null ?
null : getSignalServiceAddresses(members
),
1192 * Change the expiration timer for a contact
1194 public void setExpirationTimer(SignalServiceAddress address
, int messageExpirationTimer
) throws IOException
{
1195 ContactInfo contact
= account
.getContactStore().getContact(address
);
1196 contact
.messageExpirationTime
= messageExpirationTimer
;
1197 account
.getContactStore().updateContact(contact
);
1198 sendExpirationTimerUpdate(address
);
1202 private void sendExpirationTimerUpdate(SignalServiceAddress address
) throws IOException
{
1203 final SignalServiceDataMessage
.Builder messageBuilder
= SignalServiceDataMessage
.newBuilder()
1204 .asExpirationUpdate();
1205 sendMessage(messageBuilder
, List
.of(address
));
1209 * Change the expiration timer for a contact
1211 public void setExpirationTimer(
1212 String number
, int messageExpirationTimer
1213 ) throws IOException
, InvalidNumberException
{
1214 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(number
);
1215 setExpirationTimer(address
, messageExpirationTimer
);
1219 * Change the expiration timer for a group
1221 public void setExpirationTimer(GroupId groupId
, int messageExpirationTimer
) {
1222 GroupInfo g
= account
.getGroupStore().getGroup(groupId
);
1223 if (g
instanceof GroupInfoV1
) {
1224 GroupInfoV1 groupInfoV1
= (GroupInfoV1
) g
;
1225 groupInfoV1
.messageExpirationTime
= messageExpirationTimer
;
1226 account
.getGroupStore().updateGroup(groupInfoV1
);
1228 throw new RuntimeException("TODO Not implemented!");
1233 * Upload the sticker pack from path.
1235 * @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
1236 * @return if successful, returns the URL to install the sticker pack in the signal app
1238 public String
uploadStickerPack(File path
) throws IOException
, StickerPackInvalidException
{
1239 SignalServiceStickerManifestUpload manifest
= getSignalServiceStickerManifestUpload(path
);
1241 SignalServiceMessageSender messageSender
= createMessageSender();
1243 byte[] packKey
= KeyUtils
.createStickerUploadKey();
1244 String packId
= messageSender
.uploadStickerManifest(manifest
, packKey
);
1246 Sticker sticker
= new Sticker(Hex
.fromStringCondensed(packId
), packKey
);
1247 account
.getStickerStore().updateSticker(sticker
);
1251 return new URI("https",
1254 "pack_id=" + URLEncoder
.encode(packId
, StandardCharsets
.UTF_8
) + "&pack_key=" + URLEncoder
.encode(
1255 Hex
.toStringCondensed(packKey
),
1256 StandardCharsets
.UTF_8
)).toString();
1257 } catch (URISyntaxException e
) {
1258 throw new AssertionError(e
);
1262 private SignalServiceStickerManifestUpload
getSignalServiceStickerManifestUpload(
1264 ) throws IOException
, StickerPackInvalidException
{
1266 String rootPath
= null;
1268 if (file
.getName().endsWith(".zip")) {
1269 zip
= new ZipFile(file
);
1270 } else if (file
.getName().equals("manifest.json")) {
1271 rootPath
= file
.getParent();
1273 throw new StickerPackInvalidException("Could not find manifest.json");
1276 JsonStickerPack pack
= parseStickerPack(rootPath
, zip
);
1278 if (pack
.stickers
== null) {
1279 throw new StickerPackInvalidException("Must set a 'stickers' field.");
1282 if (pack
.stickers
.isEmpty()) {
1283 throw new StickerPackInvalidException("Must include stickers.");
1286 List
<StickerInfo
> stickers
= new ArrayList
<>(pack
.stickers
.size());
1287 for (JsonStickerPack
.JsonSticker sticker
: pack
.stickers
) {
1288 if (sticker
.file
== null) {
1289 throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
1292 Pair
<InputStream
, Long
> data
;
1294 data
= getInputStreamAndLength(rootPath
, zip
, sticker
.file
);
1295 } catch (IOException ignored
) {
1296 throw new StickerPackInvalidException("Could not find find " + sticker
.file
);
1299 String contentType
= Utils
.getFileMimeType(new File(sticker
.file
), null);
1300 StickerInfo stickerInfo
= new StickerInfo(data
.first(),
1302 Optional
.fromNullable(sticker
.emoji
).or(""),
1304 stickers
.add(stickerInfo
);
1307 StickerInfo cover
= null;
1308 if (pack
.cover
!= null) {
1309 if (pack
.cover
.file
== null) {
1310 throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
1313 Pair
<InputStream
, Long
> data
;
1315 data
= getInputStreamAndLength(rootPath
, zip
, pack
.cover
.file
);
1316 } catch (IOException ignored
) {
1317 throw new StickerPackInvalidException("Could not find find " + pack
.cover
.file
);
1320 String contentType
= Utils
.getFileMimeType(new File(pack
.cover
.file
), null);
1321 cover
= new StickerInfo(data
.first(),
1323 Optional
.fromNullable(pack
.cover
.emoji
).or(""),
1327 return new SignalServiceStickerManifestUpload(pack
.title
, pack
.author
, cover
, stickers
);
1330 private static JsonStickerPack
parseStickerPack(String rootPath
, ZipFile zip
) throws IOException
{
1331 InputStream inputStream
;
1333 inputStream
= zip
.getInputStream(zip
.getEntry("manifest.json"));
1335 inputStream
= new FileInputStream((new File(rootPath
, "manifest.json")));
1337 return new ObjectMapper().readValue(inputStream
, JsonStickerPack
.class);
1340 private static Pair
<InputStream
, Long
> getInputStreamAndLength(
1341 final String rootPath
, final ZipFile zip
, final String subfile
1342 ) throws IOException
{
1344 final ZipEntry entry
= zip
.getEntry(subfile
);
1345 return new Pair
<>(zip
.getInputStream(entry
), entry
.getSize());
1347 final File file
= new File(rootPath
, subfile
);
1348 return new Pair
<>(new FileInputStream(file
), file
.length());
1352 void requestSyncGroups() throws IOException
{
1353 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1354 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.GROUPS
)
1356 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1358 sendSyncMessage(message
);
1359 } catch (UntrustedIdentityException e
) {
1360 e
.printStackTrace();
1364 void requestSyncContacts() throws IOException
{
1365 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1366 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONTACTS
)
1368 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1370 sendSyncMessage(message
);
1371 } catch (UntrustedIdentityException e
) {
1372 e
.printStackTrace();
1376 void requestSyncBlocked() throws IOException
{
1377 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1378 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.BLOCKED
)
1380 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1382 sendSyncMessage(message
);
1383 } catch (UntrustedIdentityException e
) {
1384 e
.printStackTrace();
1388 void requestSyncConfiguration() throws IOException
{
1389 SignalServiceProtos
.SyncMessage
.Request r
= SignalServiceProtos
.SyncMessage
.Request
.newBuilder()
1390 .setType(SignalServiceProtos
.SyncMessage
.Request
.Type
.CONFIGURATION
)
1392 SignalServiceSyncMessage message
= SignalServiceSyncMessage
.forRequest(new RequestMessage(r
));
1394 sendSyncMessage(message
);
1395 } catch (UntrustedIdentityException e
) {
1396 e
.printStackTrace();
1400 private byte[] getSenderCertificate() {
1401 // TODO support UUID capable sender certificates
1402 // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy();
1405 certificate
= accountManager
.getSenderCertificate();
1406 } catch (IOException e
) {
1407 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
1410 // TODO cache for a day
1414 private void sendSyncMessage(SignalServiceSyncMessage message
) throws IOException
, UntrustedIdentityException
{
1415 SignalServiceMessageSender messageSender
= createMessageSender();
1417 messageSender
.sendMessage(message
, unidentifiedAccessHelper
.getAccessForSync());
1418 } catch (UntrustedIdentityException e
) {
1419 account
.getSignalProtocolStore()
1420 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1422 TrustLevel
.UNTRUSTED
);
1427 private Collection
<SignalServiceAddress
> getSignalServiceAddresses(Collection
<String
> numbers
) throws InvalidNumberException
{
1428 final Set
<SignalServiceAddress
> signalServiceAddresses
= new HashSet
<>(numbers
.size());
1429 final Set
<SignalServiceAddress
> missingUuids
= new HashSet
<>();
1431 for (String number
: numbers
) {
1432 final SignalServiceAddress resolvedAddress
= canonicalizeAndResolveSignalServiceAddress(number
);
1433 if (resolvedAddress
.getUuid().isPresent()) {
1434 signalServiceAddresses
.add(resolvedAddress
);
1436 missingUuids
.add(resolvedAddress
);
1440 Map
<String
, UUID
> registeredUsers
;
1442 registeredUsers
= accountManager
.getRegisteredUsers(getIasKeyStore(),
1443 missingUuids
.stream().map(a
-> a
.getNumber().get()).collect(Collectors
.toSet()),
1445 } catch (IOException
| Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
| UnauthenticatedResponseException e
) {
1446 logger
.warn("Failed to resolve uuids from server, ignoring: {}", e
.getMessage());
1447 registeredUsers
= new HashMap
<>();
1450 for (SignalServiceAddress address
: missingUuids
) {
1451 final String number
= address
.getNumber().get();
1452 if (registeredUsers
.containsKey(number
)) {
1453 final SignalServiceAddress newAddress
= resolveSignalServiceAddress(new SignalServiceAddress(
1454 registeredUsers
.get(number
),
1456 signalServiceAddresses
.add(newAddress
);
1458 signalServiceAddresses
.add(address
);
1462 return signalServiceAddresses
;
1465 private Pair
<Long
, List
<SendMessageResult
>> sendMessage(
1466 SignalServiceDataMessage
.Builder messageBuilder
, Collection
<SignalServiceAddress
> recipients
1467 ) throws IOException
{
1468 recipients
= recipients
.stream().map(this::resolveSignalServiceAddress
).collect(Collectors
.toSet());
1469 final long timestamp
= System
.currentTimeMillis();
1470 messageBuilder
.withTimestamp(timestamp
);
1471 getOrCreateMessagePipe();
1472 getOrCreateUnidentifiedMessagePipe();
1473 SignalServiceDataMessage message
= null;
1475 message
= messageBuilder
.build();
1476 if (message
.getGroupContext().isPresent()) {
1478 SignalServiceMessageSender messageSender
= createMessageSender();
1479 final boolean isRecipientUpdate
= false;
1480 List
<SendMessageResult
> result
= messageSender
.sendMessage(new ArrayList
<>(recipients
),
1481 unidentifiedAccessHelper
.getAccessFor(recipients
),
1484 for (SendMessageResult r
: result
) {
1485 if (r
.getIdentityFailure() != null) {
1486 account
.getSignalProtocolStore()
1487 .saveIdentity(r
.getAddress(),
1488 r
.getIdentityFailure().getIdentityKey(),
1489 TrustLevel
.UNTRUSTED
);
1492 return new Pair
<>(timestamp
, result
);
1493 } catch (UntrustedIdentityException e
) {
1494 account
.getSignalProtocolStore()
1495 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1497 TrustLevel
.UNTRUSTED
);
1498 return new Pair
<>(timestamp
, List
.of());
1501 // Send to all individually, so sync messages are sent correctly
1502 List
<SendMessageResult
> results
= new ArrayList
<>(recipients
.size());
1503 for (SignalServiceAddress address
: recipients
) {
1504 ContactInfo contact
= account
.getContactStore().getContact(address
);
1505 if (contact
!= null) {
1506 messageBuilder
.withExpiration(contact
.messageExpirationTime
);
1507 messageBuilder
.withProfileKey(account
.getProfileKey().serialize());
1509 messageBuilder
.withExpiration(0);
1510 messageBuilder
.withProfileKey(null);
1512 message
= messageBuilder
.build();
1513 if (address
.matches(account
.getSelfAddress())) {
1514 results
.add(sendSelfMessage(message
));
1516 results
.add(sendMessage(address
, message
));
1519 return new Pair
<>(timestamp
, results
);
1522 if (message
!= null && message
.isEndSession()) {
1523 for (SignalServiceAddress recipient
: recipients
) {
1524 handleEndSession(recipient
);
1531 private SendMessageResult
sendSelfMessage(SignalServiceDataMessage message
) throws IOException
{
1532 SignalServiceMessageSender messageSender
= createMessageSender();
1534 SignalServiceAddress recipient
= account
.getSelfAddress();
1536 final Optional
<UnidentifiedAccessPair
> unidentifiedAccess
= unidentifiedAccessHelper
.getAccessFor(recipient
);
1537 SentTranscriptMessage transcript
= new SentTranscriptMessage(Optional
.of(recipient
),
1538 message
.getTimestamp(),
1540 message
.getExpiresInSeconds(),
1541 Map
.of(recipient
, unidentifiedAccess
.isPresent()),
1543 SignalServiceSyncMessage syncMessage
= SignalServiceSyncMessage
.forSentTranscript(transcript
);
1546 long startTime
= System
.currentTimeMillis();
1547 messageSender
.sendMessage(syncMessage
, unidentifiedAccess
);
1548 return SendMessageResult
.success(recipient
,
1549 unidentifiedAccess
.isPresent(),
1551 System
.currentTimeMillis() - startTime
);
1552 } catch (UntrustedIdentityException e
) {
1553 account
.getSignalProtocolStore()
1554 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1556 TrustLevel
.UNTRUSTED
);
1557 return SendMessageResult
.identityFailure(recipient
, e
.getIdentityKey());
1561 private SendMessageResult
sendMessage(
1562 SignalServiceAddress address
, SignalServiceDataMessage message
1563 ) throws IOException
{
1564 SignalServiceMessageSender messageSender
= createMessageSender();
1567 return messageSender
.sendMessage(address
, unidentifiedAccessHelper
.getAccessFor(address
), message
);
1568 } catch (UntrustedIdentityException e
) {
1569 account
.getSignalProtocolStore()
1570 .saveIdentity(resolveSignalServiceAddress(e
.getIdentifier()),
1572 TrustLevel
.UNTRUSTED
);
1573 return SendMessageResult
.identityFailure(address
, e
.getIdentityKey());
1577 private SignalServiceContent
decryptMessage(SignalServiceEnvelope envelope
) throws InvalidMetadataMessageException
, ProtocolInvalidMessageException
, ProtocolDuplicateMessageException
, ProtocolLegacyMessageException
, ProtocolInvalidKeyIdException
, InvalidMetadataVersionException
, ProtocolInvalidVersionException
, ProtocolNoSessionException
, ProtocolInvalidKeyException
, SelfSendException
, UnsupportedDataMessageException
, org
.whispersystems
.libsignal
.UntrustedIdentityException
{
1578 SignalServiceCipher cipher
= new SignalServiceCipher(account
.getSelfAddress(),
1579 account
.getSignalProtocolStore(),
1580 certificateValidator
);
1582 return cipher
.decrypt(envelope
);
1583 } catch (ProtocolUntrustedIdentityException e
) {
1584 if (e
.getCause() instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
) {
1585 org
.whispersystems
.libsignal
.UntrustedIdentityException identityException
= (org
.whispersystems
.libsignal
.UntrustedIdentityException
) e
1587 account
.getSignalProtocolStore()
1588 .saveIdentity(resolveSignalServiceAddress(identityException
.getName()),
1589 identityException
.getUntrustedIdentity(),
1590 TrustLevel
.UNTRUSTED
);
1591 throw identityException
;
1593 throw new AssertionError(e
);
1597 private void handleEndSession(SignalServiceAddress source
) {
1598 account
.getSignalProtocolStore().deleteAllSessions(source
);
1601 private static int currentTimeDays() {
1602 return (int) TimeUnit
.MILLISECONDS
.toDays(System
.currentTimeMillis());
1605 private GroupsV2AuthorizationString
getGroupAuthForToday(
1606 final GroupSecretParams groupSecretParams
1607 ) throws IOException
{
1608 final int today
= currentTimeDays();
1609 // Returns credentials for the next 7 days
1610 final HashMap
<Integer
, AuthCredentialResponse
> credentials
= groupsV2Api
.getCredentials(today
);
1611 // TODO cache credentials until they expire
1612 AuthCredentialResponse authCredentialResponse
= credentials
.get(today
);
1614 return groupsV2Api
.getGroupsV2AuthorizationString(account
.getUuid(),
1617 authCredentialResponse
);
1618 } catch (VerificationFailedException e
) {
1619 throw new IOException(e
);
1623 private List
<HandleAction
> handleSignalServiceDataMessage(
1624 SignalServiceDataMessage message
,
1626 SignalServiceAddress source
,
1627 SignalServiceAddress destination
,
1628 boolean ignoreAttachments
1630 List
<HandleAction
> actions
= new ArrayList
<>();
1631 if (message
.getGroupContext().isPresent()) {
1632 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1633 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1634 GroupIdV1 groupId
= GroupId
.v1(groupInfo
.getGroupId());
1635 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
1636 if (group
== null || group
instanceof GroupInfoV1
) {
1637 GroupInfoV1 groupV1
= (GroupInfoV1
) group
;
1638 switch (groupInfo
.getType()) {
1640 if (groupV1
== null) {
1641 groupV1
= new GroupInfoV1(groupId
);
1644 if (groupInfo
.getAvatar().isPresent()) {
1645 SignalServiceAttachment avatar
= groupInfo
.getAvatar().get();
1646 if (avatar
.isPointer()) {
1648 retrieveGroupAvatarAttachment(avatar
.asPointer(), groupV1
.getGroupId());
1649 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1650 logger
.warn("Failed to retrieve avatar for group {}, ignoring: {}",
1657 if (groupInfo
.getName().isPresent()) {
1658 groupV1
.name
= groupInfo
.getName().get();
1661 if (groupInfo
.getMembers().isPresent()) {
1662 groupV1
.addMembers(groupInfo
.getMembers()
1665 .map(this::resolveSignalServiceAddress
)
1666 .collect(Collectors
.toSet()));
1669 account
.getGroupStore().updateGroup(groupV1
);
1673 if (groupV1
== null && !isSync
) {
1674 actions
.add(new SendGroupInfoRequestAction(source
, groupId
));
1678 if (groupV1
!= null) {
1679 groupV1
.removeMember(source
);
1680 account
.getGroupStore().updateGroup(groupV1
);
1685 if (groupV1
!= null && !isSync
) {
1686 actions
.add(new SendGroupUpdateAction(source
, groupV1
.getGroupId()));
1691 // Received a group v1 message for a v2 group
1694 if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1695 final SignalServiceGroupV2 groupContext
= message
.getGroupContext().get().getGroupV2().get();
1696 final GroupMasterKey groupMasterKey
= groupContext
.getMasterKey();
1698 getOrMigrateGroup(groupMasterKey
,
1699 groupContext
.getRevision(),
1700 groupContext
.hasSignedGroupChange() ? groupContext
.getSignedGroupChange() : null);
1704 final SignalServiceAddress conversationPartnerAddress
= isSync ? destination
: source
;
1705 if (conversationPartnerAddress
!= null && message
.isEndSession()) {
1706 handleEndSession(conversationPartnerAddress
);
1708 if (message
.isExpirationUpdate() || message
.getBody().isPresent()) {
1709 if (message
.getGroupContext().isPresent()) {
1710 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
1711 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
1712 GroupInfoV1 group
= account
.getGroupStore().getOrCreateGroupV1(GroupId
.v1(groupInfo
.getGroupId()));
1713 if (group
!= null) {
1714 if (group
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1715 group
.messageExpirationTime
= message
.getExpiresInSeconds();
1716 account
.getGroupStore().updateGroup(group
);
1719 } else if (message
.getGroupContext().get().getGroupV2().isPresent()) {
1720 // disappearing message timer already stored in the DecryptedGroup
1722 } else if (conversationPartnerAddress
!= null) {
1723 ContactInfo contact
= account
.getContactStore().getContact(conversationPartnerAddress
);
1724 if (contact
== null) {
1725 contact
= new ContactInfo(conversationPartnerAddress
);
1727 if (contact
.messageExpirationTime
!= message
.getExpiresInSeconds()) {
1728 contact
.messageExpirationTime
= message
.getExpiresInSeconds();
1729 account
.getContactStore().updateContact(contact
);
1733 if (message
.getAttachments().isPresent() && !ignoreAttachments
) {
1734 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
1735 if (attachment
.isPointer()) {
1737 retrieveAttachment(attachment
.asPointer());
1738 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1739 logger
.warn("Failed to retrieve attachment ({}), ignoring: {}",
1740 attachment
.asPointer().getRemoteId(),
1746 if (message
.getProfileKey().isPresent() && message
.getProfileKey().get().length
== 32) {
1747 final ProfileKey profileKey
;
1749 profileKey
= new ProfileKey(message
.getProfileKey().get());
1750 } catch (InvalidInputException e
) {
1751 throw new AssertionError(e
);
1753 if (source
.matches(account
.getSelfAddress())) {
1754 this.account
.setProfileKey(profileKey
);
1756 this.account
.getProfileStore().storeProfileKey(source
, profileKey
);
1758 if (message
.getPreviews().isPresent()) {
1759 final List
<SignalServiceDataMessage
.Preview
> previews
= message
.getPreviews().get();
1760 for (SignalServiceDataMessage
.Preview preview
: previews
) {
1761 if (preview
.getImage().isPresent() && preview
.getImage().get().isPointer()) {
1762 SignalServiceAttachmentPointer attachment
= preview
.getImage().get().asPointer();
1764 retrieveAttachment(attachment
);
1765 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1766 logger
.warn("Failed to retrieve preview image ({}), ignoring: {}",
1767 attachment
.getRemoteId(),
1773 if (message
.getQuote().isPresent()) {
1774 final SignalServiceDataMessage
.Quote quote
= message
.getQuote().get();
1776 for (SignalServiceDataMessage
.Quote
.QuotedAttachment quotedAttachment
: quote
.getAttachments()) {
1777 final SignalServiceAttachment attachment
= quotedAttachment
.getThumbnail();
1778 if (attachment
!= null && attachment
.isPointer()) {
1780 retrieveAttachment(attachment
.asPointer());
1781 } catch (IOException
| InvalidMessageException
| MissingConfigurationException e
) {
1782 logger
.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
1783 attachment
.asPointer().getRemoteId(),
1789 if (message
.getSticker().isPresent()) {
1790 final SignalServiceDataMessage
.Sticker messageSticker
= message
.getSticker().get();
1791 Sticker sticker
= account
.getStickerStore().getSticker(messageSticker
.getPackId());
1792 if (sticker
== null) {
1793 sticker
= new Sticker(messageSticker
.getPackId(), messageSticker
.getPackKey());
1794 account
.getStickerStore().updateSticker(sticker
);
1800 private GroupInfoV2
getOrMigrateGroup(
1801 final GroupMasterKey groupMasterKey
, final int revision
, final byte[] signedGroupChange
1803 final GroupSecretParams groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
1805 GroupIdV2 groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
1806 GroupInfo groupInfo
= account
.getGroupStore().getGroup(groupId
);
1807 final GroupInfoV2 groupInfoV2
;
1808 if (groupInfo
instanceof GroupInfoV1
) {
1809 // Received a v2 group message for a v1 group, we need to locally migrate the group
1810 account
.getGroupStore().deleteGroup(groupInfo
.getGroupId());
1811 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1812 logger
.info("Locally migrated group {} to group v2, id: {}",
1813 groupInfo
.getGroupId().toBase64(),
1814 groupInfoV2
.getGroupId().toBase64());
1815 } else if (groupInfo
instanceof GroupInfoV2
) {
1816 groupInfoV2
= (GroupInfoV2
) groupInfo
;
1818 groupInfoV2
= new GroupInfoV2(groupId
, groupMasterKey
);
1821 if (groupInfoV2
.getGroup() == null || groupInfoV2
.getGroup().getRevision() < revision
) {
1822 DecryptedGroup group
= null;
1823 if (signedGroupChange
!= null
1824 && groupInfoV2
.getGroup() != null
1825 && groupInfoV2
.getGroup().getRevision() + 1 == revision
) {
1826 group
= groupHelper
.getUpdatedDecryptedGroup(groupInfoV2
.getGroup(), signedGroupChange
, groupMasterKey
);
1828 if (group
== null) {
1829 group
= groupHelper
.getDecryptedGroup(groupSecretParams
);
1831 if (group
!= null) {
1832 storeProfileKeysFromMembers(group
);
1833 final String avatar
= group
.getAvatar();
1834 if (avatar
!= null && !avatar
.isEmpty()) {
1836 retrieveGroupAvatar(groupId
, groupSecretParams
, avatar
);
1837 } catch (IOException e
) {
1838 logger
.warn("Failed to download group avatar, ignoring: {}", e
.getMessage());
1842 groupInfoV2
.setGroup(group
);
1843 account
.getGroupStore().updateGroup(groupInfoV2
);
1849 private void storeProfileKeysFromMembers(final DecryptedGroup group
) {
1850 for (DecryptedMember member
: group
.getMembersList()) {
1851 final SignalServiceAddress address
= resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil
.parseOrThrow(
1852 member
.getUuid().toByteArray()), null));
1854 account
.getProfileStore()
1855 .storeProfileKey(address
, new ProfileKey(member
.getProfileKey().toByteArray()));
1856 } catch (InvalidInputException ignored
) {
1861 private void retryFailedReceivedMessages(
1862 ReceiveMessageHandler handler
, boolean ignoreAttachments
1864 final File cachePath
= getMessageCachePath();
1865 if (!cachePath
.exists()) {
1868 for (final File dir
: Objects
.requireNonNull(cachePath
.listFiles())) {
1869 if (!dir
.isDirectory()) {
1870 retryFailedReceivedMessage(handler
, ignoreAttachments
, dir
);
1874 for (final File fileEntry
: Objects
.requireNonNull(dir
.listFiles())) {
1875 if (!fileEntry
.isFile()) {
1878 retryFailedReceivedMessage(handler
, ignoreAttachments
, fileEntry
);
1880 // Try to delete directory if empty
1885 private void retryFailedReceivedMessage(
1886 final ReceiveMessageHandler handler
, final boolean ignoreAttachments
, final File fileEntry
1888 SignalServiceEnvelope envelope
;
1890 envelope
= MessageCacheUtils
.loadEnvelope(fileEntry
);
1891 if (envelope
== null) {
1894 } catch (IOException e
) {
1895 e
.printStackTrace();
1898 SignalServiceContent content
= null;
1899 if (!envelope
.isReceipt()) {
1901 content
= decryptMessage(envelope
);
1902 } catch (org
.whispersystems
.libsignal
.UntrustedIdentityException e
) {
1904 } catch (Exception er
) {
1905 // All other errors are not recoverable, so delete the cached message
1907 Files
.delete(fileEntry
.toPath());
1908 } catch (IOException e
) {
1909 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1913 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
1914 for (HandleAction action
: actions
) {
1916 action
.execute(this);
1917 } catch (Throwable e
) {
1918 e
.printStackTrace();
1923 handler
.handleMessage(envelope
, content
, null);
1925 Files
.delete(fileEntry
.toPath());
1926 } catch (IOException e
) {
1927 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry
, e
.getMessage());
1931 public void receiveMessages(
1934 boolean returnOnTimeout
,
1935 boolean ignoreAttachments
,
1936 ReceiveMessageHandler handler
1937 ) throws IOException
{
1938 retryFailedReceivedMessages(handler
, ignoreAttachments
);
1940 Set
<HandleAction
> queuedActions
= null;
1942 getOrCreateMessagePipe();
1944 boolean hasCaughtUpWithOldMessages
= false;
1947 SignalServiceEnvelope envelope
;
1948 SignalServiceContent content
= null;
1949 Exception exception
= null;
1950 final long now
= new Date().getTime();
1952 Optional
<SignalServiceEnvelope
> result
= messagePipe
.readOrEmpty(timeout
, unit
, envelope1
-> {
1953 // store message on disk, before acknowledging receipt to the server
1955 String source
= envelope1
.getSourceE164().isPresent() ? envelope1
.getSourceE164().get() : "";
1956 File cacheFile
= getMessageCacheFile(source
, now
, envelope1
.getTimestamp());
1957 MessageCacheUtils
.storeEnvelope(envelope1
, cacheFile
);
1958 } catch (IOException e
) {
1959 logger
.warn("Failed to store encrypted message in disk cache, ignoring: {}", e
.getMessage());
1962 if (result
.isPresent()) {
1963 envelope
= result
.get();
1965 // Received indicator that server queue is empty
1966 hasCaughtUpWithOldMessages
= true;
1968 if (queuedActions
!= null) {
1969 for (HandleAction action
: queuedActions
) {
1971 action
.execute(this);
1972 } catch (Throwable e
) {
1973 e
.printStackTrace();
1977 queuedActions
.clear();
1978 queuedActions
= null;
1981 // Continue to wait another timeout for new messages
1984 } catch (TimeoutException e
) {
1985 if (returnOnTimeout
) return;
1987 } catch (InvalidVersionException e
) {
1988 logger
.warn("Error while receiving messages, ignoring: {}", e
.getMessage());
1992 if (envelope
.hasSource()) {
1993 // Store uuid if we don't have it already
1994 SignalServiceAddress source
= envelope
.getSourceAddress();
1995 resolveSignalServiceAddress(source
);
1997 if (!envelope
.isReceipt()) {
1999 content
= decryptMessage(envelope
);
2000 } catch (Exception e
) {
2003 List
<HandleAction
> actions
= handleMessage(envelope
, content
, ignoreAttachments
);
2004 if (hasCaughtUpWithOldMessages
) {
2005 for (HandleAction action
: actions
) {
2007 action
.execute(this);
2008 } catch (Throwable e
) {
2009 e
.printStackTrace();
2013 if (queuedActions
== null) {
2014 queuedActions
= new HashSet
<>();
2016 queuedActions
.addAll(actions
);
2020 if (!isMessageBlocked(envelope
, content
)) {
2021 handler
.handleMessage(envelope
, content
, exception
);
2023 if (!(exception
instanceof org
.whispersystems
.libsignal
.UntrustedIdentityException
)) {
2024 File cacheFile
= null;
2026 String source
= envelope
.getSourceE164().isPresent() ? envelope
.getSourceE164().get() : "";
2027 cacheFile
= getMessageCacheFile(source
, now
, envelope
.getTimestamp());
2028 Files
.delete(cacheFile
.toPath());
2029 // Try to delete directory if empty
2030 getMessageCachePath().delete();
2031 } catch (IOException e
) {
2032 logger
.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile
, e
.getMessage());
2038 private boolean isMessageBlocked(
2039 SignalServiceEnvelope envelope
, SignalServiceContent content
2041 SignalServiceAddress source
;
2042 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2043 source
= envelope
.getSourceAddress();
2044 } else if (content
!= null) {
2045 source
= content
.getSender();
2049 ContactInfo sourceContact
= account
.getContactStore().getContact(source
);
2050 if (sourceContact
!= null && sourceContact
.blocked
) {
2054 if (content
!= null && content
.getDataMessage().isPresent()) {
2055 SignalServiceDataMessage message
= content
.getDataMessage().get();
2056 if (message
.getGroupContext().isPresent()) {
2057 if (message
.getGroupContext().get().getGroupV1().isPresent()) {
2058 SignalServiceGroup groupInfo
= message
.getGroupContext().get().getGroupV1().get();
2059 if (groupInfo
.getType() != SignalServiceGroup
.Type
.DELIVER
) {
2063 GroupId groupId
= GroupUtils
.getGroupId(message
.getGroupContext().get());
2064 GroupInfo group
= account
.getGroupStore().getGroup(groupId
);
2065 if (group
!= null && group
.isBlocked()) {
2073 private List
<HandleAction
> handleMessage(
2074 SignalServiceEnvelope envelope
, SignalServiceContent content
, boolean ignoreAttachments
2076 List
<HandleAction
> actions
= new ArrayList
<>();
2077 if (content
!= null) {
2078 final SignalServiceAddress sender
;
2079 if (!envelope
.isUnidentifiedSender() && envelope
.hasSource()) {
2080 sender
= envelope
.getSourceAddress();
2082 sender
= content
.getSender();
2084 // Store uuid if we don't have it already
2085 resolveSignalServiceAddress(sender
);
2087 if (content
.getDataMessage().isPresent()) {
2088 SignalServiceDataMessage message
= content
.getDataMessage().get();
2090 if (content
.isNeedsReceipt()) {
2091 actions
.add(new SendReceiptAction(sender
, message
.getTimestamp()));
2094 actions
.addAll(handleSignalServiceDataMessage(message
,
2097 account
.getSelfAddress(),
2098 ignoreAttachments
));
2100 if (content
.getSyncMessage().isPresent()) {
2101 account
.setMultiDevice(true);
2102 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
2103 if (syncMessage
.getSent().isPresent()) {
2104 SentTranscriptMessage message
= syncMessage
.getSent().get();
2105 final SignalServiceAddress destination
= message
.getDestination().orNull();
2106 actions
.addAll(handleSignalServiceDataMessage(message
.getMessage(),
2110 ignoreAttachments
));
2112 if (syncMessage
.getRequest().isPresent()) {
2113 RequestMessage rm
= syncMessage
.getRequest().get();
2114 if (rm
.isContactsRequest()) {
2115 actions
.add(SendSyncContactsAction
.create());
2117 if (rm
.isGroupsRequest()) {
2118 actions
.add(SendSyncGroupsAction
.create());
2120 if (rm
.isBlockedListRequest()) {
2121 actions
.add(SendSyncBlockedListAction
.create());
2123 // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
2125 if (syncMessage
.getGroups().isPresent()) {
2126 File tmpFile
= null;
2128 tmpFile
= IOUtils
.createTempFile();
2129 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(syncMessage
.getGroups()
2131 .asPointer(), tmpFile
)) {
2132 DeviceGroupsInputStream s
= new DeviceGroupsInputStream(attachmentAsStream
);
2134 while ((g
= s
.read()) != null) {
2135 GroupInfoV1 syncGroup
= account
.getGroupStore()
2136 .getOrCreateGroupV1(GroupId
.v1(g
.getId()));
2137 if (syncGroup
!= null) {
2138 if (g
.getName().isPresent()) {
2139 syncGroup
.name
= g
.getName().get();
2141 syncGroup
.addMembers(g
.getMembers()
2143 .map(this::resolveSignalServiceAddress
)
2144 .collect(Collectors
.toSet()));
2145 if (!g
.isActive()) {
2146 syncGroup
.removeMember(account
.getSelfAddress());
2148 // Add ourself to the member set as it's marked as active
2149 syncGroup
.addMembers(List
.of(account
.getSelfAddress()));
2151 syncGroup
.blocked
= g
.isBlocked();
2152 if (g
.getColor().isPresent()) {
2153 syncGroup
.color
= g
.getColor().get();
2156 if (g
.getAvatar().isPresent()) {
2157 retrieveGroupAvatarAttachment(g
.getAvatar().get(), syncGroup
.getGroupId());
2159 syncGroup
.inboxPosition
= g
.getInboxPosition().orNull();
2160 syncGroup
.archived
= g
.isArchived();
2161 account
.getGroupStore().updateGroup(syncGroup
);
2165 } catch (Exception e
) {
2166 logger
.warn("Failed to handle received sync groups “{}”, ignoring: {}",
2169 e
.printStackTrace();
2171 if (tmpFile
!= null) {
2173 Files
.delete(tmpFile
.toPath());
2174 } catch (IOException e
) {
2175 logger
.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
2182 if (syncMessage
.getBlockedList().isPresent()) {
2183 final BlockedListMessage blockedListMessage
= syncMessage
.getBlockedList().get();
2184 for (SignalServiceAddress address
: blockedListMessage
.getAddresses()) {
2185 setContactBlocked(resolveSignalServiceAddress(address
), true);
2187 for (GroupId groupId
: blockedListMessage
.getGroupIds()
2189 .map(GroupId
::unknownVersion
)
2190 .collect(Collectors
.toSet())) {
2192 setGroupBlocked(groupId
, true);
2193 } catch (GroupNotFoundException e
) {
2194 logger
.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
2195 groupId
.toBase64());
2199 if (syncMessage
.getContacts().isPresent()) {
2200 File tmpFile
= null;
2202 tmpFile
= IOUtils
.createTempFile();
2203 final ContactsMessage contactsMessage
= syncMessage
.getContacts().get();
2204 try (InputStream attachmentAsStream
= retrieveAttachmentAsStream(contactsMessage
.getContactsStream()
2205 .asPointer(), tmpFile
)) {
2206 DeviceContactsInputStream s
= new DeviceContactsInputStream(attachmentAsStream
);
2207 if (contactsMessage
.isComplete()) {
2208 account
.getContactStore().clear();
2211 while ((c
= s
.read()) != null) {
2212 if (c
.getAddress().matches(account
.getSelfAddress()) && c
.getProfileKey().isPresent()) {
2213 account
.setProfileKey(c
.getProfileKey().get());
2215 final SignalServiceAddress address
= resolveSignalServiceAddress(c
.getAddress());
2216 ContactInfo contact
= account
.getContactStore().getContact(address
);
2217 if (contact
== null) {
2218 contact
= new ContactInfo(address
);
2220 if (c
.getName().isPresent()) {
2221 contact
.name
= c
.getName().get();
2223 if (c
.getColor().isPresent()) {
2224 contact
.color
= c
.getColor().get();
2226 if (c
.getProfileKey().isPresent()) {
2227 account
.getProfileStore().storeProfileKey(address
, c
.getProfileKey().get());
2229 if (c
.getVerified().isPresent()) {
2230 final VerifiedMessage verifiedMessage
= c
.getVerified().get();
2231 account
.getSignalProtocolStore()
2232 .setIdentityTrustLevel(verifiedMessage
.getDestination(),
2233 verifiedMessage
.getIdentityKey(),
2234 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2236 if (c
.getExpirationTimer().isPresent()) {
2237 contact
.messageExpirationTime
= c
.getExpirationTimer().get();
2239 contact
.blocked
= c
.isBlocked();
2240 contact
.inboxPosition
= c
.getInboxPosition().orNull();
2241 contact
.archived
= c
.isArchived();
2242 account
.getContactStore().updateContact(contact
);
2244 if (c
.getAvatar().isPresent()) {
2245 retrieveContactAvatarAttachment(c
.getAvatar().get(), contact
.number
);
2249 } catch (Exception e
) {
2250 e
.printStackTrace();
2252 if (tmpFile
!= null) {
2254 Files
.delete(tmpFile
.toPath());
2255 } catch (IOException e
) {
2256 logger
.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
2263 if (syncMessage
.getVerified().isPresent()) {
2264 final VerifiedMessage verifiedMessage
= syncMessage
.getVerified().get();
2265 account
.getSignalProtocolStore()
2266 .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage
.getDestination()),
2267 verifiedMessage
.getIdentityKey(),
2268 TrustLevel
.fromVerifiedState(verifiedMessage
.getVerified()));
2270 if (syncMessage
.getStickerPackOperations().isPresent()) {
2271 final List
<StickerPackOperationMessage
> stickerPackOperationMessages
= syncMessage
.getStickerPackOperations()
2273 for (StickerPackOperationMessage m
: stickerPackOperationMessages
) {
2274 if (!m
.getPackId().isPresent()) {
2277 Sticker sticker
= account
.getStickerStore().getSticker(m
.getPackId().get());
2278 if (sticker
== null) {
2279 if (!m
.getPackKey().isPresent()) {
2282 sticker
= new Sticker(m
.getPackId().get(), m
.getPackKey().get());
2284 sticker
.setInstalled(!m
.getType().isPresent()
2285 || m
.getType().get() == StickerPackOperationMessage
.Type
.INSTALL
);
2286 account
.getStickerStore().updateSticker(sticker
);
2289 if (syncMessage
.getConfiguration().isPresent()) {
2297 private File
getContactAvatarFile(String number
) {
2298 return new File(pathConfig
.getAvatarsPath(), "contact-" + number
);
2301 private File
retrieveContactAvatarAttachment(
2302 SignalServiceAttachment attachment
, String number
2303 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2304 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2305 if (attachment
.isPointer()) {
2306 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2307 return retrieveAttachment(pointer
, getContactAvatarFile(number
), false);
2309 SignalServiceAttachmentStream stream
= attachment
.asStream();
2310 return AttachmentUtils
.retrieveAttachment(stream
, getContactAvatarFile(number
));
2314 private File
getGroupAvatarFile(GroupId groupId
) {
2315 return new File(pathConfig
.getAvatarsPath(), "group-" + groupId
.toBase64().replace("/", "_"));
2318 private File
retrieveGroupAvatarAttachment(
2319 SignalServiceAttachment attachment
, GroupId groupId
2320 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2321 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2322 if (attachment
.isPointer()) {
2323 SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
2324 return retrieveAttachment(pointer
, getGroupAvatarFile(groupId
), false);
2326 SignalServiceAttachmentStream stream
= attachment
.asStream();
2327 return AttachmentUtils
.retrieveAttachment(stream
, getGroupAvatarFile(groupId
));
2331 private File
retrieveGroupAvatar(
2332 GroupId groupId
, GroupSecretParams groupSecretParams
, String cdnKey
2333 ) throws IOException
{
2334 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2335 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2336 File outputFile
= getGroupAvatarFile(groupId
);
2337 GroupsV2Operations
.GroupOperations groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
2339 File tmpFile
= IOUtils
.createTempFile();
2340 tmpFile
.deleteOnExit();
2341 try (InputStream input
= receiver
.retrieveGroupsV2ProfileAvatar(cdnKey
,
2343 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2344 byte[] encryptedData
= IOUtils
.readFully(input
);
2346 byte[] decryptedData
= groupOperations
.decryptAvatar(encryptedData
);
2347 try (OutputStream output
= new FileOutputStream(outputFile
)) {
2348 output
.write(decryptedData
);
2352 Files
.delete(tmpFile
.toPath());
2353 } catch (IOException e
) {
2354 logger
.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
2362 private File
getProfileAvatarFile(SignalServiceAddress address
) {
2363 return new File(pathConfig
.getAvatarsPath(), "profile-" + address
.getLegacyIdentifier());
2366 private File
retrieveProfileAvatar(
2367 SignalServiceAddress address
, String avatarPath
, ProfileKey profileKey
2368 ) throws IOException
{
2369 IOUtils
.createPrivateDirectories(pathConfig
.getAvatarsPath());
2370 SignalServiceMessageReceiver receiver
= getOrCreateMessageReceiver();
2371 File outputFile
= getProfileAvatarFile(address
);
2373 File tmpFile
= IOUtils
.createTempFile();
2374 try (InputStream input
= receiver
.retrieveProfileAvatar(avatarPath
,
2377 ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
)) {
2378 // Use larger buffer size to prevent AssertionError: Need: 12272 but only have: 8192 ...
2379 IOUtils
.copyStreamToFile(input
, outputFile
, (int) ServiceConfig
.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE
);
2382 Files
.delete(tmpFile
.toPath());
2383 } catch (IOException e
) {
2384 logger
.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
2392 public File
getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId
) {
2393 return new File(pathConfig
.getAttachmentsPath(), attachmentId
.toString());
2396 private File
retrieveAttachment(SignalServiceAttachmentPointer pointer
) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2397 IOUtils
.createPrivateDirectories(pathConfig
.getAttachmentsPath());
2398 return retrieveAttachment(pointer
, getAttachmentFile(pointer
.getRemoteId()), true);
2401 private File
retrieveAttachment(
2402 SignalServiceAttachmentPointer pointer
, File outputFile
, boolean storePreview
2403 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2404 if (storePreview
&& pointer
.getPreview().isPresent()) {
2405 File previewFile
= new File(outputFile
+ ".preview");
2406 try (OutputStream output
= new FileOutputStream(previewFile
)) {
2407 byte[] preview
= pointer
.getPreview().get();
2408 output
.write(preview
, 0, preview
.length
);
2409 } catch (FileNotFoundException e
) {
2410 e
.printStackTrace();
2415 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2417 File tmpFile
= IOUtils
.createTempFile();
2418 try (InputStream input
= messageReceiver
.retrieveAttachment(pointer
,
2420 ServiceConfig
.MAX_ATTACHMENT_SIZE
)) {
2421 IOUtils
.copyStreamToFile(input
, outputFile
);
2424 Files
.delete(tmpFile
.toPath());
2425 } catch (IOException e
) {
2426 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
2434 private InputStream
retrieveAttachmentAsStream(
2435 SignalServiceAttachmentPointer pointer
, File tmpFile
2436 ) throws IOException
, InvalidMessageException
, MissingConfigurationException
{
2437 final SignalServiceMessageReceiver messageReceiver
= getOrCreateMessageReceiver();
2438 return messageReceiver
.retrieveAttachment(pointer
, tmpFile
, ServiceConfig
.MAX_ATTACHMENT_SIZE
);
2441 void sendGroups() throws IOException
, UntrustedIdentityException
{
2442 File groupsFile
= IOUtils
.createTempFile();
2445 try (OutputStream fos
= new FileOutputStream(groupsFile
)) {
2446 DeviceGroupsOutputStream out
= new DeviceGroupsOutputStream(fos
);
2447 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2448 if (record instanceof GroupInfoV1
) {
2449 GroupInfoV1 groupInfo
= (GroupInfoV1
) record;
2450 out
.write(new DeviceGroup(groupInfo
.getGroupId().serialize(),
2451 Optional
.fromNullable(groupInfo
.name
),
2452 new ArrayList
<>(groupInfo
.getMembers()),
2453 createGroupAvatarAttachment(groupInfo
.getGroupId()),
2454 groupInfo
.isMember(account
.getSelfAddress()),
2455 Optional
.of(groupInfo
.messageExpirationTime
),
2456 Optional
.fromNullable(groupInfo
.color
),
2458 Optional
.fromNullable(groupInfo
.inboxPosition
),
2459 groupInfo
.archived
));
2464 if (groupsFile
.exists() && groupsFile
.length() > 0) {
2465 try (FileInputStream groupsFileStream
= new FileInputStream(groupsFile
)) {
2466 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2467 .withStream(groupsFileStream
)
2468 .withContentType("application/octet-stream")
2469 .withLength(groupsFile
.length())
2472 sendSyncMessage(SignalServiceSyncMessage
.forGroups(attachmentStream
));
2477 Files
.delete(groupsFile
.toPath());
2478 } catch (IOException e
) {
2479 logger
.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile
, e
.getMessage());
2484 public void sendContacts() throws IOException
, UntrustedIdentityException
{
2485 File contactsFile
= IOUtils
.createTempFile();
2488 try (OutputStream fos
= new FileOutputStream(contactsFile
)) {
2489 DeviceContactsOutputStream out
= new DeviceContactsOutputStream(fos
);
2490 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2491 VerifiedMessage verifiedMessage
= null;
2492 IdentityInfo currentIdentity
= account
.getSignalProtocolStore().getIdentity(record.getAddress());
2493 if (currentIdentity
!= null) {
2494 verifiedMessage
= new VerifiedMessage(record.getAddress(),
2495 currentIdentity
.getIdentityKey(),
2496 currentIdentity
.getTrustLevel().toVerifiedState(),
2497 currentIdentity
.getDateAdded().getTime());
2500 ProfileKey profileKey
= account
.getProfileStore().getProfileKey(record.getAddress());
2501 out
.write(new DeviceContact(record.getAddress(),
2502 Optional
.fromNullable(record.name
),
2503 createContactAvatarAttachment(record.number
),
2504 Optional
.fromNullable(record.color
),
2505 Optional
.fromNullable(verifiedMessage
),
2506 Optional
.fromNullable(profileKey
),
2508 Optional
.of(record.messageExpirationTime
),
2509 Optional
.fromNullable(record.inboxPosition
),
2513 if (account
.getProfileKey() != null) {
2514 // Send our own profile key as well
2515 out
.write(new DeviceContact(account
.getSelfAddress(),
2520 Optional
.of(account
.getProfileKey()),
2528 if (contactsFile
.exists() && contactsFile
.length() > 0) {
2529 try (FileInputStream contactsFileStream
= new FileInputStream(contactsFile
)) {
2530 SignalServiceAttachmentStream attachmentStream
= SignalServiceAttachment
.newStreamBuilder()
2531 .withStream(contactsFileStream
)
2532 .withContentType("application/octet-stream")
2533 .withLength(contactsFile
.length())
2536 sendSyncMessage(SignalServiceSyncMessage
.forContacts(new ContactsMessage(attachmentStream
, true)));
2541 Files
.delete(contactsFile
.toPath());
2542 } catch (IOException e
) {
2543 logger
.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile
, e
.getMessage());
2548 void sendBlockedList() throws IOException
, UntrustedIdentityException
{
2549 List
<SignalServiceAddress
> addresses
= new ArrayList
<>();
2550 for (ContactInfo
record : account
.getContactStore().getContacts()) {
2551 if (record.blocked
) {
2552 addresses
.add(record.getAddress());
2555 List
<byte[]> groupIds
= new ArrayList
<>();
2556 for (GroupInfo
record : account
.getGroupStore().getGroups()) {
2557 if (record.isBlocked()) {
2558 groupIds
.add(record.getGroupId().serialize());
2561 sendSyncMessage(SignalServiceSyncMessage
.forBlocked(new BlockedListMessage(addresses
, groupIds
)));
2564 private void sendVerifiedMessage(
2565 SignalServiceAddress destination
, IdentityKey identityKey
, TrustLevel trustLevel
2566 ) throws IOException
, UntrustedIdentityException
{
2567 VerifiedMessage verifiedMessage
= new VerifiedMessage(destination
,
2569 trustLevel
.toVerifiedState(),
2570 System
.currentTimeMillis());
2571 sendSyncMessage(SignalServiceSyncMessage
.forVerified(verifiedMessage
));
2574 public List
<ContactInfo
> getContacts() {
2575 return account
.getContactStore().getContacts();
2578 public ContactInfo
getContact(String number
) {
2579 return account
.getContactStore().getContact(Utils
.getSignalServiceAddressFromIdentifier(number
));
2582 public GroupInfo
getGroup(GroupId groupId
) {
2583 return account
.getGroupStore().getGroup(groupId
);
2586 public List
<IdentityInfo
> getIdentities() {
2587 return account
.getSignalProtocolStore().getIdentities();
2590 public List
<IdentityInfo
> getIdentities(String number
) throws InvalidNumberException
{
2591 return account
.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number
));
2595 * Trust this the identity with this fingerprint
2597 * @param name username of the identity
2598 * @param fingerprint Fingerprint
2600 public boolean trustIdentityVerified(String name
, byte[] fingerprint
) throws InvalidNumberException
{
2601 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2602 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2606 for (IdentityInfo id
: ids
) {
2607 if (!Arrays
.equals(id
.getIdentityKey().serialize(), fingerprint
)) {
2611 account
.getSignalProtocolStore()
2612 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2614 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2615 } catch (IOException
| UntrustedIdentityException e
) {
2616 e
.printStackTrace();
2625 * Trust this the identity with this safety number
2627 * @param name username of the identity
2628 * @param safetyNumber Safety number
2630 public boolean trustIdentityVerifiedSafetyNumber(String name
, String safetyNumber
) throws InvalidNumberException
{
2631 SignalServiceAddress address
= canonicalizeAndResolveSignalServiceAddress(name
);
2632 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2636 for (IdentityInfo id
: ids
) {
2637 if (!safetyNumber
.equals(computeSafetyNumber(address
, id
.getIdentityKey()))) {
2641 account
.getSignalProtocolStore()
2642 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2644 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_VERIFIED
);
2645 } catch (IOException
| UntrustedIdentityException e
) {
2646 e
.printStackTrace();
2655 * Trust all keys of this identity without verification
2657 * @param name username of the identity
2659 public boolean trustIdentityAllKeys(String name
) {
2660 SignalServiceAddress address
= resolveSignalServiceAddress(name
);
2661 List
<IdentityInfo
> ids
= account
.getSignalProtocolStore().getIdentities(address
);
2665 for (IdentityInfo id
: ids
) {
2666 if (id
.getTrustLevel() == TrustLevel
.UNTRUSTED
) {
2667 account
.getSignalProtocolStore()
2668 .setIdentityTrustLevel(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2670 sendVerifiedMessage(address
, id
.getIdentityKey(), TrustLevel
.TRUSTED_UNVERIFIED
);
2671 } catch (IOException
| UntrustedIdentityException e
) {
2672 e
.printStackTrace();
2680 public String
computeSafetyNumber(
2681 SignalServiceAddress theirAddress
, IdentityKey theirIdentityKey
2683 return Utils
.computeSafetyNumber(ServiceConfig
.capabilities
.isUuid(),
2684 account
.getSelfAddress(),
2685 getIdentityKeyPair().getPublicKey(),
2690 void saveAccount() {
2694 public SignalServiceAddress
canonicalizeAndResolveSignalServiceAddress(String identifier
) throws InvalidNumberException
{
2695 String canonicalizedNumber
= UuidUtil
.isUuid(identifier
)
2697 : PhoneNumberFormatter
.formatNumber(identifier
, account
.getUsername());
2698 return resolveSignalServiceAddress(canonicalizedNumber
);
2701 public SignalServiceAddress
resolveSignalServiceAddress(String identifier
) {
2702 SignalServiceAddress address
= Utils
.getSignalServiceAddressFromIdentifier(identifier
);
2704 return resolveSignalServiceAddress(address
);
2707 public SignalServiceAddress
resolveSignalServiceAddress(SignalServiceAddress address
) {
2708 if (address
.matches(account
.getSelfAddress())) {
2709 return account
.getSelfAddress();
2712 return account
.getRecipientStore().resolveServiceAddress(address
);
2716 public void close() throws IOException
{
2717 if (messagePipe
!= null) {
2718 messagePipe
.shutdown();
2722 if (unidentifiedMessagePipe
!= null) {
2723 unidentifiedMessagePipe
.shutdown();
2724 unidentifiedMessagePipe
= null;
2730 public interface ReceiveMessageHandler
{
2732 void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent decryptedContent
, Throwable e
);