Profile gRPC: Define `getExpiringProfileKeyCredential` endpoint

This commit is contained in:
Katherine Yen 2023-08-30 14:56:43 -07:00 committed by GitHub
parent dd18fcaea2
commit 6a37b73463
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 696 additions and 168 deletions

View File

@ -648,8 +648,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
.addService(ServerInterceptors.intercept(new KeysGrpcService(accountsManager, keys, rateLimiters), basicCredentialAuthenticationInterceptor)) .addService(ServerInterceptors.intercept(new KeysGrpcService(accountsManager, keys, rateLimiters), basicCredentialAuthenticationInterceptor))
.addService(new KeysAnonymousGrpcService(accountsManager, keys)) .addService(new KeysAnonymousGrpcService(accountsManager, keys))
.addService(ServerInterceptors.intercept(new ProfileGrpcService(clock, accountsManager, profilesManager, dynamicConfigurationManager, .addService(ServerInterceptors.intercept(new ProfileGrpcService(clock, accountsManager, profilesManager, dynamicConfigurationManager,
config.getBadges(), asyncCdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, profileBadgeConverter, rateLimiters, config.getCdnConfiguration().bucket()), basicCredentialAuthenticationInterceptor)) config.getBadges(), asyncCdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, profileBadgeConverter, rateLimiters, zkProfileOperations, config.getCdnConfiguration().bucket()), basicCredentialAuthenticationInterceptor))
.addService(new ProfileAnonymousGrpcService(accountsManager, profilesManager, profileBadgeConverter)); .addService(new ProfileAnonymousGrpcService(accountsManager, profilesManager, profileBadgeConverter, zkProfileOperations));
RemoteDeprecationFilter remoteDeprecationFilter = new RemoteDeprecationFilter(dynamicConfigurationManager); RemoteDeprecationFilter remoteDeprecationFilter = new RemoteDeprecationFilter(dynamicConfigurationManager);
environment.servlets() environment.servlets()

View File

@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.dropwizard.auth.Auth; import io.dropwizard.auth.Auth;
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
@ -18,11 +17,7 @@ import io.vavr.Tuple;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.time.Clock; import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
@ -66,8 +61,6 @@ import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException; import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -130,9 +123,6 @@ public class ProfileController {
private final Executor batchIdentityCheckExecutor; private final Executor batchIdentityCheckExecutor;
@VisibleForTesting
static final Duration EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION = Duration.ofDays(7);
private static final String EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE = "expiringProfileKey"; private static final String EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE = "expiringProfileKey";
private static final Counter VERSION_NOT_FOUND_COUNTER = Metrics.counter(name(ProfileController.class, "versionNotFound")); private static final Counter VERSION_NOT_FOUND_COUNTER = Metrics.counter(name(ProfileController.class, "versionNotFound"));
@ -276,7 +266,6 @@ public class ProfileController {
version, version,
credentialRequest, credentialRequest,
isSelf, isSelf,
Instant.now().plus(EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION).truncatedTo(ChronoUnit.DAYS),
containerRequestContext); containerRequestContext);
} }
@ -387,11 +376,19 @@ public class ProfileController {
final String version, final String version,
final String encodedCredentialRequest, final String encodedCredentialRequest,
final boolean isSelf, final boolean isSelf,
final Instant expiration,
final ContainerRequestContext containerRequestContext) { final ContainerRequestContext containerRequestContext) {
final ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = profilesManager.get(account.getUuid(), version) final ExpiringProfileKeyCredentialResponse expiringProfileKeyCredentialResponse = profilesManager.get(account.getUuid(), version)
.map(profile -> getExpiringProfileKeyCredentialResponse(encodedCredentialRequest, profile, new ServiceId.Aci(account.getUuid()), expiration)) .map(profile -> {
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(HexFormat.of().parseHex(encodedCredentialRequest),
profile, new ServiceId.Aci(account.getUuid()), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).build(), e);
}
return profileKeyCredentialResponse;
})
.orElse(null); .orElse(null);
return new ExpiringProfileKeyCredentialProfileResponse( return new ExpiringProfileKeyCredentialProfileResponse(
@ -453,23 +450,6 @@ public class ProfileController {
new PniServiceIdentifier(account.getPhoneNumberIdentifier())); new PniServiceIdentifier(account.getPhoneNumberIdentifier()));
} }
private ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse(
final String encodedCredentialRequest,
final VersionedProfile profile,
final ServiceId.Aci accountIdentifier,
final Instant expiration) {
try {
final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.commitment());
final ProfileKeyCredentialRequest request = new ProfileKeyCredentialRequest(
HexFormat.of().parseHex(encodedCredentialRequest));
return zkProfileOperations.issueExpiringProfileKeyCredential(request, accountIdentifier, commitment, expiration);
} catch (IllegalArgumentException | VerificationFailedException | InvalidInputException e) {
throw new WebApplicationException(e, Response.status(Response.Status.BAD_REQUEST).build());
}
}
private List<Locale> getAcceptableLanguagesForRequest(final ContainerRequestContext containerRequestContext) { private List<Locale> getAcceptableLanguagesForRequest(final ContainerRequestContext containerRequestContext) {
try { try {
return containerRequestContext.getAcceptableLanguages(); return containerRequestContext.getAcceptableLanguages();

View File

@ -1,11 +1,15 @@
package org.whispersystems.textsecuregcm.grpc; package org.whispersystems.textsecuregcm.grpc;
import io.grpc.Status; import io.grpc.Status;
import org.signal.chat.profile.CredentialType;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialAnonymousRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
import org.signal.chat.profile.GetUnversionedProfileAnonymousRequest; import org.signal.chat.profile.GetUnversionedProfileAnonymousRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse; import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileAnonymousRequest; import org.signal.chat.profile.GetVersionedProfileAnonymousRequest;
import org.signal.chat.profile.GetVersionedProfileResponse; import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.ReactorProfileAnonymousGrpc; import org.signal.chat.profile.ReactorProfileAnonymousGrpc;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.IdentityType;
@ -19,14 +23,17 @@ public class ProfileAnonymousGrpcService extends ReactorProfileAnonymousGrpc.Pro
private final AccountsManager accountsManager; private final AccountsManager accountsManager;
private final ProfilesManager profilesManager; private final ProfilesManager profilesManager;
private final ProfileBadgeConverter profileBadgeConverter; private final ProfileBadgeConverter profileBadgeConverter;
private final ServerZkProfileOperations zkProfileOperations;
public ProfileAnonymousGrpcService( public ProfileAnonymousGrpcService(
final AccountsManager accountsManager, final AccountsManager accountsManager,
final ProfilesManager profilesManager, final ProfilesManager profilesManager,
final ProfileBadgeConverter profileBadgeConverter) { final ProfileBadgeConverter profileBadgeConverter,
final ServerZkProfileOperations zkProfileOperations) {
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.profilesManager = profilesManager; this.profilesManager = profilesManager;
this.profileBadgeConverter = profileBadgeConverter; this.profileBadgeConverter = profileBadgeConverter;
this.zkProfileOperations = zkProfileOperations;
} }
@Override @Override
@ -58,10 +65,28 @@ public class ProfileAnonymousGrpcService extends ReactorProfileAnonymousGrpc.Pro
.flatMap(targetAccount -> ProfileGrpcHelper.getVersionedProfile(targetAccount, profilesManager, request.getRequest().getVersion())); .flatMap(targetAccount -> ProfileGrpcHelper.getVersionedProfile(targetAccount, profilesManager, request.getRequest().getVersion()));
} }
private Mono<Account> getTargetAccountAndValidateUnidentifiedAccess(final ServiceIdentifier targetIdentifier, final byte[] unidentifiedAccessKey) { @Override
return Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier)) public Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredential(
.flatMap(Mono::justOrEmpty) final GetExpiringProfileKeyCredentialAnonymousRequest request) {
.filter(targetAccount -> UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, unidentifiedAccessKey)) final ServiceIdentifier targetIdentifier = ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getRequest().getAccountIdentifier());
.switchIfEmpty(Mono.error(Status.UNAUTHENTICATED.asException()));
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
}
if (request.getRequest().getCredentialType() != CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY) {
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asRuntimeException();
}
return getTargetAccountAndValidateUnidentifiedAccess(targetIdentifier, request.getUnidentifiedAccessKey().toByteArray())
.flatMap(account -> ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(account.getUuid(),
request.getRequest().getVersion(), request.getRequest().getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations));
}
private Mono<Account> getTargetAccountAndValidateUnidentifiedAccess(final ServiceIdentifier targetIdentifier, final byte[] unidentifiedAccessKey) {
return Mono.fromFuture(() -> accountsManager.getByServiceIdentifierAsync(targetIdentifier))
.flatMap(Mono::justOrEmpty)
.filter(targetAccount -> UnidentifiedAccessUtil.checkUnidentifiedAccess(targetAccount, unidentifiedAccessKey))
.switchIfEmpty(Mono.error(Status.UNAUTHENTICATED.asException()));
} }
} }

