Clean up GSE usage and remove combined-UAK fallback for multi-recipient send.

This commit is contained in:
Cody Henthorne
2025-07-24 14:34:00 -04:00
committed by Michelle Tang
parent 99fb70c20c
commit 7499bd77b4
6 changed files with 72 additions and 126 deletions

View File

@@ -6,6 +6,7 @@
package org.whispersystems.signalservice.api;
import org.signal.core.util.Base64;
import org.signal.libsignal.metadata.certificate.SenderCertificate;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
@@ -138,6 +139,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -286,7 +288,7 @@ public class SignalServiceMessageSender {
public void sendGroupTyping(DistributionId distributionId,
List<SignalServiceAddress> recipients,
List<UnidentifiedAccess> unidentifiedAccess,
@Nullable GroupSendEndorsements groupSendEndorsements,
@Nonnull GroupSendEndorsements groupSendEndorsements,
SignalServiceTypingMessage message)
throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException
{
@@ -388,7 +390,7 @@ public class SignalServiceMessageSender {
public List<SendMessageResult> sendCallMessage(DistributionId distributionId,
List<SignalServiceAddress> recipients,
List<UnidentifiedAccess> unidentifiedAccess,
@Nullable GroupSendEndorsements groupSendEndorsements,
@Nonnull GroupSendEndorsements groupSendEndorsements,
SignalServiceCallMessage message,
PartialSendBatchCompleteListener partialListener)
throws IOException, UntrustedIdentityException, InvalidKeyException, NoSessionException, InvalidRegistrationIdException
@@ -2356,6 +2358,7 @@ public class SignalServiceMessageSender {
return Collections.emptyList();
}
Preconditions.checkArgument(groupSendEndorsements != null || story, "[" + timestamp + "] GSE is null and not sending a story");
Preconditions.checkArgument(recipients.size() == unidentifiedAccess.size(), "[" + timestamp + "] Unidentified access mismatch!");
Map<ServiceId, UnidentifiedAccess> accessBySid = new HashMap<>();
@@ -2366,7 +2369,8 @@ public class SignalServiceMessageSender {
accessBySid.put(addressIterator.next().getServiceId(), accessIterator.next());
}
SealedSenderAccess sealedSenderAccess = SealedSenderAccess.forGroupSend(groupSendEndorsements, unidentifiedAccess, story);
SenderCertificate senderCertificate = unidentifiedAccess.stream().filter(Objects::nonNull).findFirst().map(UnidentifiedAccess::getUnidentifiedCertificate).orElse(null);
SealedSenderAccess sealedSenderAccess = SealedSenderAccess.forGroupSend(senderCertificate, groupSendEndorsements, story);
for (int i = 0; i < RETRY_COUNT; i++) {
GroupTargetInfo targetInfo = buildGroupTargetInfo(recipients);
@@ -2493,12 +2497,8 @@ public class SignalServiceMessageSender {
handleStaleDevices(address, stale.getDevices());
}
} catch (InvalidUnidentifiedAccessHeaderException e) {
sealedSenderAccess = sealedSenderAccess.switchToFallback();
if (sealedSenderAccess != null) {
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Handling invalid group send endorsements. (" + e.getMessage() + ")");
} else {
throw e;
}
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Invalid access header. (" + e.getMessage() + ")");
throw e;
}
Log.w(TAG, "[sendGroupMessage][" + timestamp + "] Attempt failed (i = " + i + ")");

View File

@@ -9,7 +9,6 @@ import org.signal.core.util.Base64
import org.signal.libsignal.metadata.certificate.SenderCertificate
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken
import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements
import org.whispersystems.util.ByteArrayUtil
/**
* Provides single interface for the various ways to send via sealed sender.
@@ -25,6 +24,8 @@ sealed class SealedSenderAccess {
abstract fun switchToFallback(): SealedSenderAccess?
open fun applyHeader(): Boolean = true
/**
* For sending to an single recipient using group send endorsement/token first and then fallback to
* access key if available.
@@ -39,7 +40,6 @@ sealed class SealedSenderAccess {
override val headerValue: String by lazy { Base64.encodeWithPadding(groupSendToken.serialize()) }
override fun switchToFallback(): SealedSenderAccess? {
fallbackListener?.onTokenToAccessFallback(unidentifiedAccess != null)
return if (unidentifiedAccess != null) {
IndividualUnidentifiedAccessFirst(unidentifiedAccess)
} else {
@@ -66,7 +66,6 @@ sealed class SealedSenderAccess {
override fun switchToFallback(): SealedSenderAccess? {
val groupSendToken = createGroupSendToken?.create()
return if (groupSendToken != null) {
fallbackListener?.onAccessToTokenFallback()
IndividualGroupSendTokenFirst(groupSendToken, senderCertificate)
} else {
null
@@ -92,27 +91,15 @@ sealed class SealedSenderAccess {
}
}
/**
* For sending to a "group" of recipients using access keys.
*/
class GroupUnidentifiedAccess(
private val unidentifiedAccess: List<UnidentifiedAccess>,
override val senderCertificate: SenderCertificate = unidentifiedAccess.first().unidentifiedCertificate
) : SealedSenderAccess() {
override val headerName: String = "Unidentified-Access-Key"
override val headerValue: String by lazy {
var joinedUnidentifiedAccess = ByteArray(16)
for (access in unidentifiedAccess) {
joinedUnidentifiedAccess = ByteArrayUtil.xor(joinedUnidentifiedAccess, access.unidentifiedAccessKey)
}
Base64.encodeWithPadding(joinedUnidentifiedAccess)
}
class StorySendNoop(override val senderCertificate: SenderCertificate) : SealedSenderAccess() {
override val headerName: String = ""
override val headerValue: String = ""
override fun switchToFallback(): SealedSenderAccess? {
return null
}
override fun applyHeader(): Boolean = false
}
/**
@@ -122,14 +109,7 @@ sealed class SealedSenderAccess {
fun create(): GroupSendFullToken?
}
interface FallbackListener {
fun onAccessToTokenFallback()
fun onTokenToAccessFallback(hasAccessKeyFallback: Boolean)
}
companion object {
var fallbackListener: FallbackListener? = null
@JvmField
val NONE: SealedSenderAccess? = null
@@ -178,12 +158,12 @@ sealed class SealedSenderAccess {
}
@JvmStatic
fun forGroupSend(groupSendEndorsements: GroupSendEndorsements?, unidentifiedAccess: List<UnidentifiedAccess>, forStory: Boolean): SealedSenderAccess {
return if (groupSendEndorsements != null && !forStory) {
GroupGroupSendToken(groupSendEndorsements)
} else {
GroupUnidentifiedAccess(unidentifiedAccess)
fun forGroupSend(senderCertificate: SenderCertificate?, groupSendEndorsements: GroupSendEndorsements?, forStory: Boolean): SealedSenderAccess {
if (forStory) {
return StorySendNoop(senderCertificate!!)
}
return GroupGroupSendToken(groupSendEndorsements!!)
}
@JvmStatic

View File

@@ -287,7 +287,9 @@ sealed class SignalWebSocket(
class UnauthenticatedWebSocket(connectionFactory: WebSocketFactory, canConnect: CanConnect, sleepTimer: SleepTimer, disconnectTimeoutMs: Long) : SignalWebSocket(connectionFactory, canConnect, sleepTimer, disconnectTimeoutMs.milliseconds) {
fun request(requestMessage: WebSocketRequestMessage, sealedSenderAccess: SealedSenderAccess): Single<WebsocketResponse> {
val headers: MutableList<String> = requestMessage.headers.toMutableList()
headers.add(sealedSenderAccess.header)
if (sealedSenderAccess.applyHeader()) {
headers.add(sealedSenderAccess.header)
}
val message = requestMessage
.newBuilder()