mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-03 03:22:45 +01:00
Add a oneof case for the unrestricted access path in gRPC services
This commit is contained in:
committed by
ravi-signal
parent
a90fa5db02
commit
1cf3bf5ecf
@@ -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");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user