0
0
mirror of https://github.com/signalapp/Signal-Server.git synced 2024-09-20 12:02:18 +02:00

Update keys gRPC endpoint to use service identifiers

This commit is contained in:
Jon Chambers 2023-07-21 13:03:01 -04:00 committed by GitHub
parent dc1cb9093a
commit 9df923d916
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 229 additions and 172 deletions

View File

@ -6,15 +6,17 @@
package org.whispersystems.textsecuregcm.grpc;
import io.grpc.Status;
import org.whispersystems.textsecuregcm.identity.IdentityType;
public enum IdentityType {
ACI,
PNI;
public class IdentityTypeUtil {
private IdentityTypeUtil() {
}
public static IdentityType fromGrpcIdentityType(final org.signal.chat.common.IdentityType grpcIdentityType) {
return switch (grpcIdentityType) {
case IDENTITY_TYPE_ACI -> ACI;
case IDENTITY_TYPE_PNI -> PNI;
case IDENTITY_TYPE_ACI -> IdentityType.ACI;
case IDENTITY_TYPE_PNI -> IdentityType.PNI;
case IDENTITY_TYPE_UNSPECIFIED, UNRECOGNIZED -> throw Status.INVALID_ARGUMENT.asRuntimeException();
};
}

View File

@ -10,6 +10,7 @@ import org.signal.chat.keys.GetPreKeysAnonymousRequest;
import org.signal.chat.keys.GetPreKeysResponse;
import org.signal.chat.keys.ReactorKeysAnonymousGrpc;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.KeysManager;
import reactor.core.publisher.Mono;
@ -26,15 +27,15 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
@Override
public Mono<GetPreKeysResponse> getPreKeys(final GetPreKeysAnonymousRequest request) {
return KeysGrpcHelper.findAccount(request.getTargetIdentifier(), accountsManager)
.switchIfEmpty(Mono.error(Status.UNAUTHENTICATED.asException()))
.flatMap(targetAccount -> {
final IdentityType identityType =
IdentityType.fromGrpcIdentityType(request.getTargetIdentifier().getIdentityType());
final ServiceIdentifier serviceIdentifier =
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getTargetIdentifier());
return UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, request.getUnidentifiedAccessKey().toByteArray())
? KeysGrpcHelper.getPreKeys(targetAccount, identityType, request.getDeviceId(), keysManager)
: Mono.error(Status.UNAUTHENTICATED.asException());
});
return Mono.fromFuture(accountsManager.getByServiceIdentifierAsync(serviceIdentifier))
.flatMap(Mono::justOrEmpty)
.switchIfEmpty(Mono.error(Status.UNAUTHENTICATED.asException()))
.flatMap(targetAccount ->
UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, request.getUnidentifiedAccessKey().toByteArray())
? KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier.identityType(), request.getDeviceId(), keysManager)
: Mono.error(Status.UNAUTHENTICATED.asException()));
}
}

View File

