Fix sealed sender certificates and group PNI handling for numberless accounts.

This commit is contained in:
Greyson Parrelli
2026-09-02 16:11:27 -03:00
committed by Alex Hart
parent 68dcb851eb
commit 01c83249e8
4 changed files with 29 additions and 11 deletions
@@ -608,7 +608,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
if (groupRecord.isPresent && groupRecord.get().hasV2GroupProperties) {
val pendingMembers: List<DecryptedPendingMember> = groupRecord.get().requireV2GroupProperties().decryptedGroup.pendingMembers
val invitedByAci: ByteString? = DecryptedGroupUtil.findPendingByServiceId(pendingMembers, Recipient.self().requireAci())
.or { DecryptedGroupUtil.findPendingByServiceId(pendingMembers, Recipient.self().requirePni()) }
.or { Recipient.self().pni.flatMap { pni -> DecryptedGroupUtil.findPendingByServiceId(pendingMembers, pni) } }
.map { it.addedByAci }
.orElse(null)
@@ -106,6 +106,7 @@ final class GroupManagerV2 {
private final GroupsV2Authorization authorization;
private final ServiceIds serviceIds;
private final ACI selfAci;
@Nullable
private final PNI selfPni;
private final GroupCandidateHelper groupCandidateHelper;
private final SendGroupUpdateHelper sendGroupUpdateHelper;
@@ -137,7 +138,7 @@ final class GroupManagerV2 {
this.authorization = authorization;
this.serviceIds = serviceIds;
this.selfAci = serviceIds.getAci();
this.selfPni = serviceIds.requirePni();
this.selfPni = serviceIds.getPni();
this.groupCandidateHelper = groupCandidateHelper;
this.sendGroupUpdateHelper = sendGroupUpdateHelper;
}
@@ -476,7 +477,7 @@ final class GroupManagerV2 {
DecryptedGroup decryptedGroup = v2GroupProperties.getDecryptedGroup();
Optional<DecryptedMember> selfMember = DecryptedGroupUtil.findMemberByAci(decryptedGroup.members, selfAci);
Optional<DecryptedPendingMember> aciPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembers, selfAci);
Optional<DecryptedPendingMember> pniPendingMember = DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembers, selfPni);
Optional<DecryptedPendingMember> pniPendingMember = selfPni != null ? DecryptedGroupUtil.findPendingByServiceId(decryptedGroup.pendingMembers, selfPni) : Optional.empty();
Optional<DecryptedPendingMember> selfPendingMember = Optional.empty();
ServiceId serviceId = selfAci;
@@ -573,7 +574,7 @@ final class GroupManagerV2 {
}
Optional<DecryptedPendingMember> aciInPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfAci);
Optional<DecryptedPendingMember> pniInPending = DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfPni);
Optional<DecryptedPendingMember> pniInPending = selfPni != null ? DecryptedGroupUtil.findPendingByServiceId(group.pendingMembers, selfPni) : Optional.empty();
GroupCandidate selfGroupCandidate = groupCandidateHelper.recipientIdToCandidate(Recipient.self().getId());
@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.network.exceptions.NonSuccessfulResponseCodeException;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SealedSenderConstraint;
@@ -65,16 +66,26 @@ public final class RotateCertificateJob extends BaseJob {
synchronized (RotateCertificateJob.class) {
Collection<CertificateType> certificateTypes = SignalStore.phoneNumberPrivacy()
.getAllCertificateTypes();
Collection<CertificateType> requiredTypes = SignalStore.phoneNumberPrivacy()
.getRequiredCertificateTypes();
Log.i(TAG, "Rotating these certificates " + certificateTypes);
for (CertificateType certificateType: certificateTypes) {
byte[] certificate;
switch (certificateType) {
case ACI_AND_E164: certificate = NetworkResultUtil.toBasicLegacy(SignalNetwork.certificate().getSenderCertificate()); break;
case ACI_ONLY : certificate = NetworkResultUtil.toBasicLegacy(SignalNetwork.certificate().getSenderCertificateForPhoneNumberPrivacy()); break;
default : throw new AssertionError();
try {
switch (certificateType) {
case ACI_AND_E164: certificate = NetworkResultUtil.toBasicLegacy(SignalNetwork.certificate().getSenderCertificate()); break;
case ACI_ONLY : certificate = NetworkResultUtil.toBasicLegacy(SignalNetwork.certificate().getSenderCertificateForPhoneNumberPrivacy()); break;
default : throw new AssertionError();
}
} catch (NonSuccessfulResponseCodeException e) {
if (requiredTypes.contains(certificateType)) {
throw e;
}
Log.w(TAG, String.format("The server rejected the request for the non-required %s certificate. Skipping it.", certificateType), e);
continue;
}
Log.i(TAG, String.format("Successfully got %s certificate", certificateType));
@@ -80,7 +80,9 @@ public final class PhoneNumberPrivacyValues extends SignalStoreValues {
* these certificates types.
*/
public Collection<CertificateType> getRequiredCertificateTypes() {
if (isPhoneNumberSharingEnabled()) {
if (SignalStore.account().isPhoneNumberless()) {
return ACI_ONLY_CERTIFICATE;
} else if (isPhoneNumberSharingEnabled()) {
return ACI_AND_E164_CERTIFICATE;
} else {
return ACI_ONLY_CERTIFICATE;
@@ -88,10 +90,14 @@ public final class PhoneNumberPrivacyValues extends SignalStoreValues {
}
/**
* All certificate types required according to the feature flags.
* All certificate types the account can fetch.
*/
public Collection<CertificateType> getAllCertificateTypes() {
return BOTH_CERTIFICATES;
if (SignalStore.account().isPhoneNumberless()) {
return ACI_ONLY_CERTIFICATE;
} else {
return BOTH_CERTIFICATES;
}
}
public enum PhoneNumberSharingMode {