import com.fasterxml.jackson.databind.ObjectMapper;
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.groups.GroupIdV1;
+import org.asamk.signal.manager.groups.GroupIdV2;
+import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.GroupHelper;
+import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
-import org.asamk.signal.storage.SignalAccount;
-import org.asamk.signal.storage.contacts.ContactInfo;
-import org.asamk.signal.storage.groups.GroupInfo;
-import org.asamk.signal.storage.groups.GroupInfoV1;
-import org.asamk.signal.storage.groups.GroupInfoV2;
-import org.asamk.signal.storage.profiles.SignalProfile;
-import org.asamk.signal.storage.profiles.SignalProfileEntry;
-import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
-import org.asamk.signal.storage.stickers.Sticker;
-import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.storage.contacts.ContactInfo;
+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.profiles.SignalProfile;
+import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
+import org.asamk.signal.manager.storage.protocol.IdentityInfo;
+import org.asamk.signal.manager.storage.stickers.Sticker;
+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.signal.libsignal.metadata.ProtocolDuplicateMessageException;
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SelfSendException;
+import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
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.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
+import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
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 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.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
public class Manager implements Closeable {
+ 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 UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
private final GroupHelper groupHelper;
+ private PinHelper pinHelper;
- public Manager(
+ Manager(
SignalAccount account,
PathConfig pathConfig,
SignalServiceConfiguration serviceConfiguration,
this.userAgent = userAgent;
this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
serviceConfiguration)) : null;
- this.accountManager = createSignalServiceAccountManager();
- this.groupsV2Api = accountManager.getGroupsV2Api();
+ createSignalServiceAccountManager();
this.account.setResolver(this::resolveSignalServiceAddress);
return account.getSelfAddress();
}
- private SignalServiceAccountManager createSignalServiceAccountManager() {
- return new SignalServiceAccountManager(serviceConfiguration,
+ private void createSignalServiceAccountManager() {
+ this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
new DynamicCredentialsProvider(account.getUuid(),
account.getUsername(),
account.getPassword(),
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.getDeviceId();
}
- private String getMessageCachePath() {
- return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache";
+ private File getMessageCachePath() {
+ return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername());
}
- private String getMessageCachePath(String sender) {
+ private File getMessageCachePath(String sender) {
if (sender == null || sender.isEmpty()) {
return getMessageCachePath();
}
- return getMessageCachePath() + "/" + sender.replace("/", "_");
+ return new File(getMessageCachePath(), sender.replace("/", "_"));
}
private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
- String cachePath = getMessageCachePath(sender);
+ File cachePath = getMessageCachePath(sender);
IOUtils.createPrivateDirectories(cachePath);
- return new File(cachePath + "/" + now + "_" + timestamp);
+ return new File(cachePath, now + "_" + timestamp);
}
public static Manager init(
- String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
+ String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
) throws IOException {
PathConfig pathConfig = PathConfig.createDefault(settingsPath);
return account.isRegistered();
}
+ /**
+ * This is used for checking a set of phone numbers for registration on Signal
+ *
+ * @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
+ */
+ 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
+ List<ContactTokenDetails> contactDetails = this.accountManager.getContacts(numbers);
+
+ // Make the initial map with all numbers set to false for now
+ Map<String, Boolean> usersRegistered = numbers.stream().collect(Collectors.toMap(x -> x, x -> false));
+
+ // Override the contacts we did obtain
+ for (ContactTokenDetails contactDetail : contactDetails) {
+ usersRegistered.put(contactDetail.getNumber(), true);
+ }
+
+ return usersRegistered;
+ }
+
public void register(boolean voiceVerification, String captcha) throws IOException {
account.setPassword(KeyUtils.createPassword());
// Resetting UUID, because registering doesn't work otherwise
account.setUuid(null);
- accountManager = createSignalServiceAccountManager();
- this.groupsV2Api = accountManager.getGroupsV2Api();
+ createSignalServiceAccountManager();
if (voiceVerification) {
accountManager.requestVoiceVerificationCode(Locale.getDefault(),
accountManager.setAccountAttributes(account.getSignalingKey(),
account.getSignalProtocolStore().getLocalRegistrationId(),
true,
- account.getRegistrationLockPin(),
- account.getRegistrationLock(),
+ // 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,
capabilities,
}
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
- Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
+ DeviceLinkInfo info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
addDevice(info.deviceIdentifier, info.deviceKey);
}
}
}
- public void verifyAccount(String verificationCode, String pin) throws IOException {
+ public void verifyAccount(
+ String verificationCode,
+ String pin
+ ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
verificationCode = verificationCode.replace("-", "");
account.setSignalingKey(KeyUtils.createSignalingKey());
- // TODO make unrestricted unidentified access configurable
- VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode,
- account.getSignalingKey(),
- account.getSignalProtocolStore().getLocalRegistrationId(),
- true,
- pin,
- null,
- unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
- unrestrictedUnidentifiedAccess,
- capabilities,
- discoverableByPhoneNumber);
+ 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());
+ }
- UUID uuid = UuidUtil.parseOrNull(response.getUuid());
// TODO response.isStorageCapable()
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
+
account.setRegistered(true);
- account.setUuid(uuid);
+ account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
account.setRegistrationLockPin(pin);
account.getSignalProtocolStore()
.saveIdentity(account.getSelfAddress(),
account.save();
}
- public void setRegistrationLockPin(Optional<String> pin) throws IOException {
+ 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
+ ? account.getPinMasterKey()
+ : KeyUtils.createMasterKey();
+
+ pinHelper.setRegistrationLockPin(pin.get(), masterKey);
+
account.setRegistrationLockPin(pin.get());
- throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
+ account.setPinMasterKey(masterKey);
} else {
- account.setRegistrationLockPin(null);
+ // Remove legacy registration lock
accountManager.removeRegistrationLockV1();
+
+ // Remove KBS Pin
+ pinHelper.removeRegistrationLockPin();
+
+ account.setRegistrationLockPin(null);
+ account.setPinMasterKey(null);
}
account.save();
}
try {
profile = retrieveRecipientProfile(address, profileKey);
} catch (IOException e) {
- System.err.println("Failed to retrieve profile, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
profileEntry.setRequestPending(false);
return null;
}
profileAndCredential = profileHelper.retrieveProfileSync(address,
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL);
} catch (IOException e) {
- System.err.println("Failed to retrieve profile key credential, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
return null;
}
? null
: retrieveProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
} catch (Throwable e) {
- System.err.println("Failed to retrieve profile avatar, ignoring: " + e.getMessage());
+ logger.warn("Failed to retrieve profile avatar, ignoring: {}", e.getMessage());
}
ProfileCipher profileCipher = new ProfileCipher(profileKey);
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
return Optional.absent();
}
- return Optional.of(Utils.createAttachment(file));
+ return Optional.of(AttachmentUtils.createAttachment(file));
}
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
+ messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
}
return sendGroupMessage(messageBuilder, groupId);
GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile);
if (gv2 == null) {
GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom());
- gv1.addMembers(Collections.singleton(account.getSelfAddress()));
+ gv1.addMembers(List.of(account.getSelfAddress()));
updateGroupV1(gv1, name, members, avatarFile);
messageBuilder = getGroupUpdateMessageBuilder(gv1);
g = gv1;
if (members != null) {
final Set<SignalServiceAddress> newMembers = new HashSet<>(members);
- newMembers.removeAll(group.getMembers());
+ newMembers.removeAll(group.getMembers()
+ .stream()
+ .map(this::resolveSignalServiceAddress)
+ .collect(Collectors.toSet()));
if (newMembers.size() > 0) {
Pair<DecryptedGroup, GroupChange> groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2,
newMembers);
newE164Members.remove(contact.getNumber());
}
throw new IOException("Failed to add members "
- + Util.join(", ", newE164Members)
+ + String.join(", ", newE164Members)
+ " to group: Not registered on Signal");
}
SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
// Send group message only to the recipient who requested it
- return sendMessage(messageBuilder, Collections.singleton(recipient));
+ return sendMessage(messageBuilder, List.of(recipient));
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
File aFile = getGroupAvatarFile(g.getGroupId());
if (aFile.exists()) {
try {
- group.withAvatar(Utils.createAttachment(aFile));
+ group.withAvatar(AttachmentUtils.createAttachment(aFile));
} catch (IOException e) {
throw new AttachmentInvalidException(aFile.toString(), e);
}
.asGroupMessage(group.build());
// Send group info request message to the recipient who sent us a message with this groupId
- return sendMessage(messageBuilder, Collections.singleton(recipient));
+ return sendMessage(messageBuilder, List.of(recipient));
}
void sendReceipt(
SignalServiceAddress remoteAddress, long messageId
) throws IOException, UntrustedIdentityException {
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
- Collections.singletonList(messageId),
+ List.of(messageId),
System.currentTimeMillis());
createMessageSender().sendReceipt(remoteAddress,
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withBody(messageText);
if (attachments != null) {
- List<SignalServiceAttachment> attachmentStreams = Utils.getSignalServiceAttachments(attachments);
+ List<SignalServiceAttachment> attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
// Upload attachments here, so we only upload once even for multiple recipients
SignalServiceMessageSender messageSender = createMessageSender();
private void sendExpirationTimerUpdate(SignalServiceAddress address) throws IOException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.asExpirationUpdate();
- sendMessage(messageBuilder, Collections.singleton(address));
+ sendMessage(messageBuilder, List.of(address));
}
/**
* @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
* @return if successful, returns the URL to install the sticker pack in the signal app
*/
- public String uploadStickerPack(String path) throws IOException, StickerPackInvalidException {
+ public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path);
SignalServiceMessageSender messageSender = createMessageSender();
}
private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(
- final String path
+ final File file
) throws IOException, StickerPackInvalidException {
ZipFile zip = null;
String rootPath = null;
- final File file = new File(path);
if (file.getName().endsWith(".zip")) {
zip = new ZipFile(file);
} else if (file.getName().equals("manifest.json")) {
try {
certificate = accountManager.getSenderCertificate();
} catch (IOException e) {
- System.err.println("Failed to get sender certificate: " + e);
+ logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
return null;
}
// TODO cache for a day
missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()),
CDS_MRENCLAVE);
} catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) {
- System.err.println("Failed to resolve uuids from server: " + e.getMessage());
+ logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage());
registeredUsers = new HashMap<>();
}
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
- return new Pair<>(timestamp, Collections.emptyList());
+ return new Pair<>(timestamp, List.of());
}
} else {
// Send to all individually, so sync messages are sent correctly
message.getTimestamp(),
message,
message.getExpiresInSeconds(),
- Collections.singletonMap(recipient, unidentifiedAccess.isPresent()),
+ Map.of(recipient, unidentifiedAccess.isPresent()),
false);
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException {
SignalServiceCipher cipher = new SignalServiceCipher(account.getSelfAddress(),
account.getSignalProtocolStore(),
- Utils.getCertificateValidator());
+ certificateValidator);
try {
return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
try {
retrieveGroupAvatarAttachment(avatar.asPointer(), groupV1.getGroupId());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve group avatar (" + avatar.asPointer()
- .getRemoteId() + "): " + e.getMessage());
+ logger.warn("Failed to retrieve avatar for group {}, ignoring: {}",
+ groupId.toBase64(),
+ e.getMessage());
}
}
}
}
case DELIVER:
if (groupV1 == null && !isSync) {
- actions.add(new SendGroupInfoRequestAction(source, groupV1.getGroupId()));
+ actions.add(new SendGroupInfoRequestAction(source, groupId));
}
break;
case QUIT: {
}
final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
- if (message.isEndSession()) {
+ if (conversationPartnerAddress != null && message.isEndSession()) {
handleEndSession(conversationPartnerAddress);
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
} else if (message.getGroupContext().get().getGroupV2().isPresent()) {
// disappearing message timer already stored in the DecryptedGroup
}
- } else {
+ } else if (conversationPartnerAddress != null) {
ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress);
if (contact == null) {
contact = new ContactInfo(conversationPartnerAddress);
try {
retrieveAttachment(attachment.asPointer());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.asPointer().getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve attachment ({}), ignoring: {}",
+ attachment.asPointer().getRemoteId(),
+ e.getMessage());
}
}
}
try {
retrieveAttachment(attachment);
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve preview image ({}), ignoring: {}",
+ attachment.getRemoteId(),
+ e.getMessage());
}
}
}
try {
retrieveAttachment(attachment.asPointer());
} catch (IOException | InvalidMessageException | MissingConfigurationException e) {
- System.err.println("Failed to retrieve attachment ("
- + attachment.asPointer().getRemoteId()
- + "): "
- + e.getMessage());
+ logger.warn("Failed to retrieve quote attachment thumbnail ({}), ignoring: {}",
+ attachment.asPointer().getRemoteId(),
+ e.getMessage());
}
}
}
// Received a v2 group message for a v1 group, we need to locally migrate the group
account.getGroupStore().deleteGroup(groupInfo.getGroupId());
groupInfoV2 = new GroupInfoV2(groupId, groupMasterKey);
- System.err.println("Locally migrated group "
- + groupInfo.getGroupId().toBase64()
- + " to group v2, id: "
- + groupInfoV2.getGroupId().toBase64()
- + " !!!");
+ logger.info("Locally migrated group {} to group v2, id: {}",
+ groupInfo.getGroupId().toBase64(),
+ groupInfoV2.getGroupId().toBase64());
} else if (groupInfo instanceof GroupInfoV2) {
groupInfoV2 = (GroupInfoV2) groupInfo;
} else {
}
if (group != null) {
storeProfileKeysFromMembers(group);
+ final String avatar = group.getAvatar();
+ if (avatar != null && !avatar.isEmpty()) {
+ try {
+ retrieveGroupAvatar(groupId, groupSecretParams, avatar);
+ } catch (IOException e) {
+ logger.warn("Failed to download group avatar, ignoring: {}", e.getMessage());
+ }
+ }
}
groupInfoV2.setGroup(group);
account.getGroupStore().updateGroup(groupInfoV2);
private void retryFailedReceivedMessages(
ReceiveMessageHandler handler, boolean ignoreAttachments
) {
- final File cachePath = new File(getMessageCachePath());
+ final File cachePath = getMessageCachePath();
if (!cachePath.exists()) {
return;
}
) {
SignalServiceEnvelope envelope;
try {
- envelope = Utils.loadEnvelope(fileEntry);
+ envelope = MessageCacheUtils.loadEnvelope(fileEntry);
if (envelope == null) {
return;
}
try {
Files.delete(fileEntry.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
}
return;
}
try {
Files.delete(fileEntry.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
}
}
try {
String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
- Utils.storeEnvelope(envelope1, cacheFile);
+ MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
} catch (IOException e) {
- System.err.println("Failed to store encrypted message in disk cache, ignoring: "
- + e.getMessage());
+ logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
}
});
if (result.isPresent()) {
if (returnOnTimeout) return;
continue;
} catch (InvalidVersionException e) {
- System.err.println("Ignoring error: " + e.getMessage());
+ logger.warn("Error while receiving messages, ignoring: {}", e.getMessage());
continue;
}
cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp());
Files.delete(cacheFile.toPath());
// Try to delete directory if empty
- new File(getMessageCachePath()).delete();
+ getMessageCachePath().delete();
} catch (IOException e) {
- System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage());
}
}
}
if (syncMessage.getSent().isPresent()) {
SentTranscriptMessage message = syncMessage.getSent().get();
final SignalServiceAddress destination = message.getDestination().orNull();
- if (destination != null) {
- actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
- true,
- sender,
- destination,
- ignoreAttachments));
- }
+ actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
+ true,
+ sender,
+ destination,
+ ignoreAttachments));
}
if (syncMessage.getRequest().isPresent()) {
RequestMessage rm = syncMessage.getRequest().get();
syncGroup.removeMember(account.getSelfAddress());
} else {
// Add ourself to the member set as it's marked as active
- syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
+ syncGroup.addMembers(List.of(account.getSelfAddress()));
}
syncGroup.blocked = g.isBlocked();
if (g.getColor().isPresent()) {
}
}
} catch (Exception e) {
+ logger.warn("Failed to handle received sync groups “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
e.printStackTrace();
} finally {
if (tmpFile != null) {
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received groups temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received groups temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
}
try {
setGroupBlocked(groupId, true);
} catch (GroupNotFoundException e) {
- System.err.println("BlockedListMessage contained groupID that was not found in GroupStore: "
- + groupId.toBase64());
+ logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
+ groupId.toBase64());
}
}
}
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received contacts temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received contacts temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
}
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
+ return AttachmentUtils.retrieveAttachment(stream, getContactAvatarFile(number));
}
}
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
+ return AttachmentUtils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
}
}
+ private File retrieveGroupAvatar(
+ 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,
+ tmpFile,
+ ServiceConfig.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE)) {
+ byte[] encryptedData = IOUtils.readFully(input);
+
+ byte[] decryptedData = groupOperations.decryptAvatar(encryptedData);
+ try (OutputStream output = new FileOutputStream(outputFile)) {
+ output.write(decryptedData);
+ }
+ } finally {
+ try {
+ Files.delete(tmpFile.toPath());
+ } catch (IOException e) {
+ logger.warn("Failed to delete received group avatar temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
+ }
+ }
+ return outputFile;
+ }
+
private File getProfileAvatarFile(SignalServiceAddress address) {
return new File(pathConfig.getAvatarsPath(), "profile-" + address.getLegacyIdentifier());
}
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received avatar temp file “" + tmpFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete received profile avatar temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
return outputFile;
try {
Files.delete(tmpFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete received attachment temp file “"
- + tmpFile
- + "”: "
- + e.getMessage());
+ logger.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
}
}
return outputFile;
try {
Files.delete(groupsFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete groups temp file “" + groupsFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete groups temp file “{}”, ignoring: {}", groupsFile, e.getMessage());
}
}
}
DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos);
for (ContactInfo record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null;
- JsonIdentityKeyStore.Identity currentIdentity = account.getSignalProtocolStore()
- .getIdentity(record.getAddress());
+ IdentityInfo currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(record.getAddress(),
currentIdentity.getIdentityKey(),
try {
Files.delete(contactsFile.toPath());
} catch (IOException e) {
- System.err.println("Failed to delete contacts temp file “" + contactsFile + "”: " + e.getMessage());
+ logger.warn("Failed to delete contacts temp file “{}”, ignoring: {}", contactsFile, e.getMessage());
}
}
}
}
public ContactInfo getContact(String number) {
- return account.getContactStore().getContact(Util.getSignalServiceAddressFromIdentifier(number));
+ return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number));
}
public GroupInfo getGroup(GroupId groupId) {
return account.getGroupStore().getGroup(groupId);
}
- public List<JsonIdentityKeyStore.Identity> getIdentities() {
+ public List<IdentityInfo> getIdentities() {
return account.getSignalProtocolStore().getIdentities();
}
- public List<JsonIdentityKeyStore.Identity> getIdentities(String number) throws InvalidNumberException {
+ public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
}
*/
public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (!Arrays.equals(id.getIdentityKey().serialize(), fingerprint)) {
continue;
}
*/
public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (!safetyNumber.equals(computeSafetyNumber(address, id.getIdentityKey()))) {
continue;
}
*/
public boolean trustIdentityAllKeys(String name) {
SignalServiceAddress address = resolveSignalServiceAddress(name);
- List<JsonIdentityKeyStore.Identity> ids = account.getSignalProtocolStore().getIdentities(address);
+ List<IdentityInfo> ids = account.getSignalProtocolStore().getIdentities(address);
if (ids == null) {
return false;
}
- for (JsonIdentityKeyStore.Identity id : ids) {
+ for (IdentityInfo id : ids) {
if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
account.getSignalProtocolStore()
.setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
public String computeSafetyNumber(
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
) {
- return Utils.computeSafetyNumber(account.getSelfAddress(),
+ return Utils.computeSafetyNumber(ServiceConfig.capabilities.isUuid(),
+ account.getSelfAddress(),
getIdentityKeyPair().getPublicKey(),
theirAddress,
theirIdentityKey);
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
String canonicalizedNumber = UuidUtil.isUuid(identifier)
? identifier
- : Util.canonicalizeNumber(identifier, account.getUsername());
+ : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
return resolveSignalServiceAddress(canonicalizedNumber);
}
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
- SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+ SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(identifier);
return resolveSignalServiceAddress(address);
}