/*
- Copyright (C) 2015-2020 AsamK and contributors
+ Copyright (C) 2015-2021 AsamK and contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
+import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.profiles.SignalProfile;
import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.KeyUtils;
-import org.asamk.signal.manager.util.MessageCacheUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.KeyHelper;
-import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupService;
-import org.whispersystems.signalservice.api.KeyBackupServicePinException;
-import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
-import org.whispersystems.signalservice.internal.push.LockedException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
-import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.util.Base64;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
-import java.security.KeyStore;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
public class Manager implements Closeable {
- final static Logger logger = LoggerFactory.getLogger(Manager.class);
+ private final static Logger logger = LoggerFactory.getLogger(Manager.class);
- private final SleepTimer timer = new UptimeSleepTimer();
private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
private final SignalServiceConfiguration serviceConfiguration;
private final String userAgent;
- // TODO make configurable
- private final boolean discoverableByPhoneNumber = true;
- private final boolean unrestrictedUnidentifiedAccess = false;
-
- private final SignalAccount account;
+ private SignalAccount account;
private final PathConfig pathConfig;
- private SignalServiceAccountManager accountManager;
- private GroupsV2Api groupsV2Api;
+ private final SignalServiceAccountManager accountManager;
+ private final GroupsV2Api groupsV2Api;
private final GroupsV2Operations groupsV2Operations;
+ private final SignalServiceMessageReceiver messageReceiver;
+ private final ClientZkProfileOperations clientZkProfileOperations;
- private SignalServiceMessageReceiver messageReceiver = null;
private SignalServiceMessagePipe messagePipe = null;
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
private final GroupHelper groupHelper;
- private PinHelper pinHelper;
+ private final PinHelper pinHelper;
Manager(
SignalAccount account,
this.userAgent = userAgent;
this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
serviceConfiguration)) : null;
- createSignalServiceAccountManager();
+ final SleepTimer timer = new UptimeSleepTimer();
+ this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
+ new DynamicCredentialsProvider(account.getUuid(),
+ account.getUsername(),
+ account.getPassword(),
+ account.getSignalingKey(),
+ account.getDeviceId()),
+ userAgent,
+ groupsV2Operations,
+ timer);
+ this.groupsV2Api = accountManager.getGroupsV2Api();
+ final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager);
+ this.pinHelper = new PinHelper(keyBackupService);
+ this.clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(serviceConfiguration)
+ .getProfileOperations() : null;
+ this.messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration,
+ account.getUuid(),
+ account.getUsername(),
+ account.getPassword(),
+ account.getDeviceId(),
+ account.getSignalingKey(),
+ userAgent,
+ null,
+ timer,
+ clientZkProfileOperations);
this.account.setResolver(this::resolveSignalServiceAddress);
this.profileHelper = new ProfileHelper(account.getProfileStore()::getProfileKey,
unidentifiedAccessHelper::getAccessFor,
unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
- this::getOrCreateMessageReceiver);
+ () -> messageReceiver);
this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential,
this::getRecipientProfile,
account::getSelfAddress,
return account.getSelfAddress();
}
- private void createSignalServiceAccountManager() {
- this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
- new DynamicCredentialsProvider(account.getUuid(),
- account.getUsername(),
- account.getPassword(),
- null,
- account.getDeviceId()),
- userAgent,
- groupsV2Operations,
- timer);
- this.groupsV2Api = accountManager.getGroupsV2Api();
- this.pinHelper = new PinHelper(createKeyBackupService());
- }
-
- private KeyBackupService createKeyBackupService() {
- KeyStore keyStore = ServiceConfig.getIasKeyStore();
-
- return accountManager.getKeyBackupService(keyStore,
- ServiceConfig.KEY_BACKUP_ENCLAVE_NAME,
- ServiceConfig.KEY_BACKUP_SERVICE_ID,
- ServiceConfig.KEY_BACKUP_MRENCLAVE,
- 10);
- }
-
private IdentityKeyPair getIdentityKeyPair() {
return account.getSignalProtocolStore().getIdentityKeyPair();
}
return account.getDeviceId();
}
- private File getMessageCachePath() {
- return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername());
- }
-
- private File getMessageCachePath(String sender) {
- if (sender == null || sender.isEmpty()) {
- return getMessageCachePath();
- }
-
- return new File(getMessageCachePath(), sender.replace("/", "_"));
- }
-
- private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
- File cachePath = getMessageCachePath(sender);
- IOUtils.createPrivateDirectories(cachePath);
- return new File(cachePath, now + "_" + timestamp);
- }
-
public static Manager init(
String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
- ) throws IOException {
+ ) throws IOException, NotRegisteredException {
PathConfig pathConfig = PathConfig.createDefault(settingsPath);
if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
- IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair();
- int registrationId = KeyHelper.generateRegistrationId(false);
-
- ProfileKey profileKey = KeyUtils.createProfileKey();
- SignalAccount account = SignalAccount.create(pathConfig.getDataPath(),
- username,
- identityKey,
- registrationId,
- profileKey);
- account.save();
-
- return new Manager(account, pathConfig, serviceConfiguration, userAgent);
+ throw new NotRegisteredException();
}
SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
- Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
-
- m.migrateLegacyConfigs();
+ if (!account.isRegistered()) {
+ throw new NotRegisteredException();
+ }
- return m;
+ return new Manager(account, pathConfig, serviceConfiguration, userAgent);
}
- private void migrateLegacyConfigs() {
- if (account.getProfileKey() == null && isRegistered()) {
- // Old config file, creating new profile key
- account.setProfileKey(KeyUtils.createProfileKey());
+ public void checkAccountState() throws IOException {
+ if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ refreshPreKeys();
account.save();
}
- // Store profile keys only in profile store
- for (ContactInfo contact : account.getContactStore().getContacts()) {
- String profileKeyString = contact.profileKey;
- if (profileKeyString == null) {
- continue;
- }
- final ProfileKey profileKey;
- try {
- profileKey = new ProfileKey(Base64.decode(profileKeyString));
- } catch (InvalidInputException | IOException e) {
- continue;
- }
- contact.profileKey = null;
- account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
- }
- // Ensure our profile key is stored in profile store
- account.getProfileStore().storeProfileKey(getSelfAddress(), account.getProfileKey());
- }
-
- public void checkAccountState() throws IOException {
- if (account.isRegistered()) {
- if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
- refreshPreKeys();
- account.save();
- }
- if (account.getUuid() == null) {
- account.setUuid(accountManager.getOwnUuid());
- account.save();
- }
- updateAccountAttributes();
+ if (account.getUuid() == null) {
+ account.setUuid(accountManager.getOwnUuid());
+ account.save();
}
- }
-
- public boolean isRegistered() {
- return account.isRegistered();
+ updateAccountAttributes();
}
/**
*
* @param numbers The set of phone number in question
* @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
- * @throws IOException if its unable to check if the users are registered
+ * @throws IOException if its unable to get the contacts to check if they're registered
*/
public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
// Note "contactDetails" has no optionals. It only gives us info on users who are registered
return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
}
- public void register(boolean voiceVerification, String captcha) throws IOException {
- account.setPassword(KeyUtils.createPassword());
-
- // Resetting UUID, because registering doesn't work otherwise
- account.setUuid(null);
- createSignalServiceAccountManager();
-
- if (voiceVerification) {
- accountManager.requestVoiceVerificationCode(Locale.getDefault(),
- Optional.fromNullable(captcha),
- Optional.absent());
- } else {
- accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent());
- }
-
- account.setRegistered(false);
- account.save();
- }
-
public void updateAccountAttributes() throws IOException {
accountManager.setAccountAttributes(account.getSignalingKey(),
account.getSignalProtocolStore().getLocalRegistrationId(),
// set legacy pin only if no KBS master key is set
account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null,
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
- unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
- unrestrictedUnidentifiedAccess,
+ account.getSelfUnidentifiedAccessKey(),
+ account.isUnrestrictedUnidentifiedAccess(),
capabilities,
- discoverableByPhoneNumber);
+ account.isDiscoverableByPhoneNumber());
}
public void setProfile(String name, File avatar) throws IOException {
account.save();
}
- private List<PreKeyRecord> generatePreKeys() {
- List<PreKeyRecord> records = new ArrayList<>(ServiceConfig.PREKEY_BATCH_SIZE);
-
- final int offset = account.getPreKeyIdOffset();
- for (int i = 0; i < ServiceConfig.PREKEY_BATCH_SIZE; i++) {
- int preKeyId = (offset + i) % Medium.MAX_VALUE;
- ECKeyPair keyPair = Curve.generateKeyPair();
- PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
-
- records.add(record);
- }
-
- account.addPreKeys(records);
- account.save();
-
- return records;
- }
-
- private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
- try {
- ECKeyPair keyPair = Curve.generateKeyPair();
- byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(),
- keyPair.getPublicKey().serialize());
- SignedPreKeyRecord record = new SignedPreKeyRecord(account.getNextSignedPreKeyId(),
- System.currentTimeMillis(),
- keyPair,
- signature);
-
- account.addSignedPreKey(record);
- account.save();
-
- return record;
- } catch (InvalidKeyException e) {
- throw new AssertionError(e);
- }
- }
-
- public void verifyAccount(
- String verificationCode, String pin
- ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
- verificationCode = verificationCode.replace("-", "");
- account.setSignalingKey(KeyUtils.createSignalingKey());
- VerifyAccountResponse response;
- try {
- response = verifyAccountWithCode(verificationCode, pin, null);
- } catch (LockedException e) {
- if (pin == null) {
- throw e;
- }
-
- KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e);
- if (registrationLockData == null) {
- throw e;
- }
-
- String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
- try {
- response = verifyAccountWithCode(verificationCode, null, registrationLock);
- } catch (LockedException _e) {
- throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
- }
- account.setPinMasterKey(registrationLockData.getMasterKey());
- }
-
- // TODO response.isStorageCapable()
- //accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
-
- account.setRegistered(true);
- account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
- account.setRegistrationLockPin(pin);
- account.getSignalProtocolStore()
- .saveIdentity(account.getSelfAddress(),
- getIdentityKeyPair().getPublicKey(),
- TrustLevel.TRUSTED_VERIFIED);
-
- refreshPreKeys();
- account.save();
- }
-
- private VerifyAccountResponse verifyAccountWithCode(
- final String verificationCode, final String legacyPin, final String registrationLock
- ) throws IOException {
- return accountManager.verifyAccountWithCode(verificationCode,
- account.getSignalingKey(),
- account.getSignalProtocolStore().getLocalRegistrationId(),
- true,
- legacyPin,
- registrationLock,
- unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
- unrestrictedUnidentifiedAccess,
- capabilities,
- discoverableByPhoneNumber);
- }
-
public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
if (pin.isPresent()) {
final MasterKey masterKey = account.getPinMasterKey() != null
accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
}
- private SignalServiceMessageReceiver createMessageReceiver() {
- final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(
- serviceConfiguration).getProfileOperations() : null;
- return new SignalServiceMessageReceiver(serviceConfiguration,
- account.getUuid(),
- account.getUsername(),
- account.getPassword(),
- account.getDeviceId(),
- account.getSignalingKey(),
- userAgent,
- null,
- timer,
- clientZkProfileOperations);
+ private List<PreKeyRecord> generatePreKeys() {
+ final int offset = account.getPreKeyIdOffset();
+
+ List<PreKeyRecord> records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
+ account.addPreKeys(records);
+ account.save();
+
+ return records;
}
- private SignalServiceMessageReceiver getOrCreateMessageReceiver() {
- if (messageReceiver == null) {
- messageReceiver = createMessageReceiver();
- }
- return messageReceiver;
+ private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
+ final int signedPreKeyId = account.getNextSignedPreKeyId();
+
+ SignedPreKeyRecord record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
+ account.addSignedPreKey(record);
+ account.save();
+
+ return record;
}
private SignalServiceMessagePipe getOrCreateMessagePipe() {
if (messagePipe == null) {
- messagePipe = getOrCreateMessageReceiver().createMessagePipe();
+ messagePipe = messageReceiver.createMessagePipe();
}
return messagePipe;
}
private SignalServiceMessagePipe getOrCreateUnidentifiedMessagePipe() {
if (unidentifiedMessagePipe == null) {
- unidentifiedMessagePipe = getOrCreateMessageReceiver().createUnidentifiedMessagePipe();
+ unidentifiedMessagePipe = messageReceiver.createUnidentifiedMessagePipe();
}
return unidentifiedMessagePipe;
}
private SignalServiceMessageSender createMessageSender() {
- final ClientZkProfileOperations clientZkProfileOperations = capabilities.isGv2() ? ClientZkOperations.create(
- serviceConfiguration).getProfileOperations() : null;
final ExecutorService executor = null;
return new SignalServiceMessageSender(serviceConfiguration,
account.getUuid(),
ServiceConfig.MAX_ENVELOPE_SIZE);
}
- private SignalServiceProfile getEncryptedRecipientProfile(SignalServiceAddress address) throws IOException {
- return profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE).getProfile();
- }
-
private SignalProfile getRecipientProfile(
SignalServiceAddress address
) {
private SignalProfile retrieveRecipientProfile(
SignalServiceAddress address, ProfileKey profileKey
) throws IOException {
- final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address);
+ final SignalServiceProfile encryptedProfile = profileHelper.retrieveProfileSync(address,
+ SignalServiceProfile.RequestType.PROFILE).getProfile();
return decryptProfile(address, profileKey, encryptedProfile);
}
}
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
- GroupInfo g = account.getGroupStore().getGroup(groupId);
+ GroupInfo g = getGroup(groupId);
if (g == null) {
throw new GroupNotFoundException(groupId);
}
}
private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
- GroupInfo g = account.getGroupStore().getGroup(groupId);
+ GroupInfo g = getGroup(groupId);
if (g == null) {
throw new GroupNotFoundException(groupId);
}
* Change the expiration timer for a group
*/
public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
- GroupInfo g = account.getGroupStore().getGroup(groupId);
+ GroupInfo g = getGroup(groupId);
if (g instanceof GroupInfoV1) {
GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
groupInfoV1.messageExpirationTime = messageExpirationTimer;
if (message.getGroupContext().get().getGroupV1().isPresent()) {
SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
GroupIdV1 groupId = GroupId.v1(groupInfo.getGroupId());
- GroupInfo group = account.getGroupStore().getGroup(groupId);
+ GroupInfo group = getGroup(groupId);
if (group == null || group instanceof GroupInfoV1) {
GroupInfoV1 groupV1 = (GroupInfoV1) group;
switch (groupInfo.getType()) {
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams);
- GroupInfo groupInfo = account.getGroupStore().getGroup(groupId);
+ GroupInfo groupInfo = getGroup(groupId);
final GroupInfoV2 groupInfoV2;
if (groupInfo instanceof GroupInfoV1) {
// Received a v2 group message for a v1 group, we need to locally migrate the group
}
}
- private void retryFailedReceivedMessages(
- ReceiveMessageHandler handler, boolean ignoreAttachments
- ) {
- final File cachePath = getMessageCachePath();
- if (!cachePath.exists()) {
- return;
- }
- for (final File dir : Objects.requireNonNull(cachePath.listFiles())) {
- if (!dir.isDirectory()) {
- retryFailedReceivedMessage(handler, ignoreAttachments, dir);
- continue;
- }
-
- for (final File fileEntry : Objects.requireNonNull(dir.listFiles())) {
- if (!fileEntry.isFile()) {
- continue;
- }
- retryFailedReceivedMessage(handler, ignoreAttachments, fileEntry);
- }
- // Try to delete directory if empty
- dir.delete();
+ private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
+ for (CachedMessage cachedMessage : account.getMessageCache().getCachedMessages()) {
+ retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
}
}
private void retryFailedReceivedMessage(
- final ReceiveMessageHandler handler, final boolean ignoreAttachments, final File fileEntry
+ final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
) {
- SignalServiceEnvelope envelope;
- try {
- envelope = MessageCacheUtils.loadEnvelope(fileEntry);
- if (envelope == null) {
- return;
- }
- } catch (IOException e) {
- e.printStackTrace();
+ SignalServiceEnvelope envelope = cachedMessage.loadEnvelope();
+ if (envelope == null) {
return;
}
SignalServiceContent content = null;
return;
} catch (Exception er) {
// All other errors are not recoverable, so delete the cached message
- try {
- Files.delete(fileEntry.toPath());
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
- }
+ cachedMessage.delete();
return;
}
List<HandleAction> actions = handleMessage(envelope, content, ignoreAttachments);
}
account.save();
handler.handleMessage(envelope, content, null);
- try {
- Files.delete(fileEntry.toPath());
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
- }
+ cachedMessage.delete();
}
public void receiveMessages(
Set<HandleAction> queuedActions = null;
- getOrCreateMessagePipe();
+ final SignalServiceMessagePipe messagePipe = getOrCreateMessagePipe();
boolean hasCaughtUpWithOldMessages = false;
SignalServiceEnvelope envelope;
SignalServiceContent content = null;
Exception exception = null;
- final long now = new Date().getTime();
+ final CachedMessage[] cachedMessage = {null};
try {
Optional<SignalServiceEnvelope> result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
// store message on disk, before acknowledging receipt to the server
- try {
- String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
- File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
- MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
- } catch (IOException e) {
- logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
- }
+ cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1);
});
if (result.isPresent()) {
envelope = result.get();
}
}
account.save();
- if (!isMessageBlocked(envelope, content)) {
+ if (isMessageBlocked(envelope, content)) {
+ logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
+ } else if (isNotAGroupMember(envelope, content)) {
+ logger.info("Ignoring a message from a non group member: {}", envelope.getTimestamp());
+ } else {
handler.handleMessage(envelope, content, exception);
}
if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
- File cacheFile = null;
- try {
- String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "";
- cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp());
- Files.delete(cacheFile.toPath());
- // Try to delete directory if empty
- getMessageCachePath().delete();
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage());
+ if (cachedMessage[0] != null) {
+ cachedMessage[0].delete();
}
}
}
return true;
}
+ if (content != null && content.getDataMessage().isPresent()) {
+ SignalServiceDataMessage message = content.getDataMessage().get();
+ if (message.getGroupContext().isPresent()) {
+ GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
+ GroupInfo group = getGroup(groupId);
+ if (group != null && group.isBlocked()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isNotAGroupMember(
+ SignalServiceEnvelope envelope, SignalServiceContent content
+ ) {
+ SignalServiceAddress source;
+ if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
+ source = envelope.getSourceAddress();
+ } else if (content != null) {
+ source = content.getSender();
+ } else {
+ return false;
+ }
+
if (content != null && content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
- if (groupInfo.getType() != SignalServiceGroup.Type.DELIVER) {
+ if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
return false;
}
}
GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
- GroupInfo group = account.getGroupStore().getGroup(groupId);
- if (group != null && group.isBlocked()) {
+ GroupInfo group = getGroup(groupId);
+ if (group != null && !group.isMember(source)) {
return true;
}
}
GroupId groupId, GroupSecretParams groupSecretParams, String cdnKey
) throws IOException {
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
- SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver();
File outputFile = getGroupAvatarFile(groupId);
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
File tmpFile = IOUtils.createTempFile();
tmpFile.deleteOnExit();
- try (InputStream input = receiver.retrieveGroupsV2ProfileAvatar(cdnKey,
+ try (InputStream input = messageReceiver.retrieveGroupsV2ProfileAvatar(cdnKey,
tmpFile,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
byte[] encryptedData = IOUtils.readFully(input);
SignalServiceAddress address, String avatarPath, ProfileKey profileKey
) throws IOException {
IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
- SignalServiceMessageReceiver receiver = getOrCreateMessageReceiver();
File outputFile = getProfileAvatarFile(address);
File tmpFile = IOUtils.createTempFile();
- try (InputStream input = receiver.retrieveProfileAvatar(avatarPath,
+ try (InputStream input = messageReceiver.retrieveProfileAvatar(avatarPath,
tmpFile,
profileKey,
ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
}
}
- final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
-
File tmpFile = IOUtils.createTempFile();
try (InputStream input = messageReceiver.retrieveAttachment(pointer,
tmpFile,
private InputStream retrieveAttachmentAsStream(
SignalServiceAttachmentPointer pointer, File tmpFile
) throws IOException, InvalidMessageException, MissingConfigurationException {
- final SignalServiceMessageReceiver messageReceiver = getOrCreateMessageReceiver();
return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
}
try {
try (OutputStream fos = new FileOutputStream(groupsFile)) {
DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos);
- for (GroupInfo record : account.getGroupStore().getGroups()) {
+ for (GroupInfo record : getGroups()) {
if (record instanceof GroupInfoV1) {
GroupInfoV1 groupInfo = (GroupInfoV1) record;
out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
}
}
List<byte[]> groupIds = new ArrayList<>();
- for (GroupInfo record : account.getGroupStore().getGroups()) {
+ for (GroupInfo record : getGroups()) {
if (record.isBlocked()) {
groupIds.add(record.getGroupId().serialize());
}
}
public GroupInfo getGroup(GroupId groupId) {
- return account.getGroupStore().getGroup(groupId);
+ final GroupInfo group = account.getGroupStore().getGroup(groupId);
+ if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) {
+ final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(((GroupInfoV2) group).getMasterKey());
+ ((GroupInfoV2) group).setGroup(groupHelper.getDecryptedGroup(groupSecretParams));
+ account.getGroupStore().updateGroup(group);
+ }
+ return group;
}
public List<IdentityInfo> getIdentities() {
@Override
public void close() throws IOException {
+ close(true);
+ }
+
+ void close(boolean closeAccount) throws IOException {
if (messagePipe != null) {
messagePipe.shutdown();
messagePipe = null;
unidentifiedMessagePipe = null;
}
- account.close();
+ if (closeAccount && account != null) {
+ account.close();
+ }
+ account = null;
}
public interface ReceiveMessageHandler {