View File

@ -8,9 +8,15 @@ import java.util.UUID;
import io.grpc.Status; import io.grpc.Status;
import org.signal.chat.profile.Badge; import org.signal.chat.profile.Badge;
import org.signal.chat.profile.BadgeSvg; import org.signal.chat.profile.BadgeSvg;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
import org.signal.chat.profile.GetUnversionedProfileResponse; import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileResponse; import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.UserCapabilities; import org.signal.chat.profile.UserCapabilities;
import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
@ -119,4 +125,28 @@ public class ProfileGrpcHelper {
return responseBuilder.build(); return responseBuilder.build();
} }
static Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredentialResponse(
final UUID targetUuid,
final String version,
final byte[] encodedCredentialRequest,
final ProfilesManager profilesManager,
final ServerZkProfileOperations zkProfileOperations) {
return Mono.fromFuture(profilesManager.getAsync(targetUuid, version))
.flatMap(Mono::justOrEmpty)
.map(profile -> {
final ExpiringProfileKeyCredentialResponse profileKeyCredentialResponse;
try {
profileKeyCredentialResponse = ProfileHelper.getExpiringProfileKeyCredential(encodedCredentialRequest,
profile, new ServiceId.Aci(targetUuid), zkProfileOperations);
} catch (VerificationFailedException | InvalidInputException e) {
throw Status.INVALID_ARGUMENT.withCause(e).asRuntimeException();
}
return GetExpiringProfileKeyCredentialResponse.newBuilder()
.setProfileKeyCredential(ByteString.copyFrom(profileKeyCredentialResponse.serialize()))
.build();
})
.switchIfEmpty(Mono.error(Status.NOT_FOUND.withDescription("Profile version not found").asException()));
}
} }

View File

