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.identities.IdentityInfo;
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.profiles.SignalProfile;
-import org.asamk.signal.manager.storage.protocol.IdentityInfo;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.Sticker;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
clientZkProfileOperations,
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
- this.account.setResolver(this::resolveSignalServiceAddress);
-
this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
account.getProfileStore()::getProfileKey,
this::getRecipientProfile,
var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
account.addPreKeys(records);
- account.save();
return records;
}
var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
account.addSignedPreKey(record);
- account.save();
return record;
}
return getRecipientProfile(address, false);
}
- private SignalProfile getRecipientProfile(
+ SignalProfile getRecipientProfile(
SignalServiceAddress address, boolean force
) {
var profileEntry = account.getProfileStore().getProfileEntry(address);
if (profileEntry == null) {
+ // retrieve profile to get identity key
+ retrieveEncryptedProfile(address);
return null;
}
var now = new Date().getTime();
profileEntry.setRequestPending(true);
final SignalServiceProfile encryptedProfile;
try {
- encryptedProfile = profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE)
- .getProfile();
- } catch (IOException e) {
- logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
- return null;
+ encryptedProfile = retrieveEncryptedProfile(address);
} finally {
profileEntry.setRequestPending(false);
}
+ if (encryptedProfile == null) {
+ return null;
+ }
final var profileKey = profileEntry.getProfileKey();
final var profile = decryptProfileAndDownloadAvatar(address, profileKey, encryptedProfile);
return profileEntry.getProfile();
}
+ private SignalServiceProfile retrieveEncryptedProfile(SignalServiceAddress address) {
+ try {
+ final var profile = profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE)
+ .getProfile();
+ try {
+ account.getIdentityKeyStore()
+ .saveIdentity(resolveRecipient(address),
+ new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
+ new Date());
+ } catch (InvalidKeyException ignored) {
+ logger.warn("Got invalid identity key in profile for {}", address.getLegacyIdentifier());
+ }
+ return profile;
+ } catch (IOException e) {
+ logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
+ return null;
+ }
+ }
+
private ProfileKeyCredential getRecipientProfileKeyCredential(SignalServiceAddress address) {
var profileEntry = account.getProfileStore().getProfileEntry(address);
if (profileEntry == null) {
private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException {
var messageSender = createMessageSender();
- try {
- messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
- } catch (UntrustedIdentityException e) {
- if (e.getIdentityKey() != null) {
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
- e.getIdentityKey(),
- TrustLevel.UNTRUSTED);
- }
- throw e;
- }
+ messageSender.sendMessage(message, unidentifiedAccessHelper.getAccessForSync());
}
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
unidentifiedAccessHelper.getAccessFor(recipients),
isRecipientUpdate,
message);
+
for (var r : result) {
if (r.getIdentityFailure() != null) {
- account.getSignalProtocolStore()
- .saveIdentity(r.getAddress(),
+ account.getIdentityKeyStore().
+ saveIdentity(resolveRecipient(r.getAddress()),
r.getIdentityFailure().getIdentityKey(),
- TrustLevel.UNTRUSTED);
+ new Date());
}
}
+
return new Pair<>(timestamp, result);
} catch (UntrustedIdentityException e) {
- if (e.getIdentityKey() != null) {
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
- e.getIdentityKey(),
- TrustLevel.UNTRUSTED);
- }
return new Pair<>(timestamp, List.of());
}
} else {
false,
System.currentTimeMillis() - startTime);
} catch (UntrustedIdentityException e) {
- if (e.getIdentityKey() != null) {
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
- e.getIdentityKey(),
- TrustLevel.UNTRUSTED);
- }
return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
}
}
try {
return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(address), message);
} catch (UntrustedIdentityException e) {
- if (e.getIdentityKey() != null) {
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
- e.getIdentityKey(),
- TrustLevel.UNTRUSTED);
- }
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
- var identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
- final var untrustedIdentity = identityException.getUntrustedIdentity();
- if (untrustedIdentity != null) {
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(identityException.getName()),
- untrustedIdentity,
- TrustLevel.UNTRUSTED);
- }
- throw identityException;
+ throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
}
throw new AssertionError(e);
}
}
private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
+ Set<HandleAction> queuedActions = new HashSet<>();
for (var cachedMessage : account.getMessageCache().getCachedMessages()) {
- retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
+ var actions = retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
+ if (actions != null) {
+ queuedActions.addAll(actions);
+ }
+ }
+ for (var action : queuedActions) {
+ try {
+ action.execute(this);
+ } catch (Throwable e) {
+ logger.warn("Message action failed.", e);
+ }
}
}
- private void retryFailedReceivedMessage(
+ private List<HandleAction> retryFailedReceivedMessage(
final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
) {
var envelope = cachedMessage.loadEnvelope();
if (envelope == null) {
- return;
+ return null;
}
SignalServiceContent content = null;
+ List<HandleAction> actions = null;
if (!envelope.isReceipt()) {
try {
content = decryptMessage(envelope);
} catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
- return;
+ if (!envelope.hasSource()) {
+ final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) e)
+ .getName());
+ try {
+ account.getMessageCache().replaceSender(cachedMessage, recipientId);
+ } catch (IOException ioException) {
+ logger.warn("Failed to move cached message to recipient folder: {}", ioException.getMessage());
+ }
+ }
+ return null;
} catch (Exception er) {
// All other errors are not recoverable, so delete the cached message
cachedMessage.delete();
- return;
- }
- var actions = handleMessage(envelope, content, ignoreAttachments);
- for (var action : actions) {
- try {
- action.execute(this);
- } catch (Throwable e) {
- logger.warn("Message action failed.", e);
- }
+ return null;
}
+ actions = handleMessage(envelope, content, ignoreAttachments);
}
account.save();
handler.handleMessage(envelope, content, null);
cachedMessage.delete();
+ return actions;
}
public void receiveMessages(
final CachedMessage[] cachedMessage = {null};
try {
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
+ final var recipientId = envelope1.hasSource()
+ ? resolveRecipient(envelope1.getSourceIdentifier())
+ : null;
// store message on disk, before acknowledging receipt to the server
- cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1);
+ cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
});
if (result.isPresent()) {
envelope = result.get();
if (envelope.hasSource()) {
// Store uuid if we don't have it already
- var source = envelope.getSourceAddress();
- resolveSignalServiceAddress(source);
+ resolveRecipientTrusted(envelope.getSourceAddress());
}
+ final var notAGroupMember = isNotAGroupMember(envelope, content);
if (!envelope.isReceipt()) {
try {
content = decryptMessage(envelope);
account.save();
if (isMessageBlocked(envelope, content)) {
logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
- } else if (isNotAGroupMember(envelope, content)) {
+ } else if (notAGroupMember) {
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)) {
- if (cachedMessage[0] != null) {
+ if (cachedMessage[0] != null) {
+ if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
+ final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) exception)
+ .getName());
+ queuedActions.add(new RetrieveProfileAction(resolveSignalServiceAddress(recipientId)));
+ if (!envelope.hasSource()) {
+ try {
+ cachedMessage[0] = account.getMessageCache().replaceSender(cachedMessage[0], recipientId);
+ } catch (IOException ioException) {
+ logger.warn("Failed to move cached message to recipient folder: {}",
+ ioException.getMessage());
+ }
+ }
+ } else {
cachedMessage[0].delete();
}
}
destination,
ignoreAttachments));
}
- if (syncMessage.getRequest().isPresent()) {
+ if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
var rm = syncMessage.getRequest().get();
if (rm.isContactsRequest()) {
actions.add(SendSyncContactsAction.create());
try (var attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream()
.asPointer(), tmpFile)) {
var s = new DeviceContactsInputStream(attachmentAsStream);
- if (contactsMessage.isComplete()) {
- account.getContactStore().clear();
- }
DeviceContact c;
while ((c = s.read()) != null) {
if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) {
}
if (c.getVerified().isPresent()) {
final var verifiedMessage = c.getVerified().get();
- account.getSignalProtocolStore()
- .setIdentityTrustLevel(verifiedMessage.getDestination(),
+ account.getIdentityKeyStore()
+ .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
}
if (syncMessage.getVerified().isPresent()) {
final var verifiedMessage = syncMessage.getVerified().get();
- account.getSignalProtocolStore()
- .setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()),
+ account.getIdentityKeyStore()
+ .setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
var out = new DeviceContactsOutputStream(fos);
for (var record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null;
- var currentIdentity = account.getSignalProtocolStore().getIdentity(record.getAddress());
+ var currentIdentity = account.getIdentityKeyStore()
+ .getIdentity(resolveRecipientTrusted(record.getAddress()));
if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(record.getAddress(),
currentIdentity.getIdentityKey(),
}
public List<IdentityInfo> getIdentities() {
- return account.getSignalProtocolStore().getIdentities();
+ return account.getIdentityKeyStore().getIdentities();
}
public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
- return account.getSignalProtocolStore().getIdentities(canonicalizeAndResolveSignalServiceAddress(number));
+ final var identity = account.getIdentityKeyStore().getIdentity(canonicalizeAndResolveRecipient(number));
+ return identity == null ? List.of() : List.of(identity);
}
/**
* @param fingerprint Fingerprint
*/
public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
- var address = canonicalizeAndResolveSignalServiceAddress(name);
- return trustIdentity(address, (identityKey) -> Arrays.equals(identityKey.serialize(), fingerprint));
+ var recipientId = canonicalizeAndResolveRecipient(name);
+ return trustIdentity(recipientId,
+ identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
+ TrustLevel.TRUSTED_VERIFIED);
}
/**
* @param safetyNumber Safety number
*/
public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
- var address = canonicalizeAndResolveSignalServiceAddress(name);
- return trustIdentity(address, (identityKey) -> safetyNumber.equals(computeSafetyNumber(address, identityKey)));
+ var recipientId = canonicalizeAndResolveRecipient(name);
+ var address = account.getRecipientStore().resolveServiceAddress(recipientId);
+ return trustIdentity(recipientId,
+ identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
+ TrustLevel.TRUSTED_VERIFIED);
}
- private boolean trustIdentity(SignalServiceAddress address, Function<IdentityKey, Boolean> verifier) {
- var ids = account.getSignalProtocolStore().getIdentities(address);
- if (ids == null) {
- return false;
- }
-
- IdentityInfo foundIdentity = null;
+ /**
+ * Trust all keys of this identity without verification
+ *
+ * @param name username of the identity
+ */
+ public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
+ var recipientId = canonicalizeAndResolveRecipient(name);
+ return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
+ }
- for (var id : ids) {
- if (verifier.apply(id.getIdentityKey())) {
- foundIdentity = id;
- break;
- }
+ private boolean trustIdentity(
+ RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
+ ) {
+ var identity = account.getIdentityKeyStore().getIdentity(recipientId);
+ if (identity == null) {
+ return false;
}
- if (foundIdentity == null) {
+ if (!verifier.apply(identity.getIdentityKey())) {
return false;
}
- account.getSignalProtocolStore()
- .setIdentityTrustLevel(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+ account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
try {
- sendVerifiedMessage(address, foundIdentity.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
+ var address = account.getRecipientStore().resolveServiceAddress(recipientId);
+ sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
} catch (IOException | UntrustedIdentityException e) {
logger.warn("Failed to send verification sync message: {}", e.getMessage());
}
- // Successfully trusted the new identity, now remove all other identities for that number
- for (var id : ids) {
- if (id == foundIdentity) {
- continue;
- }
- account.getSignalProtocolStore().removeIdentity(address, id.getIdentityKey());
- }
-
- account.save();
- return true;
- }
-
- /**
- * Trust all keys of this identity without verification
- *
- * @param name username of the identity
- */
- public boolean trustIdentityAllKeys(String name) {
- var address = resolveSignalServiceAddress(name);
- var ids = account.getSignalProtocolStore().getIdentities(address);
- if (ids == null) {
- return false;
- }
- for (var id : ids) {
- if (id.getTrustLevel() == TrustLevel.UNTRUSTED) {
- account.getSignalProtocolStore()
- .setIdentityTrustLevel(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
- try {
- sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
- } catch (IOException | UntrustedIdentityException e) {
- logger.warn("Failed to send verification sync message: {}", e.getMessage());
- }
- }
- }
- account.save();
return true;
}
theirIdentityKey);
}
+ @Deprecated
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
var canonicalizedNumber = UuidUtil.isUuid(identifier)
? identifier
return resolveSignalServiceAddress(canonicalizedNumber);
}
+ @Deprecated
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
return resolveSignalServiceAddress(address);
}
+ @Deprecated
public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
if (address.matches(account.getSelfAddress())) {
return account.getSelfAddress();
return account.getRecipientStore().resolveServiceAddress(address);
}
+ public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
+ return account.getRecipientStore().resolveServiceAddress(recipientId);
+ }
+
+ public RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
+ var canonicalizedNumber = UuidUtil.isUuid(identifier)
+ ? identifier
+ : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
+
+ return resolveRecipient(canonicalizedNumber);
+ }
+
+ private RecipientId resolveRecipient(final String identifier) {
+ var address = Utils.getSignalServiceAddressFromIdentifier(identifier);
+
+ return resolveRecipient(address);
+ }
+
+ public RecipientId resolveRecipient(SignalServiceAddress address) {
+ return account.getRecipientStore().resolveRecipientUntrusted(address);
+ }
+
+ private RecipientId resolveRecipientTrusted(SignalServiceAddress address) {
+ return account.getRecipientStore().resolveRecipient(address);
+ }
+
@Override
public void close() throws IOException {
close(true);