]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/Manager.java
Update argparse4j
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
index 05aec952aa02bfe33544883d376dc78eb93bf8a0..096f1c076fbee7b736ec000039920a789722d778 100644 (file)
@@ -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,8 @@ public class Manager implements Closeable {
     }
 
     public void updateAccountAttributes() throws IOException {
-        accountManager.setAccountAttributes(null,
+        accountManager.setAccountAttributes(account.getEncryptedDeviceName(),
+                null,
                 account.getLocalRegistrationId(),
                 true,
                 // set legacy pin only if no KBS master key is set
@@ -387,6 +400,7 @@ public class Manager implements Closeable {
                     newProfile.getInternalServiceName(),
                     newProfile.getAbout() == null ? "" : newProfile.getAbout(),
                     newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
+                    Optional.absent(),
                     streamDetails);
         }
 
@@ -421,10 +435,21 @@ public class Manager implements Closeable {
         account.setRegistered(false);
     }
 
-    public List<DeviceInfo> getLinkedDevices() throws IOException {
+    public List<Device> 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 {
@@ -521,6 +546,7 @@ public class Manager implements Closeable {
                 account.getPassword(),
                 account.getDeviceId(),
                 account.getSignalProtocolStore(),
+                sessionLock,
                 userAgent,
                 account.isMultiDevice(),
                 Optional.fromNullable(messagePipe),
@@ -543,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();
@@ -577,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;
@@ -756,10 +785,11 @@ public class Manager implements Closeable {
     public Pair<GroupId, List<SendMessageResult>> updateGroup(
             GroupId groupId, String name, List<String> 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<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
@@ -1088,6 +1118,13 @@ public class Manager implements Closeable {
         }
     }
 
+    void renewSession(RecipientId recipientId) throws IOException {
+        account.getSessionStore().archiveSessions(recipientId);
+        if (!recipientId.equals(getSelfRecipientId())) {
+            sendNullMessage(recipientId);
+        }
+    }
+
     public String getContactName(String number) throws InvalidNumberException {
         var contact = account.getContactStore().getContact(canonicalizeAndResolveRecipient(number));
         return contact == null || contact.getName() == null ? "" : contact.getName();
@@ -1266,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;
@@ -1468,20 +1507,31 @@ 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 {
-        var cipher = new SignalServiceCipher(account.getSelfAddress(),
-                account.getSignalProtocolStore(),
-                certificateValidator);
+    private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
+        var messageSender = createMessageSender();
+
+        final var address = resolveSignalServiceAddress(recipientId);
         try {
-            return cipher.decrypt(envelope);
-        } catch (ProtocolUntrustedIdentityException e) {
-            if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
-                throw (org.whispersystems.libsignal.UntrustedIdentityException) e.getCause();
+            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));
             }
-            throw new AssertionError(e);
+        } catch (UntrustedIdentityException e) {
+            return SendMessageResult.identityFailure(address, e.getIdentityKey());
         }
     }
 
+    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);
+        return cipher.decrypt(envelope);
+    }
+
     private void handleEndSession(RecipientId recipientId) {
         account.getSessionStore().deleteAllSessions(recipientId);
     }
@@ -1723,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);
@@ -1802,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);
@@ -1811,7 +1862,17 @@ 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());
+                    logger.debug("Received invalid message, queuing renew session action.");
+                    actions.add(new RenewSessionAction(sender));
+                }
                 if (hasCaughtUpWithOldMessages) {
                     for (var action : actions) {
                         try {
@@ -1835,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()) {