@ -2,6 +2,9 @@ package org.whispersystems.textsecuregcm.grpc;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.grpc.Status; import io.grpc.Status;
import org.signal.chat.profile.CredentialType;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
import org.signal.chat.profile.GetUnversionedProfileRequest; import org.signal.chat.profile.GetUnversionedProfileRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse; import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileRequest; import org.signal.chat.profile.GetVersionedProfileRequest;
@ -11,6 +14,7 @@ import org.signal.chat.profile.ProfileAvatarUploadAttributes;
import org.signal.chat.profile.ReactorProfileGrpc; import org.signal.chat.profile.ReactorProfileGrpc;
import org.signal.chat.profile.SetProfileRequest; import org.signal.chat.profile.SetProfileRequest;
import org.signal.chat.profile.SetProfileResponse; import org.signal.chat.profile.SetProfileResponse;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice; import org.whispersystems.textsecuregcm.auth.grpc.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil; import org.whispersystems.textsecuregcm.auth.grpc.AuthenticationUtil;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
@ -56,6 +60,7 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
private final PolicySigner policySigner; private final PolicySigner policySigner;
private final ProfileBadgeConverter profileBadgeConverter; private final ProfileBadgeConverter profileBadgeConverter;
private final RateLimiters rateLimiters; private final RateLimiters rateLimiters;
private final ServerZkProfileOperations zkProfileOperations;
private final String bucket; private final String bucket;
private record AvatarData(Optional<String> currentAvatar, private record AvatarData(Optional<String> currentAvatar,
@ -73,6 +78,7 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
final PolicySigner policySigner, final PolicySigner policySigner,
final ProfileBadgeConverter profileBadgeConverter, final ProfileBadgeConverter profileBadgeConverter,
final RateLimiters rateLimiters, final RateLimiters rateLimiters,
final ServerZkProfileOperations zkProfileOperations,
final String bucket) { final String bucket) {
this.clock = clock; this.clock = clock;
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
@ -85,6 +91,7 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
this.policySigner = policySigner; this.policySigner = policySigner;
this.profileBadgeConverter = profileBadgeConverter; this.profileBadgeConverter = profileBadgeConverter;
this.rateLimiters = rateLimiters; this.rateLimiters = rateLimiters;
this.zkProfileOperations = zkProfileOperations;
this.bucket = bucket; this.bucket = bucket;
} }
@ -186,6 +193,26 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
.flatMap(account -> ProfileGrpcHelper.getVersionedProfile(account, profilesManager, request.getVersion())); .flatMap(account -> ProfileGrpcHelper.getVersionedProfile(account, profilesManager, request.getVersion()));
} }
@Override
public Mono<GetExpiringProfileKeyCredentialResponse> getExpiringProfileKeyCredential(
final GetExpiringProfileKeyCredentialRequest request) {
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
final ServiceIdentifier targetIdentifier = ServiceIdentifierUtil.fromGrpcServiceIdentifier(request.getAccountIdentifier());
if (targetIdentifier.identityType() != IdentityType.ACI) {
throw Status.INVALID_ARGUMENT.withDescription("Expected ACI service identifier").asRuntimeException();
}
if (request.getCredentialType() != CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY) {
throw Status.INVALID_ARGUMENT.withDescription("Expected expiring profile key credential type").asRuntimeException();
}
return validateRateLimitAndGetAccount(authenticatedDevice.accountIdentifier(), targetIdentifier)
.flatMap(targetAccount -> ProfileGrpcHelper.getExpiringProfileKeyCredentialResponse(targetAccount.getUuid(),
request.getVersion(), request.getCredentialRequest().toByteArray(), profilesManager, zkProfileOperations));
}
private Mono<Account> validateRateLimitAndGetAccount(final UUID requesterUuid, private Mono<Account> validateRateLimitAndGetAccount(final UUID requesterUuid,
final ServiceIdentifier targetIdentifier) { final ServiceIdentifier targetIdentifier) {
return rateLimiters.getProfileLimiter().validateReactive(requesterUuid) return rateLimiters.getProfileLimiter().validateReactive(requesterUuid)

View File

@ -1,12 +1,23 @@
package org.whispersystems.textsecuregcm.util; package org.whispersystems.textsecuregcm.util;
import com.google.common.annotations.VisibleForTesting;
import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration; import org.whispersystems.textsecuregcm.configuration.BadgeConfiguration;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.AccountBadge; import org.whispersystems.textsecuregcm.storage.AccountBadge;
import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -16,6 +27,9 @@ import java.util.UUID;
public class ProfileHelper { public class ProfileHelper {
public static int MAX_PROFILE_AVATAR_SIZE_BYTES = 10 * 1024 * 1024; public static int MAX_PROFILE_AVATAR_SIZE_BYTES = 10 * 1024 * 1024;
@VisibleForTesting
public static final Duration EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION = Duration.ofDays(7);
public static List<AccountBadge> mergeBadgeIdsWithExistingAccountBadges( public static List<AccountBadge> mergeBadgeIdsWithExistingAccountBadges(
final Clock clock, final Clock clock,
final Map<String, BadgeConfiguration> badgeConfigurationMap, final Map<String, BadgeConfiguration> badgeConfigurationMap,
@ -70,4 +84,17 @@ public class ProfileHelper {
public static boolean isSelfProfileRequest(@Nullable final UUID requesterUuid, final AciServiceIdentifier targetIdentifier) { public static boolean isSelfProfileRequest(@Nullable final UUID requesterUuid, final AciServiceIdentifier targetIdentifier) {
return targetIdentifier.uuid().equals(requesterUuid); return targetIdentifier.uuid().equals(requesterUuid);
} }
public static ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredential(
final byte[] encodedCredentialRequest,
final VersionedProfile profile,
final ServiceId.Aci accountIdentifier,
final ServerZkProfileOperations zkProfileOperations) throws InvalidInputException, VerificationFailedException {
final Instant expiration = Instant.now().plus(EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION).truncatedTo(ChronoUnit.DAYS);
final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.commitment());
final ProfileKeyCredentialRequest request = new ProfileKeyCredentialRequest(
encodedCredentialRequest);
return zkProfileOperations.issueExpiringProfileKeyCredential(request, accountIdentifier, commitment, expiration);
}
} }

View File

@ -253,6 +253,10 @@ message GetExpiringProfileKeyCredentialRequest {
* The type of credential being requested. * The type of credential being requested.
*/ */
CredentialType credential_type = 3; CredentialType credential_type = 3;
/**
* The profile version for which to generate a profile key credential.
*/
string version = 4;
} }
message GetExpiringProfileKeyCredentialAnonymousRequest { message GetExpiringProfileKeyCredentialAnonymousRequest {
@ -271,7 +275,7 @@ message GetExpiringProfileKeyCredentialResponse {
* A zkgroup credential used by a client to prove that it has the profile key * A zkgroup credential used by a client to prove that it has the profile key
* of a targeted account. * of a targeted account.
*/ */
bytes profileKeyCredential = 2; bytes profileKeyCredential = 1;
} }
message CheckIdentityKeysRequest { message CheckIdentityKeysRequest {

View File

@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
@ -107,7 +108,7 @@ import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.VersionedProfile; import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.ProfileTestHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestClock; import org.whispersystems.textsecuregcm.util.TestClock;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
@ -222,9 +223,9 @@ class ProfileControllerTest {
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(capabilitiesAccount)); when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(capabilitiesAccount));
when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(Optional.of(capabilitiesAccount)); when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(AuthHelper.VALID_UUID))).thenReturn(Optional.of(capabilitiesAccount));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
when(profilesManager.get(eq(AuthHelper.VALID_UUID), eq("someversion"))).thenReturn(Optional.empty()); when(profilesManager.get(eq(AuthHelper.VALID_UUID), eq("someversion"))).thenReturn(Optional.empty());
when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile( when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile(
@ -409,14 +410,14 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWantAvatarUpload() throws InvalidInputException { void testSetProfileWantAvatarUpload() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final ProfileAvatarUploadAttributes uploadAttributes = resources.getJerseyTest() final ProfileAvatarUploadAttributes uploadAttributes = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new CreateProfileRequest(commitment, "someversion", .put(Entity.entity(new CreateProfileRequest(commitment, "someversion",
ProfileHelper.encodeToBase64(name), null, null, ProfileTestHelper.encodeToBase64(name), null, null,
null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class);
final ArgumentCaptor<VersionedProfile> profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class); final ArgumentCaptor<VersionedProfile> profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class);
@ -437,7 +438,7 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWantAvatarUploadWithBadProfileSize() throws InvalidInputException { void testSetProfileWantAvatarUploadWithBadProfileSize() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final String name = ProfileHelper.generateRandomBase64FromByteArray(82); final String name = ProfileTestHelper.generateRandomBase64FromByteArray(82);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
@ -453,7 +454,7 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWithoutAvatarUpload() throws InvalidInputException { void testSetProfileWithoutAvatarUpload() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
@ -461,7 +462,7 @@ class ProfileControllerTest {
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", ProfileHelper.encodeToBase64(name), null, null, .put(Entity.entity(new CreateProfileRequest(commitment, "anotherversion", ProfileTestHelper.encodeToBase64(name), null, null,
null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -486,14 +487,14 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWithAvatarUploadAndPreviousAvatar() throws InvalidInputException { void testSetProfileWithAvatarUploadAndPreviousAvatar() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
resources.getJerseyTest() resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity(new CreateProfileRequest(commitment, "validversion", .put(Entity.entity(new CreateProfileRequest(commitment, "validversion",
ProfileHelper.encodeToBase64(name), null, null, ProfileTestHelper.encodeToBase64(name), null, null,
null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); null, true, false, List.of()), MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class);
final ArgumentCaptor<VersionedProfile> profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class); final ArgumentCaptor<VersionedProfile> profileArgumentCaptor = ArgumentCaptor.forClass(VersionedProfile.class);
@ -513,13 +514,13 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileClearPreviousAvatar() throws InvalidInputException { void testSetProfileClearPreviousAvatar() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileTestHelper.encodeToBase64(name),
null, null, null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) { null, null, null, false, false, List.of()), MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -543,13 +544,13 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWithSameAvatar() throws InvalidInputException { void testSetProfileWithSameAvatar() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileTestHelper.encodeToBase64(name),
null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -573,13 +574,13 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileClearPreviousAvatarDespiteSameAvatarFlagSet() throws InvalidInputException { void testSetProfileClearPreviousAvatarDespiteSameAvatarFlagSet() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
try (final Response ignored = resources.getJerseyTest() try (final Response ignored = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileTestHelper.encodeToBase64(name),
null, null, null, null,
null, false, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { null, false, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) {
@ -601,13 +602,13 @@ class ProfileControllerTest {
@Test @Test
void testSetProfileWithSameAvatarDespiteNoPreviousAvatar() throws InvalidInputException { void testSetProfileWithSameAvatarDespiteNoPreviousAvatar() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), .put(Entity.entity(new CreateProfileRequest(commitment, "validversion", ProfileTestHelper.encodeToBase64(name),
null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) { null, null, null, true, true, List.of()), MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -632,14 +633,14 @@ class ProfileControllerTest {
void testSetProfileExtendedName() throws InvalidInputException { void testSetProfileExtendedName() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID_TWO));
final byte[] name = ProfileHelper.generateRandomByteArray(285); final byte[] name = ProfileTestHelper.generateRandomByteArray(285);
resources.getJerseyTest() resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity( .put(Entity.entity(
new CreateProfileRequest(commitment, "validversion", ProfileHelper.encodeToBase64(name), new CreateProfileRequest(commitment, "validversion", ProfileTestHelper.encodeToBase64(name),
null, null, null, true, false, List.of()), null, null, null, true, false, List.of()),
MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class); MediaType.APPLICATION_JSON_TYPE), ProfileAvatarUploadAttributes.class);
@ -663,17 +664,17 @@ class ProfileControllerTest {
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity( .put(Entity.entity(
new CreateProfileRequest(commitment, "anotherversion", ProfileHelper.encodeToBase64(name), new CreateProfileRequest(commitment, "anotherversion", ProfileTestHelper.encodeToBase64(name),
ProfileHelper.encodeToBase64(emoji), ProfileHelper.encodeToBase64(about), null, false, false, List.of()), ProfileTestHelper.encodeToBase64(emoji), ProfileTestHelper.encodeToBase64(about), null, false, false, List.of()),
MediaType.APPLICATION_JSON_TYPE))) { MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -703,16 +704,16 @@ class ProfileControllerTest {
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile") .target("/v1/profile")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity( .put(Entity.entity(
new CreateProfileRequest(commitment, "yetanotherversion", ProfileHelper.encodeToBase64(name), new CreateProfileRequest(commitment, "yetanotherversion", ProfileTestHelper.encodeToBase64(name),
null, null, ProfileHelper.encodeToBase64(paymentAddress), false, false, null, null, ProfileTestHelper.encodeToBase64(paymentAddress), false, false,
List.of()), MediaType.APPLICATION_JSON_TYPE))) { List.of()), MediaType.APPLICATION_JSON_TYPE))) {
assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getStatus()).isEqualTo(200);
@ -745,8 +746,8 @@ class ProfileControllerTest {
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final String name = ProfileHelper.generateRandomBase64FromByteArray(81); final String name = ProfileTestHelper.generateRandomBase64FromByteArray(81);
final String paymentAddress = ProfileHelper.generateRandomBase64FromByteArray(582); final String paymentAddress = ProfileTestHelper.generateRandomBase64FromByteArray(582);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile") .target("/v1/profile")
@ -772,15 +773,15 @@ class ProfileControllerTest {
.thenReturn(List.of(AuthHelper.VALID_NUMBER_TWO.substring(0, 3))); .thenReturn(List.of(AuthHelper.VALID_NUMBER_TWO.substring(0, 3)));
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), any())) when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), any()))
.thenReturn(Optional.of( .thenReturn(Optional.of(
new VersionedProfile("1", name, null, null, null, new VersionedProfile("1", name, null, null, null,
existingPaymentAddressOnProfile ? ProfileHelper.generateRandomByteArray(582) : null, existingPaymentAddressOnProfile ? ProfileTestHelper.generateRandomByteArray(582) : null,
commitment.serialize()))); commitment.serialize())));
@ -789,8 +790,8 @@ class ProfileControllerTest {
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO))
.put(Entity.entity( .put(Entity.entity(
new CreateProfileRequest(commitment, "yetanotherversion", ProfileHelper.encodeToBase64(name), new CreateProfileRequest(commitment, "yetanotherversion", ProfileTestHelper.encodeToBase64(name),
null, null, ProfileHelper.encodeToBase64(paymentAddress), false, false, null, null, ProfileTestHelper.encodeToBase64(paymentAddress), false, false,
List.of()), MediaType.APPLICATION_JSON_TYPE))) { List.of()), MediaType.APPLICATION_JSON_TYPE))) {
if (existingPaymentAddressOnProfile) { if (existingPaymentAddressOnProfile) {
@ -823,9 +824,9 @@ class ProfileControllerTest {
@Test @Test
void testGetProfileByVersion() throws RateLimitExceededException { void testGetProfileByVersion() throws RateLimitExceededException {
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile( when(profilesManager.get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"))).thenReturn(Optional.of(new VersionedProfile(
"validversion", name, "profiles/validavatar", emoji, about, null, "validcommitmnet".getBytes()))); "validversion", name, "profiles/validavatar", emoji, about, null, "validcommitmnet".getBytes())));
@ -837,9 +838,9 @@ class ProfileControllerTest {
.get(VersionedProfileResponse.class); .get(VersionedProfileResponse.class);
assertThat(profile.getBaseProfileResponse().getIdentityKey()).isEqualTo(ACCOUNT_TWO_IDENTITY_KEY); assertThat(profile.getBaseProfileResponse().getIdentityKey()).isEqualTo(ACCOUNT_TWO_IDENTITY_KEY);
assertThat(profile.getName()).isEqualTo(ProfileHelper.encodeToBase64(name)); assertThat(profile.getName()).isEqualTo(ProfileTestHelper.encodeToBase64(name));
assertThat(profile.getAbout()).isEqualTo(ProfileHelper.encodeToBase64(about)); assertThat(profile.getAbout()).isEqualTo(ProfileTestHelper.encodeToBase64(about));
assertThat(profile.getAboutEmoji()).isEqualTo(ProfileHelper.encodeToBase64(emoji)); assertThat(profile.getAboutEmoji()).isEqualTo(ProfileTestHelper.encodeToBase64(emoji));
assertThat(profile.getAvatar()).isEqualTo("profiles/validavatar"); assertThat(profile.getAvatar()).isEqualTo("profiles/validavatar");
assertThat(profile.getBaseProfileResponse().getCapabilities().gv1Migration()).isTrue(); assertThat(profile.getBaseProfileResponse().getCapabilities().gv1Migration()).isTrue();
assertThat(profile.getBaseProfileResponse().getUuid()).isEqualTo(new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO)); assertThat(profile.getBaseProfileResponse().getUuid()).isEqualTo(new AciServiceIdentifier(AuthHelper.VALID_UUID_TWO));
@ -858,8 +859,8 @@ class ProfileControllerTest {
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final String name = ProfileHelper.generateRandomBase64FromByteArray(81); final String name = ProfileTestHelper.generateRandomBase64FromByteArray(81);
final String paymentAddress = ProfileHelper.generateRandomBase64FromByteArray(582); final String paymentAddress = ProfileTestHelper.generateRandomBase64FromByteArray(582);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile") .target("/v1/profile")
@ -878,7 +879,7 @@ class ProfileControllerTest {
@Test @Test
void testGetProfileReturnsNoPaymentAddressIfCurrentVersionMismatch() { void testGetProfileReturnsNoPaymentAddressIfCurrentVersionMismatch() {
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
when(profilesManager.get(AuthHelper.VALID_UUID_TWO, "validversion")).thenReturn( when(profilesManager.get(AuthHelper.VALID_UUID_TWO, "validversion")).thenReturn(
Optional.of(new VersionedProfile(null, null, null, null, null, paymentAddress, null))); Optional.of(new VersionedProfile(null, null, null, null, null, paymentAddress, null)));
@ -889,7 +890,7 @@ class ProfileControllerTest {
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.get(VersionedProfileResponse.class); .get(VersionedProfileResponse.class);
assertThat(profile.getPaymentAddress()).isEqualTo(ProfileHelper.encodeToBase64(paymentAddress)); assertThat(profile.getPaymentAddress()).isEqualTo(ProfileTestHelper.encodeToBase64(paymentAddress));
} }
when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("validversion")); when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("validversion"));
@ -901,7 +902,7 @@ class ProfileControllerTest {
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.get(VersionedProfileResponse.class); .get(VersionedProfileResponse.class);
assertThat(profile.getPaymentAddress()).isEqualTo(ProfileHelper.encodeToBase64(paymentAddress)); assertThat(profile.getPaymentAddress()).isEqualTo(ProfileTestHelper.encodeToBase64(paymentAddress));
} }
when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("someotherversion")); when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.of("someotherversion"));
@ -948,9 +949,9 @@ class ProfileControllerTest {
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final String name = ProfileHelper.generateRandomBase64FromByteArray(81); final String name = ProfileTestHelper.generateRandomBase64FromByteArray(81);
final String emoji = ProfileHelper.generateRandomBase64FromByteArray(60); final String emoji = ProfileTestHelper.generateRandomBase64FromByteArray(60);
final String about = ProfileHelper.generateRandomBase64FromByteArray(156); final String about = ProfileTestHelper.generateRandomBase64FromByteArray(156);
try (final Response response = resources.getJerseyTest() try (final Response response = resources.getJerseyTest()
.target("/v1/profile/") .target("/v1/profile/")
@ -1081,7 +1082,7 @@ class ProfileControllerTest {
when(account.isEnabled()).thenReturn(true); when(account.isEnabled()).thenReturn(true);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY)); when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
final Instant expiration = Instant.now().plus(ProfileController.EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION) final Instant expiration = Instant.now().plus(org.whispersystems.textsecuregcm.util.ProfileHelper.EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION)
.truncatedTo(ChronoUnit.DAYS); .truncatedTo(ChronoUnit.DAYS);
final ExpiringProfileKeyCredentialResponse credentialResponse = final ExpiringProfileKeyCredentialResponse credentialResponse =
@ -1089,7 +1090,7 @@ class ProfileControllerTest {
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account));
when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile)); when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile));
when(zkProfileOperations.issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(AuthHelper.VALID_UUID), profileKeyCommitment, expiration)) when(zkProfileOperations.issueExpiringProfileKeyCredential(eq(credentialRequest), eq(new ServiceId.Aci(AuthHelper.VALID_UUID)), eq(profileKeyCommitment), any()))
.thenReturn(credentialResponse); .thenReturn(credentialResponse);
final ExpiringProfileKeyCredentialProfileResponse profile = resources.getJerseyTest() final ExpiringProfileKeyCredentialProfileResponse profile = resources.getJerseyTest()
@ -1119,15 +1120,60 @@ class ProfileControllerTest {
); );
} }
@Test
void testGetProfileWithExpiringProfileKeyCredentialBadRequest()
throws VerificationFailedException, InvalidInputException {
final String version = "version";
final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
final ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
final ClientZkProfileOperations clientZkProfile = new ClientZkProfileOperations(serverPublicParams);
final byte[] profileKeyBytes = new byte[32];
new SecureRandom().nextBytes(profileKeyBytes);
final ProfileKey profileKey = new ProfileKey(profileKeyBytes);
final ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
final VersionedProfile versionedProfile = mock(VersionedProfile.class);
when(versionedProfile.commitment()).thenReturn(profileKeyCommitment.serialize());
final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext =
clientZkProfile.createProfileKeyCredentialRequestContext(new ServiceId.Aci(AuthHelper.VALID_UUID), profileKey);
final ProfileKeyCredentialRequest credentialRequest = profileKeyCredentialRequestContext.getRequest();
final Account account = mock(Account.class);
when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID);
when(account.isEnabled()).thenReturn(true);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(UNIDENTIFIED_ACCESS_KEY));
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account));
when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile));
when(zkProfileOperations.issueExpiringProfileKeyCredential(any(), any(), any(), any()))
.thenThrow(new VerificationFailedException());
final Response response = resources.getJerseyTest()
.target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, version,
HexFormat.of().formatHex(credentialRequest.serialize())))
.queryParam("credentialType", "expiringProfileKey")
.request()
.headers(new MultivaluedHashMap<>(Map.of(OptionalAccess.UNIDENTIFIED, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_KEY))))
.get();
assertEquals(400, response.getStatus());
}
@Test @Test
void testSetProfileBadgesMissingFromRequest() throws InvalidInputException { void testSetProfileBadgesMissingFromRequest() throws InvalidInputException {
final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID)); final ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(AuthHelper.VALID_UUID));
clearInvocations(AuthHelper.VALID_ACCOUNT_TWO); clearInvocations(AuthHelper.VALID_ACCOUNT_TWO);
final String name = ProfileHelper.generateRandomBase64FromByteArray(81); final String name = ProfileTestHelper.generateRandomBase64FromByteArray(81);
final String emoji = ProfileHelper.generateRandomBase64FromByteArray(60); final String emoji = ProfileTestHelper.generateRandomBase64FromByteArray(60);
final String text = ProfileHelper.generateRandomBase64FromByteArray(156); final String text = ProfileTestHelper.generateRandomBase64FromByteArray(156);
when(AuthHelper.VALID_ACCOUNT_TWO.getBadges()).thenReturn(List.of( when(AuthHelper.VALID_ACCOUNT_TWO.getBadges()).thenReturn(List.of(
new AccountBadge("TEST", Instant.ofEpochSecond(42 + 86400), true) new AccountBadge("TEST", Instant.ofEpochSecond(42 + 86400), true)

View File

@ -1,17 +1,23 @@
package org.whispersystems.textsecuregcm.grpc; package org.whispersystems.textsecuregcm.grpc;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.grpc.Metadata; import io.grpc.Metadata;
import io.grpc.Status; import io.grpc.Status;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -28,6 +34,10 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.signal.chat.common.IdentityType; import org.signal.chat.common.IdentityType;
import org.signal.chat.common.ServiceIdentifier; import org.signal.chat.common.ServiceIdentifier;
import org.signal.chat.profile.CredentialType;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialAnonymousRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
import org.signal.chat.profile.GetUnversionedProfileAnonymousRequest; import org.signal.chat.profile.GetUnversionedProfileAnonymousRequest;
import org.signal.chat.profile.GetUnversionedProfileRequest; import org.signal.chat.profile.GetUnversionedProfileRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse; import org.signal.chat.profile.GetUnversionedProfileResponse;
@ -36,8 +46,20 @@ import org.signal.chat.profile.GetVersionedProfileRequest;
import org.signal.chat.profile.GetVersionedProfileResponse; import org.signal.chat.profile.GetVersionedProfileResponse;
import org.signal.chat.profile.ProfileAnonymousGrpc; import org.signal.chat.profile.ProfileAnonymousGrpc;
import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.ServerPublicParams;
import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
import org.whispersystems.textsecuregcm.entities.Badge; import org.whispersystems.textsecuregcm.entities.Badge;
@ -48,7 +70,7 @@ import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.VersionedProfile; import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.ProfileTestHelper;
import org.whispersystems.textsecuregcm.util.UUIDUtil; import org.whispersystems.textsecuregcm.util.UUIDUtil;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -58,6 +80,7 @@ public class ProfileAnonymousGrpcServiceTest {
private ProfilesManager profilesManager; private ProfilesManager profilesManager;
private ProfileBadgeConverter profileBadgeConverter; private ProfileBadgeConverter profileBadgeConverter;
private ProfileAnonymousGrpc.ProfileAnonymousBlockingStub profileAnonymousBlockingStub; private ProfileAnonymousGrpc.ProfileAnonymousBlockingStub profileAnonymousBlockingStub;
private ServerZkProfileOperations serverZkProfileOperations;
@RegisterExtension @RegisterExtension
static final GrpcServerExtension GRPC_SERVER_EXTENSION = new GrpcServerExtension(); static final GrpcServerExtension GRPC_SERVER_EXTENSION = new GrpcServerExtension();
@ -68,6 +91,7 @@ public class ProfileAnonymousGrpcServiceTest {
accountsManager = mock(AccountsManager.class); accountsManager = mock(AccountsManager.class);
profilesManager = mock(ProfilesManager.class); profilesManager = mock(ProfilesManager.class);
profileBadgeConverter = mock(ProfileBadgeConverter.class); profileBadgeConverter = mock(ProfileBadgeConverter.class);
serverZkProfileOperations = mock(ServerZkProfileOperations.class);
final Metadata metadata = new Metadata(); final Metadata metadata = new Metadata();
metadata.put(AcceptLanguageInterceptor.ACCEPTABLE_LANGUAGES_GRPC_HEADER, "en-us"); metadata.put(AcceptLanguageInterceptor.ACCEPTABLE_LANGUAGES_GRPC_HEADER, "en-us");
@ -79,7 +103,8 @@ public class ProfileAnonymousGrpcServiceTest {
final ProfileAnonymousGrpcService profileAnonymousGrpcService = new ProfileAnonymousGrpcService( final ProfileAnonymousGrpcService profileAnonymousGrpcService = new ProfileAnonymousGrpcService(
accountsManager, accountsManager,
profilesManager, profilesManager,
profileBadgeConverter profileBadgeConverter,
serverZkProfileOperations
); );
GRPC_SERVER_EXTENSION.getServiceRegistry() GRPC_SERVER_EXTENSION.getServiceRegistry()
@ -187,11 +212,11 @@ public class ProfileAnonymousGrpcServiceTest {
new SecureRandom().nextBytes(unidentifiedAccessKey); new SecureRandom().nextBytes(unidentifiedAccessKey);
final VersionedProfile profile = mock(VersionedProfile.class); final VersionedProfile profile = mock(VersionedProfile.class);
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
when(profile.name()).thenReturn(name); when(profile.name()).thenReturn(name);
when(profile.aboutEmoji()).thenReturn(emoji); when(profile.aboutEmoji()).thenReturn(emoji);
@ -326,4 +351,190 @@ public class ProfileAnonymousGrpcServiceTest {
assertEquals(Status.INVALID_ARGUMENT.getCode(), statusRuntimeException.getStatus().getCode()); assertEquals(Status.INVALID_ARGUMENT.getCode(), statusRuntimeException.getStatus().getCode());
} }
@Test
void getExpiringProfileKeyCredential() throws InvalidInputException, VerificationFailedException {
final byte[] unidentifiedAccessKey = new byte[16];
new SecureRandom().nextBytes(unidentifiedAccessKey);
final UUID targetUuid = UUID.randomUUID();
final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
final ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
final ServerZkProfileOperations serverZkProfile = new ServerZkProfileOperations(serverSecretParams);
final ClientZkProfileOperations clientZkProfile = new ClientZkProfileOperations(serverPublicParams);
final byte[] profileKeyBytes = new byte[32];
new SecureRandom().nextBytes(profileKeyBytes);
final ProfileKey profileKey = new ProfileKey(profileKeyBytes);
final ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(new ServiceId.Aci(targetUuid));
final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext =
clientZkProfile.createProfileKeyCredentialRequestContext(new ServiceId.Aci(targetUuid), profileKey);
final VersionedProfile profile = mock(VersionedProfile.class);
when(profile.commitment()).thenReturn(profileKeyCommitment.serialize());
when(account.getUuid()).thenReturn(targetUuid);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(Optional.of(profile)));
final ProfileKeyCredentialRequest credentialRequest = profileKeyCredentialRequestContext.getRequest();
final Instant expiration = Instant.now().plus(org.whispersystems.textsecuregcm.util.ProfileHelper.EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION)
.truncatedTo(ChronoUnit.DAYS);
final ExpiringProfileKeyCredentialResponse credentialResponse =
serverZkProfile.issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration);
when(serverZkProfileOperations.issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration))
.thenReturn(credentialResponse);
final GetExpiringProfileKeyCredentialAnonymousRequest request = GetExpiringProfileKeyCredentialAnonymousRequest.newBuilder()
.setRequest(GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom(credentialRequest.serialize()))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build())
.setUnidentifiedAccessKey(ByteString.copyFrom(unidentifiedAccessKey))
.build();
final GetExpiringProfileKeyCredentialResponse response = profileAnonymousBlockingStub.getExpiringProfileKeyCredential(request);
assertArrayEquals(credentialResponse.serialize(), response.getProfileKeyCredential().toByteArray());
verify(serverZkProfileOperations).issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration);
final ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams);
assertThatNoException().isThrownBy(() ->
clientZkProfileCipher.receiveExpiringProfileKeyCredential(profileKeyCredentialRequestContext, new ExpiringProfileKeyCredentialResponse(response.getProfileKeyCredential().toByteArray())));
}
@ParameterizedTest
@MethodSource
void getExpiringProfileKeyCredentialUnauthenticated(final boolean missingAccount, final boolean missingUnidentifiedAccessKey) {
final byte[] unidentifiedAccessKey = new byte[16];
new SecureRandom().nextBytes(unidentifiedAccessKey);
final UUID targetUuid = UUID.randomUUID();
when(account.getUuid()).thenReturn(targetUuid);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(
CompletableFuture.completedFuture(missingAccount ? Optional.empty() : Optional.of(account)));
final GetExpiringProfileKeyCredentialAnonymousRequest.Builder requestBuilder = GetExpiringProfileKeyCredentialAnonymousRequest.newBuilder()
.setRequest(GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build());
if (!missingUnidentifiedAccessKey) {
requestBuilder.setUnidentifiedAccessKey(ByteString.copyFrom(unidentifiedAccessKey));
}
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class,
() -> profileAnonymousBlockingStub.getExpiringProfileKeyCredential(requestBuilder.build()));
assertEquals(Status.UNAUTHENTICATED.getCode(), statusRuntimeException.getStatus().getCode());
verifyNoInteractions(profilesManager);
}
private static Stream<Arguments> getExpiringProfileKeyCredentialUnauthenticated() {
return Stream.of(
Arguments.of(true, false),
Arguments.of(false, true)
);
}
@Test
void getExpiringProfileKeyCredentialProfileNotFound() {
final byte[] unidentifiedAccessKey = new byte[16];
new SecureRandom().nextBytes(unidentifiedAccessKey);
final UUID targetUuid = UUID.randomUUID();
when(account.getUuid()).thenReturn(targetUuid);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(
CompletableFuture.completedFuture(Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(Optional.empty()));
final GetExpiringProfileKeyCredentialAnonymousRequest request = GetExpiringProfileKeyCredentialAnonymousRequest.newBuilder()
.setUnidentifiedAccessKey(ByteString.copyFrom(unidentifiedAccessKey))
.setRequest(GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build())
.build();
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class,
() -> profileAnonymousBlockingStub.getExpiringProfileKeyCredential(request));
assertEquals(Status.NOT_FOUND.getCode(), statusRuntimeException.getStatus().getCode());
}
@ParameterizedTest
@MethodSource
void getExpiringProfileKeyCredentialInvalidArgument(final IdentityType identityType, final CredentialType credentialType,
final boolean throwZkVerificationException) throws VerificationFailedException {
final UUID targetUuid = UUID.randomUUID();
final byte[] unidentifiedAccessKey = new byte[16];
new SecureRandom().nextBytes(unidentifiedAccessKey);
if (throwZkVerificationException) {
when(serverZkProfileOperations.issueExpiringProfileKeyCredential(any(), any(), any(), any())).thenThrow(new VerificationFailedException());
}
final VersionedProfile profile = mock(VersionedProfile.class);
when(profile.commitment()).thenReturn("commitment".getBytes(StandardCharsets.UTF_8));
when(account.getUuid()).thenReturn(targetUuid);
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey));
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(Optional.of(profile)));
final GetExpiringProfileKeyCredentialAnonymousRequest request = GetExpiringProfileKeyCredentialAnonymousRequest.newBuilder()
.setUnidentifiedAccessKey(ByteString.copyFrom(unidentifiedAccessKey))
.setRequest(GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(identityType)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(credentialType)
.setVersion("someVersion")
.build())
.build();
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class,
() -> profileAnonymousBlockingStub.getExpiringProfileKeyCredential(request));
assertEquals(Status.INVALID_ARGUMENT.getCode(), statusRuntimeException.getStatus().getCode());
}
private static Stream<Arguments> getExpiringProfileKeyCredentialInvalidArgument() {
return Stream.of(
// Credential type unspecified
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_UNSPECIFIED, false),
// Illegal identity type
Arguments.of(IdentityType.IDENTITY_TYPE_PNI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, false),
// Artificially fails zero knowledge verification
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, true)
);
}
} }

