Add a oneof case for the unrestricted access path in gRPC services

This commit is contained in:
Ravi Khadiwala
2026-03-03 12:39:25 -06:00
committed by ravi-signal
parent a90fa5db02
commit 1cf3bf5ecf
6 changed files with 119 additions and 1 deletions

View File

@@ -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<KeysAnonymousGrpcS
assertEquals(expectedResponse, response);
}
@CartesianTest
void getPreKeysUnrestricted(@CartesianTest.Values(booleans = {true, false}) boolean includeUak) {
final Account targetAccount = mock(Account.class);
final Device targetDevice = DevicesHelper.createDevice(Device.PRIMARY_ID);
when(targetAccount.getDevice(Device.PRIMARY_ID)).thenReturn(Optional.of(targetDevice));
final ECKeyPair identityKeyPair = ECKeyPair.generate();
final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey());
final UUID uuid = UUID.randomUUID();
final AciServiceIdentifier identifier = new AciServiceIdentifier(uuid);
final byte[] unidentifiedAccessKey = TestRandomUtil.nextBytes(UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH);
when(targetAccount.isUnrestrictedUnidentifiedAccess()).thenReturn(true);
when(targetAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(targetAccount.getIdentifier(IdentityType.ACI)).thenReturn(uuid);
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(identityKey);
when(accountsManager.getByServiceIdentifier(identifier))
.thenReturn(Optional.of(targetAccount));
final ECPreKey ecPreKey = new ECPreKey(1, ECKeyPair.generate().getPublicKey());
final ECSignedPreKey ecSignedPreKey = KeysHelper.signedECPreKey(2, identityKeyPair);
final KEMSignedPreKey kemSignedPreKey = KeysHelper.signedKEMPreKey(3, identityKeyPair);
final KeysManager.DevicePreKeys devicePreKeys =
new KeysManager.DevicePreKeys(ecSignedPreKey, Optional.of(ecPreKey), kemSignedPreKey);
when(keysManager.takeDevicePreKeys(eq(Device.PRIMARY_ID), eq(identifier), any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(devicePreKeys)));
final GetPreKeysAnonymousRequest.Builder request = GetPreKeysAnonymousRequest.newBuilder()
.setRequest(GetPreKeysRequest.newBuilder()
.setTargetIdentifier(ServiceIdentifierUtil.toGrpcServiceIdentifier(identifier))
.setDeviceId(Device.PRIMARY_ID));
if (includeUak) {
request.setUnidentifiedAccessKey(ByteString.copyFrom(TestRandomUtil.nextBytes(16)));
} else {
request.setUnrestrictedAccess(Empty.getDefaultInstance());
}
final GetPreKeysAnonymousResponse response = unauthenticatedServiceStub().getPreKeys(request.build());
final GetPreKeysAnonymousResponse expectedResponse = GetPreKeysAnonymousResponse.newBuilder()
.setPreKeys(AccountPreKeyBundles.newBuilder()
.setIdentityKey(ByteString.copyFrom(identityKey.serialize()))
.putDevicePreKeys(Device.PRIMARY_ID, DevicePreKeyBundle.newBuilder()
.setEcOneTimePreKey(toGrpcEcPreKey(ecPreKey))
.setEcSignedPreKey(toGrpcEcSignedPreKey(ecSignedPreKey))
.setKemOneTimePreKey(toGrpcKemSignedPreKey(kemSignedPreKey))
.build()))
.build();
assertEquals(expectedResponse, response);
}
@Test
void getPreKeysNoAuth() {
assertGetKeysFailure(Status.INVALID_ARGUMENT, GetPreKeysAnonymousRequest.newBuilder()

View File

@@ -250,6 +250,43 @@ class MessagesAnonymousGrpcServiceTest extends
generateRequest(serviceIdentifier, false, true, messages, UNIDENTIFIED_ACCESS_KEY, null)));
verifyNoInteractions(messageSender);
}
@CartesianTest
void sendUnrestrictedAccessMessage(
@CartesianTest.Values(booleans = {true, false}) final boolean useUak,
@CartesianTest.Values(booleans = {true, false}) final boolean isUua)
throws MessageTooLargeException, MismatchedDevicesException {
final byte deviceId = Device.PRIMARY_ID;
final int registrationId = 7;
final Device destinationDevice = DevicesHelper.createDevice(deviceId, CLOCK.millis(), registrationId);
final Account destinationAccount = mock(Account.class);
when(destinationAccount.getDevices()).thenReturn(List.of(destinationDevice));
when(destinationAccount.getDevice(deviceId)).thenReturn(Optional.of(destinationDevice));
when(destinationAccount.isUnrestrictedUnidentifiedAccess()).thenReturn(isUua);
when(destinationAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
final AciServiceIdentifier serviceIdentifier = new AciServiceIdentifier(UUID.randomUUID());
when(accountsManager.getByServiceIdentifier(serviceIdentifier)).thenReturn(Optional.of(destinationAccount));
final byte[] payload = TestRandomUtil.nextBytes(128);
final Map<Byte, IndividualRecipientMessageBundle.Message> 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();
}
}