X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/6eb486e85869995263c543db94e6b07ac2691db7..8e8eed7b061f1ed47cf9e30abb5e29ee08e3a3dc:/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 a1a4b278..096f1c07 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -16,6 +16,7 @@ */ package org.asamk.signal.manager; +import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; @@ -82,6 +83,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessagePipe; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; +import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; @@ -108,7 +110,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsO import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; -import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; @@ -119,6 +120,7 @@ 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.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.SleepTimer; @@ -160,6 +162,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; @@ -192,6 +195,15 @@ public class Manager implements Closeable { private final PinHelper pinHelper; private final AvatarStore avatarStore; private final AttachmentStore attachmentStore; + private final SignalSessionLock sessionLock = new SignalSessionLock() { + private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); + + @Override + public Lock acquire() { + LEGACY_LOCK.lock(); + return LEGACY_LOCK::unlock; + } + }; Manager( SignalAccount account, @@ -287,7 +299,7 @@ public class Manager implements Closeable { throw new NotRegisteredException(); } - var account = SignalAccount.load(pathConfig.getDataPath(), username); + var account = SignalAccount.load(pathConfig.getDataPath(), username, true); if (!account.isRegistered()) { throw new NotRegisteredException(); @@ -341,7 +353,7 @@ public class Manager implements Closeable { } public void updateAccountAttributes() throws IOException { - accountManager.setAccountAttributes(account.getDeviceName(), + accountManager.setAccountAttributes(account.getEncryptedDeviceName(), null, account.getLocalRegistrationId(), true, @@ -388,6 +400,7 @@ public class Manager implements Closeable { newProfile.getInternalServiceName(), newProfile.getAbout() == null ? "" : newProfile.getAbout(), newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(), + Optional.absent(), streamDetails); } @@ -422,10 +435,21 @@ public class Manager implements Closeable { account.setRegistered(false); } - public List getLinkedDevices() throws IOException { + public List getLinkedDevices() throws IOException { var devices = accountManager.getDevices(); account.setMultiDevice(devices.size() > 1); - return devices; + var identityKey = account.getIdentityKeyPair().getPrivateKey(); + return devices.stream().map(d -> { + String deviceName = d.getName(); + if (deviceName != null) { + try { + deviceName = DeviceNameUtil.decryptDeviceName(deviceName, identityKey); + } catch (IOException e) { + logger.debug("Failed to decrypt device name, maybe plain text?", e); + } + } + return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen()); + }).collect(Collectors.toList()); } public void removeLinkedDevices(int deviceId) throws IOException { @@ -522,6 +546,7 @@ public class Manager implements Closeable { account.getPassword(), account.getDeviceId(), account.getSignalProtocolStore(), + sessionLock, userAgent, account.isMultiDevice(), Optional.fromNullable(messagePipe), @@ -544,14 +569,6 @@ public class Manager implements Closeable { Profile getRecipientProfile( RecipientId recipientId, boolean force ) { - var profileKey = account.getProfileStore().getProfileKey(recipientId); - if (profileKey == null) { - if (force) { - // retrieve profile to get identity key - retrieveEncryptedProfile(recipientId); - } - return null; - } var profile = account.getProfileStore().getProfile(recipientId); var now = new Date().getTime(); @@ -578,7 +595,18 @@ public class Manager implements Closeable { return null; } - profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile); + var profileKey = account.getProfileStore().getProfileKey(recipientId); + if (profileKey == null) { + profile = new Profile(new Date().getTime(), + null, + null, + null, + null, + ProfileUtils.getUnidentifiedAccessMode(encryptedProfile, null), + ProfileUtils.getCapabilities(encryptedProfile)); + } else { + profile = decryptProfileAndDownloadAvatar(recipientId, profileKey, encryptedProfile); + } account.getProfileStore().storeProfile(recipientId, profile); return profile; @@ -757,10 +785,11 @@ public class Manager implements Closeable { public Pair> updateGroup( GroupId groupId, String name, List members, File avatarFile ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException { - return sendUpdateGroupMessage(groupId, - name, - members == null ? null : getSignalServiceAddresses(members), - avatarFile); + final var membersRecipientIds = members == null ? null : getSignalServiceAddresses(members); + if (membersRecipientIds != null) { + membersRecipientIds.remove(account.getSelfRecipientId()); + } + return sendUpdateGroupMessage(groupId, name, membersRecipientIds, avatarFile); } private Pair> sendUpdateGroupMessage( @@ -1089,9 +1118,11 @@ public class Manager implements Closeable { } } - SendMessageResult renewSession(RecipientId recipientId) throws IOException { + void renewSession(RecipientId recipientId) throws IOException { account.getSessionStore().archiveSessions(recipientId); - return sendNullMessage(recipientId); + if (!recipientId.equals(getSelfRecipientId())) { + sendNullMessage(recipientId); + } } public String getContactName(String number) throws InvalidNumberException { @@ -1272,11 +1303,13 @@ public class Manager implements Closeable { } private byte[] getSenderCertificate() { - // TODO support UUID capable sender certificates - // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy(); byte[] certificate; try { - certificate = accountManager.getSenderCertificate(); + if (account.isPhoneNumberShared()) { + certificate = accountManager.getSenderCertificate(); + } else { + certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy(); + } } catch (IOException e) { logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage()); return null; @@ -1491,18 +1524,12 @@ public class Manager implements Closeable { } } - private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, org.whispersystems.libsignal.UntrustedIdentityException { + private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException { var cipher = new SignalServiceCipher(account.getSelfAddress(), account.getSignalProtocolStore(), + sessionLock, certificateValidator); - try { - return cipher.decrypt(envelope); - } catch (ProtocolUntrustedIdentityException e) { - if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) { - throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause(); - } - throw new AssertionError(e); - } + return cipher.decrypt(envelope); } private void handleEndSession(RecipientId recipientId) { @@ -1746,9 +1773,9 @@ public class Manager implements Closeable { if (!envelope.isReceipt()) { try { content = decryptMessage(envelope); - } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { + } catch (ProtocolUntrustedIdentityException e) { if (!envelope.hasSource()) { - final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName(); + final var identifier = e.getSender(); final var recipientId = resolveRecipient(identifier); try { account.getMessageCache().replaceSender(cachedMessage, recipientId); @@ -1825,6 +1852,7 @@ public class Manager implements Closeable { if (envelope.hasSource()) { // Store uuid if we don't have it already + // address/uuid in envelope is sent by server resolveRecipientTrusted(envelope.getSourceAddress()); } final var notAGroupMember = isNotAGroupMember(envelope, content); @@ -1834,6 +1862,11 @@ public class Manager implements Closeable { } catch (Exception e) { exception = e; } + if (!envelope.hasSource() && content != null) { + // Store uuid if we don't have it already + // address/uuid is validated by unidentified sender certificate + resolveRecipientTrusted(content.getSender()); + } var actions = handleMessage(envelope, content, ignoreAttachments); if (exception instanceof ProtocolInvalidMessageException) { final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender()); @@ -1863,8 +1896,8 @@ public class Manager implements Closeable { handler.handleMessage(envelope, content, exception); } if (cachedMessage[0] != null) { - if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) { - final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName(); + if (exception instanceof ProtocolUntrustedIdentityException) { + final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender(); final var recipientId = resolveRecipient(identifier); queuedActions.add(new RetrieveProfileAction(recipientId)); if (!envelope.hasSource()) {