View File

@ -19,6 +19,9 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.signal.chat.common.IdentityType; import org.signal.chat.common.IdentityType;
import org.signal.chat.common.ServiceIdentifier; import org.signal.chat.common.ServiceIdentifier;
import org.signal.chat.profile.CredentialType;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialRequest;
import org.signal.chat.profile.GetExpiringProfileKeyCredentialResponse;
import org.signal.chat.profile.GetUnversionedProfileRequest; import org.signal.chat.profile.GetUnversionedProfileRequest;
import org.signal.chat.profile.GetUnversionedProfileResponse; import org.signal.chat.profile.GetUnversionedProfileResponse;
import org.signal.chat.profile.GetVersionedProfileRequest; import org.signal.chat.profile.GetVersionedProfileRequest;
@ -32,7 +35,16 @@ import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.ServerPublicParams;
import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredentialResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum; import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessChecksum;
import org.whispersystems.textsecuregcm.auth.grpc.MockAuthenticationInterceptor; import org.whispersystems.textsecuregcm.auth.grpc.MockAuthenticationInterceptor;
import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ProfileBadgeConverter;
@ -57,15 +69,18 @@ import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.ProfilesManager; import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.VersionedProfile; import org.whispersystems.textsecuregcm.storage.VersionedProfile;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.ProfileTestHelper;
import org.whispersystems.textsecuregcm.util.UUIDUtil; import org.whispersystems.textsecuregcm.util.UUIDUtil;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -75,6 +90,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -103,6 +120,7 @@ public class ProfileGrpcServiceTest {
private Account account; private Account account;
private RateLimiter rateLimiter; private RateLimiter rateLimiter;
private ProfileBadgeConverter profileBadgeConverter; private ProfileBadgeConverter profileBadgeConverter;
private ServerZkProfileOperations serverZkProfileOperations;
private ProfileGrpc.ProfileBlockingStub profileBlockingStub; private ProfileGrpc.ProfileBlockingStub profileBlockingStub;
@RegisterExtension @RegisterExtension
@ -118,6 +136,7 @@ public class ProfileGrpcServiceTest {
account = mock(Account.class); account = mock(Account.class);
rateLimiter = mock(RateLimiter.class); rateLimiter = mock(RateLimiter.class);
profileBadgeConverter = mock(ProfileBadgeConverter.class); profileBadgeConverter = mock(ProfileBadgeConverter.class);
serverZkProfileOperations = mock(ServerZkProfileOperations.class);
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(DynamicConfigurationManager.class); @SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
@ -160,6 +179,7 @@ public class ProfileGrpcServiceTest {
policySigner, policySigner,
profileBadgeConverter, profileBadgeConverter,
rateLimiters, rateLimiters,
serverZkProfileOperations,
S3_BUCKET S3_BUCKET
); );
@ -482,11 +502,11 @@ public class ProfileGrpcServiceTest {
@MethodSource @MethodSource
void getVersionedProfile(final String requestVersion, @Nullable final String accountVersion, final boolean expectResponseHasPaymentAddress) { void getVersionedProfile(final String requestVersion, @Nullable final String accountVersion, final boolean expectResponseHasPaymentAddress) {
final VersionedProfile profile = mock(VersionedProfile.class); final VersionedProfile profile = mock(VersionedProfile.class);
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final GetVersionedProfileRequest request = GetVersionedProfileRequest.newBuilder() final GetVersionedProfileRequest request = GetVersionedProfileRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder() .setAccountIdentifier(ServiceIdentifier.newBuilder()
@ -594,4 +614,162 @@ public class ProfileGrpcServiceTest {
() -> profileBlockingStub.getVersionedProfile(request)); () -> profileBlockingStub.getVersionedProfile(request));
assertEquals(Status.INVALID_ARGUMENT.getCode(), exception.getStatus().getCode()); assertEquals(Status.INVALID_ARGUMENT.getCode(), exception.getStatus().getCode());
} }
@Test
void getExpiringProfileKeyCredential() throws InvalidInputException, VerificationFailedException {
final UUID targetUuid = UUID.randomUUID();
final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
final ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
final ServerZkProfileOperations serverZkProfile = new ServerZkProfileOperations(serverSecretParams);
final ClientZkProfileOperations clientZkProfile = new ClientZkProfileOperations(serverPublicParams);
final byte[] profileKeyBytes = new byte[32];
new SecureRandom().nextBytes(profileKeyBytes);
final ProfileKey profileKey = new ProfileKey(profileKeyBytes);
final ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(new ServiceId.Aci(targetUuid));
final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext =
clientZkProfile.createProfileKeyCredentialRequestContext(new ServiceId.Aci(targetUuid), profileKey);
when(account.getUuid()).thenReturn(targetUuid);
when(profile.commitment()).thenReturn(profileKeyCommitment.serialize());
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(Optional.of(profile)));
final ProfileKeyCredentialRequest credentialRequest = profileKeyCredentialRequestContext.getRequest();
final Instant expiration = Instant.now().plus(org.whispersystems.textsecuregcm.util.ProfileHelper.EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION)
.truncatedTo(ChronoUnit.DAYS);
final ExpiringProfileKeyCredentialResponse credentialResponse =
serverZkProfile.issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration);
when(serverZkProfileOperations.issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration))
.thenReturn(credentialResponse);
final GetExpiringProfileKeyCredentialRequest request = GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom(credentialRequest.serialize()))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build();
final GetExpiringProfileKeyCredentialResponse response = profileBlockingStub.getExpiringProfileKeyCredential(request);
assertArrayEquals(credentialResponse.serialize(), response.getProfileKeyCredential().toByteArray());
verify(serverZkProfileOperations).issueExpiringProfileKeyCredential(credentialRequest, new ServiceId.Aci(targetUuid), profileKeyCommitment, expiration);
final ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams);
assertThatNoException().isThrownBy(() ->
clientZkProfileCipher.receiveExpiringProfileKeyCredential(profileKeyCredentialRequestContext, new ExpiringProfileKeyCredentialResponse(response.getProfileKeyCredential().toByteArray())));
}
@Test
void getExpiringProfileKeyCredentialRateLimited() {
final Duration retryAfterDuration = Duration.ofMinutes(5);
when(rateLimiter.validateReactive(AUTHENTICATED_ACI))
.thenReturn(Mono.error(new RateLimitExceededException(retryAfterDuration, false)));
when(accountsManager.getByServiceIdentifierAsync(any())).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
final GetExpiringProfileKeyCredentialRequest request = GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(UUID.randomUUID())))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build();
StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
() -> profileBlockingStub.getExpiringProfileKeyCredential(request));
assertEquals(Status.Code.RESOURCE_EXHAUSTED, exception.getStatus().getCode());
assertNotNull(exception.getTrailers());
assertEquals(retryAfterDuration, exception.getTrailers().get(RateLimitUtil.RETRY_AFTER_DURATION_KEY));
verifyNoInteractions(profilesManager);
}
@ParameterizedTest
@MethodSource
void getExpiringProfileKeyCredentialAccountOrProfileNotFound(final boolean missingAccount,
final boolean missingProfile) {
final UUID targetUuid = UUID.randomUUID();
when(account.getUuid()).thenReturn(targetUuid);
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(CompletableFuture.completedFuture(
missingAccount ? Optional.empty() : Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(missingProfile ? Optional.empty() : Optional.of(profile)));
final GetExpiringProfileKeyCredentialRequest request = GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(IdentityType.IDENTITY_TYPE_ACI)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY)
.setVersion("someVersion")
.build();
final StatusRuntimeException statusRuntimeException = assertThrows(StatusRuntimeException.class,
() -> profileBlockingStub.getExpiringProfileKeyCredential(request));
assertEquals(Status.Code.NOT_FOUND, statusRuntimeException.getStatus().getCode());
}
private static Stream<Arguments> getExpiringProfileKeyCredentialAccountOrProfileNotFound() {
return Stream.of(
Arguments.of(true, false),
Arguments.of(false, true)
);
}
@ParameterizedTest
@MethodSource
void getExpiringProfileKeyCredentialInvalidArgument(final IdentityType identityType, final CredentialType credentialType,
final boolean throwZkVerificationException) throws VerificationFailedException {
final UUID targetUuid = UUID.randomUUID();
if (throwZkVerificationException) {
when(serverZkProfileOperations.issueExpiringProfileKeyCredential(any(), any(), any(), any())).thenThrow(new VerificationFailedException());
}
when(account.getUuid()).thenReturn(targetUuid);
when(profile.commitment()).thenReturn("commitment".getBytes(StandardCharsets.UTF_8));
when(accountsManager.getByServiceIdentifierAsync(new AciServiceIdentifier(targetUuid))).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
when(profilesManager.getAsync(targetUuid, "someVersion")).thenReturn(CompletableFuture.completedFuture(Optional.of(profile)));
final GetExpiringProfileKeyCredentialRequest request = GetExpiringProfileKeyCredentialRequest.newBuilder()
.setAccountIdentifier(ServiceIdentifier.newBuilder()
.setIdentityType(identityType)
.setUuid(ByteString.copyFrom(UUIDUtil.toBytes(targetUuid)))
.build())
.setCredentialRequest(ByteString.copyFrom("credentialRequest".getBytes(StandardCharsets.UTF_8)))
.setCredentialType(credentialType)
.setVersion("someVersion")
.build();
StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
() -> profileBlockingStub.getExpiringProfileKeyCredential(request));
assertEquals(Status.Code.INVALID_ARGUMENT, exception.getStatus().getCode());
}
private static Stream<Arguments> getExpiringProfileKeyCredentialInvalidArgument() {
return Stream.of(
// Credential type unspecified
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_UNSPECIFIED, false),
// Illegal identity type
Arguments.of(IdentityType.IDENTITY_TYPE_PNI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, false),
// Artificially fails zero knowledge verification
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, true)
);
}
} }

