From 1cf3bf5ecfded54d7e59fcfc788897481c3ecfb0 Mon Sep 17 00:00:00 2001 From: Ravi Khadiwala Date: Tue, 3 Mar 2026 12:39:25 -0600 Subject: [PATCH] Add a oneof case for the unrestricted access path in gRPC services --- .../grpc/KeysAnonymousGrpcService.java | 12 +++- .../grpc/MessagesAnonymousGrpcService.java | 2 + .../src/main/proto/org/signal/chat/keys.proto | 5 ++ .../main/proto/org/signal/chat/messages.proto | 3 + .../grpc/KeysAnonymousGrpcServiceTest.java | 57 +++++++++++++++++++ .../MessagesAnonymousGrpcServiceTest.java | 41 +++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcService.java index 50d6b5cb1..93dac9430 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcService.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.concurrent.Flow; import org.signal.chat.errors.FailedUnidentifiedAuthorization; import org.signal.chat.errors.NotFound; +import org.signal.chat.keys.AccountPreKeyBundles; import org.signal.chat.keys.CheckIdentityKeyRequest; import org.signal.chat.keys.CheckIdentityKeyResponse; import org.signal.chat.keys.GetPreKeysAnonymousRequest; @@ -22,6 +23,7 @@ import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; +import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.KeysManager; import reactor.adapter.JdkFlowAdapter; @@ -74,7 +76,15 @@ public class KeysAnonymousGrpcService extends SimpleKeysAnonymousGrpc.KeysAnonym .setFailedUnidentifiedAuthorization(FailedUnidentifiedAuthorization.getDefaultInstance()) .build()); - default -> throw GrpcExceptions.fieldViolation("authorization", "invalid authorization type"); + case UNRESTRICTED_ACCESS -> accountsManager.getByServiceIdentifier(serviceIdentifier) + .filter(Account::isUnrestrictedUnidentifiedAccess) + .flatMap(targetAccount -> KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier, deviceId, keysManager)) + .map(accountPreKeyBundles -> GetPreKeysAnonymousResponse.newBuilder().setPreKeys(accountPreKeyBundles).build()) + .orElseGet(() -> GetPreKeysAnonymousResponse.newBuilder() + .setFailedUnidentifiedAuthorization(FailedUnidentifiedAuthorization.getDefaultInstance()) + .build()); + + case AUTHORIZATION_NOT_SET -> throw GrpcExceptions.fieldViolation("authorization", "invalid authorization type"); }; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/MessagesAnonymousGrpcService.java b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/MessagesAnonymousGrpcService.java index f5888d280..fa5b46695 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/grpc/MessagesAnonymousGrpcService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/grpc/MessagesAnonymousGrpcService.java @@ -102,6 +102,8 @@ public class MessagesAnonymousGrpcService extends SimpleMessagesAnonymousGrpc.Me } case GROUP_SEND_TOKEN -> groupSendTokenUtil.checkGroupSendToken(request.getGroupSendToken(), destinationServiceIdentifier); + case UNRESTRICTED_ACCESS -> + maybeDestination.map(account -> account.isUnrestrictedUnidentifiedAccess()).orElse(false); case AUTHORIZATION_NOT_SET -> throw GrpcExceptions.fieldViolation("authorization", "expected authorization token not provided"); }; diff --git a/service/src/main/proto/org/signal/chat/keys.proto b/service/src/main/proto/org/signal/chat/keys.proto index eb3a81164..7c107c004 100644 --- a/service/src/main/proto/org/signal/chat/keys.proto +++ b/service/src/main/proto/org/signal/chat/keys.proto @@ -9,6 +9,8 @@ option java_multiple_files = true; package org.signal.chat.keys; +import "google/protobuf/empty.proto"; + import "org/signal/chat/common.proto"; import "org/signal/chat/errors.proto"; @@ -111,6 +113,9 @@ message GetPreKeysAnonymousRequest { // A group send endorsement token for the targeted account. bytes group_send_token = 3; + + // The destination account allows unrestricted unidentified access + google.protobuf.Empty unrestricted_access = 4; } } diff --git a/service/src/main/proto/org/signal/chat/messages.proto b/service/src/main/proto/org/signal/chat/messages.proto index 9042567fa..2eb7bd08a 100644 --- a/service/src/main/proto/org/signal/chat/messages.proto +++ b/service/src/main/proto/org/signal/chat/messages.proto @@ -202,6 +202,9 @@ message SendSealedSenderMessageRequest { // A group send endorsement token for the destination account. bytes group_send_token = 6; + + // The destination account allows unrestricted unidentified access + google.protobuf.Empty unrestricted_access = 7; } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcServiceTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcServiceTest.java index adf70dfe3..272efcdb5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcServiceTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/grpc/KeysAnonymousGrpcServiceTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.when; import static org.whispersystems.textsecuregcm.grpc.GrpcTestUtils.assertStatusException; import com.google.protobuf.ByteString; +import com.google.protobuf.Empty; import io.grpc.Status; import io.grpc.stub.StreamObserver; import java.security.MessageDigest; @@ -36,6 +37,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junitpioneer.jupiter.cartesian.CartesianTest; import org.mockito.Mock; import org.signal.chat.common.EcPreKey; import org.signal.chat.common.EcSignedPreKey; @@ -189,6 +192,60 @@ class KeysAnonymousGrpcServiceTest extends SimpleBaseGrpcTest messages = + Map.of(deviceId, IndividualRecipientMessageBundle.Message.newBuilder() + .setRegistrationId(registrationId) + .setPayload(ByteString.copyFrom(payload)) + .setType(SendMessageType.UNIDENTIFIED_SENDER) + .build()); + final SendSealedSenderMessageRequest request = + generateRequest(serviceIdentifier, false, true, messages, useUak ? TestRandomUtil.nextBytes(16) : null, null); + final SendMessageResponse response = unauthenticatedServiceStub().sendSingleRecipientMessage(request); + final SendMessageResponse.ResponseCase expectedResponse = isUua + ? SendMessageResponse.ResponseCase.SUCCESS + : SendMessageResponse.ResponseCase.FAILED_UNIDENTIFIED_AUTHORIZATION; + assertEquals(expectedResponse, response.getResponseCase()); + } @Test void mismatchedDevices() throws MessageTooLargeException, MismatchedDevicesException { @@ -544,6 +581,10 @@ class MessagesAnonymousGrpcServiceTest extends requestBuilder.setGroupSendToken(ByteString.copyFrom(groupSendToken)); } + if (groupSendToken == null && unidentifiedAccessKey == null) { + requestBuilder.setUnrestrictedAccess(Empty.getDefaultInstance()); + } + return requestBuilder.build(); } }