diff --git a/pom.xml b/pom.xml index ae29bb099..c4c70a9b8 100644 --- a/pom.xml +++ b/pom.xml @@ -290,7 +290,7 @@ org.signal libsignal-server - 0.22.0 + 0.23.0 org.apache.logging.log4j diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java index 123d34447..9b8968972 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java @@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPrivateKey; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; @@ -37,7 +38,7 @@ public class UnidentifiedDeliveryConfiguration { return certificate; } - public ECPrivateKey getPrivateKey() { + public ECPrivateKey getPrivateKey() throws InvalidKeyException { return Curve.decodePrivatePoint(privateKey); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 99ea4ff62..42b9b3422 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -46,8 +46,6 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.ForbiddenException; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.NotAuthorizedException; @@ -69,10 +67,8 @@ import org.apache.commons.lang3.StringUtils; 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.PniCredentialResponse; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCommitment; import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialRequest; -import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,9 +86,7 @@ import org.whispersystems.textsecuregcm.entities.BatchIdentityCheckResponse; import org.whispersystems.textsecuregcm.entities.CreateProfileRequest; import org.whispersystems.textsecuregcm.entities.CredentialProfileResponse; import org.whispersystems.textsecuregcm.entities.ExpiringProfileKeyCredentialProfileResponse; -import org.whispersystems.textsecuregcm.entities.PniCredentialProfileResponse; import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes; -import org.whispersystems.textsecuregcm.entities.ProfileKeyCredentialProfileResponse; import org.whispersystems.textsecuregcm.entities.UserCapabilities; import org.whispersystems.textsecuregcm.entities.VersionedProfileResponse; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -137,14 +131,10 @@ public class ProfileController { @VisibleForTesting static final Duration EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION = Duration.ofDays(7); - private static final String PROFILE_KEY_CREDENTIAL_TYPE = "profileKey"; - private static final String PNI_CREDENTIAL_TYPE = "pni"; 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 String INVALID_ACCEPT_LANGUAGE_COUNTER_NAME = name(ProfileController.class, "invalidAcceptLanguage"); - private static final String GET_PROFILE_CREDENTIAL_COUNTER_NAME = name(ProfileController.class, "getProfileCredential"); - private static final String CREDENTIAL_TYPE_TAG_NAME = "credentialType"; public ProfileController( Clock clock, @@ -272,56 +262,23 @@ public class ProfileController { @PathParam("uuid") UUID uuid, @PathParam("version") String version, @PathParam("credentialRequest") String credentialRequest, - @QueryParam("credentialType") @DefaultValue(PROFILE_KEY_CREDENTIAL_TYPE) String credentialType) + @QueryParam("credentialType") String credentialType) throws RateLimitExceededException { + if (!EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE.equals(credentialType)) { + throw new BadRequestException(); + } + final Optional maybeRequester = auth.map(AuthenticatedAccount::getAccount); final Account targetAccount = verifyPermissionToReceiveAccountIdentityProfile(maybeRequester, accessKey, uuid); final boolean isSelf = isSelfProfileRequest(maybeRequester, uuid); - String credentialTypeTagValue = "unrecognized"; - - try { - switch (credentialType) { - case PROFILE_KEY_CREDENTIAL_TYPE -> { - credentialTypeTagValue = PROFILE_KEY_CREDENTIAL_TYPE; - - return buildProfileKeyCredentialProfileResponse(targetAccount, - version, - credentialRequest, - isSelf, - containerRequestContext); - } - - case PNI_CREDENTIAL_TYPE -> { - credentialTypeTagValue = PNI_CREDENTIAL_TYPE; - - if (!isSelf) { - throw new ForbiddenException(); - } - - return buildPniCredentialProfileResponse(targetAccount, - version, - credentialRequest, - containerRequestContext); - } - - case EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE -> { - credentialTypeTagValue = EXPIRING_PROFILE_KEY_CREDENTIAL_TYPE; - - return buildExpiringProfileKeyCredentialProfileResponse(targetAccount, - version, - credentialRequest, - isSelf, - Instant.now().plus(EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION).truncatedTo(ChronoUnit.DAYS), - containerRequestContext); - } - - default -> throw new BadRequestException(); - } - } finally { - Metrics.counter(GET_PROFILE_CREDENTIAL_COUNTER_NAME, CREDENTIAL_TYPE_TAG_NAME, credentialTypeTagValue).increment(); - } + return buildExpiringProfileKeyCredentialProfileResponse(targetAccount, + version, + credentialRequest, + isSelf, + Instant.now().plus(EXPIRING_PROFILE_KEY_CREDENTIAL_EXPIRATION).truncatedTo(ChronoUnit.DAYS), + containerRequestContext); } // Although clients should generally be using versioned profiles wherever possible, there are still a few lingering @@ -443,35 +400,6 @@ public class ProfileController { }); } - private ProfileKeyCredentialProfileResponse buildProfileKeyCredentialProfileResponse(final Account account, - final String version, - final String encodedCredentialRequest, - final boolean isSelf, - final ContainerRequestContext containerRequestContext) { - - final ProfileKeyCredentialResponse profileKeyCredentialResponse = profilesManager.get(account.getUuid(), version) - .map(profile -> getProfileCredential(encodedCredentialRequest, profile, account.getUuid())) - .orElse(null); - - return new ProfileKeyCredentialProfileResponse( - buildVersionedProfileResponse(account, version, isSelf, containerRequestContext), - profileKeyCredentialResponse); - } - - private PniCredentialProfileResponse buildPniCredentialProfileResponse(final Account account, - final String version, - final String encodedCredentialRequest, - final ContainerRequestContext containerRequestContext) { - - final PniCredentialResponse pniCredentialResponse = profilesManager.get(account.getUuid(), version) - .map(profile -> getPniCredential(encodedCredentialRequest, profile, account.getUuid(), account.getPhoneNumberIdentifier())) - .orElse(null); - - return new PniCredentialProfileResponse( - buildVersionedProfileResponse(account, version, true, containerRequestContext), - pniCredentialResponse); - } - private ExpiringProfileKeyCredentialProfileResponse buildExpiringProfileKeyCredentialProfileResponse( final Account account, final String version, @@ -542,36 +470,6 @@ public class ProfileController { account.getPhoneNumberIdentifier()); } - private ProfileKeyCredentialResponse getProfileCredential(final String encodedProfileCredentialRequest, - final VersionedProfile profile, - final UUID uuid) { - try { - final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.getCommitment()); - final ProfileKeyCredentialRequest request = new ProfileKeyCredentialRequest( - HexFormat.of().parseHex(encodedProfileCredentialRequest)); - - return zkProfileOperations.issueProfileKeyCredential(request, uuid, commitment); - } catch (IllegalArgumentException | VerificationFailedException | InvalidInputException e) { - throw new WebApplicationException(e, Response.status(Response.Status.BAD_REQUEST).build()); - } - } - - private PniCredentialResponse getPniCredential(final String encodedCredentialRequest, - final VersionedProfile profile, - final UUID accountIdentifier, - final UUID phoneNumberIdentifier) { - - try { - final ProfileKeyCommitment commitment = new ProfileKeyCommitment(profile.getCommitment()); - final ProfileKeyCredentialRequest request = new ProfileKeyCredentialRequest( - HexFormat.of().parseHex(encodedCredentialRequest)); - - return zkProfileOperations.issuePniCredential(request, accountIdentifier, phoneNumberIdentifier, commitment); - } catch (IllegalArgumentException | VerificationFailedException | InvalidInputException e) { - throw new WebApplicationException(e, Response.status(Response.Status.BAD_REQUEST).build()); - } - } - private ExpiringProfileKeyCredentialResponse getExpiringProfileKeyCredentialResponse( final String encodedCredentialRequest, final VersionedProfile profile, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialProfileResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialProfileResponse.java deleted file mode 100644 index 327bf0888..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialProfileResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.entities; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import javax.annotation.Nullable; -import org.signal.libsignal.zkgroup.profiles.PniCredentialResponse; - -public class PniCredentialProfileResponse extends CredentialProfileResponse { - - @JsonProperty - @JsonSerialize(using = PniCredentialResponseAdapter.Serializing.class) - @JsonDeserialize(using = PniCredentialResponseAdapter.Deserializing.class) - @Nullable - private PniCredentialResponse pniCredential; - - public PniCredentialProfileResponse() { - } - - public PniCredentialProfileResponse(final VersionedProfileResponse versionedProfileResponse, - @Nullable final PniCredentialResponse pniCredential) { - - super(versionedProfileResponse); - this.pniCredential = pniCredential; - } - - @Nullable - public PniCredentialResponse getPniCredential() { - return pniCredential; - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialResponseAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialResponseAdapter.java deleted file mode 100644 index a981bd0d1..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PniCredentialResponseAdapter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.entities; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import java.io.IOException; -import java.util.Base64; -import org.signal.libsignal.zkgroup.InvalidInputException; -import org.signal.libsignal.zkgroup.profiles.PniCredentialResponse; - -public class PniCredentialResponseAdapter { - - public static class Serializing extends JsonSerializer { - @Override - public void serialize(PniCredentialResponse response, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - if (response == null) jsonGenerator.writeNull(); - else jsonGenerator.writeString(Base64.getEncoder().encodeToString(response.serialize())); - } - } - - public static class Deserializing extends JsonDeserializer { - @Override - public PniCredentialResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - try { - return new PniCredentialResponse(Base64.getDecoder().decode(jsonParser.getValueAsString())); - } catch (InvalidInputException e) { - throw new IOException(e); - } - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialProfileResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialProfileResponse.java deleted file mode 100644 index 72f00fd78..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialProfileResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.entities; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse; -import javax.annotation.Nullable; - -public class ProfileKeyCredentialProfileResponse extends CredentialProfileResponse { - - @JsonProperty - @JsonSerialize(using = ProfileKeyCredentialResponseAdapter.Serializing.class) - @JsonDeserialize(using = ProfileKeyCredentialResponseAdapter.Deserializing.class) - @Nullable - private ProfileKeyCredentialResponse credential; - - public ProfileKeyCredentialProfileResponse() { - } - - public ProfileKeyCredentialProfileResponse(final VersionedProfileResponse versionedProfileResponse, - @Nullable final ProfileKeyCredentialResponse credential) { - - super(versionedProfileResponse); - this.credential = credential; - } - - @Nullable - public ProfileKeyCredentialResponse getCredential() { - return credential; - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialResponseAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialResponseAdapter.java deleted file mode 100644 index ff60178d7..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ProfileKeyCredentialResponseAdapter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.entities; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import java.io.IOException; -import java.util.Base64; -import org.signal.libsignal.zkgroup.InvalidInputException; -import org.signal.libsignal.zkgroup.profiles.ProfileKeyCredentialResponse; - -public class ProfileKeyCredentialResponseAdapter { - - public static class Serializing extends JsonSerializer { - @Override - public void serialize(ProfileKeyCredentialResponse response, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - if (response == null) jsonGenerator.writeNull(); - else jsonGenerator.writeString(Base64.getEncoder().encodeToString(response.serialize())); - } - } - - public static class Deserializing extends JsonDeserializer { - @Override - public ProfileKeyCredentialResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - try { - return new ProfileKeyCredentialResponse(Base64.getDecoder().decode(jsonParser.getValueAsString())); - } catch (InvalidInputException e) { - throw new IOException(e); - } - } - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java index c0186c25a..b54aaae0a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java @@ -15,7 +15,6 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPrivateKey; import org.whispersystems.textsecuregcm.entities.MessageProtos; -import java.io.IOException; import java.security.InvalidKeyException; import java.util.Base64; import java.util.Set; @@ -64,7 +63,7 @@ public class CertificateCommand extends Command { System.out.println("Private key: " + Base64.getEncoder().encodeToString(keyPair.getPrivateKey().serialize())); } - private void runCertificateCommand(Namespace namespace) throws IOException, InvalidKeyException { + private void runCertificateCommand(Namespace namespace) throws InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException { if (namespace.getString("key") == null) { System.out.println("No key specified!"); return; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java index 62903a7e9..7bb8a4c19 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java @@ -25,7 +25,7 @@ class CertificateGeneratorTest { private static final String IDENTITY_KEY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"; @Test - void testCreateFor() throws IOException, InvalidKeyException { + void testCreateFor() throws IOException, InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException { final Account account = mock(Account.class); final Device device = mock(Device.class); final CertificateGenerator certificateGenerator = new CertificateGenerator(Base64.getDecoder().decode(SIGNING_CERTIFICATE), Curve.decodePrivatePoint(Base64.getDecoder().decode(SIGNING_KEY)), 1); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java index 14374ce1f..44ee1979a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -67,12 +67,10 @@ 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.PniCredentialResponse; 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.ProfileKeyCredentialResponse; import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; @@ -88,9 +86,7 @@ import org.whispersystems.textsecuregcm.entities.BatchIdentityCheckRequest; import org.whispersystems.textsecuregcm.entities.BatchIdentityCheckResponse; import org.whispersystems.textsecuregcm.entities.CreateProfileRequest; import org.whispersystems.textsecuregcm.entities.ExpiringProfileKeyCredentialProfileResponse; -import org.whispersystems.textsecuregcm.entities.PniCredentialProfileResponse; import org.whispersystems.textsecuregcm.entities.ProfileAvatarUploadAttributes; -import org.whispersystems.textsecuregcm.entities.ProfileKeyCredentialProfileResponse; import org.whispersystems.textsecuregcm.entities.VersionedProfileResponse; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -868,6 +864,29 @@ class ProfileControllerTest { assertThat(profile.getPaymentAddress()).isNull(); } + @Test + void testGetProfileWithExpiringProfileKeyCredentialVersionNotFound() throws VerificationFailedException { + final Account account = mock(Account.class); + when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); + when(account.getCurrentProfileVersion()).thenReturn(Optional.of("version")); + when(account.isEnabled()).thenReturn(true); + + when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); + when(profilesManager.get(any(), any())).thenReturn(Optional.empty()); + + final ExpiringProfileKeyCredentialProfileResponse profile = resources.getJerseyTest() + .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, "version-that-does-not-exist", "credential-request")) + .queryParam("credentialType", "expiringProfileKey") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) + .get(ExpiringProfileKeyCredentialProfileResponse.class); + + assertThat(profile.getVersionedProfileResponse().getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID); + assertThat(profile.getCredential()).isNull(); + + verify(zkProfileOperations, never()).issueExpiringProfileKeyCredential(any(), any(), any(), any()); + } + @Test void testSetProfileBadges() throws InvalidInputException { ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(AuthHelper.VALID_UUID); @@ -963,196 +982,6 @@ class ProfileControllerTest { new AccountBadge("TEST3", Instant.ofEpochSecond(42 + 86400), false)); } - @ParameterizedTest - @MethodSource - void testGetProfileWithProfileKeyCredential(final MultivaluedMap authHeaders) - throws VerificationFailedException, InvalidInputException { - final String version = "version"; - final byte[] unidentifiedAccessKey = "test-uak".getBytes(StandardCharsets.UTF_8); - - 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(AuthHelper.VALID_UUID); - - final VersionedProfile versionedProfile = mock(VersionedProfile.class); - when(versionedProfile.getCommitment()).thenReturn(profileKeyCommitment.serialize()); - - final ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext = - clientZkProfile.createProfileKeyCredentialRequestContext(AuthHelper.VALID_UUID, profileKey); - - final ProfileKeyCredentialRequest credentialRequest = profileKeyCredentialRequestContext.getRequest(); - - final Account account = mock(Account.class); - when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); - when(account.getCurrentProfileVersion()).thenReturn(Optional.of(version)); - when(account.isEnabled()).thenReturn(true); - when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of(unidentifiedAccessKey)); - - final ProfileKeyCredentialResponse credentialResponse = - serverZkProfile.issueProfileKeyCredential(credentialRequest, AuthHelper.VALID_UUID, profileKeyCommitment); - - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); - when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile)); - when(zkProfileOperations.issueProfileKeyCredential(credentialRequest, AuthHelper.VALID_UUID, profileKeyCommitment)) - .thenReturn(credentialResponse); - - final ProfileKeyCredentialProfileResponse profile = resources.getJerseyTest() - .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, version, - HexFormat.of().formatHex(credentialRequest.serialize()))) - .request() - .headers(authHeaders) - .get(ProfileKeyCredentialProfileResponse.class); - - assertThat(profile.getVersionedProfileResponse().getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID); - assertThat(profile.getCredential()).isEqualTo(credentialResponse); - - verify(zkProfileOperations).issueProfileKeyCredential(credentialRequest, AuthHelper.VALID_UUID, profileKeyCommitment); - verify(zkProfileOperations, never()).issuePniCredential(any(), any(), any(), any()); - - final ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams); - assertThatNoException().isThrownBy(() -> - clientZkProfileCipher.receiveProfileKeyCredential(profileKeyCredentialRequestContext, profile.getCredential())); - } - - private static Stream testGetProfileWithProfileKeyCredential() { - return Stream.of( - Arguments.of(new MultivaluedHashMap<>(Map.of(OptionalAccess.UNIDENTIFIED, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_KEY)))), - Arguments.of(new MultivaluedHashMap<>(Map.of("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)))), - Arguments.of(new MultivaluedHashMap<>(Map.of("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)))) - ); - } - - @Test - void testGetProfileWithProfileKeyCredentialVersionNotFound() throws VerificationFailedException { - final Account account = mock(Account.class); - when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); - when(account.getCurrentProfileVersion()).thenReturn(Optional.of("version")); - when(account.isEnabled()).thenReturn(true); - - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); - when(profilesManager.get(any(), any())).thenReturn(Optional.empty()); - - final ProfileKeyCredentialProfileResponse profile = resources.getJerseyTest() - .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, "version-that-does-not-exist", "credential-request")) - .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .get(ProfileKeyCredentialProfileResponse.class); - - assertThat(profile.getVersionedProfileResponse().getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID); - assertThat(profile.getCredential()).isNull(); - - verify(zkProfileOperations, never()).issueProfileKeyCredential(any(), any(), any()); - verify(zkProfileOperations, never()).issuePniCredential(any(), any(), any(), any()); - } - - @Test - void testGetProfileWithPniCredential() throws InvalidInputException, VerificationFailedException { - final String version = "version"; - - 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(AuthHelper.VALID_UUID); - - final VersionedProfile versionedProfile = mock(VersionedProfile.class); - when(versionedProfile.getCommitment()).thenReturn(profileKeyCommitment.serialize()); - - final ProfileKeyCredentialRequest credentialRequest = - clientZkProfile.createPniCredentialRequestContext(AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKey) - .getRequest(); - - final Account account = mock(Account.class); - when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); - when(account.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI); - when(account.getCurrentProfileVersion()).thenReturn(Optional.of(version)); - when(account.isEnabled()).thenReturn(true); - - final PniCredentialResponse credentialResponse = - serverZkProfile.issuePniCredential(credentialRequest, AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKeyCommitment); - - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); - when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile)); - when(zkProfileOperations.issuePniCredential(credentialRequest, AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKeyCommitment)) - .thenReturn(credentialResponse); - - final PniCredentialProfileResponse profile = resources.getJerseyTest() - .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, version, - HexFormat.of().formatHex(credentialRequest.serialize()))) - .queryParam("credentialType", "pni") - .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .get(PniCredentialProfileResponse.class); - - assertThat(profile.getVersionedProfileResponse().getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID); - assertThat(profile.getPniCredential()).isEqualTo(credentialResponse); - - verify(zkProfileOperations, never()).issueProfileKeyCredential(any(), any(), any()); - verify(zkProfileOperations).issuePniCredential(credentialRequest, AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKeyCommitment); - } - - @Test - void testGetProfileWithPniCredentialNotSelf() throws InvalidInputException, VerificationFailedException { - final String version = "version"; - - 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(AuthHelper.VALID_UUID); - - final VersionedProfile versionedProfile = mock(VersionedProfile.class); - when(versionedProfile.getCommitment()).thenReturn(profileKeyCommitment.serialize()); - - final ProfileKeyCredentialRequest credentialRequest = - clientZkProfile.createProfileKeyCredentialRequestContext(AuthHelper.VALID_UUID, profileKey).getRequest(); - - final Account account = mock(Account.class); - when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); - when(account.getPhoneNumberIdentifier()).thenReturn(AuthHelper.VALID_PNI); - when(account.getCurrentProfileVersion()).thenReturn(Optional.of(version)); - when(account.isEnabled()).thenReturn(true); - - final PniCredentialResponse credentialResponse = - serverZkProfile.issuePniCredential(credentialRequest, AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKeyCommitment); - - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); - when(profilesManager.get(AuthHelper.VALID_UUID, version)).thenReturn(Optional.of(versionedProfile)); - when(zkProfileOperations.issuePniCredential(credentialRequest, AuthHelper.VALID_UUID, AuthHelper.VALID_PNI, profileKeyCommitment)) - .thenReturn(credentialResponse); - - final Response response = resources.getJerseyTest() - .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, version, - HexFormat.of().formatHex(credentialRequest.serialize()))) - .queryParam("credentialType", "pni") - .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID_TWO, AuthHelper.VALID_PASSWORD_TWO)) - .get(); - - assertThat(response.getStatus()).isEqualTo(403); - - verify(zkProfileOperations, never()).issueProfileKeyCredential(any(), any(), any()); - verify(zkProfileOperations, never()).issuePniCredential(any(), any(), any(), any()); - } - @ParameterizedTest @MethodSource void testGetProfileWithExpiringProfileKeyCredential(final MultivaluedMap authHeaders) @@ -1209,7 +1038,6 @@ class ProfileControllerTest { assertThat(profile.getCredential()).isEqualTo(credentialResponse); verify(zkProfileOperations).issueExpiringProfileKeyCredential(credentialRequest, AuthHelper.VALID_UUID, profileKeyCommitment, expiration); - verify(zkProfileOperations, never()).issuePniCredential(any(), any(), any(), any()); final ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams); assertThatNoException().isThrownBy(() -> @@ -1224,30 +1052,6 @@ class ProfileControllerTest { ); } - @Test - void testGetProfileWithPniCredentialVersionNotFound() throws VerificationFailedException { - final Account account = mock(Account.class); - when(account.getUuid()).thenReturn(AuthHelper.VALID_UUID); - when(account.getCurrentProfileVersion()).thenReturn(Optional.of("version")); - when(account.isEnabled()).thenReturn(true); - - when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(account)); - when(profilesManager.get(any(), any())).thenReturn(Optional.empty()); - - final PniCredentialProfileResponse profile = resources.getJerseyTest() - .target(String.format("/v1/profile/%s/%s/%s", AuthHelper.VALID_UUID, "version-that-does-not-exist", "credential-request")) - .queryParam("credentialType", "pni") - .request() - .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) - .get(PniCredentialProfileResponse.class); - - assertThat(profile.getVersionedProfileResponse().getBaseProfileResponse().getUuid()).isEqualTo(AuthHelper.VALID_UUID); - assertThat(profile.getPniCredential()).isNull(); - - verify(zkProfileOperations, never()).issueProfileKeyCredential(any(), any(), any()); - verify(zkProfileOperations, never()).issuePniCredential(any(), any(), any(), any()); - } - @Test void testSetProfileBadgesMissingFromRequest() throws InvalidInputException { ProfileKeyCommitment commitment = new ProfileKey(new byte[32]).getCommitment(AuthHelper.VALID_UUID); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index fc8119a8f..d27cd71b8 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse; @@ -68,7 +69,7 @@ class CertificateControllerTest { certificateGenerator = new CertificateGenerator(Base64.getDecoder().decode(signingCertificate), Curve.decodePrivatePoint(Base64.getDecoder().decode(signingKey)), 1); serverZkAuthOperations = new ServerZkAuthOperations(serverSecretParams); - } catch (IOException e) { + } catch (IOException | InvalidKeyException e) { throw new AssertionError(e); } }