X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/5b8c0c4e2de174de892af65b75a7e4818cbc29c7..6eb486e85869995263c543db94e6b07ac2691db7:/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 3c2d130d..a1a4b278 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -118,6 +118,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; +import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.SleepTimer; @@ -316,11 +317,9 @@ public class Manager implements Closeable { public void checkAccountState() throws IOException { if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) { refreshPreKeys(); - account.save(); } if (account.getUuid() == null) { account.setUuid(accountManager.getOwnUuid()); - account.save(); } updateAccountAttributes(); } @@ -342,7 +341,8 @@ public class Manager implements Closeable { } public void updateAccountAttributes() throws IOException { - accountManager.setAccountAttributes(null, + accountManager.setAccountAttributes(account.getDeviceName(), + null, account.getLocalRegistrationId(), true, // set legacy pin only if no KBS master key is set @@ -355,18 +355,22 @@ public class Manager implements Closeable { } /** - * @param name if null, the previous name will be kept + * @param givenName if null, the previous givenName will be kept + * @param familyName if null, the previous familyName will be kept * @param about if null, the previous about text will be kept * @param aboutEmoji if null, the previous about emoji will be kept * @param avatar if avatar is null the image from the local avatar store is used (if present), - * if it's Optional.absent(), the avatar will be removed */ - public void setProfile(String name, String about, String aboutEmoji, Optional avatar) throws IOException { + public void setProfile( + String givenName, final String familyName, String about, String aboutEmoji, Optional avatar + ) throws IOException { var profile = getRecipientProfile(account.getSelfRecipientId()); var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile); - if (name != null) { - builder.withGivenName(name); - builder.withFamilyName(null); + if (givenName != null) { + builder.withGivenName(givenName); + } + if (familyName != null) { + builder.withFamilyName(familyName); } if (about != null) { builder.withAbout(about); @@ -382,8 +386,8 @@ public class Manager implements Closeable { accountManager.setVersionedProfile(account.getUuid(), account.getProfileKey(), newProfile.getInternalServiceName(), - newProfile.getAbout(), - newProfile.getAboutEmoji(), + newProfile.getAbout() == null ? "" : newProfile.getAbout(), + newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(), streamDetails); } @@ -408,16 +412,19 @@ public class Manager implements Closeable { // If this is the master device, other users can't send messages to this number anymore. // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore. accountManager.setGcmId(Optional.absent()); + + account.setRegistered(false); + } + + public void deleteAccount() throws IOException { accountManager.deleteAccount(); account.setRegistered(false); - account.save(); } public List getLinkedDevices() throws IOException { var devices = accountManager.getDevices(); account.setMultiDevice(devices.size() > 1); - account.save(); return devices; } @@ -425,7 +432,6 @@ public class Manager implements Closeable { accountManager.removeDevice(deviceId); var devices = accountManager.getDevices(); account.setMultiDevice(devices.size() > 1); - account.save(); } public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { @@ -444,7 +450,6 @@ public class Manager implements Closeable { Optional.of(account.getProfileKey().serialize()), verificationCode); account.setMultiDevice(true); - account.save(); } public void setRegistrationLockPin(Optional pin) throws IOException, UnauthenticatedResponseException { @@ -458,8 +463,7 @@ public class Manager implements Closeable { pinHelper.setRegistrationLockPin(pin.get(), masterKey); - account.setRegistrationLockPin(pin.get()); - account.setPinMasterKey(masterKey); + account.setRegistrationLockPin(pin.get(), masterKey); } else { // Remove legacy registration lock accountManager.removeRegistrationLockV1(); @@ -467,10 +471,8 @@ public class Manager implements Closeable { // Remove KBS Pin pinHelper.removeRegistrationLockPin(); - account.setRegistrationLockPin(null); - account.setPinMasterKey(null); + account.setRegistrationLockPin(null, null); } - account.save(); } void refreshPreKeys() throws IOException { @@ -531,12 +533,6 @@ public class Manager implements Closeable { ServiceConfig.AUTOMATIC_NETWORK_RETRY); } - public Profile getRecipientProfile( - SignalServiceAddress address - ) { - return getRecipientProfile(resolveRecipient(address), false); - } - public Profile getRecipientProfile( RecipientId recipientId ) { @@ -604,10 +600,14 @@ public class Manager implements Closeable { final var profile = profileAndCredential.getProfile(); try { - account.getIdentityKeyStore() + var newIdentity = account.getIdentityKeyStore() .saveIdentity(recipientId, new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())), new Date()); + + if (newIdentity) { + account.getSessionStore().archiveSessions(recipientId); + } } catch (InvalidKeyException ignored) { logger.warn("Got invalid identity key in profile for {}", resolveSignalServiceAddress(recipientId).getLegacyIdentifier()); @@ -712,9 +712,10 @@ public class Manager implements Closeable { public Pair> sendGroupMessageReaction( String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException { + var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor); var reaction = new SignalServiceDataMessage.Reaction(emoji, remove, - canonicalizeAndResolveSignalServiceAddress(targetAuthor), + resolveSignalServiceAddress(targetAuthorRecipientId), targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); @@ -947,14 +948,15 @@ public class Manager implements Closeable { } g = (GroupInfoV1) group; - if (!g.isMember(resolveRecipient(recipient))) { + final var recipientId = resolveRecipient(recipient); + if (!g.isMember(recipientId)) { throw new NotAGroupMemberException(groupId, g.name); } var messageBuilder = getGroupUpdateMessageBuilder(g); // Send group message only to the recipient who requested it - return sendMessage(messageBuilder, Set.of(resolveRecipient(recipient))); + return sendMessage(messageBuilder, Set.of(recipientId)); } private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { @@ -1064,9 +1066,10 @@ public class Manager implements Closeable { public Pair> sendMessageReaction( String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List recipients ) throws IOException, InvalidNumberException { + var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor); var reaction = new SignalServiceDataMessage.Reaction(emoji, remove, - canonicalizeAndResolveSignalServiceAddress(targetAuthor), + resolveSignalServiceAddress(targetAuthorRecipientId), targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); @@ -1082,25 +1085,36 @@ public class Manager implements Closeable { for (var address : signalServiceAddresses) { handleEndSession(address); } - account.save(); throw e; } } + SendMessageResult renewSession(RecipientId recipientId) throws IOException { + account.getSessionStore().archiveSessions(recipientId); + return sendNullMessage(recipientId); + } + public String getContactName(String number) throws InvalidNumberException { var contact = account.getContactStore().getContact(canonicalizeAndResolveRecipient(number)); return contact == null || contact.getName() == null ? "" : contact.getName(); } - public void setContactName(String number, String name) throws InvalidNumberException { + public void setContactName(String number, String name) throws InvalidNumberException, NotMasterDeviceException { + if (!account.isMasterDevice()) { + throw new NotMasterDeviceException(); + } final var recipientId = canonicalizeAndResolveRecipient(number); var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); account.getContactStore().storeContact(recipientId, builder.withName(name).build()); - account.save(); } - public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException { + public void setContactBlocked( + String number, boolean blocked + ) throws InvalidNumberException, NotMasterDeviceException { + if (!account.isMasterDevice()) { + throw new NotMasterDeviceException(); + } setContactBlocked(canonicalizeAndResolveRecipient(number), blocked); } @@ -1108,7 +1122,6 @@ public class Manager implements Closeable { var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build()); - account.save(); } public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException { @@ -1119,7 +1132,6 @@ public class Manager implements Closeable { group.setBlocked(blocked); account.getGroupStore().updateGroup(group); - account.save(); } private void setExpirationTimer(RecipientId recipientId, int messageExpirationTimer) { @@ -1146,7 +1158,6 @@ public class Manager implements Closeable { var recipientId = canonicalizeAndResolveRecipient(number); setExpirationTimer(recipientId, messageExpirationTimer); sendExpirationTimerUpdate(recipientId); - account.save(); } /** @@ -1179,7 +1190,6 @@ public class Manager implements Closeable { var sticker = new Sticker(StickerPackId.deserialize(Hex.fromStringCondensed(packId)), packKey); account.getStickerStore().updateSticker(sticker); - account.save(); try { return new URI("https", @@ -1193,7 +1203,15 @@ public class Manager implements Closeable { } } - void requestSyncGroups() throws IOException { + public void requestAllSyncData() throws IOException { + requestSyncGroups(); + requestSyncContacts(); + requestSyncBlocked(); + requestSyncConfiguration(); + requestSyncKeys(); + } + + private void requestSyncGroups() throws IOException { var r = SignalServiceProtos.SyncMessage.Request.newBuilder() .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS) .build(); @@ -1205,7 +1223,7 @@ public class Manager implements Closeable { } } - void requestSyncContacts() throws IOException { + private void requestSyncContacts() throws IOException { var r = SignalServiceProtos.SyncMessage.Request.newBuilder() .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS) .build(); @@ -1217,7 +1235,7 @@ public class Manager implements Closeable { } } - void requestSyncBlocked() throws IOException { + private void requestSyncBlocked() throws IOException { var r = SignalServiceProtos.SyncMessage.Request.newBuilder() .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED) .build(); @@ -1229,7 +1247,7 @@ public class Manager implements Closeable { } } - void requestSyncConfiguration() throws IOException { + private void requestSyncConfiguration() throws IOException { var r = SignalServiceProtos.SyncMessage.Request.newBuilder() .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION) .build(); @@ -1241,7 +1259,7 @@ public class Manager implements Closeable { } } - void requestSyncKeys() throws IOException { + private void requestSyncKeys() throws IOException { var r = SignalServiceProtos.SyncMessage.Request.newBuilder() .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS) .build(); @@ -1277,7 +1295,7 @@ public class Manager implements Closeable { final var addressesMissingUuid = new HashSet(); for (var number : numbers) { - final var resolvedAddress = canonicalizeAndResolveSignalServiceAddress(number); + final var resolvedAddress = resolveSignalServiceAddress(canonicalizeAndResolveRecipient(number)); if (resolvedAddress.getUuid().isPresent()) { signalServiceAddresses.add(resolvedAddress); } else { @@ -1311,10 +1329,20 @@ public class Manager implements Closeable { return signalServiceAddresses.stream().map(this::resolveRecipient).collect(Collectors.toSet()); } - private Map getRegisteredUsers(final Set numbersMissingUuid) throws IOException { + private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException { + final var address = resolveSignalServiceAddress(recipientId); + if (!address.getNumber().isPresent()) { + return recipientId; + } + final var number = address.getNumber().get(); + final var uuidMap = getRegisteredUsers(Set.of(number)); + return resolveRecipientTrusted(new SignalServiceAddress(uuidMap.getOrDefault(number, null), number)); + } + + private Map getRegisteredUsers(final Set numbers) throws IOException { try { return accountManager.getRegisteredUsers(ServiceConfig.getIasKeyStore(), - numbersMissingUuid, + numbers, serviceEnvironmentConfig.getCdsMrenclave()); } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) { throw new IOException(e); @@ -1346,10 +1374,12 @@ public class Manager implements Closeable { for (var r : result) { if (r.getIdentityFailure() != null) { - account.getIdentityKeyStore(). - saveIdentity(resolveRecipient(r.getAddress()), - r.getIdentityFailure().getIdentityKey(), - new Date()); + final var recipientId = resolveRecipient(r.getAddress()); + final var newIdentity = account.getIdentityKeyStore() + .saveIdentity(recipientId, r.getIdentityFailure().getIdentityKey(), new Date()); + if (newIdentity) { + account.getSessionStore().archiveSessions(recipientId); + } } } @@ -1366,7 +1396,7 @@ public class Manager implements Closeable { final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; messageBuilder.withExpiration(expirationTime); message = messageBuilder.build(); - results.add(sendMessage(resolveSignalServiceAddress(recipientId), message)); + results.add(sendMessage(recipientId, message)); } return new Pair<>(timestamp, results); } @@ -1376,7 +1406,6 @@ public class Manager implements Closeable { handleEndSession(recipient); } } - account.save(); } } @@ -1387,27 +1416,24 @@ public class Manager implements Closeable { messageBuilder.withTimestamp(timestamp); getOrCreateMessagePipe(); getOrCreateUnidentifiedMessagePipe(); - try { - final var recipientId = account.getSelfRecipientId(); + final var recipientId = account.getSelfRecipientId(); - final var contact = account.getContactStore().getContact(recipientId); - final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; - messageBuilder.withExpiration(expirationTime); + final var contact = account.getContactStore().getContact(recipientId); + final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; + messageBuilder.withExpiration(expirationTime); - var message = messageBuilder.build(); - final var result = sendSelfMessage(message); - return new Pair<>(timestamp, result); - } finally { - account.save(); - } + var message = messageBuilder.build(); + final var result = sendSelfMessage(message); + return new Pair<>(timestamp, result); } private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException { var messageSender = createMessageSender(); - var recipient = account.getSelfAddress(); + var recipientId = account.getSelfRecipientId(); - final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(resolveRecipient(recipient)); + final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipientId); + var recipient = resolveSignalServiceAddress(recipientId); var transcript = new SentTranscriptMessage(Optional.of(recipient), message.getTimestamp(), message, @@ -1429,14 +1455,37 @@ public class Manager implements Closeable { } private SendMessageResult sendMessage( - SignalServiceAddress address, SignalServiceDataMessage message + RecipientId recipientId, SignalServiceDataMessage message ) throws IOException { var messageSender = createMessageSender(); + final var address = resolveSignalServiceAddress(recipientId); + try { + try { + return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId), message); + } catch (UnregisteredUserException e) { + final var newRecipientId = refreshRegisteredUser(recipientId); + return messageSender.sendMessage(resolveSignalServiceAddress(newRecipientId), + unidentifiedAccessHelper.getAccessFor(newRecipientId), + message); + } + } catch (UntrustedIdentityException e) { + return SendMessageResult.identityFailure(address, e.getIdentityKey()); + } + } + + private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException { + var messageSender = createMessageSender(); + + final var address = resolveSignalServiceAddress(recipientId); try { - return messageSender.sendMessage(address, - unidentifiedAccessHelper.getAccessFor(resolveRecipient(address)), - message); + try { + return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId)); + } catch (UnregisteredUserException e) { + final var newRecipientId = refreshRegisteredUser(recipientId); + final var newAddress = resolveSignalServiceAddress(newRecipientId); + return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId)); + } } catch (UntrustedIdentityException e) { return SendMessageResult.identityFailure(address, e.getIdentityKey()); } @@ -1658,11 +1707,11 @@ public class Manager implements Closeable { private void storeProfileKeysFromMembers(final DecryptedGroup group) { for (var member : group.getMembersList()) { - final var address = resolveRecipient(new SignalServiceAddress(UuidUtil.parseOrThrow(member.getUuid() - .toByteArray()), null)); + final var uuid = UuidUtil.parseOrThrow(member.getUuid().toByteArray()); + final var recipientId = account.getRecipientStore().resolveRecipient(uuid); try { account.getProfileStore() - .storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray())); + .storeProfileKey(recipientId, new ProfileKey(member.getProfileKey().toByteArray())); } catch (InvalidInputException ignored) { } } @@ -1699,8 +1748,8 @@ public class Manager implements Closeable { content = decryptMessage(envelope); } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { if (!envelope.hasSource()) { - final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) e) - .getName()); + final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName(); + final var recipientId = resolveRecipient(identifier); try { account.getMessageCache().replaceSender(cachedMessage, recipientId); } catch (IOException ioException) { @@ -1715,7 +1764,6 @@ public class Manager implements Closeable { } actions = handleMessage(envelope, content, ignoreAttachments); } - account.save(); handler.handleMessage(envelope, content, null); cachedMessage.delete(); return actions; @@ -1763,7 +1811,6 @@ public class Manager implements Closeable { logger.warn("Message action failed.", e); } } - account.save(); queuedActions.clear(); queuedActions = null; } @@ -1788,6 +1835,11 @@ public class Manager implements Closeable { exception = e; } var actions = handleMessage(envelope, content, ignoreAttachments); + if (exception instanceof ProtocolInvalidMessageException) { + final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender()); + logger.debug("Received invalid message, queuing renew session action."); + actions.add(new RenewSessionAction(sender)); + } if (hasCaughtUpWithOldMessages) { for (var action : actions) { try { @@ -1803,7 +1855,6 @@ public class Manager implements Closeable { queuedActions.addAll(actions); } } - account.save(); if (isMessageBlocked(envelope, content)) { logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp()); } else if (notAGroupMember) { @@ -1813,8 +1864,8 @@ public class Manager implements Closeable { } if (cachedMessage[0] != null) { if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) { - final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) exception) - .getName()); + final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName(); + final var recipientId = resolveRecipient(identifier); queuedActions.add(new RetrieveProfileAction(recipientId)); if (!envelope.hasSource()) { try { @@ -2529,14 +2580,6 @@ public class Manager implements Closeable { theirIdentityKey); } - @Deprecated - public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException { - var canonicalizedNumber = UuidUtil.isUuid(identifier) - ? identifier - : PhoneNumberFormatter.formatNumber(identifier, account.getUsername()); - return resolveSignalServiceAddress(canonicalizedNumber); - } - @Deprecated public SignalServiceAddress resolveSignalServiceAddress(String identifier) { var address = Utils.getSignalServiceAddressFromIdentifier(identifier);