Define identity key check endpoint in keys anonymous service

This commit is contained in:
Katherine
2023-09-11 11:57:00 -07:00
committed by GitHub
parent c11b74e9c0
commit cbc3887226
6 changed files with 172 additions and 31 deletions

View File

@@ -20,4 +20,11 @@ public class IdentityTypeUtil {
case IDENTITY_TYPE_UNSPECIFIED, UNRECOGNIZED -> throw Status.INVALID_ARGUMENT.asRuntimeException();
};
}
public static org.signal.chat.common.IdentityType toGrpcIdentityType(final IdentityType identityType) {
return switch (identityType) {
case ACI -> org.signal.chat.common.IdentityType.IDENTITY_TYPE_ACI;
case PNI -> org.signal.chat.common.IdentityType.IDENTITY_TYPE_PNI;
};
}
}

View File

@@ -5,15 +5,24 @@
package org.whispersystems.textsecuregcm.grpc;
import com.google.protobuf.ByteString;
import io.grpc.Status;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
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.ReactorKeysAnonymousGrpc;
import org.signal.libsignal.protocol.IdentityKey;
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.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;
public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnonymousImplBase {
@@ -38,4 +47,35 @@ public class KeysAnonymousGrpcService extends ReactorKeysAnonymousGrpc.KeysAnony
? KeysGrpcHelper.getPreKeys(targetAccount, serviceIdentifier.identityType(), request.getRequest().getDeviceId(), keysManager)
: Mono.error(Status.UNAUTHENTICATED.asException()));
}
@Override
public Flux<CheckIdentityKeyResponse> checkIdentityKeys(final Flux<CheckIdentityKeyRequest> requests) {
return requests
.map(request -> Tuples.of(ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getTargetIdentifier()),
request.getFingerprint().toByteArray()))
.flatMap(serviceIdentifierAndFingerprint -> Mono.fromFuture(
() -> accountsManager.getByServiceIdentifierAsync(serviceIdentifierAndFingerprint.getT1()))
.flatMap(Mono::justOrEmpty)
.filter(account -> !fingerprintMatches(account.getIdentityKey(serviceIdentifierAndFingerprint.getT1()
.identityType()), serviceIdentifierAndFingerprint.getT2()))
.map(account -> CheckIdentityKeyResponse.newBuilder()
.setTargetIdentifier(
ServiceIdentifierUtil.toGrpcServiceIdentifier(serviceIdentifierAndFingerprint.getT1()))
.setIdentityKey(ByteString.copyFrom(account.getIdentityKey(serviceIdentifierAndFingerprint.getT1()
.identityType()).serialize()))
.build())
);
}
private static boolean fingerprintMatches(final IdentityKey identityKey, final byte[] fingerprint) {
final byte[] digest;
try {
digest = MessageDigest.getInstance("SHA-256").digest(identityKey.serialize());
} catch (NoSuchAlgorithmException e) {
// SHA-256 should always be supported as an algorithm
throw new AssertionError("All Java implementations must support the SHA-256 message digest");
}
return Arrays.equals(digest, 0, 4, fingerprint, 0, 4);
}
}

View File

@@ -5,8 +5,10 @@
package org.whispersystems.textsecuregcm.grpc;
import com.google.protobuf.ByteString;
import io.grpc.Status;
import java.util.UUID;
import org.signal.chat.common.IdentityType;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
@@ -31,4 +33,11 @@ public class ServiceIdentifierUtil {
case PNI -> new PniServiceIdentifier(uuid);
};
}
public static org.signal.chat.common.ServiceIdentifier toGrpcServiceIdentifier(final ServiceIdentifier serviceIdentifier) {
return org.signal.chat.common.ServiceIdentifier.newBuilder()
.setIdentityType(IdentityTypeUtil.toGrpcIdentityType(serviceIdentifier.identityType()))
.setUuid(UUIDUtil.toByteString(serviceIdentifier.uuid()))
.build();
}
}

View File

@@ -104,6 +104,14 @@ service KeysAnonymous {
* devices.
*/
rpc GetPreKeys(GetPreKeysAnonymousRequest) returns (GetPreKeysResponse) {}
/**
* Checks identity key fingerprints of the target accounts.
*
* Returns a stream of elements, each one representing an account that had a mismatched
* identity key fingerprint with the server and the corresponding identity key stored by the server.
*/
rpc CheckIdentityKeys(stream CheckIdentityKeyRequest) returns (stream CheckIdentityKeyResponse) {}
}
message GetPreKeyCountRequest {
@@ -247,3 +255,25 @@ message SetKemLastResortPreKeyRequest {
message SetPreKeyResponse {
}
message CheckIdentityKeyRequest {
/**
* The service identifier of the account for which we want to check the associated identity key fingerprint.
*/
common.ServiceIdentifier target_identifier = 1;
/**
* The most significant 4 bytes of the SHA-256 hash of the identity key associated with the target account/identity type.
*/
bytes fingerprint = 2;
}
message CheckIdentityKeyResponse {
/**
* The service identifier of the account for which there is a mismatch between the client and server identity key fingerprints.
*/
common.ServiceIdentifier target_identifier = 1;
/**
* The identity key that is stored by the server for the target account/identity type.
*/
bytes identity_key = 2;
}

View File

@@ -96,14 +96,6 @@ service ProfileAnonymous {
* access key, or an `INVALID_ARGUMENT` status if the given credential type is invalid.
*/
rpc GetExpiringProfileKeyCredential(GetExpiringProfileKeyCredentialAnonymousRequest) returns (GetExpiringProfileKeyCredentialResponse) {}
/**
* Checks identity key fingerprints of the target accounts.
*
* Returns a stream of elements, each one representing an account that had a mismatched
* identity key fingerprint with the server and the corresponding identity key stored by the server.
*/
rpc CheckIdentityKeys(stream CheckIdentityKeysRequest) returns (stream CheckIdentityKeysResponse) {}
}
message SetProfileRequest {
@@ -278,28 +270,6 @@ message GetExpiringProfileKeyCredentialResponse {
bytes profileKeyCredential = 1;
}
message CheckIdentityKeysRequest {
/**
* The service identifier of the account for which we want to check the associated identity key fingerprint.
*/
common.ServiceIdentifier target_identifier = 1;
/**
* The most significant 4 bytes of the SHA-256 hash of the identity key associated with the target account/identity type.
*/
bytes fingerprint = 2;
}
message CheckIdentityKeysResponse {
/**
* The service identifier of the account for which there is a mismatch between the client and server identity key fingerprints.
*/
common.ServiceIdentifier target_identifier = 1;
/**
* The identity key that is stored by the server for the target account/identity type.
*/
bytes identity_key = 2;
}
message ProfileAvatarUploadAttributes {
/**
* The S3 upload path for the profile's avatar.