X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/1d2c7a479d791dedde6c2022ac65e9c82400af79..a96bd91770880fc53e279f8a0a75e90cd32d078a:/lib/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index d8a1f563..4bced5e2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -34,9 +34,10 @@ 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.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; @@ -145,6 +146,7 @@ import java.nio.file.Files; 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; @@ -234,8 +236,6 @@ public class Manager implements Closeable { clientZkProfileOperations, ServiceConfig.AUTOMATIC_NETWORK_RETRY); - this.account.setResolver(this::resolveSignalServiceAddress); - this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, account.getProfileStore()::getProfileKey, this::getRecipientProfile, @@ -263,7 +263,7 @@ public class Manager implements Closeable { } private IdentityKeyPair getIdentityKeyPair() { - return account.getSignalProtocolStore().getIdentityKeyPair(); + return account.getIdentityKeyPair(); } public int getDeviceId() { @@ -336,7 +336,7 @@ public class Manager implements Closeable { public void updateAccountAttributes() throws IOException { accountManager.setAccountAttributes(null, - account.getSignalProtocolStore().getLocalRegistrationId(), + account.getLocalRegistrationId(), true, // set legacy pin only if no KBS master key is set account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null, @@ -482,7 +482,6 @@ public class Manager implements Closeable { var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE); account.addPreKeys(records); - account.save(); return records; } @@ -492,7 +491,6 @@ public class Manager implements Closeable { var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId); account.addSignedPreKey(record); - account.save(); return record; } @@ -535,11 +533,13 @@ public class Manager implements Closeable { 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(); @@ -552,14 +552,13 @@ public class Manager implements Closeable { 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); @@ -570,6 +569,25 @@ public class Manager implements Closeable { 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) { @@ -992,6 +1010,22 @@ public class Manager implements Closeable { return sendSelfMessage(messageBuilder); } + public Pair> sendRemoteDeleteMessage( + long targetSentTimestamp, List recipients + ) throws IOException, InvalidNumberException { + var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); + final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); + return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); + } + + public Pair> sendGroupRemoteDeleteMessage( + long targetSentTimestamp, GroupId groupId + ) throws IOException, NotAGroupMemberException, GroupNotFoundException { + var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); + final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); + return sendGroupMessage(messageBuilder, groupId); + } + public Pair> sendMessageReaction( String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List recipients ) throws IOException, InvalidNumberException { @@ -1209,17 +1243,7 @@ public class Manager implements Closeable { 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 getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { @@ -1289,22 +1313,18 @@ public class Manager implements Closeable { 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 { @@ -1374,12 +1394,6 @@ public class Manager implements Closeable { 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()); } } @@ -1392,12 +1406,6 @@ public class Manager implements Closeable { 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()); } } @@ -1410,22 +1418,14 @@ public class Manager implements Closeable { 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 handleEndSession(SignalServiceAddress source) { - account.getSignalProtocolStore().deleteAllSessions(source); + account.getSessionStore().deleteAllSessions(source.getIdentifier()); } private List handleSignalServiceDataMessage( @@ -1643,41 +1643,56 @@ public class Manager implements Closeable { } private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) { + Set 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 retryFailedReceivedMessage( final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage ) { var envelope = cachedMessage.loadEnvelope(); if (envelope == null) { - return; + return null; } SignalServiceContent content = null; + List 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( @@ -1702,8 +1717,11 @@ public class Manager implements Closeable { 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(); @@ -1734,9 +1752,9 @@ public class Manager implements Closeable { 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); @@ -1762,13 +1780,25 @@ public class Manager implements Closeable { 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(); } } @@ -1874,7 +1904,7 @@ public class Manager implements Closeable { destination, ignoreAttachments)); } - if (syncMessage.getRequest().isPresent()) { + if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) { var rm = syncMessage.getRequest().get(); if (rm.isContactsRequest()) { actions.add(SendSyncContactsAction.create()); @@ -1966,9 +1996,6 @@ public class Manager implements Closeable { 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()) { @@ -1990,8 +2017,8 @@ public class Manager implements Closeable { } 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())); } @@ -2026,8 +2053,8 @@ public class Manager implements Closeable { } 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())); } @@ -2269,7 +2296,8 @@ public class Manager implements Closeable { 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(), @@ -2367,7 +2395,6 @@ public class Manager implements Closeable { if (profileEntry != null && profileEntry.getProfile() != null) { return profileEntry.getProfile().getDisplayName(); } - return null; } @@ -2382,11 +2409,12 @@ public class Manager implements Closeable { } public List getIdentities() { - return account.getSignalProtocolStore().getIdentities(); + return account.getIdentityKeyStore().getIdentities(); } public List 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); } /** @@ -2396,8 +2424,10 @@ public class Manager implements Closeable { * @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); } /** @@ -2407,72 +2437,43 @@ public class Manager implements Closeable { * @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 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 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; } @@ -2486,6 +2487,7 @@ public class Manager implements Closeable { theirIdentityKey); } + @Deprecated public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { var canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier @@ -2493,12 +2495,14 @@ public class Manager implements Closeable { 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(); @@ -2507,6 +2511,32 @@ public class Manager implements Closeable { 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);