package org.asamk.signal.manager.helper;
-import org.asamk.signal.manager.SignalDependencies;
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.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.jetbrains.annotations.Nullable;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.signal.libsignal.metadata.certificate.SenderCertificate;
-import org.signal.zkgroup.profiles.ProfileKey;
+import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
-import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import static org.asamk.signal.manager.util.Utils.handleResponseException;
+
public class UnidentifiedAccessHelper {
- private final static Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
- private final static long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
+ 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 SignalAccount account;
private final SignalDependencies dependencies;
- private final SelfProfileKeyProvider selfProfileKeyProvider;
- private final ProfileProvider profileProvider;
+ private final Context context;
private SenderCertificate privacySenderCertificate;
private SenderCertificate senderCertificate;
- public UnidentifiedAccessHelper(
- final SignalAccount account,
- final SignalDependencies dependencies,
- final SelfProfileKeyProvider selfProfileKeyProvider,
- final ProfileProvider profileProvider
- ) {
- this.account = account;
- this.dependencies = dependencies;
- this.selfProfileKeyProvider = selfProfileKeyProvider;
- this.profileProvider = profileProvider;
+ public UnidentifiedAccessHelper(final Context context) {
+ this.account = context.getAccount();
+ this.dependencies = context.getDependencies();
+ this.context = context;
+ }
+
+ public void rotateSenderCertificates() {
+ privacySenderCertificate = null;
+ senderCertificate = null;
+ }
+
+ public List<SealedSenderAccess> getSealedSenderAccessFor(List<RecipientId> recipients) {
+ return recipients.stream().map(this::getAccessFor).map(SealedSenderAccess::forIndividual).toList();
+ }
+
+ 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;
+ }
+
+ var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
+ if (selfUnidentifiedAccessKey == null) {
+ logger.trace("Unidentified access not available for self");
+ return null;
+ }
+
+ var senderCertificate = getSenderCertificateFor(recipientId);
+ if (senderCertificate == null) {
+ logger.trace("Unidentified access not available due to missing sender certificate");
+ return null;
+ }
+
+ try {
+ return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false);
+ } catch (InvalidCertificateException e) {
+ return null;
+ }
}
private byte[] getSenderCertificateFor(final RecipientId recipientId) {
sharingMode == PhoneNumberSharingMode.CONTACTS
&& account.getContactStore().getContact(recipientId) != null
)) {
- logger.debug("Using normal sender certificate for message to {}", recipientId);
+ logger.trace("Using normal sender certificate for message to {}", recipientId);
return getSenderCertificate();
} else {
- logger.debug("Using phone number privacy sender certificate for message to {}", recipientId);
+ logger.trace("Using phone number privacy sender certificate for message to {}", recipientId);
return getSenderCertificateForPhoneNumberPrivacy();
}
}
return privacySenderCertificate.getSerialized();
}
try {
- final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
+ final var certificate = handleResponseException(dependencies.getCertificateApi()
+ .getSenderCertificateForPhoneNumberPrivacy());
privacySenderCertificate = new SenderCertificate(certificate);
return certificate;
} catch (IOException | InvalidCertificateException e) {
- logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
+ logger.warn("Failed to get sender certificate (pnp), ignoring: {}", e.getMessage());
return null;
}
}
return senderCertificate.getSerialized();
}
try {
- final var certificate = dependencies.getAccountManager().getSenderCertificate();
+ final var certificate = handleResponseException(dependencies.getCertificateApi().getSenderCertificate());
this.senderCertificate = new SenderCertificate(certificate);
return certificate;
} catch (IOException | InvalidCertificateException e) {
private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
var selfProfile = noRefresh
? account.getProfileStore().getProfile(account.getSelfRecipientId())
- : profileProvider.getProfile(account.getSelfRecipientId());
+ : context.getProfileHelper().getSelfProfile();
if (selfProfile != null
&& selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
return createUnrestrictedUnidentifiedAccess();
}
- return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
+ return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
}
private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
var targetProfile = noRefresh
? account.getProfileStore().getProfile(recipientId)
- : profileProvider.getProfile(recipientId);
+ : context.getProfileHelper().getRecipientProfile(recipientId);
if (targetProfile == null) {
return null;
}
}
private static byte[] getTargetUnidentifiedAccessKey(
- final Profile targetProfile, final ProfileKey theirProfileKey
+ final Profile targetProfile,
+ final ProfileKey theirProfileKey
) {
- switch (targetProfile.getUnidentifiedAccessMode()) {
- case ENABLED:
- if (theirProfileKey == null) {
- return null;
- }
-
- return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
- case UNRESTRICTED:
- return createUnrestrictedUnidentifiedAccess();
- default:
- return null;
- }
- }
-
- public Optional<UnidentifiedAccessPair> getAccessForSync() {
- var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
- var 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();
- }
- }
-
- public List<Optional<UnidentifiedAccessPair>> getAccessFor(List<RecipientId> recipients) {
- return recipients.stream().map(this::getAccessFor).toList();
- }
-
- public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
- return getAccessFor(recipient, false);
- }
-
- public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient, boolean noRefresh) {
- var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient, noRefresh);
- if (recipientUnidentifiedAccessKey == null) {
- return Optional.absent();
- }
-
- var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
- var selfUnidentifiedAccessCertificate = getSenderCertificateFor(recipient);
- if (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();
- }
+ return switch (targetProfile.getUnidentifiedAccessMode()) {
+ case ENABLED -> theirProfileKey == null ? null : UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
+ case UNRESTRICTED -> createUnrestrictedUnidentifiedAccess();
+ default -> null;
+ };
}
private static byte[] createUnrestrictedUnidentifiedAccess() {