]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java
Update libsignal-service
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / UnidentifiedAccessHelper.java
index 0b82fb18ef03b83b041bc08c202f6b5e658651b7..cd2719a1f12d60f591cc8410a914b9de5fbf7371 100644 (file)
 package org.asamk.signal.manager.helper;
 
+import org.asamk.signal.manager.api.PhoneNumberSharingMode;
+import org.asamk.signal.manager.api.Profile;
+import org.asamk.signal.manager.internal.SignalDependencies;
+import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.jetbrains.annotations.Nullable;
 import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
-import org.whispersystems.libsignal.util.guava.Optional;
+import org.signal.libsignal.metadata.certificate.SenderCertificate;
+import org.signal.libsignal.zkgroup.profiles.ProfileKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
-import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
 
-import java.util.Collection;
+import java.io.IOException;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.concurrent.TimeUnit;
 
-import static org.whispersystems.signalservice.internal.util.Util.getSecretBytes;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
 
 public class UnidentifiedAccessHelper {
 
-    private final SelfProfileKeyProvider selfProfileKeyProvider;
+    private static final Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
+    private static final long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
+    private static final byte[] UNRESTRICTED_KEY = new byte[16];
 
-    private final ProfileKeyProvider profileKeyProvider;
+    private final SignalAccount account;
+    private final SignalDependencies dependencies;
+    private final Context context;
 
-    private final ProfileProvider profileProvider;
+    private SenderCertificate privacySenderCertificate;
+    private SenderCertificate senderCertificate;
 
-    private final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider;
+    public UnidentifiedAccessHelper(final Context context) {
+        this.account = context.getAccount();
+        this.dependencies = context.getDependencies();
+        this.context = context;
+    }
 
-    public UnidentifiedAccessHelper(
-            final SelfProfileKeyProvider selfProfileKeyProvider,
-            final ProfileKeyProvider profileKeyProvider,
-            final ProfileProvider profileProvider,
-            final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider
-    ) {
-        this.selfProfileKeyProvider = selfProfileKeyProvider;
-        this.profileKeyProvider = profileKeyProvider;
-        this.profileProvider = profileProvider;
-        this.senderCertificateProvider = senderCertificateProvider;
+    public void rotateSenderCertificates() {
+        privacySenderCertificate = null;
+        senderCertificate = null;
     }
 
-    private byte[] getSelfUnidentifiedAccessKey() {
-        return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
+    public List<SealedSenderAccess> getSealedSenderAccessFor(List<RecipientId> recipients) {
+        return recipients.stream().map(this::getAccessFor).map(SealedSenderAccess::forIndividual).toList();
     }
 
-    public byte[] getTargetUnidentifiedAccessKey(RecipientId recipient) {
-        var targetProfile = profileProvider.getProfile(recipient);
-        if (targetProfile == null) {
+    public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient) {
+        return getSealedSenderAccessFor(recipient, false);
+    }
+
+    public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient, boolean noRefresh) {
+        return SealedSenderAccess.forIndividual(getAccessFor(recipient, noRefresh));
+    }
+
+    public List<UnidentifiedAccess> getAccessFor(List<RecipientId> recipients) {
+        return recipients.stream().map(this::getAccessFor).toList();
+    }
+
+    private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipient) {
+        return getAccessFor(recipient, false);
+    }
+
+    private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipientId, boolean noRefresh) {
+        var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh);
+        if (recipientUnidentifiedAccessKey == null) {
+            logger.trace("Unidentified access not available for {}", recipientId);
             return null;
         }
 
-        switch (targetProfile.getUnidentifiedAccessMode()) {
-            case ENABLED:
-                var theirProfileKey = profileKeyProvider.getProfileKey(recipient);
-                if (theirProfileKey == null) {
-                    return null;
-                }
-
-                return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
-            case UNRESTRICTED:
-                return createUnrestrictedUnidentifiedAccess();
-            default:
-                return null;
+        var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
+        if (selfUnidentifiedAccessKey == null) {
+            logger.trace("Unidentified access not available for self");
+            return null;
         }
-    }
 
-    public Optional<UnidentifiedAccessPair> getAccessForSync() {
-        var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
-        var selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate();
-
-        if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
-            return Optional.absent();
+        var senderCertificate = getSenderCertificateFor(recipientId);
+        if (senderCertificate == null) {
+            logger.trace("Unidentified access not available due to missing sender certificate");
+            return null;
         }
 
         try {
-            return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
-                    selfUnidentifiedAccessCertificate),
-                    new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
+            return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false);
         } catch (InvalidCertificateException e) {
-            return Optional.absent();
+            return null;
         }
     }
 
-    public List<Optional<UnidentifiedAccessPair>> getAccessFor(Collection<RecipientId> recipients) {
-        return recipients.stream().map(this::getAccessFor).collect(Collectors.toList());
+    private byte[] getSenderCertificateFor(final RecipientId recipientId) {
+        final var sharingMode = account.getConfigurationStore().getPhoneNumberSharingMode();
+        if (sharingMode == PhoneNumberSharingMode.EVERYBODY || (
+                sharingMode == PhoneNumberSharingMode.CONTACTS
+                        && account.getContactStore().getContact(recipientId) != null
+        )) {
+            logger.trace("Using normal sender certificate for message to {}", recipientId);
+            return getSenderCertificate();
+        } else {
+            logger.trace("Using phone number privacy sender certificate for message to {}", recipientId);
+            return getSenderCertificateForPhoneNumberPrivacy();
+        }
     }
 
-    public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
-        var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
-        var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
-        var selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate();
-
-        if (recipientUnidentifiedAccessKey == null
-                || selfUnidentifiedAccessKey == null
-                || selfUnidentifiedAccessCertificate == null) {
-            return Optional.absent();
+    private byte[] getSenderCertificateForPhoneNumberPrivacy() {
+        if (privacySenderCertificate != null && System.currentTimeMillis() < (
+                privacySenderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
+        )) {
+            return privacySenderCertificate.getSerialized();
         }
+        try {
+            final var certificate = handleResponseException(dependencies.getCertificateApi()
+                    .getSenderCertificateForPhoneNumberPrivacy());
+            privacySenderCertificate = new SenderCertificate(certificate);
+            return certificate;
+        } catch (IOException | InvalidCertificateException e) {
+            logger.warn("Failed to get sender certificate (pnp), ignoring: {}", e.getMessage());
+            return null;
+        }
+    }
 
+    private byte[] getSenderCertificate() {
+        if (senderCertificate != null && System.currentTimeMillis() < (
+                senderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
+        )) {
+            return senderCertificate.getSerialized();
+        }
         try {
-            return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey,
-                    selfUnidentifiedAccessCertificate),
-                    new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
-        } catch (InvalidCertificateException e) {
-            return Optional.absent();
+            final var certificate = handleResponseException(dependencies.getCertificateApi().getSenderCertificate());
+            this.senderCertificate = new SenderCertificate(certificate);
+            return certificate;
+        } catch (IOException | InvalidCertificateException e) {
+            logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
+        var selfProfile = noRefresh
+                ? account.getProfileStore().getProfile(account.getSelfRecipientId())
+                : context.getProfileHelper().getSelfProfile();
+        if (selfProfile != null
+                && selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
+            return createUnrestrictedUnidentifiedAccess();
         }
+        return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
+    }
+
+    private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
+        var targetProfile = noRefresh
+                ? account.getProfileStore().getProfile(recipientId)
+                : context.getProfileHelper().getRecipientProfile(recipientId);
+        if (targetProfile == null) {
+            return null;
+        }
+
+        var theirProfileKey = account.getProfileStore().getProfileKey(recipientId);
+        return getTargetUnidentifiedAccessKey(targetProfile, theirProfileKey);
+    }
+
+    private static byte[] getTargetUnidentifiedAccessKey(
+            final Profile targetProfile,
+            final ProfileKey theirProfileKey
+    ) {
+        return switch (targetProfile.getUnidentifiedAccessMode()) {
+            case ENABLED -> theirProfileKey == null ? null : UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
+            case UNRESTRICTED -> createUnrestrictedUnidentifiedAccess();
+            default -> null;
+        };
     }
 
     private static byte[] createUnrestrictedUnidentifiedAccess() {
-        return getSecretBytes(16);
+        return UNRESTRICTED_KEY;
     }
 }