@ -8,19 +8,17 @@ package org.whispersystems.textsecuregcm.grpc;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import io.grpc.Status;
import java.util.UUID;
import org.signal.chat.common.EcPreKey;
import org.signal.chat.common.EcSignedPreKey;
import org.signal.chat.common.KemSignedPreKey;
import org.signal.chat.common.ServiceIdentifier;
import org.signal.chat.keys.GetPreKeysResponse;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.textsecuregcm.entities.ECPreKey;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.KeysManager;
import org.whispersystems.textsecuregcm.util.UUIDUtil;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@ -31,37 +29,10 @@ class KeysGrpcHelper {
@VisibleForTesting
static final long ALL_DEVICES = 0;
static Mono<Account> findAccount(final ServiceIdentifier targetIdentifier, final AccountsManager accountsManager) {
return Mono.just(IdentityType.fromGrpcIdentityType(targetIdentifier.getIdentityType()))
.flatMap(identityType -> {
final UUID uuid = UUIDUtil.fromByteString(targetIdentifier.getUuid());
return Mono.fromFuture(switch (identityType) {
case ACI -> accountsManager.getByAccountIdentifierAsync(uuid);
case PNI -> accountsManager.getByPhoneNumberIdentifierAsync(uuid);
});
})
.flatMap(Mono::justOrEmpty)
.onErrorMap(IllegalArgumentException.class, throwable -> Status.INVALID_ARGUMENT.asException());
}
static Tuple2<UUID, IdentityKey> getIdentifierAndIdentityKey(final Account account, final IdentityType identityType) {
final UUID identifier = switch (identityType) {
case ACI -> account.getUuid();
case PNI -> account.getPhoneNumberIdentifier();
};
final IdentityKey identityKey = switch (identityType) {
case ACI -> account.getIdentityKey();
case PNI -> account.getPhoneNumberIdentityKey();
};
return Tuples.of(identifier, identityKey);
}
static Mono<GetPreKeysResponse> getPreKeys(final Account targetAccount, final IdentityType identityType, final long targetDeviceId, final KeysManager keysManager) {
final Tuple2<UUID, IdentityKey> identifierAndIdentityKey = getIdentifierAndIdentityKey(targetAccount, identityType);
static Mono<GetPreKeysResponse> getPreKeys(final Account targetAccount,
final IdentityType identityType,
final long targetDeviceId,
final KeysManager keysManager) {
final Flux<Device> devices = targetDeviceId == ALL_DEVICES
? Flux.fromIterable(targetAccount.getDevices())
@ -70,37 +41,43 @@ class KeysGrpcHelper {
return devices
.filter(Device::isEnabled)
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()))
.flatMap(device -> Mono.zip(Mono.fromFuture(keysManager.takeEC(identifierAndIdentityKey.getT1(), device.getId())),
Mono.fromFuture(keysManager.takePQ(identifierAndIdentityKey.getT1(), device.getId())))
.map(oneTimePreKeys -> {
final ECSignedPreKey ecSignedPreKey = switch (identityType) {
case ACI -> device.getSignedPreKey();
case PNI -> device.getPhoneNumberIdentitySignedPreKey();
};
.flatMap(device -> {
final ECSignedPreKey ecSignedPreKey = device.getSignedPreKey(identityType);
final GetPreKeysResponse.PreKeyBundle.Builder preKeyBundleBuilder = GetPreKeysResponse.PreKeyBundle.newBuilder()
.setEcSignedPreKey(EcSignedPreKey.newBuilder()
.setKeyId(ecSignedPreKey.keyId())
.setPublicKey(ByteString.copyFrom(ecSignedPreKey.serializedPublicKey()))
.setSignature(ByteString.copyFrom(ecSignedPreKey.signature()))
final GetPreKeysResponse.PreKeyBundle.Builder preKeyBundleBuilder = GetPreKeysResponse.PreKeyBundle.newBuilder()
.setEcSignedPreKey(EcSignedPreKey.newBuilder()
.setKeyId(ecSignedPreKey.keyId())
.setPublicKey(ByteString.copyFrom(ecSignedPreKey.serializedPublicKey()))
.setSignature(ByteString.copyFrom(ecSignedPreKey.signature()))
.build());
return Flux.merge(
Mono.fromFuture(keysManager.takeEC(targetAccount.getIdentifier(identityType), device.getId())),
Mono.fromFuture(keysManager.takePQ(targetAccount.getIdentifier(identityType), device.getId())))
.flatMap(Mono::justOrEmpty)
.reduce(preKeyBundleBuilder, (builder, preKey) -> {
if (preKey instanceof ECPreKey ecPreKey) {
builder.setEcOneTimePreKey(EcPreKey.newBuilder()
.setKeyId(ecPreKey.keyId())
.setPublicKey(ByteString.copyFrom(ecPreKey.serializedPublicKey()))
.build());
} else if (preKey instanceof KEMSignedPreKey kemSignedPreKey) {
preKeyBundleBuilder.setKemOneTimePreKey(KemSignedPreKey.newBuilder()
.setKeyId(kemSignedPreKey.keyId())
.setPublicKey(ByteString.copyFrom(kemSignedPreKey.serializedPublicKey()))
.setSignature(ByteString.copyFrom(kemSignedPreKey.signature()))
.build());
} else {
throw new AssertionError("Unexpected pre-key type: " + preKey.getClass());
}
oneTimePreKeys.getT1().ifPresent(ecPreKey -> preKeyBundleBuilder.setEcOneTimePreKey(EcPreKey.newBuilder()
.setKeyId(ecPreKey.keyId())
.setPublicKey(ByteString.copyFrom(ecPreKey.serializedPublicKey()))
.build()));
oneTimePreKeys.getT2().ifPresent(kemSignedPreKey -> preKeyBundleBuilder.setKemOneTimePreKey(KemSignedPreKey.newBuilder()
.setKeyId(kemSignedPreKey.keyId())
.setPublicKey(ByteString.copyFrom(kemSignedPreKey.serializedPublicKey()))
.setSignature(ByteString.copyFrom(kemSignedPreKey.signature()))
.build()));
return Tuples.of(device.getId(), preKeyBundleBuilder.build());
}))
return builder;
})
.map(builder -> Tuples.of(device.getId(), builder.build()));
})
.collectMap(Tuple2::getT1, Tuple2::getT2)
.map(preKeyBundles -> GetPreKeysResponse.newBuilder()
.setIdentityKey(ByteString.copyFrom(identifierAndIdentityKey.getT2().serialize()))
.setIdentityKey(ByteString.copyFrom(targetAccount.getIdentityKey(identityType).serialize()))
.putAllPreKeys(preKeyBundles)
.build());
}

View File

@ -5,8 +5,6 @@
package org.whispersystems.textsecuregcm.grpc;
import static org.whispersystems.textsecuregcm.grpc.IdentityType.ACI;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import java.util.List;
@ -37,15 +35,15 @@ import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
import org.whispersystems.textsecuregcm.entities.ECPreKey;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.KeysManager;
import org.whispersystems.textsecuregcm.util.UUIDUtil;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
@ -89,7 +87,7 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
.map(account -> Tuples.of(account, authenticatedDevice.deviceId()))
.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException)))
.flatMapMany(accountAndDeviceId -> Flux.just(
Tuples.of(ACI, accountAndDeviceId.getT1().getUuid(), accountAndDeviceId.getT2()),
Tuples.of(IdentityType.ACI, accountAndDeviceId.getT1().getUuid(), accountAndDeviceId.getT2()),
Tuples.of(IdentityType.PNI, accountAndDeviceId.getT1().getPhoneNumberIdentifier(), accountAndDeviceId.getT2())
))
.flatMap(identityTypeUuidAndDeviceId -> Flux.merge(
@ -128,31 +126,20 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
public Mono<GetPreKeysResponse> getPreKeys(final GetPreKeysRequest request) {
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
final String rateLimitKey;
{
final UUID targetUuid;
final ServiceIdentifier targetIdentifier =
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getTargetIdentifier());
try {
targetUuid = UUIDUtil.fromByteString(request.getTargetIdentifier().getUuid());
} catch (final IllegalArgumentException e) {
throw Status.INVALID_ARGUMENT.asRuntimeException();
}
rateLimitKey = authenticatedDevice.accountIdentifier() + "." +
authenticatedDevice.deviceId() + "__" +
targetUuid + "." +
request.getDeviceId();
}
final String rateLimitKey = authenticatedDevice.accountIdentifier() + "." +
authenticatedDevice.deviceId() + "__" +
targetIdentifier.uuid() + "." +
request.getDeviceId();
return rateLimiters.getPreKeysLimiter().validateReactive(rateLimitKey)
.then(KeysGrpcHelper.findAccount(request.getTargetIdentifier(), accountsManager))
.then(Mono.fromFuture(accountsManager.getByServiceIdentifierAsync(targetIdentifier))
.flatMap(Mono::justOrEmpty))
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()))
.flatMap(targetAccount -> {
final IdentityType identityType =
IdentityType.fromGrpcIdentityType(request.getTargetIdentifier().getIdentityType());
return KeysGrpcHelper.getPreKeys(targetAccount, identityType, request.getDeviceId(), keysManager);
});
.flatMap(targetAccount ->
KeysGrpcHelper.getPreKeys(targetAccount, targetIdentifier.identityType(), request.getDeviceId(), keysManager));
}
@Override
@ -160,7 +147,7 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
.flatMap(authenticatedDevice -> storeOneTimePreKeys(authenticatedDevice.accountIdentifier(),
request.getPreKeysList(),
IdentityType.fromGrpcIdentityType(request.getIdentityType()),
IdentityTypeUtil.fromGrpcIdentityType(request.getIdentityType()),
(requestPreKey, ignored) -> checkEcPreKey(requestPreKey),
(identifier, preKeys) -> keysManager.storeEcOneTimePreKeys(identifier, authenticatedDevice.deviceId(), preKeys)));
}
@ -170,7 +157,7 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
.flatMap(authenticatedDevice -> storeOneTimePreKeys(authenticatedDevice.accountIdentifier(),
request.getPreKeysList(),
IdentityType.fromGrpcIdentityType(request.getIdentityType()),
IdentityTypeUtil.fromGrpcIdentityType(request.getIdentityType()),
KeysGrpcService::checkKemSignedPreKey,
(identifier, preKeys) -> keysManager.storeKemOneTimePreKeys(identifier, authenticatedDevice.deviceId(), preKeys)));
}
@ -184,18 +171,15 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
return Mono.fromFuture(accountsManager.getByAccountIdentifierAsync(authenticatedAccountUuid))
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
.map(account -> {
final Tuple2<UUID, IdentityKey> identifierAndIdentityKey =
KeysGrpcHelper.getIdentifierAndIdentityKey(account, identityType);
final List<K> preKeys = requestPreKeys.stream()
.map(requestPreKey -> extractPreKeyFunction.apply(requestPreKey, identifierAndIdentityKey.getT2()))
.map(requestPreKey -> extractPreKeyFunction.apply(requestPreKey, account.getIdentityKey(identityType)))
.toList();
if (preKeys.isEmpty()) {
throw Status.INVALID_ARGUMENT.asRuntimeException();
}
return Tuples.of(identifierAndIdentityKey.getT1(), preKeys);
return Tuples.of(account.getIdentifier(identityType), preKeys);
})
.flatMap(identifierAndPreKeys -> Mono.fromFuture(storeKeysFunction.apply(identifierAndPreKeys.getT1(), identifierAndPreKeys.getT2())))
.thenReturn(SetPreKeyResponse.newBuilder().build());
@ -209,15 +193,14 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
request.getSignedPreKey(),
KeysGrpcService::checkEcSignedPreKey,
(account, signedPreKey) -> {
final Consumer<Device> deviceUpdater = switch (IdentityType.fromGrpcIdentityType(request.getIdentityType())) {
final IdentityType identityType = IdentityTypeUtil.fromGrpcIdentityType(request.getIdentityType());
final Consumer<Device> deviceUpdater = switch (identityType) {
case ACI -> device -> device.setSignedPreKey(signedPreKey);
case PNI -> device -> device.setPhoneNumberIdentitySignedPreKey(signedPreKey);
};
final UUID identifier = switch (IdentityType.fromGrpcIdentityType(request.getIdentityType())) {
case ACI -> account.getUuid();
case PNI -> account.getPhoneNumberIdentifier();
};
final UUID identifier = account.getIdentifier(identityType);
return Flux.merge(
Mono.fromFuture(keysManager.storeEcSignedPreKeys(identifier, Map.of(authenticatedDevice.deviceId(), signedPreKey))),
@ -234,10 +217,8 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
request.getSignedPreKey(),
KeysGrpcService::checkKemSignedPreKey,
(account, lastResortKey) -> {
final UUID identifier = switch (IdentityType.fromGrpcIdentityType(request.getIdentityType())) {
case ACI -> account.getUuid();
case PNI -> account.getPhoneNumberIdentifier();
};
final UUID identifier =
account.getIdentifier(IdentityTypeUtil.fromGrpcIdentityType(request.getIdentityType()));
return Mono.fromFuture(keysManager.storePqLastResort(identifier, Map.of(authenticatedDevice.deviceId(), lastResortKey)));
}));
@ -252,11 +233,7 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
return Mono.fromFuture(accountsManager.getByAccountIdentifierAsync(authenticatedAccountUuid))
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
.map(account -> {
final IdentityKey identityKey = switch (IdentityType.fromGrpcIdentityType(identityType)) {
case ACI -> account.getIdentityKey();
case PNI -> account.getPhoneNumberIdentityKey();
};
final IdentityKey identityKey = account.getIdentityKey(IdentityTypeUtil.fromGrpcIdentityType(identityType));
final K key = extractKeyFunction.apply(storeKeyRequest, identityKey);
return Tuples.of(account, key);

View File

@ -0,0 +1,34 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.grpc;
import io.grpc.Status;
import java.util.UUID;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.UUIDUtil;
public class ServiceIdentifierUtil {
private ServiceIdentifierUtil() {
}
public static ServiceIdentifier fromGrpcServiceIdentifier(final org.signal.chat.common.ServiceIdentifier serviceIdentifier) {
final UUID uuid;
try {
uuid = UUIDUtil.fromByteString(serviceIdentifier.getUuid());
} catch (final IllegalArgumentException e) {
throw Status.INVALID_ARGUMENT.asRuntimeException();
}
return switch (IdentityTypeUtil.fromGrpcIdentityType(serviceIdentifier.getIdentityType())) {
case ACI -> new AciServiceIdentifier(uuid);
case PNI -> new PniServiceIdentifier(uuid);
};
}
}

View File

@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
@ -105,6 +106,12 @@ public class Account {
@JsonIgnore
private boolean stale;
public UUID getIdentifier(final IdentityType identityType) {
return switch (identityType) {
case ACI -> getUuid();
case PNI -> getPhoneNumberIdentifier();
};
}
public UUID getUuid() {
// this is the one method that may be called on a stale account
@ -325,12 +332,29 @@ public class Account {
this.identityKey = identityKey;
}
public IdentityKey getIdentityKey(final IdentityType identityType) {
requireNotStale();
return switch (identityType) {
case ACI -> identityKey;
case PNI -> phoneNumberIdentityKey;
};
}
/**
* @deprecated Please use {@link #getIdentityKey(IdentityType)} instead.
*/
@Deprecated
public IdentityKey getIdentityKey() {
requireNotStale();
return identityKey;
}
/**
* @deprecated Please use {@link #getIdentityKey(IdentityType)} instead.
*/
@Deprecated
public IdentityKey getPhoneNumberIdentityKey() {
return phoneNumberIdentityKey;
}

View File

@ -811,6 +811,13 @@ public class AccountsManager {
};
}
public CompletableFuture<Optional<Account>> getByServiceIdentifierAsync(final ServiceIdentifier serviceIdentifier) {
return switch (serviceIdentifier.identityType()) {
case ACI -> getByAccountIdentifierAsync(serviceIdentifier.uuid());
case PNI -> getByPhoneNumberIdentifierAsync(serviceIdentifier.uuid());
};
}
public Optional<Account> getByAccountIdentifier(final UUID uuid) {
return checkRedisThenAccounts(
getByUuidTimer,

View File

@ -14,6 +14,7 @@ import java.util.stream.LongStream;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.util.Util;
public class Device {
@ -230,6 +231,17 @@ public class Device {
this.phoneNumberIdentityRegistrationId = phoneNumberIdentityRegistrationId;
}
public ECSignedPreKey getSignedPreKey(final IdentityType identityType) {
return switch (identityType) {
case ACI -> signedPreKey;
case PNI -> phoneNumberIdentitySignedPreKey;
};
}
/**
* @deprecated Please use {@link #getSignedPreKey(IdentityType)} instead.
*/
@Deprecated
public ECSignedPreKey getSignedPreKey() {
return signedPreKey;
}
@ -238,6 +250,10 @@ public class Device {
this.signedPreKey = signedPreKey;
}
/**
* @deprecated Please use {@link #getSignedPreKey(IdentityType)} instead.
*/
@Deprecated
public ECSignedPreKey getPhoneNumberIdentitySignedPreKey() {
return phoneNumberIdentitySignedPreKey;
}

View File

@ -27,7 +27,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.signal.chat.common.EcPreKey;
import org.signal.chat.common.EcSignedPreKey;
import org.signal.chat.common.IdentityType;
import org.signal.chat.common.KemSignedPreKey;
import org.signal.chat.common.ServiceIdentifier;
import org.signal.chat.keys.GetPreKeysAnonymousRequest;
@ -39,6 +38,8 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.whispersystems.textsecuregcm.entities.ECPreKey;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
@ -86,9 +87,9 @@ class KeysAnonymousGrpcServiceTest {
when(targetAccount.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(targetDevice));
when(targetAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(targetAccount.getUuid()).thenReturn(identifier);
when(targetAccount.getIdentityKey()).thenReturn(identityKey);
when(accountsManager.getByAccountIdentifierAsync(identifier))
when(targetAccount.getIdentifier(IdentityType.ACI)).thenReturn(identifier);
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(identityKey);
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(identifier)))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
final ECPreKey ecPreKey = new ECPreKey(1, Curve.generateKeyPair().getPublicKey());
@ -97,11 +98,11 @@ class KeysAnonymousGrpcServiceTest {
when(keysManager.takeEC(identifier, Device.MASTER_ID)).thenReturn(CompletableFuture.completedFuture(Optional.of(ecPreKey)));
when(keysManager.takePQ(identifier, Device.MASTER_ID)).thenReturn(CompletableFuture.completedFuture(Optional.of(kemSignedPreKey)));
when(targetDevice.getSignedPreKey()).thenReturn(ecSignedPreKey);
when(targetDevice.getSignedPreKey(IdentityType.ACI)).thenReturn(ecSignedPreKey);
final GetPreKeysResponse response = keysAnonymousStub.getPreKeys(GetPreKeysAnonymousRequest.newBuilder()
.setTargetIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setIdentityType(org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI)
.setUuid(UUIDUtil.toByteString(identifier))
.build())
.setDeviceId(Device.MASTER_ID)
@ -144,15 +145,15 @@ class KeysAnonymousGrpcServiceTest {
when(targetAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(targetAccount.getUuid()).thenReturn(identifier);
when(targetAccount.getIdentityKey()).thenReturn(identityKey);
when(accountsManager.getByAccountIdentifierAsync(identifier))
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(identityKey);
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(identifier)))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
@SuppressWarnings("ResultOfMethodCallIgnored") final StatusRuntimeException statusRuntimeException =
assertThrows(StatusRuntimeException.class,
() -> keysAnonymousStub.getPreKeys(GetPreKeysAnonymousRequest.newBuilder()
.setTargetIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setIdentityType(org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI)
.setUuid(UUIDUtil.toByteString(identifier))
.build())
.setDeviceId(Device.MASTER_ID)
@ -163,7 +164,7 @@ class KeysAnonymousGrpcServiceTest {
@Test
void getPreKeysAccountNotFound() {
when(accountsManager.getByAccountIdentifierAsync(any()))
when(accountsManager.getByServiceIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
@SuppressWarnings("ResultOfMethodCallIgnored") final StatusRuntimeException exception =
@ -188,12 +189,12 @@ class KeysAnonymousGrpcServiceTest {
final Account targetAccount = mock(Account.class);
when(targetAccount.getUuid()).thenReturn(accountIdentifier);
when(targetAccount.getIdentityKey()).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getDevices()).thenReturn(Collections.emptyList());
when(targetAccount.getDevice(anyLong())).thenReturn(Optional.empty());
when(targetAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(accountsManager.getByAccountIdentifierAsync(accountIdentifier))
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(accountIdentifier)))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
@SuppressWarnings("ResultOfMethodCallIgnored") final StatusRuntimeException exception =

View File

@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -59,6 +60,8 @@ import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.ECPreKey;
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
@ -109,8 +112,10 @@ class KeysGrpcServiceTest {
final Account authenticatedAccount = mock(Account.class);
when(authenticatedAccount.getUuid()).thenReturn(AUTHENTICATED_ACI);
when(authenticatedAccount.getPhoneNumberIdentifier()).thenReturn(AUTHENTICATED_PNI);
when(authenticatedAccount.getIdentityKey()).thenReturn(new IdentityKey(ACI_IDENTITY_KEY_PAIR.getPublicKey()));
when(authenticatedAccount.getPhoneNumberIdentityKey()).thenReturn(new IdentityKey(PNI_IDENTITY_KEY_PAIR.getPublicKey()));
when(authenticatedAccount.getIdentifier(IdentityType.ACI)).thenReturn(AUTHENTICATED_ACI);
when(authenticatedAccount.getIdentifier(IdentityType.PNI)).thenReturn(AUTHENTICATED_PNI);
when(authenticatedAccount.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(ACI_IDENTITY_KEY_PAIR.getPublicKey()));
when(authenticatedAccount.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(PNI_IDENTITY_KEY_PAIR.getPublicKey()));
when(authenticatedAccount.getDevice(AUTHENTICATED_DEVICE_ID)).thenReturn(Optional.of(authenticatedDevice));
final MockAuthenticationInterceptor mockAuthenticationInterceptor = new MockAuthenticationInterceptor();
@ -172,7 +177,7 @@ class KeysGrpcServiceTest {
.toList())
.build());
final UUID expectedIdentifier = switch (IdentityType.fromGrpcIdentityType(identityType)) {
final UUID expectedIdentifier = switch (IdentityTypeUtil.fromGrpcIdentityType(identityType)) {
case ACI -> AUTHENTICATED_ACI;
case PNI -> AUTHENTICATED_PNI;
};
@ -218,7 +223,7 @@ class KeysGrpcServiceTest {
@ParameterizedTest
@EnumSource(value = org.signal.chat.common.IdentityType.class, names = {"IDENTITY_TYPE_ACI", "IDENTITY_TYPE_PNI"})
void setOneTimeKemSignedPreKeys(final org.signal.chat.common.IdentityType identityType) {
final ECKeyPair identityKeyPair = switch (IdentityType.fromGrpcIdentityType(identityType)) {
final ECKeyPair identityKeyPair = switch (IdentityTypeUtil.fromGrpcIdentityType(identityType)) {
case ACI -> ACI_IDENTITY_KEY_PAIR;
case PNI -> PNI_IDENTITY_KEY_PAIR;
};
@ -245,7 +250,7 @@ class KeysGrpcServiceTest {
.toList())
.build());
final UUID expectedIdentifier = switch (IdentityType.fromGrpcIdentityType(identityType)) {
final UUID expectedIdentifier = switch (IdentityTypeUtil.fromGrpcIdentityType(identityType)) {
case ACI -> AUTHENTICATED_ACI;
case PNI -> AUTHENTICATED_PNI;
};
@ -317,7 +322,7 @@ class KeysGrpcServiceTest {
when(keysManager.storeEcSignedPreKeys(any(), any())).thenReturn(CompletableFuture.completedFuture(null));
final ECKeyPair identityKeyPair = switch (IdentityType.fromGrpcIdentityType(identityType)) {
final ECKeyPair identityKeyPair = switch (IdentityTypeUtil.fromGrpcIdentityType(identityType)) {
case ACI -> ACI_IDENTITY_KEY_PAIR;
case PNI -> PNI_IDENTITY_KEY_PAIR;
};
@ -401,7 +406,7 @@ class KeysGrpcServiceTest {
void setLastResortPreKey(final org.signal.chat.common.IdentityType identityType) {
when(keysManager.storePqLastResort(any(), any())).thenReturn(CompletableFuture.completedFuture(null));
final ECKeyPair identityKeyPair = switch (IdentityType.fromGrpcIdentityType(identityType)) {
final ECKeyPair identityKeyPair = switch (IdentityTypeUtil.fromGrpcIdentityType(identityType)) {
case ACI -> ACI_IDENTITY_KEY_PAIR;
case PNI -> PNI_IDENTITY_KEY_PAIR;
};
@ -478,25 +483,20 @@ class KeysGrpcServiceTest {
@ParameterizedTest
@EnumSource(value = org.signal.chat.common.IdentityType.class, names = {"IDENTITY_TYPE_ACI", "IDENTITY_TYPE_PNI"})
void getPreKeys(final org.signal.chat.common.IdentityType identityType) {
void getPreKeys(final org.signal.chat.common.IdentityType grpcIdentityType) {
final Account targetAccount = mock(Account.class);
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey());
final UUID identifier = UUID.randomUUID();
if (identityType == org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI) {
when(targetAccount.getUuid()).thenReturn(identifier);
when(targetAccount.getIdentityKey()).thenReturn(identityKey);
when(accountsManager.getByAccountIdentifierAsync(identifier))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
} else {
when(targetAccount.getUuid()).thenReturn(UUID.randomUUID());
when(targetAccount.getPhoneNumberIdentifier()).thenReturn(identifier);
when(targetAccount.getPhoneNumberIdentityKey()).thenReturn(identityKey);
when(accountsManager.getByPhoneNumberIdentifierAsync(identifier))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
}
final IdentityType identityType = IdentityTypeUtil.fromGrpcIdentityType(grpcIdentityType);
when(targetAccount.getUuid()).thenReturn(UUID.randomUUID());
when(targetAccount.getIdentifier(identityType)).thenReturn(identifier);
when(targetAccount.getIdentityKey(identityType)).thenReturn(identityKey);
when(accountsManager.getByServiceIdentifierAsync(argThat(serviceIdentifier -> serviceIdentifier.uuid().equals(identifier))))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
final Map<Long, ECPreKey> ecOneTimePreKeys = new HashMap<>();
final Map<Long, KEMSignedPreKey> kemPreKeys = new HashMap<>();
@ -512,12 +512,7 @@ class KeysGrpcServiceTest {
final Device device = mock(Device.class);
when(device.getId()).thenReturn(deviceId);
when(device.isEnabled()).thenReturn(true);
if (identityType == org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI) {
when(device.getSignedPreKey()).thenReturn(ecSignedPreKeys.get(deviceId));
} else {
when(device.getPhoneNumberIdentitySignedPreKey()).thenReturn(ecSignedPreKeys.get(deviceId));
}
when(device.getSignedPreKey(identityType)).thenReturn(ecSignedPreKeys.get(deviceId));
devices.put(deviceId, device);
when(targetAccount.getDevice(deviceId)).thenReturn(Optional.of(device));
@ -534,7 +529,7 @@ class KeysGrpcServiceTest {
{
final GetPreKeysResponse response = keysStub.getPreKeys(GetPreKeysRequest.newBuilder()
.setTargetIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(identityType)
.setIdentityType(grpcIdentityType)
.setUuid(UUIDUtil.toByteString(identifier))
.build())
.setDeviceId(1)
@ -569,7 +564,7 @@ class KeysGrpcServiceTest {
{
final GetPreKeysResponse response = keysStub.getPreKeys(GetPreKeysRequest.newBuilder()
.setTargetIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(identityType)
.setIdentityType(grpcIdentityType)
.setUuid(UUIDUtil.toByteString(identifier))
.build())
.build());
@ -607,7 +602,7 @@ class KeysGrpcServiceTest {
@Test
void getPreKeysAccountNotFound() {
when(accountsManager.getByAccountIdentifierAsync(any()))
when(accountsManager.getByServiceIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
@SuppressWarnings("ResultOfMethodCallIgnored") final StatusRuntimeException exception =
@ -628,11 +623,11 @@ class KeysGrpcServiceTest {
final Account targetAccount = mock(Account.class);
when(targetAccount.getUuid()).thenReturn(accountIdentifier);
when(targetAccount.getIdentityKey()).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getDevices()).thenReturn(Collections.emptyList());
when(targetAccount.getDevice(anyLong())).thenReturn(Optional.empty());
when(accountsManager.getByAccountIdentifierAsync(accountIdentifier))
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(accountIdentifier)))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
@SuppressWarnings("ResultOfMethodCallIgnored") final StatusRuntimeException exception =
@ -651,11 +646,11 @@ class KeysGrpcServiceTest {
void getPreKeysRateLimited() {
final Account targetAccount = mock(Account.class);
when(targetAccount.getUuid()).thenReturn(UUID.randomUUID());
when(targetAccount.getIdentityKey()).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(Curve.generateKeyPair().getPublicKey()));
when(targetAccount.getDevices()).thenReturn(Collections.emptyList());
when(targetAccount.getDevice(anyLong())).thenReturn(Optional.empty());
when(accountsManager.getByAccountIdentifierAsync(any()))
when(accountsManager.getByServiceIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.of(targetAccount)));
final Duration retryAfterDuration = Duration.ofMinutes(7);

View File

@ -218,7 +218,7 @@ class AccountsManagerTest {
when(commands.get(eq("AccountMap::" + pni))).thenReturn(aci.toString());
when(commands.get(eq("Account3::" + aci))).thenReturn(
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}");
"{\"number\": \"+14152222222\", \"pni\": \"" + pni + "\"}");
assertTrue(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(aci)).isPresent());
assertTrue(accountsManager.getByServiceIdentifier(new PniServiceIdentifier(pni)).isPresent());
@ -226,6 +226,29 @@ class AccountsManagerTest {
assertFalse(accountsManager.getByServiceIdentifier(new PniServiceIdentifier(aci)).isPresent());
}
@Test
void testGetByServiceIdentifierAsync() {
final UUID aci = UUID.randomUUID();
final UUID pni = UUID.randomUUID();
when(asyncCommands.get(eq("AccountMap::" + pni))).thenReturn(MockRedisFuture.completedFuture(aci.toString()));
when(asyncCommands.get(eq("Account3::" + aci))).thenReturn(MockRedisFuture.completedFuture(
"{\"number\": \"+14152222222\", \"pni\": \"" + pni + "\"}"));
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
when(accounts.getByAccountIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
when(accounts.getByPhoneNumberIdentifierAsync(any()))
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
assertTrue(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(aci)).join().isPresent());
assertTrue(accountsManager.getByServiceIdentifierAsync(new PniServiceIdentifier(pni)).join().isPresent());
assertFalse(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(pni)).join().isPresent());
assertFalse(accountsManager.getByServiceIdentifierAsync(new PniServiceIdentifier(aci)).join().isPresent());
}
@Test
void testGetAccountByNumberInCache() {
UUID uuid = UUID.randomUUID();
@ -315,7 +338,7 @@ class AccountsManagerTest {
}
@Test
void testGetByPniInCache() {
void testGetAccountByPniInCache() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
@ -337,7 +360,7 @@ class AccountsManagerTest {
}
@Test
void testGetByPniInCacheAsync() {
void testGetAccountByPniInCacheAsync() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
@ -363,7 +386,7 @@ class AccountsManagerTest {
}
@Test
void testGetByUsernameHashInCache() {
void testGetAccountByUsernameHashInCache() {
UUID uuid = UUID.randomUUID();
when(commands.get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1))).thenReturn(uuid.toString());
when(commands.get(eq("Account3::" + uuid))).thenReturn(