X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/ad509e8097733e91837338f6470441567f6970b2..6d016bcfc97c49deb76fa2c3a07ce62aee4ca8d8:/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 2ce59cdc..83a3926a 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -96,10 +96,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; -import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.StreamDetails; @@ -124,6 +121,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @@ -133,7 +131,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -160,7 +157,7 @@ public class Manager implements Closeable { private SignalServiceAccountManager accountManager; private SignalServiceMessagePipe messagePipe = null; private SignalServiceMessagePipe unidentifiedMessagePipe = null; - private boolean discoverableByPhoneNumber = true; + private final boolean discoverableByPhoneNumber = true; public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) { this.account = account; @@ -267,6 +264,21 @@ public class Manager implements Closeable { account.setProfileKey(KeyUtils.createProfileKey()); 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); + } } public void checkAccountState() throws IOException { @@ -286,7 +298,7 @@ public class Manager implements Closeable { return account.isRegistered(); } - public void register(boolean voiceVerification) throws IOException { + public void register(boolean voiceVerification, String captcha) throws IOException { account.setPassword(KeyUtils.createPassword()); // Resetting UUID, because registering doesn't work otherwise @@ -294,9 +306,9 @@ public class Manager implements Closeable { accountManager = createSignalServiceAccountManager(); if (voiceVerification) { - accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent()); + accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.fromNullable(captcha), Optional.absent()); } else { - accountManager.requestSmsVerificationCode(false, Optional.absent(), Optional.absent()); + accountManager.requestSmsVerificationCode(false, Optional.fromNullable(captcha), Optional.absent()); } account.setRegistered(false); @@ -431,10 +443,9 @@ public class Manager implements Closeable { private SignalServiceMessageSender getMessageSender() { // TODO implement ZkGroup support final ClientZkProfileOperations clientZkProfileOperations = null; - final boolean attachmentsV3 = false; final ExecutorService executor = null; return new SignalServiceMessageSender(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), - account.getDeviceId(), account.getSignalProtocolStore(), userAgent, account.isMultiDevice(), attachmentsV3, Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations, executor); + account.getDeviceId(), account.getSignalProtocolStore(), userAgent, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations, executor, ServiceConfig.MAX_ENVELOPE_SIZE); } private SignalServiceProfile getEncryptedRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { @@ -525,9 +536,12 @@ public class Manager implements Closeable { return account.getGroupStore().getGroups(); } - public long sendGroupMessage(String messageText, List attachments, - byte[] groupId) - throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { + public Pair> sendGroupMessage( + String messageText, + List attachments, + byte[] groupId + ) + throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments)); @@ -543,12 +557,12 @@ public class Manager implements Closeable { messageBuilder.withExpiration(g.messageExpirationTime); - return sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - public void sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, - long targetSentTimestamp, byte[] groupId) - throws IOException, EncapsulatedExceptions, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { + public Pair> sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor, + long targetSentTimestamp, byte[] groupId) + throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withReaction(reaction); @@ -559,10 +573,10 @@ public class Manager implements Closeable { messageBuilder.asGroupMessage(group); } final GroupInfo g = getGroupForSending(groupId); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, NotAGroupMemberException { + public Pair> sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException { SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT) .withId(groupId) .build(); @@ -574,10 +588,10 @@ public class Manager implements Closeable { g.removeMember(account.getSelfAddress()); account.getGroupStore().updateGroup(g); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); } - private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { + private Pair> sendUpdateGroupMessage(byte[] groupId, String name, Collection members, String avatarFile) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { GroupInfo g; if (groupId == null) { // Create new group @@ -622,24 +636,21 @@ public class Manager implements Closeable { SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); - sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); - return g.groupId; + final Pair> result = sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress())); + return new Pair<>(g.groupId, result.second()); } - void sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { - if (groupId == null) { - return; - } + Pair> sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { GroupInfo g = getGroupForSending(groupId); if (!g.isMember(recipient)) { - return; + throw new NotAGroupMemberException(groupId, g.name); } SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g); // Send group message only to the recipient who requested it - sendMessageLegacy(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, Collections.singleton(recipient)); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) throws AttachmentInvalidException { @@ -662,11 +673,7 @@ public class Manager implements Closeable { .withExpiration(g.messageExpirationTime); } - void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions { - if (groupId == null) { - return; - } - + Pair> sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException { SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO) .withId(groupId); @@ -674,7 +681,7 @@ public class Manager implements Closeable { .asGroupMessage(group.build()); // Send group info request message to the recipient who sent us a message with this groupId - sendMessageLegacy(messageBuilder, Collections.singleton(recipient)); + return sendMessage(messageBuilder, Collections.singleton(recipient)); } void sendReceipt(SignalServiceAddress remoteAddress, long messageId) throws IOException, UntrustedIdentityException { @@ -685,9 +692,9 @@ public class Manager implements Closeable { getMessageSender().sendReceipt(remoteAddress, getAccessFor(remoteAddress), receiptMessage); } - public long sendMessage(String messageText, List attachments, - List recipients) - throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException { + public Pair> sendMessage(String messageText, List attachments, + List recipients) + throws IOException, AttachmentInvalidException, InvalidNumberException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { List attachmentStreams = Utils.getSignalServiceAttachments(attachments); @@ -705,25 +712,25 @@ public class Manager implements Closeable { messageBuilder.withAttachments(attachmentPointers); } - return sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); + return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); } - public void sendMessageReaction(String emoji, boolean remove, String targetAuthor, - long targetSentTimestamp, List recipients) - throws IOException, EncapsulatedExceptions, InvalidNumberException { + public Pair> sendMessageReaction(String emoji, boolean remove, String targetAuthor, + long targetSentTimestamp, List recipients) + throws IOException, InvalidNumberException { SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp); final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .withReaction(reaction); - sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients)); + return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); } - public void sendEndSessionMessage(List recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException { + public Pair> sendEndSessionMessage(List recipients) throws IOException, InvalidNumberException { SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() .asEndSessionMessage(); final Collection signalServiceAddresses = getSignalServiceAddresses(recipients); try { - sendMessageLegacy(messageBuilder, signalServiceAddresses); + return sendMessage(messageBuilder, signalServiceAddresses); } catch (Exception e) { for (SignalServiceAddress address : signalServiceAddresses) { handleEndSession(address); @@ -778,7 +785,7 @@ public class Manager implements Closeable { account.save(); } - public byte[] updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { + public Pair> updateGroup(byte[] groupId, String name, List members, String avatar) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { if (groupId.length == 0) { groupId = null; } @@ -843,7 +850,7 @@ public class Manager implements Closeable { String packId = messageSender.uploadStickerManifest(manifest, packKey); try { - return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, "utf-8") + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), "utf-8")) + return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, StandardCharsets.UTF_8) + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)) .toString(); } catch (URISyntaxException e) { throw new AssertionError(e); @@ -994,16 +1001,10 @@ public class Manager implements Closeable { } private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { - ContactInfo contact = account.getContactStore().getContact(recipient); - if (contact == null || contact.profileKey == null) { + ProfileKey theirProfileKey = account.getProfileStore().getProfileKey(recipient); + if (theirProfileKey == null) { return null; } - ProfileKey theirProfileKey; - try { - theirProfileKey = new ProfileKey(Base64.decode(contact.profileKey)); - } catch (InvalidInputException | IOException e) { - throw new AssertionError(e); - } SignalProfile targetProfile; try { targetProfile = getRecipientProfile(recipient, Optional.absent(), theirProfileKey); @@ -1089,34 +1090,6 @@ public class Manager implements Closeable { } } - /** - * This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult. - */ - private long sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) - throws EncapsulatedExceptions, IOException { - final long timestamp = System.currentTimeMillis(); - messageBuilder.withTimestamp(timestamp); - List results = sendMessage(messageBuilder, recipients); - - List untrustedIdentities = new LinkedList<>(); - List unregisteredUsers = new LinkedList<>(); - List networkExceptions = new LinkedList<>(); - - for (SendMessageResult result : results) { - if (result.isUnregisteredFailure()) { - unregisteredUsers.add(new UnregisteredUserException(result.getAddress().getLegacyIdentifier(), null)); - } else if (result.isNetworkFailure()) { - networkExceptions.add(new NetworkFailureException(result.getAddress().getLegacyIdentifier(), null)); - } else if (result.getIdentityFailure() != null) { - untrustedIdentities.add(new UntrustedIdentityException("Untrusted", result.getAddress().getLegacyIdentifier(), result.getIdentityFailure().getIdentityKey())); - } - } - if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) { - throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions); - } - return timestamp; - } - private Collection getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { final Set signalServiceAddresses = new HashSet<>(numbers.size()); @@ -1126,8 +1099,10 @@ public class Manager implements Closeable { return signalServiceAddresses; } - private List sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) + private Pair> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) throws IOException { + final long timestamp = System.currentTimeMillis(); + messageBuilder.withTimestamp(timestamp); if (messagePipe == null) { messagePipe = getMessageReceiver().createMessagePipe(); } @@ -1147,10 +1122,10 @@ public class Manager implements Closeable { account.getSignalProtocolStore().saveIdentity(r.getAddress(), r.getIdentityFailure().getIdentityKey(), TrustLevel.UNTRUSTED); } } - return result; + return new Pair<>(timestamp, result); } catch (UntrustedIdentityException e) { account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); - return Collections.emptyList(); + return new Pair<>(timestamp, Collections.emptyList()); } } else { // Send to all individually, so sync messages are sent correctly @@ -1171,7 +1146,7 @@ public class Manager implements Closeable { results.add(sendMessage(address, message)); } } - return results; + return new Pair<>(timestamp, results); } } finally { if (message != null && message.isEndSession()) { @@ -1198,8 +1173,9 @@ public class Manager implements Closeable { SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript); try { + long startTime = System.currentTimeMillis(); messageSender.sendMessage(syncMessage, unidentifiedAccess); - return SendMessageResult.success(recipient, unidentifiedAccess.isPresent(), false); + return SendMessageResult.success(recipient, unidentifiedAccess.isPresent(), false, System.currentTimeMillis() - startTime); } catch (UntrustedIdentityException e) { account.getSignalProtocolStore().saveIdentity(resolveSignalServiceAddress(e.getIdentifier()), e.getIdentityKey(), TrustLevel.UNTRUSTED); return SendMessageResult.identityFailure(recipient, e.getIdentityKey()); @@ -1326,24 +1302,16 @@ public class Manager implements Closeable { } } if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) { + final ProfileKey profileKey; + try { + profileKey = new ProfileKey(message.getProfileKey().get()); + } catch (InvalidInputException e) { + throw new AssertionError(e); + } if (source.matches(account.getSelfAddress())) { - try { - this.account.setProfileKey(new ProfileKey(message.getProfileKey().get())); - } catch (InvalidInputException ignored) { - } - ContactInfo contact = account.getContactStore().getContact(source); - if (contact != null) { - contact.profileKey = Base64.encodeBytes(message.getProfileKey().get()); - account.getContactStore().updateContact(contact); - } - } else { - ContactInfo contact = account.getContactStore().getContact(source); - if (contact == null) { - contact = new ContactInfo(source); - } - contact.profileKey = Base64.encodeBytes(message.getProfileKey().get()); - account.getContactStore().updateContact(contact); + this.account.setProfileKey(profileKey); } + this.account.getProfileStore().storeProfileKey(source, profileKey); } if (message.getPreviews().isPresent()) { final List previews = message.getPreviews().get(); @@ -1398,7 +1366,15 @@ public class Manager implements Closeable { if (!envelope.isReceipt()) { try { content = decryptMessage(envelope); - } catch (Exception e) { + } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { + return; + } catch (Exception er) { + // All other errors are not recoverable, so delete the cached message + try { + Files.delete(fileEntry.toPath()); + } catch (IOException e) { + System.err.println("Failed to delete cached message file “" + fileEntry + "”: " + e.getMessage()); + } return; } List actions = handleMessage(envelope, content, ignoreAttachments); @@ -1461,6 +1437,7 @@ public class Manager implements Closeable { e.printStackTrace(); } } + account.save(); queuedActions.clear(); queuedActions = null; } @@ -1476,6 +1453,7 @@ public class Manager implements Closeable { System.err.println("Ignoring error: " + e.getMessage()); continue; } + if (envelope.hasSource()) { // Store uuid if we don't have it already SignalServiceAddress source = envelope.getSourceAddress(); @@ -1510,7 +1488,8 @@ public class Manager implements Closeable { if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { File cacheFile = null; try { - cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp()); + String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""; + cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp()); Files.delete(cacheFile.toPath()); // Try to delete directory if empty new File(getMessageCachePath()).delete(); @@ -1540,9 +1519,7 @@ public class Manager implements Closeable { if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) { SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get(); GroupInfo group = getGroup(groupInfo.getGroupId()); - if (groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked) { - return true; - } + return groupInfo.getType() == SignalServiceGroup.Type.DELIVER && group != null && group.blocked; } } return false; @@ -1679,7 +1656,7 @@ public class Manager implements Closeable { contact.color = c.getColor().get(); } if (c.getProfileKey().isPresent()) { - contact.profileKey = Base64.encodeBytes(c.getProfileKey().get().serialize()); + account.getProfileStore().storeProfileKey(address, c.getProfileKey().get()); } if (c.getVerified().isPresent()) { final VerifiedMessage verifiedMessage = c.getVerified().get(); @@ -1863,11 +1840,7 @@ public class Manager implements Closeable { verifiedMessage = new VerifiedMessage(record.getAddress(), currentIdentity.getIdentityKey(), currentIdentity.getTrustLevel().toVerifiedState(), currentIdentity.getDateAdded().getTime()); } - ProfileKey profileKey = null; - try { - profileKey = record.profileKey == null ? null : new ProfileKey(Base64.decode(record.profileKey)); - } catch (InvalidInputException ignored) { - } + ProfileKey profileKey = account.getProfileStore().getProfileKey(record.getAddress()); out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name), createContactAvatarAttachment(record.number), Optional.fromNullable(record.color), Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked,