mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-18 20:05:18 +01:00
Update error model in keys.proto
This commit is contained in:
@@ -6,19 +6,24 @@
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Clock;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.signal.chat.errors.FailedUnidentifiedAuthorization;
|
||||
import org.signal.chat.errors.NotFound;
|
||||
import org.signal.chat.keys.CheckIdentityKeyRequest;
|
||||
import org.signal.chat.keys.CheckIdentityKeyResponse;
|
||||
import org.signal.chat.keys.GetPreKeysAnonymousRequest;
|
||||
import org.signal.chat.keys.GetPreKeysResponse;
|
||||
import org.signal.chat.keys.GetPreKeysAnonymousResponse;
|
||||
import org.signal.chat.keys.ReactorKeysAnonymousGrpc;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendDerivedKeyPair;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
|
||||
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
@@ -32,17 +37,19 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final KeysManager keysManager;
|
||||
private final GroupSendTokenUtil groupSendTokenUtil;
|
||||
private final ServerSecretParams serverSecretParams;
|
||||
private final Clock clock;
|
||||
|
||||
public KeysAnonymousGrpcService(
|
||||
final AccountsManager accountsManager, final KeysManager keysManager, final ServerSecretParams serverSecretParams, final Clock clock) {
|
||||
this.accountsManager = accountsManager;
|
||||
this.keysManager = keysManager;
|
||||
this.groupSendTokenUtil = new GroupSendTokenUtil(serverSecretParams, clock);
|
||||
}
|
||||
this.serverSecretParams = serverSecretParams;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetPreKeysResponse> getPreKeys(final GetPreKeysAnonymousRequest request) {
|
||||
public Mono<GetPreKeysAnonymousResponse> getPreKeys(final GetPreKeysAnonymousRequest request) {
|
||||
final ServiceIdentifier serviceIdentifier =
|
||||
ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getTargetIdentifier());
|
||||
|
||||
@@ -53,23 +60,35 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
|
||||
return switch (request.getAuthorizationCase()) {
|
||||
case GROUP_SEND_TOKEN -> {
|
||||
try {
|
||||
groupSendTokenUtil.checkGroupSendToken(request.getGroupSendToken(), serviceIdentifier);
|
||||
final GroupSendFullToken token = new GroupSendFullToken(request.getGroupSendToken().toByteArray());
|
||||
token.verify(List.of(serviceIdentifier.toLibsignal()), clock.instant(),
|
||||
GroupSendDerivedKeyPair.forExpiration(token.getExpiration(), serverSecretParams));
|
||||
|
||||
yield lookUpAccount(serviceIdentifier, Status.NOT_FOUND)
|
||||
.flatMap(targetAccount -> KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier, deviceId, keysManager));
|
||||
} catch (final StatusException e) {
|
||||
yield Mono.error(e);
|
||||
yield lookUpAccount(serviceIdentifier)
|
||||
.flatMap(targetAccount -> KeysGrpcHelper
|
||||
.getPreKeys(targetAccount, serviceIdentifier, deviceId, keysManager))
|
||||
.map(preKeys -> GetPreKeysAnonymousResponse.newBuilder().setPreKeys(preKeys).build())
|
||||
.switchIfEmpty(Mono.fromSupplier(() -> GetPreKeysAnonymousResponse.newBuilder()
|
||||
.setTargetNotFound(NotFound.getDefaultInstance())
|
||||
.build()));
|
||||
} catch (InvalidInputException e) {
|
||||
throw GrpcExceptions.fieldViolation("group_send_token", "malformed group send token");
|
||||
} catch (VerificationFailedException e) {
|
||||
yield Mono.fromSupplier(() -> GetPreKeysAnonymousResponse.newBuilder()
|
||||
.setFailedUnidentifiedAuthorization(FailedUnidentifiedAuthorization.getDefaultInstance())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
case UNIDENTIFIED_ACCESS_KEY -> lookUpAccount(serviceIdentifier)
|
||||
.filter(targetAccount ->
|
||||
UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, request.getUnidentifiedAccessKey().toByteArray()))
|
||||
.flatMap(targetAccount -> KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier, deviceId, keysManager))
|
||||
.map(preKeys -> GetPreKeysAnonymousResponse.newBuilder().setPreKeys(preKeys).build())
|
||||
.switchIfEmpty(Mono.fromSupplier(() -> GetPreKeysAnonymousResponse.newBuilder()
|
||||
.setFailedUnidentifiedAuthorization(FailedUnidentifiedAuthorization.getDefaultInstance())
|
||||
.build()));
|
||||
|
||||
case UNIDENTIFIED_ACCESS_KEY ->
|
||||
lookUpAccount(serviceIdentifier, Status.UNAUTHENTICATED)
|
||||
.flatMap(targetAccount ->
|
||||
UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, request.getUnidentifiedAccessKey().toByteArray())
|
||||
? KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier, deviceId, keysManager)
|
||||
: Mono.error(Status.UNAUTHENTICATED.asException()));
|
||||
|
||||
default -> Mono.error(Status.INVALID_ARGUMENT.asException());
|
||||
default -> Mono.error(GrpcExceptions.fieldViolation("authorization", "invalid authorization type"));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,10 +111,9 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<Account> lookUpAccount(final ServiceIdentifier serviceIdentifier, final Status onNotFound) {
|
||||
private Mono<Account> lookUpAccount(final ServiceIdentifier serviceIdentifier) {
|
||||
return Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(serviceIdentifier))
|
||||
.flatMap(Mono::justOrEmpty)
|
||||
.switchIfEmpty(Mono.error(onNotFound.asException()));
|
||||
.flatMap(Mono::justOrEmpty);
|
||||
}
|
||||
|
||||
private static boolean fingerprintMatches(final IdentityKey identityKey, final byte[] fingerprint) {
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.grpc.Status;
|
||||
import org.signal.chat.common.EcPreKey;
|
||||
import org.signal.chat.common.EcSignedPreKey;
|
||||
import org.signal.chat.common.KemSignedPreKey;
|
||||
import org.signal.chat.keys.GetPreKeysResponse;
|
||||
import org.signal.chat.keys.AccountPreKeyBundles;
|
||||
import org.signal.chat.keys.DevicePreKeyBundle;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
@@ -19,12 +20,22 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
import java.util.Optional;
|
||||
|
||||
class KeysGrpcHelper {
|
||||
|
||||
static final byte ALL_DEVICES = 0;
|
||||
|
||||
static Mono<GetPreKeysResponse> getPreKeys(final Account targetAccount,
|
||||
/**
|
||||
* Fetch {@link AccountPreKeyBundles} from the targetAccount
|
||||
*
|
||||
* @param targetAccount The targetAccount to fetch pre-key bundles from
|
||||
* @param targetServiceIdentifier The serviceIdentifier used to lookup the targetAccount
|
||||
* @param targetDeviceId The deviceId to retrieve pre-key bundles for, or ALL_DEVICES if all devices should be retrieved
|
||||
* @param keysManager The {@link KeysManager} to lookup pre-keys from
|
||||
* @return The requested bundles, or an empty Mono if the keys for the targetAccount do not exist
|
||||
*/
|
||||
static Mono<AccountPreKeyBundles> getPreKeys(final Account targetAccount,
|
||||
final ServiceIdentifier targetServiceIdentifier,
|
||||
final byte targetDeviceId,
|
||||
final KeysManager keysManager) {
|
||||
@@ -41,7 +52,7 @@ class KeysGrpcHelper {
|
||||
.fromFuture(keysManager.takeDevicePreKeys(device.getId(), targetServiceIdentifier, userAgent))
|
||||
.flatMap(Mono::justOrEmpty)
|
||||
.map(devicePreKeys -> {
|
||||
final GetPreKeysResponse.PreKeyBundle.Builder builder = GetPreKeysResponse.PreKeyBundle.newBuilder()
|
||||
final DevicePreKeyBundle.Builder builder = DevicePreKeyBundle.newBuilder()
|
||||
.setEcSignedPreKey(EcSignedPreKey.newBuilder()
|
||||
.setKeyId(devicePreKeys.ecSignedPreKey().keyId())
|
||||
.setPublicKey(ByteString.copyFrom(devicePreKeys.ecSignedPreKey().serializedPublicKey()))
|
||||
@@ -54,21 +65,25 @@ class KeysGrpcHelper {
|
||||
.build())
|
||||
.setRegistrationId(registrationId);
|
||||
devicePreKeys.ecPreKey().ifPresent(ecPreKey -> builder.setEcOneTimePreKey(EcPreKey.newBuilder()
|
||||
.setKeyId(ecPreKey.keyId())
|
||||
.setPublicKey(ByteString.copyFrom(ecPreKey.serializedPublicKey()))
|
||||
.build()));
|
||||
.setKeyId(ecPreKey.keyId())
|
||||
.setPublicKey(ByteString.copyFrom(ecPreKey.serializedPublicKey()))
|
||||
.build()));
|
||||
// Cast device IDs to `int` to match data types in the response object’s protobuf definition
|
||||
return Tuples.of((int) device.getId(), builder.build());
|
||||
});
|
||||
})
|
||||
// If there were no devices with valid prekey bundles in the account, the account is gone
|
||||
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()))
|
||||
.collectMap(Tuple2::getT1, Tuple2::getT2)
|
||||
.map(preKeyBundles -> GetPreKeysResponse.newBuilder()
|
||||
.setIdentityKey(ByteString
|
||||
.copyFrom(targetAccount.getIdentityKey(targetServiceIdentifier.identityType())
|
||||
.serialize()))
|
||||
.putAllPreKeys(preKeyBundles)
|
||||
.build());
|
||||
.flatMap(preKeyBundles -> {
|
||||
if (preKeyBundles.isEmpty()) {
|
||||
// If there were no devices with valid prekey bundles in the account, the account is gone
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
final IdentityKey targetIdentityKey = targetAccount.getIdentityKey(targetServiceIdentifier.identityType());
|
||||
return Mono.just(AccountPreKeyBundles.newBuilder()
|
||||
.setIdentityKey(ByteString.copyFrom(targetIdentityKey.serialize()))
|
||||
.putAllDevicePreKeys(preKeyBundles)
|
||||
.build());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import org.signal.chat.common.EcPreKey;
|
||||
import org.signal.chat.common.EcSignedPreKey;
|
||||
import org.signal.chat.common.KemSignedPreKey;
|
||||
import org.signal.chat.errors.NotFound;
|
||||
import org.signal.chat.keys.GetPreKeyCountRequest;
|
||||
import org.signal.chat.keys.GetPreKeyCountResponse;
|
||||
import org.signal.chat.keys.GetPreKeysRequest;
|
||||
@@ -50,13 +49,11 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
private final KeysManager keysManager;
|
||||
private final RateLimiters rateLimiters;
|
||||
|
||||
private static final StatusRuntimeException INVALID_PUBLIC_KEY_EXCEPTION = Status.fromCode(Status.Code.INVALID_ARGUMENT)
|
||||
.withDescription("Invalid public key")
|
||||
.asRuntimeException();
|
||||
private static final StatusRuntimeException INVALID_PUBLIC_KEY_EXCEPTION =
|
||||
GrpcExceptions.fieldViolation("pre_keys", "invalid public key");
|
||||
|
||||
private static final StatusRuntimeException INVALID_SIGNATURE_EXCEPTION = Status.fromCode(Status.Code.INVALID_ARGUMENT)
|
||||
.withDescription("Invalid signature")
|
||||
.asRuntimeException();
|
||||
private static final StatusRuntimeException INVALID_SIGNATURE_EXCEPTION =
|
||||
GrpcExceptions.fieldViolation("pre_keys", "pre-key signature did not match account identity key");
|
||||
|
||||
private enum PreKeyType {
|
||||
EC,
|
||||
@@ -75,10 +72,8 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
@Override
|
||||
public Mono<GetPreKeyCountResponse> getPreKeyCount(final GetPreKeyCountRequest request) {
|
||||
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
|
||||
.flatMap(authenticatedDevice -> Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier()))
|
||||
.map(maybeAccount -> maybeAccount
|
||||
.map(account -> Tuples.of(account, authenticatedDevice.deviceId()))
|
||||
.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException)))
|
||||
.flatMap(authenticatedDevice -> getAuthenticatedAccount(authenticatedDevice.accountIdentifier())
|
||||
.zipWith(Mono.just(authenticatedDevice.deviceId())))
|
||||
.flatMapMany(accountAndDeviceId -> Flux.just(
|
||||
Tuples.of(IdentityType.ACI, accountAndDeviceId.getT1().getUuid(), accountAndDeviceId.getT2()),
|
||||
Tuples.of(IdentityType.PNI, accountAndDeviceId.getT1().getPhoneNumberIdentifier(), accountAndDeviceId.getT2())
|
||||
@@ -132,15 +127,22 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
deviceId;
|
||||
|
||||
return rateLimiters.getPreKeysLimiter().validateReactive(rateLimitKey)
|
||||
.then(Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
|
||||
.flatMap(Mono::justOrEmpty))
|
||||
.switchIfEmpty(Mono.error(Status.NOT_FOUND.asException()))
|
||||
.flatMap(targetAccount ->
|
||||
KeysGrpcHelper.getPreKeys(targetAccount, targetIdentifier, deviceId, keysManager));
|
||||
.then(Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier)))
|
||||
.flatMap(Mono::justOrEmpty)
|
||||
.flatMap(targetAccount -> KeysGrpcHelper.getPreKeys(targetAccount, targetIdentifier, deviceId, keysManager))
|
||||
.map(bundles -> GetPreKeysResponse.newBuilder()
|
||||
.setPreKeys(bundles)
|
||||
.build())
|
||||
.switchIfEmpty(Mono.fromSupplier(() -> GetPreKeysResponse.newBuilder()
|
||||
.setTargetNotFound(NotFound.getDefaultInstance())
|
||||
.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SetPreKeyResponse> setOneTimeEcPreKeys(final SetOneTimeEcPreKeysRequest request) {
|
||||
if (request.getPreKeysList().isEmpty()) {
|
||||
throw GrpcExceptions.fieldViolation("pre_keys", "pre_keys must be non-empty");
|
||||
}
|
||||
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
|
||||
.flatMap(authenticatedDevice -> storeOneTimePreKeys(authenticatedDevice.accountIdentifier(),
|
||||
request.getPreKeysList(),
|
||||
@@ -151,6 +153,9 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
|
||||
@Override
|
||||
public Mono<SetPreKeyResponse> setOneTimeKemSignedPreKeys(final SetOneTimeKemSignedPreKeysRequest request) {
|
||||
if (request.getPreKeysList().isEmpty()) {
|
||||
throw GrpcExceptions.fieldViolation("pre_keys", "pre_keys must be non-empty");
|
||||
}
|
||||
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
|
||||
.flatMap(authenticatedDevice -> storeOneTimePreKeys(authenticatedDevice.accountIdentifier(),
|
||||
request.getPreKeysList(),
|
||||
@@ -165,17 +170,12 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
final BiFunction<R, IdentityKey, K> extractPreKeyFunction,
|
||||
final BiFunction<UUID, List<K>, CompletableFuture<Void>> storeKeysFunction) {
|
||||
|
||||
return Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedAccountUuid))
|
||||
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
|
||||
return getAuthenticatedAccount(authenticatedAccountUuid)
|
||||
.map(account -> {
|
||||
final List<K> preKeys = requestPreKeys.stream()
|
||||
.map(requestPreKey -> extractPreKeyFunction.apply(requestPreKey, account.getIdentityKey(identityType)))
|
||||
.toList();
|
||||
|
||||
if (preKeys.isEmpty()) {
|
||||
throw Status.INVALID_ARGUMENT.asRuntimeException();
|
||||
}
|
||||
|
||||
return Tuples.of(account.getIdentifier(identityType), preKeys);
|
||||
})
|
||||
.flatMap(identifierAndPreKeys -> Mono.fromFuture(() -> storeKeysFunction.apply(identifierAndPreKeys.getT1(), identifierAndPreKeys.getT2())))
|
||||
@@ -218,8 +218,7 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
final BiFunction<R, IdentityKey, K> extractKeyFunction,
|
||||
final BiFunction<Account, K, Mono<?>> storeKeyFunction) {
|
||||
|
||||
return Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedAccountUuid))
|
||||
.map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
|
||||
return getAuthenticatedAccount(authenticatedAccountUuid)
|
||||
.map(account -> {
|
||||
final IdentityKey identityKey = account.getIdentityKey(IdentityTypeUtil.fromGrpcIdentityType(identityType));
|
||||
final K key = extractKeyFunction.apply(storeKeyRequest, identityKey);
|
||||
@@ -269,4 +268,9 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
throw INVALID_PUBLIC_KEY_EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Account> getAuthenticatedAccount(final UUID authenticatedAccountId) {
|
||||
return Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedAccountId))
|
||||
.map(maybeAccount -> maybeAccount.orElseThrow(() -> GrpcExceptions.invalidCredentials("invalid credentials")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ServiceIdentifierUtil {
|
||||
try {
|
||||
uuid = UUIDUtil.fromByteString(serviceIdentifier.getUuid());
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw Status.INVALID_ARGUMENT.asRuntimeException();
|
||||
throw GrpcExceptions.invalidArguments("invalid service identifier");
|
||||
}
|
||||
|
||||
return switch (IdentityTypeUtil.fromGrpcIdentityType(serviceIdentifier.getIdentityType())) {
|
||||
|
||||
Reference in New Issue
Block a user