X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/e1134d832a1460cc5e1bcc7edf5e908bf013e0bd..644aacf59516dd1ecafc58878d287557f20dc112:/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 38c43753..c4f32460 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -114,6 +114,7 @@ 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.KeysMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; @@ -121,9 +122,9 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; 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.MissingConfigurationException; +import org.whispersystems.signalservice.api.storage.StorageKey; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.SleepTimer; @@ -138,6 +139,7 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import org.whispersystems.signalservice.internal.util.Hex; +import org.whispersystems.signalservice.internal.util.Util; import java.io.Closeable; import java.io.File; @@ -217,6 +219,7 @@ public class Manager implements Closeable { account.getDeviceId()), userAgent, groupsV2Operations, + ServiceConfig.AUTOMATIC_NETWORK_RETRY, timer); this.groupsV2Api = accountManager.getGroupsV2Api(); final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager); @@ -232,7 +235,8 @@ public class Manager implements Closeable { userAgent, null, timer, - clientZkProfileOperations); + clientZkProfileOperations, + ServiceConfig.AUTOMATIC_NETWORK_RETRY); this.account.setResolver(this::resolveSignalServiceAddress); @@ -325,11 +329,9 @@ public class Manager implements Closeable { */ public Map areUsersRegistered(Set numbers) throws IOException { // Note "contactDetails" has no optionals. It only gives us info on users who are registered - List contactDetails = this.accountManager.getContacts(numbers); + Map contactDetails = getRegisteredUsers(numbers); - Set registeredUsers = contactDetails.stream() - .map(ContactTokenDetails::getNumber) - .collect(Collectors.toSet()); + Set registeredUsers = contactDetails.keySet(); return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains)); } @@ -352,10 +354,19 @@ public class Manager implements Closeable { * if it's Optional.absent(), the avatar will be removed */ public void setProfile(String name, Optional avatar) throws IOException { + // TODO + String about = null; + String aboutEmoji = null; + try (final StreamDetails streamDetails = avatar == null ? avatarStore.retrieveProfileAvatar(getSelfAddress()) : avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) { - accountManager.setVersionedProfile(account.getUuid(), account.getProfileKey(), name, streamDetails); + accountManager.setVersionedProfile(account.getUuid(), + account.getProfileKey(), + name, + about, + aboutEmoji, + streamDetails); } if (avatar != null) { @@ -366,6 +377,11 @@ public class Manager implements Closeable { avatarStore.deleteProfileAvatar(getSelfAddress()); } } + + try { + sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE)); + } catch (UntrustedIdentityException ignored) { + } } public void unregister() throws IOException { @@ -373,6 +389,7 @@ 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()); + accountManager.deleteAccount(); account.setRegistered(false); account.save(); @@ -412,6 +429,9 @@ public class Manager implements Closeable { } public void setRegistrationLockPin(Optional pin) throws IOException, UnauthenticatedResponseException { + if (!account.isMasterDevice()) { + throw new RuntimeException("Only master device can set a PIN"); + } if (pin.isPresent()) { final MasterKey masterKey = account.getPinMasterKey() != null ? account.getPinMasterKey() @@ -491,11 +511,18 @@ public class Manager implements Closeable { Optional.absent(), clientZkProfileOperations, executor, - ServiceConfig.MAX_ENVELOPE_SIZE); + ServiceConfig.MAX_ENVELOPE_SIZE, + ServiceConfig.AUTOMATIC_NETWORK_RETRY); } private SignalProfile getRecipientProfile( SignalServiceAddress address + ) { + return getRecipientProfile(address, false); + } + + private SignalProfile getRecipientProfile( + SignalServiceAddress address, boolean force ) { SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address); if (profileEntry == null) { @@ -504,7 +531,9 @@ public class Manager implements Closeable { long now = new Date().getTime(); // Profiles are cached for 24h before retrieving them again if (!profileEntry.isRequestPending() && ( - profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000 + force + || profileEntry.getProfile() == null + || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000 )) { profileEntry.setRequestPending(true); final SignalServiceProfile encryptedProfile; @@ -775,12 +804,10 @@ public class Manager implements Closeable { newE164Members.add(member.getNumber().get()); } - final List contacts = accountManager.getContacts(newE164Members); - if (contacts.size() != newE164Members.size()) { + final Map registeredUsers = getRegisteredUsers(newE164Members); + if (registeredUsers.size() != newE164Members.size()) { // Some of the new members are not registered on Signal - for (ContactTokenDetails contact : contacts) { - newE164Members.remove(contact.getNumber()); - } + newE164Members.removeAll(registeredUsers.keySet()); throw new IOException("Failed to add members " + String.join(", ", newE164Members) + " to group: Not registered on Signal"); @@ -953,7 +980,7 @@ public class Manager implements Closeable { return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); } - public Pair> sendSelfMessage( + public Pair sendSelfMessage( String messageText, List attachments ) throws IOException, AttachmentInvalidException { final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() @@ -1155,6 +1182,18 @@ public class Manager implements Closeable { } } + void requestSyncKeys() throws IOException { + SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder() + .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS) + .build(); + SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); + try { + sendSyncMessage(message); + } catch (UntrustedIdentityException e) { + throw new AssertionError(e); + } + } + private byte[] getSenderCertificate() { // TODO support UUID capable sender certificates // byte[] certificate = accountManager.getSenderCertificateForPhoneNumberPrivacy(); @@ -1184,28 +1223,29 @@ public class Manager implements Closeable { private Collection getSignalServiceAddresses(Collection numbers) throws InvalidNumberException { final Set signalServiceAddresses = new HashSet<>(numbers.size()); - final Set missingUuids = new HashSet<>(); + final Set addressesMissingUuid = new HashSet<>(); for (String number : numbers) { final SignalServiceAddress resolvedAddress = canonicalizeAndResolveSignalServiceAddress(number); if (resolvedAddress.getUuid().isPresent()) { signalServiceAddresses.add(resolvedAddress); } else { - missingUuids.add(resolvedAddress); + addressesMissingUuid.add(resolvedAddress); } } + final Set numbersMissingUuid = addressesMissingUuid.stream() + .map(a -> a.getNumber().get()) + .collect(Collectors.toSet()); Map registeredUsers; try { - registeredUsers = accountManager.getRegisteredUsers(getIasKeyStore(), - missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()), - CDS_MRENCLAVE); - } catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) { + registeredUsers = getRegisteredUsers(numbersMissingUuid); + } catch (IOException e) { logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage()); - registeredUsers = new HashMap<>(); + registeredUsers = Map.of(); } - for (SignalServiceAddress address : missingUuids) { + for (SignalServiceAddress address : addressesMissingUuid) { final String number = address.getNumber().get(); if (registeredUsers.containsKey(number)) { final SignalServiceAddress newAddress = resolveSignalServiceAddress(new SignalServiceAddress( @@ -1220,6 +1260,14 @@ public class Manager implements Closeable { return signalServiceAddresses; } + private Map getRegisteredUsers(final Set numbersMissingUuid) throws IOException { + try { + return accountManager.getRegisteredUsers(getIasKeyStore(), numbersMissingUuid, CDS_MRENCLAVE); + } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) { + throw new IOException(e); + } + } + private Pair> sendMessage( SignalServiceDataMessage.Builder messageBuilder, Collection recipients ) throws IOException { @@ -1278,7 +1326,7 @@ public class Manager implements Closeable { } } - private Pair> sendSelfMessage( + private Pair sendSelfMessage( SignalServiceDataMessage.Builder messageBuilder ) throws IOException { final long timestamp = System.currentTimeMillis(); @@ -1294,7 +1342,7 @@ public class Manager implements Closeable { SignalServiceDataMessage message = messageBuilder.build(); final SendMessageResult result = sendSelfMessage(message); - return new Pair<>(timestamp, List.of(result)); + return new Pair<>(timestamp, result); } finally { account.save(); } @@ -1356,10 +1404,13 @@ public class Manager implements Closeable { if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) { org.whispersystems.libsignal.UntrustedIdentityException identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e .getCause(); - account.getSignalProtocolStore() - .saveIdentity(resolveSignalServiceAddress(identityException.getName()), - identityException.getUntrustedIdentity(), - TrustLevel.UNTRUSTED); + final IdentityKey untrustedIdentity = identityException.getUntrustedIdentity(); + if (untrustedIdentity != null) { + account.getSignalProtocolStore() + .saveIdentity(resolveSignalServiceAddress(identityException.getName()), + untrustedIdentity, + TrustLevel.UNTRUSTED); + } throw identityException; } throw new AssertionError(e); @@ -1988,6 +2039,21 @@ public class Manager implements Closeable { account.getStickerStore().updateSticker(sticker); } } + if (syncMessage.getFetchType().isPresent()) { + switch (syncMessage.getFetchType().get()) { + case LOCAL_PROFILE: + getRecipientProfile(getSelfAddress(), true); + case STORAGE_MANIFEST: + // TODO + } + } + if (syncMessage.getKeys().isPresent()) { + final KeysMessage keysMessage = syncMessage.getKeys().get(); + if (keysMessage.getStorageService().isPresent()) { + final StorageKey storageKey = keysMessage.getStorageService().get(); + account.setStorageKey(storageKey); + } + } if (syncMessage.getConfiguration().isPresent()) { // TODO } @@ -2279,8 +2345,20 @@ public class Manager implements Closeable { return account.getContactStore().getContacts(); } - public ContactInfo getContact(String number) { - return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number)); + public String getContactOrProfileName(String number) { + final SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(number); + + final ContactInfo contact = account.getContactStore().getContact(address); + if (contact != null && !Util.isEmpty(contact.name)) { + return contact.name; + } + + final SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address); + if (profileEntry != null && profileEntry.getProfile() != null) { + return profileEntry.getProfile().getName(); + } + + return null; } public GroupInfo getGroup(GroupId groupId) {