View File

@ -32,7 +32,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.tests.util.MockRedisFuture; import org.whispersystems.textsecuregcm.tests.util.MockRedisFuture;
import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.ProfileTestHelper;
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper; import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
@Timeout(value = 10, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) @Timeout(value = 10, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@ -62,12 +62,12 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileInCache() throws InvalidInputException { public void testGetProfileInCache() throws InvalidInputException {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize(); final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize();
when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(String.format( when(commands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(String.format(
"{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}", "{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}",
ProfileHelper.encodeToBase64(name), ProfileTestHelper.encodeToBase64(name),
ProfileHelper.encodeToBase64(commitment))); ProfileTestHelper.encodeToBase64(commitment)));
Optional<VersionedProfile> profile = profilesManager.get(uuid, "someversion"); Optional<VersionedProfile> profile = profilesManager.get(uuid, "someversion");
@ -84,13 +84,13 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileAsyncInCache() throws InvalidInputException { public void testGetProfileAsyncInCache() throws InvalidInputException {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize(); final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(uuid)).serialize();
when(asyncCommands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn( when(asyncCommands.hget(eq("profiles::" + uuid), eq("someversion"))).thenReturn(
MockRedisFuture.completedFuture(String.format("{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}", MockRedisFuture.completedFuture(String.format("{\"version\": \"someversion\", \"name\": \"%s\", \"avatar\": \"someavatar\", \"commitment\":\"%s\"}",
ProfileHelper.encodeToBase64(name), ProfileTestHelper.encodeToBase64(name),
ProfileHelper.encodeToBase64(commitment)))); ProfileTestHelper.encodeToBase64(commitment))));
Optional<VersionedProfile> profile = profilesManager.getAsync(uuid, "someversion").join(); Optional<VersionedProfile> profile = profilesManager.getAsync(uuid, "someversion").join();
@ -107,7 +107,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileNotInCache() { public void testGetProfileNotInCache() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());
@ -130,7 +130,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileAsyncNotInCache() { public void testGetProfileAsyncNotInCache() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());
@ -154,7 +154,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileBrokenCache() { public void testGetProfileBrokenCache() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());
@ -177,7 +177,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testGetProfileAsyncBrokenCache() { public void testGetProfileAsyncBrokenCache() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());
@ -201,7 +201,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testSet() { public void testSet() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());
@ -217,7 +217,7 @@ public class ProfilesManagerTest {
@Test @Test
public void testSetAsync() { public void testSetAsync() {
final UUID uuid = UUID.randomUUID(); final UUID uuid = UUID.randomUUID();
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null, final VersionedProfile profile = new VersionedProfile("someversion", name, "someavatar", null, null,
null, "somecommitment".getBytes()); null, "somecommitment".getBytes());

View File

@ -15,7 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.signal.libsignal.protocol.ServiceId; import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.textsecuregcm.tests.util.ProfileHelper; import org.whispersystems.textsecuregcm.tests.util.ProfileTestHelper;
import org.whispersystems.textsecuregcm.util.AttributeValues; import org.whispersystems.textsecuregcm.util.AttributeValues;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@ -44,10 +44,10 @@ public class ProfilesTest {
Tables.PROFILES.tableName()); Tables.PROFILES.tableName());
final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitment = new ProfileKey(new byte[32]).getCommitment(new ServiceId.Aci(ACI)).serialize();
final String version = "someVersion"; final String version = "someVersion";
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] validAboutEmoji = ProfileHelper.generateRandomByteArray(60); final byte[] validAboutEmoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] validAbout = ProfileHelper.generateRandomByteArray(156); final byte[] validAbout = ProfileTestHelper.generateRandomByteArray(156);
final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
validProfile = new VersionedProfile(version, name, avatar, validAboutEmoji, validAbout, null, commitment); validProfile = new VersionedProfile(version, name, avatar, validAboutEmoji, validAbout, null, commitment);
} }
@ -87,12 +87,12 @@ public class ProfilesTest {
profiles.deleteAll(ACI); profiles.deleteAll(ACI);
final String version = "someVersion"; final String version = "someVersion";
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final String differentAvatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String differentAvatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final byte[] differentEmoji = ProfileHelper.generateRandomByteArray(60); final byte[] differentEmoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] differentAbout = ProfileHelper.generateRandomByteArray(156); final byte[] differentAbout = ProfileTestHelper.generateRandomByteArray(156);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitment = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
VersionedProfile updatedProfile = new VersionedProfile(version, name, differentAvatar, VersionedProfile updatedProfile = new VersionedProfile(version, name, differentAvatar,
differentEmoji, differentAbout, paymentAddress, commitment); differentEmoji, differentAbout, paymentAddress, commitment);
@ -112,8 +112,8 @@ public class ProfilesTest {
@Test @Test
void testSetGetNullOptionalFields() throws InvalidInputException { void testSetGetNullOptionalFields() throws InvalidInputException {
final String version = "someVersion"; final String version = "someVersion";
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitment = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
VersionedProfile profile = new VersionedProfile(version, name, null, null, null, null, VersionedProfile profile = new VersionedProfile(version, name, null, null, null, null,
commitment); commitment);
@ -143,11 +143,11 @@ public class ProfilesTest {
assertThat(retrieved.get().aboutEmoji()).isEqualTo(validProfile.aboutEmoji()); assertThat(retrieved.get().aboutEmoji()).isEqualTo(validProfile.aboutEmoji());
assertThat(retrieved.get().paymentAddress()).isNull(); assertThat(retrieved.get().paymentAddress()).isNull();
final byte[] differentName = ProfileHelper.generateRandomByteArray(81); final byte[] differentName = ProfileTestHelper.generateRandomByteArray(81);
final byte[] differentEmoji = ProfileHelper.generateRandomByteArray(60); final byte[] differentEmoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] differentAbout = ProfileHelper.generateRandomByteArray(156); final byte[] differentAbout = ProfileTestHelper.generateRandomByteArray(156);
final String differentAvatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String differentAvatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final byte[] differentCommitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] differentCommitment = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
VersionedProfile updated = new VersionedProfile(validProfile.version(), differentName, differentAvatar, differentEmoji, differentAbout, null, VersionedProfile updated = new VersionedProfile(validProfile.version(), differentName, differentAvatar, differentEmoji, differentAbout, null,
differentCommitment); differentCommitment);
@ -170,17 +170,17 @@ public class ProfilesTest {
final String versionOne = "versionOne"; final String versionOne = "versionOne";
final String versionTwo = "versionTwo"; final String versionTwo = "versionTwo";
final byte[] nameOne = ProfileHelper.generateRandomByteArray(81); final byte[] nameOne = ProfileTestHelper.generateRandomByteArray(81);
final byte[] nameTwo = ProfileHelper.generateRandomByteArray(81); final byte[] nameTwo = ProfileTestHelper.generateRandomByteArray(81);
final String avatarOne = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatarOne = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final String avatarTwo = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatarTwo = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final byte[] aboutEmoji = ProfileHelper.generateRandomByteArray(60); final byte[] aboutEmoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final byte[] commitmentOne = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitmentOne = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
final byte[] commitmentTwo = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitmentTwo = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null, VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null,
null, commitmentOne); null, commitmentOne);
@ -223,17 +223,17 @@ public class ProfilesTest {
final String versionOne = "versionOne"; final String versionOne = "versionOne";
final String versionTwo = "versionTwo"; final String versionTwo = "versionTwo";
final byte[] nameOne = ProfileHelper.generateRandomByteArray(81); final byte[] nameOne = ProfileTestHelper.generateRandomByteArray(81);
final byte[] nameTwo = ProfileHelper.generateRandomByteArray(81); final byte[] nameTwo = ProfileTestHelper.generateRandomByteArray(81);
final byte[] aboutEmoji = ProfileHelper.generateRandomByteArray(60); final byte[] aboutEmoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final String avatarOne = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatarOne = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final String avatarTwo = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16); final String avatarTwo = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);
final byte[] commitmentOne = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitmentOne = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
final byte[] commitmentTwo = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitmentTwo = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null, VersionedProfile profileOne = new VersionedProfile(versionOne, nameOne, avatarOne, null, null,
null, commitmentOne); null, commitmentOne);
@ -261,12 +261,12 @@ public class ProfilesTest {
private static Stream<Arguments> buildUpdateExpression() throws InvalidInputException { private static Stream<Arguments> buildUpdateExpression() throws InvalidInputException {
final String version = "someVersion"; final String version = "someVersion";
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16);; final String avatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);;
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitment = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
return Stream.of( return Stream.of(
Arguments.of( Arguments.of(
@ -303,12 +303,12 @@ public class ProfilesTest {
private static Stream<Arguments> buildUpdateExpressionAttributeValues() throws InvalidInputException { private static Stream<Arguments> buildUpdateExpressionAttributeValues() throws InvalidInputException {
final String version = "someVersion"; final String version = "someVersion";
final byte[] name = ProfileHelper.generateRandomByteArray(81); final byte[] name = ProfileTestHelper.generateRandomByteArray(81);
final String avatar = "profiles/" + ProfileHelper.generateRandomBase64FromByteArray(16);; final String avatar = "profiles/" + ProfileTestHelper.generateRandomBase64FromByteArray(16);;
final byte[] emoji = ProfileHelper.generateRandomByteArray(60); final byte[] emoji = ProfileTestHelper.generateRandomByteArray(60);
final byte[] about = ProfileHelper.generateRandomByteArray(156); final byte[] about = ProfileTestHelper.generateRandomByteArray(156);
final byte[] paymentAddress = ProfileHelper.generateRandomByteArray(582); final byte[] paymentAddress = ProfileTestHelper.generateRandomByteArray(582);
final byte[] commitment = new ProfileKey(ProfileHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize(); final byte[] commitment = new ProfileKey(ProfileTestHelper.generateRandomByteArray(32)).getCommitment(new ServiceId.Aci(ACI)).serialize();
return Stream.of( return Stream.of(
Arguments.of( Arguments.of(

View File

@ -3,7 +3,7 @@ package org.whispersystems.textsecuregcm.tests.util;
import java.util.Base64; import java.util.Base64;
import java.util.Random; import java.util.Random;
public class ProfileHelper { public class ProfileTestHelper {
public static String generateRandomBase64FromByteArray(final int byteArrayLength) { public static String generateRandomBase64FromByteArray(final int byteArrayLength) {
return encodeToBase64(generateRandomByteArray(byteArrayLength)); return encodeToBase64(generateRandomByteArray(byteArrayLength));
} }