X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/d7f7c84e6c023e96064efcfcc29a78440866218a..a6562b3b7baf7ba5621d00318e9fc52fb0b3e739:/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 69e45102..fffa5013 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -41,7 +41,9 @@ import org.signal.libsignal.metadata.ProtocolLegacyMessageException; import org.signal.libsignal.metadata.ProtocolNoSessionException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; +import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.VerificationFailedException; import org.signal.zkgroup.profiles.ProfileKey; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -61,6 +63,8 @@ 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.crypto.InvalidCiphertextException; +import org.whispersystems.signalservice.api.crypto.ProfileCipher; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -446,6 +450,25 @@ public class Manager implements Signal { account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent()); } + private SignalServiceProfile getRecipientProfile(SignalServiceAddress address, Optional unidentifiedAccess) throws IOException { + SignalServiceMessagePipe pipe = unidentifiedMessagePipe != null && unidentifiedAccess.isPresent() ? unidentifiedMessagePipe + : messagePipe; + + if (pipe != null) { + try { + return pipe.getProfile(address, Optional.absent(), unidentifiedAccess, SignalServiceProfile.RequestType.PROFILE).getProfile(); + } catch (IOException ignored) { + } + } + + SignalServiceMessageReceiver receiver = getMessageReceiver(); + try { + return receiver.retrieveProfile(address, Optional.absent(), unidentifiedAccess, SignalServiceProfile.RequestType.PROFILE).getProfile(); + } catch (VerificationFailedException e) { + throw new AssertionError(e); + } + } + private Optional createGroupAvatarAttachment(byte[] groupId) throws IOException { File file = getGroupAvatarFile(groupId); if (!file.exists()) { @@ -874,31 +897,98 @@ public class Manager implements Signal { } } + private byte[] getSenderCertificate() throws IOException { + byte[] certificate = accountManager.getSenderCertificate(); + // TODO cache for a day + return certificate; + } + private byte[] getSelfUnidentifiedAccessKey() { return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey()); } - private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { - // TODO implement - return null; + private static SignalProfile decryptProfile(SignalServiceProfile encryptedProfile, ProfileKey profileKey) throws IOException { + ProfileCipher profileCipher = new ProfileCipher(profileKey); + try { + return new SignalProfile( + encryptedProfile.getIdentityKey(), + encryptedProfile.getName() == null ? null : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName()))), + encryptedProfile.getAvatar(), + encryptedProfile.getUnidentifiedAccess() == null || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess())) ? null : encryptedProfile.getUnidentifiedAccess(), + encryptedProfile.isUnrestrictedUnidentifiedAccess() + ); + } catch (InvalidCiphertextException e) { + return null; + } } - private Optional getAccessForSync() { - // TODO implement - return Optional.absent(); + private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) throws IOException { + ContactInfo contact = account.getContactStore().getContact(recipient.getNumber().get()); + if (contact == null || contact.profileKey == null) { + return null; + } + ProfileKey theirProfileKey; + try { + theirProfileKey = new ProfileKey(Base64.decode(contact.profileKey)); + } catch (InvalidInputException e) { + throw new AssertionError(e); + } + SignalProfile targetProfile = decryptProfile(getRecipientProfile(recipient, Optional.absent()), theirProfileKey); + + if (targetProfile == null || targetProfile.getUnidentifiedAccess() == null) { + return null; + } + + if (targetProfile.isUnrestrictedUnidentifiedAccess()) { + return KeyUtils.createUnrestrictedUnidentifiedAccess(); + } + + return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); + } + + private Optional getAccessForSync() throws IOException { + byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); + byte[] selfUnidentifiedAccessCertificate = getSenderCertificate(); + + if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) { + return Optional.absent(); + } + + try { + return Optional.of(new UnidentifiedAccessPair( + new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate), + new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate) + )); + } catch (InvalidCertificateException e) { + return Optional.absent(); + } } - private List> getAccessFor(Collection recipients) { + private List> getAccessFor(Collection recipients) throws IOException { List> result = new ArrayList<>(recipients.size()); for (SignalServiceAddress recipient : recipients) { - result.add(Optional.absent()); + result.add(getAccessFor(recipient)); } return result; } - private Optional getAccessFor(SignalServiceAddress recipient) { - // TODO implement - return Optional.absent(); + private Optional getAccessFor(SignalServiceAddress recipient) throws IOException { + byte[] recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient); + byte[] selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(); + byte[] selfUnidentifiedAccessCertificate = getSenderCertificate(); + + if (recipientUnidentifiedAccessKey == null || selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) { + return Optional.absent(); + } + + try { + return Optional.of(new UnidentifiedAccessPair( + new UnidentifiedAccess(recipientUnidentifiedAccessKey, selfUnidentifiedAccessCertificate), + new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate) + )); + } catch (InvalidCertificateException e) { + return Optional.absent(); + } } private void sendSyncMessage(SignalServiceSyncMessage message) @@ -945,6 +1035,12 @@ public class Manager implements Signal { return Collections.emptyList(); } + if (messagePipe == null) { + messagePipe = getMessageReceiver().createMessagePipe(); + } + if (unidentifiedMessagePipe == null) { + unidentifiedMessagePipe = getMessageReceiver().createUnidentifiedMessagePipe(); + } SignalServiceDataMessage message = null; try { SignalServiceMessageSender messageSender = getMessageSender();