merge GroupController into CertificateController
This commit is contained in:
parent
a25e967978
commit
0fdfdabf2a
|
@ -86,7 +86,6 @@ import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller;
|
import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller;
|
||||||
import org.whispersystems.textsecuregcm.controllers.DonationController;
|
import org.whispersystems.textsecuregcm.controllers.DonationController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.GroupController;
|
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
|
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||||
|
@ -625,14 +624,13 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()),
|
new AttachmentControllerV1(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getBucket()),
|
||||||
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
|
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
|
||||||
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()),
|
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()),
|
||||||
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations),
|
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations, clock),
|
||||||
new ChallengeController(rateLimitChallengeManager),
|
new ChallengeController(rateLimitChallengeManager),
|
||||||
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
|
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
|
||||||
new DirectoryController(directoryCredentialsGenerator),
|
new DirectoryController(directoryCredentialsGenerator),
|
||||||
new DirectoryV2Controller(directoryV2CredentialsGenerator),
|
new DirectoryV2Controller(directoryV2CredentialsGenerator),
|
||||||
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
|
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
|
||||||
ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()),
|
ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()),
|
||||||
new GroupController(zkAuthOperations),
|
|
||||||
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager,
|
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager,
|
||||||
messagesManager, apnFallbackManager, reportMessageManager, multiRecipientMessageExecutor),
|
messagesManager, apnFallbackManager, reportMessageManager, multiRecipientMessageExecutor),
|
||||||
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
|
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2013-2021 Signal Messenger, LLC
|
* Copyright 2013-2022 Signal Messenger, LLC
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -8,13 +8,22 @@ package org.whispersystems.textsecuregcm.controllers;
|
||||||
import static com.codahale.metrics.MetricRegistry.name;
|
import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
|
||||||
import com.codahale.metrics.annotation.Timed;
|
import com.codahale.metrics.annotation.Timed;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.dropwizard.auth.Auth;
|
import io.dropwizard.auth.Auth;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.ws.rs.BadRequestException;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
@ -34,15 +43,22 @@ import org.whispersystems.textsecuregcm.util.Util;
|
||||||
@Path("/v1/certificate")
|
@Path("/v1/certificate")
|
||||||
public class CertificateController {
|
public class CertificateController {
|
||||||
|
|
||||||
private final CertificateGenerator certificateGenerator;
|
private final CertificateGenerator certificateGenerator;
|
||||||
private final ServerZkAuthOperations serverZkAuthOperations;
|
private final ServerZkAuthOperations serverZkAuthOperations;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static final Duration MAX_REDEMPTION_DURATION = Duration.ofDays(7);
|
||||||
private static final String GENERATE_DELIVERY_CERTIFICATE_COUNTER_NAME = name(CertificateGenerator.class, "generateCertificate");
|
private static final String GENERATE_DELIVERY_CERTIFICATE_COUNTER_NAME = name(CertificateGenerator.class, "generateCertificate");
|
||||||
private static final String INCLUDE_E164_TAG_NAME = "includeE164";
|
private static final String INCLUDE_E164_TAG_NAME = "includeE164";
|
||||||
|
|
||||||
public CertificateController(CertificateGenerator certificateGenerator, ServerZkAuthOperations serverZkAuthOperations) {
|
public CertificateController(
|
||||||
this.certificateGenerator = certificateGenerator;
|
@Nonnull CertificateGenerator certificateGenerator,
|
||||||
this.serverZkAuthOperations = serverZkAuthOperations;
|
@Nonnull ServerZkAuthOperations serverZkAuthOperations,
|
||||||
|
@Nonnull Clock clock) {
|
||||||
|
this.certificateGenerator = Objects.requireNonNull(certificateGenerator);
|
||||||
|
this.serverZkAuthOperations = Objects.requireNonNull(serverZkAuthOperations);
|
||||||
|
this.clock = Objects.requireNonNull(clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
|
@ -76,10 +92,11 @@ public class CertificateController {
|
||||||
if (startRedemptionTime > endRedemptionTime) {
|
if (startRedemptionTime > endRedemptionTime) {
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
if (endRedemptionTime > Util.currentDaysSinceEpoch() + 7) {
|
final int currentDaysSinceEpoch = Util.currentDaysSinceEpoch(clock);
|
||||||
|
if (endRedemptionTime > currentDaysSinceEpoch + 7) {
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
if (startRedemptionTime < Util.currentDaysSinceEpoch()) {
|
if (startRedemptionTime < currentDaysSinceEpoch) {
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,4 +115,42 @@ public class CertificateController {
|
||||||
return new GroupCredentials(credentials);
|
return new GroupCredentials(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Timed
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/auth/group")
|
||||||
|
public GroupCredentials getGroupAuthenticationCredentials(
|
||||||
|
@Auth AuthenticatedAccount auth,
|
||||||
|
@QueryParam("redemptionStartSeconds") int startSeconds,
|
||||||
|
@QueryParam("redemptionEndSeconds") int endSeconds) {
|
||||||
|
|
||||||
|
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
||||||
|
final Instant redemptionStart = Instant.ofEpochSecond(startSeconds);
|
||||||
|
final Instant redemptionEnd = Instant.ofEpochSecond(endSeconds);
|
||||||
|
|
||||||
|
if (redemptionStart.isAfter(redemptionEnd) ||
|
||||||
|
redemptionStart.isBefore(startOfDay) ||
|
||||||
|
redemptionEnd.isAfter(startOfDay.plus(MAX_REDEMPTION_DURATION)) ||
|
||||||
|
!redemptionStart.equals(redemptionStart.truncatedTo(ChronoUnit.DAYS)) ||
|
||||||
|
!redemptionEnd.equals(redemptionEnd.truncatedTo(ChronoUnit.DAYS))) {
|
||||||
|
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<GroupCredentials.GroupCredential> credentials = new ArrayList<>();
|
||||||
|
|
||||||
|
Instant redemption = redemptionStart;
|
||||||
|
|
||||||
|
UUID aci = auth.getAccount().getUuid();
|
||||||
|
UUID pni = auth.getAccount().getPhoneNumberIdentifier();
|
||||||
|
while (!redemption.isAfter(redemptionEnd)) {
|
||||||
|
credentials.add(new GroupCredentials.GroupCredential(
|
||||||
|
serverZkAuthOperations.issueAuthCredentialWithPni(aci, pni, redemption).serialize(),
|
||||||
|
(int) redemption.getEpochSecond()));
|
||||||
|
|
||||||
|
redemption = redemption.plus(Duration.ofDays(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GroupCredentials(credentials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2022 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.controllers;
|
|
||||||
|
|
||||||
import com.codahale.metrics.annotation.Timed;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import io.dropwizard.auth.Auth;
|
|
||||||
import javax.ws.rs.BadRequestException;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
|
||||||
import org.whispersystems.textsecuregcm.entities.GroupCredentials;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Path("/v1/group")
|
|
||||||
public class GroupController {
|
|
||||||
|
|
||||||
private final ServerZkAuthOperations serverZkAuthOperations;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final Duration MAX_REDEMPTION_DURATION = Duration.ofDays(7);
|
|
||||||
|
|
||||||
public GroupController(final ServerZkAuthOperations serverZkAuthOperations) {
|
|
||||||
this(serverZkAuthOperations, Clock.systemUTC());
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
GroupController(final ServerZkAuthOperations serverZkAuthOperations, final Clock clock) {
|
|
||||||
this.serverZkAuthOperations = serverZkAuthOperations;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Timed
|
|
||||||
@GET
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
@Path("/auth")
|
|
||||||
public GroupCredentials getGroupAuthenticationCredentials(@Auth AuthenticatedAccount auth,
|
|
||||||
@QueryParam("redemptionStartSeconds") int startSeconds,
|
|
||||||
@QueryParam("redemptionEndSeconds") int endSeconds) {
|
|
||||||
|
|
||||||
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
|
||||||
final Instant redemptionStart = Instant.ofEpochSecond(startSeconds);
|
|
||||||
final Instant redemptionEnd = Instant.ofEpochSecond(endSeconds);
|
|
||||||
|
|
||||||
if (redemptionStart.isAfter(redemptionEnd) ||
|
|
||||||
redemptionStart.isBefore(startOfDay) ||
|
|
||||||
redemptionEnd.isAfter(startOfDay.plus(MAX_REDEMPTION_DURATION)) ||
|
|
||||||
!redemptionStart.equals(redemptionStart.truncatedTo(ChronoUnit.DAYS)) ||
|
|
||||||
!redemptionEnd.equals(redemptionEnd.truncatedTo(ChronoUnit.DAYS))) {
|
|
||||||
|
|
||||||
throw new BadRequestException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<GroupCredentials.GroupCredential> credentials = new ArrayList<>();
|
|
||||||
|
|
||||||
Instant redemption = redemptionStart;
|
|
||||||
|
|
||||||
while (!redemption.isAfter(redemptionEnd)) {
|
|
||||||
credentials.add(new GroupCredentials.GroupCredential(serverZkAuthOperations.issueAuthCredentialWithPni(
|
|
||||||
auth.getAccount().getUuid(),
|
|
||||||
auth.getAccount().getPhoneNumberIdentifier(),
|
|
||||||
redemption).serialize(),
|
|
||||||
(int) redemption.getEpochSecond()));
|
|
||||||
|
|
||||||
redemption = redemption.plus(Duration.ofDays(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GroupCredentials(credentials);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
|
|
||||||
|
@ -166,8 +167,18 @@ public class Util {
|
||||||
return (int) value;
|
return (int) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int currentDaysSinceEpoch(@Nonnull Clock clock) {
|
||||||
|
return toIntExact(clock.millis() / 1000 / 60/ 60 / 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current number of days since the epoch.
|
||||||
|
*
|
||||||
|
* @deprecated use {@link #currentDaysSinceEpoch(Clock)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public static int currentDaysSinceEpoch() {
|
public static int currentDaysSinceEpoch() {
|
||||||
return Util.toIntExact(System.currentTimeMillis() / 1000 / 60 / 60 / 24);
|
return currentDaysSinceEpoch(Clock.systemUTC());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sleep(long i) {
|
public static void sleep(long i) {
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2013-2022 Signal Messenger, LLC
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.controllers;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
|
||||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
|
||||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
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.zkgroup.ServerSecretParams;
|
|
||||||
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
|
|
||||||
import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations;
|
|
||||||
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
|
||||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
|
||||||
import org.whispersystems.textsecuregcm.entities.GroupCredentials;
|
|
||||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
|
||||||
class GroupControllerTest {
|
|
||||||
|
|
||||||
private static final ServerSecretParams SERVER_SECRET_PARAMS = ServerSecretParams.generate();
|
|
||||||
private static final ServerZkAuthOperations SERVER_ZK_AUTH_OPERATIONS = new ServerZkAuthOperations(SERVER_SECRET_PARAMS);
|
|
||||||
|
|
||||||
private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault());
|
|
||||||
|
|
||||||
private static final ResourceExtension RESOURCE_EXTENSION = ResourceExtension.builder()
|
|
||||||
.addProvider(AuthHelper.getAuthFilter())
|
|
||||||
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(
|
|
||||||
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
|
||||||
.setMapper(SystemMapper.getMapper())
|
|
||||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
|
||||||
.addResource(new GroupController(SERVER_ZK_AUTH_OPERATIONS, CLOCK))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetSingleGroupCredential() {
|
|
||||||
final Instant startOfDay = CLOCK.instant().truncatedTo(ChronoUnit.DAYS);
|
|
||||||
|
|
||||||
final GroupCredentials credentials = RESOURCE_EXTENSION.getJerseyTest()
|
|
||||||
.target("/v1/group/auth")
|
|
||||||
.queryParam("redemptionStartSeconds", startOfDay.getEpochSecond())
|
|
||||||
.queryParam("redemptionEndSeconds", startOfDay.getEpochSecond())
|
|
||||||
.request()
|
|
||||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
|
||||||
.get(GroupCredentials.class);
|
|
||||||
|
|
||||||
assertEquals(1, credentials.getCredentials().size());
|
|
||||||
assertEquals(startOfDay.getEpochSecond(), credentials.getCredentials().get(0).getRedemptionTime());
|
|
||||||
|
|
||||||
final ClientZkAuthOperations clientZkAuthOperations =
|
|
||||||
new ClientZkAuthOperations(SERVER_SECRET_PARAMS.getPublicParams());
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
clientZkAuthOperations.receiveAuthCredentialWithPni(AuthHelper.VALID_UUID,
|
|
||||||
AuthHelper.VALID_PNI,
|
|
||||||
(int) startOfDay.getEpochSecond(),
|
|
||||||
new AuthCredentialWithPniResponse(credentials.getCredentials().get(0).getCredential()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetWeekLongGroupCredentials() {
|
|
||||||
final Instant startOfDay = CLOCK.instant().truncatedTo(ChronoUnit.DAYS);
|
|
||||||
|
|
||||||
final GroupCredentials credentials = RESOURCE_EXTENSION.getJerseyTest()
|
|
||||||
.target("/v1/group/auth")
|
|
||||||
.queryParam("redemptionStartSeconds", startOfDay.getEpochSecond())
|
|
||||||
.queryParam("redemptionEndSeconds", startOfDay.plus(Duration.ofDays(7)).getEpochSecond())
|
|
||||||
.request()
|
|
||||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
|
||||||
.get(GroupCredentials.class);
|
|
||||||
|
|
||||||
assertEquals(8, credentials.getCredentials().size());
|
|
||||||
|
|
||||||
final ClientZkAuthOperations clientZkAuthOperations =
|
|
||||||
new ClientZkAuthOperations(SERVER_SECRET_PARAMS.getPublicParams());
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
final Instant redemptionTime = startOfDay.plus(Duration.ofDays(i));
|
|
||||||
assertEquals(redemptionTime.getEpochSecond(), credentials.getCredentials().get(i).getRedemptionTime());
|
|
||||||
|
|
||||||
final int index = i;
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
clientZkAuthOperations.receiveAuthCredentialWithPni(AuthHelper.VALID_UUID,
|
|
||||||
AuthHelper.VALID_PNI,
|
|
||||||
redemptionTime.getEpochSecond(),
|
|
||||||
new AuthCredentialWithPniResponse(credentials.getCredentials().get(index).getCredential()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource
|
|
||||||
void testBadRedemptionTimes(final Instant redemptionStart, final Instant redemptionEnd) {
|
|
||||||
final Response response = RESOURCE_EXTENSION.getJerseyTest()
|
|
||||||
.target("/v1/group/auth")
|
|
||||||
.queryParam("redemptionStartSeconds", redemptionStart.getEpochSecond())
|
|
||||||
.queryParam("redemptionEndSeconds", redemptionEnd.getEpochSecond())
|
|
||||||
.request()
|
|
||||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
|
||||||
.get();
|
|
||||||
|
|
||||||
assertEquals(400, response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<Arguments> testBadRedemptionTimes() {
|
|
||||||
return Stream.of(
|
|
||||||
// Start is after end
|
|
||||||
Arguments.of(CLOCK.instant().plus(Duration.ofDays(1)), CLOCK.instant()),
|
|
||||||
|
|
||||||
// Start is in the past
|
|
||||||
Arguments.of(CLOCK.instant().minus(Duration.ofDays(1)), CLOCK.instant()),
|
|
||||||
|
|
||||||
// End is too far in the future
|
|
||||||
Arguments.of(CLOCK.instant(), CLOCK.instant().plus(GroupController.MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1))),
|
|
||||||
|
|
||||||
// Start is not at a day boundary
|
|
||||||
Arguments.of(CLOCK.instant().plusSeconds(17), CLOCK.instant().plus(Duration.ofDays(1))),
|
|
||||||
|
|
||||||
// End is not at a day boundary
|
|
||||||
Arguments.of(CLOCK.instant(), CLOCK.instant().plusSeconds(17))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
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.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ -17,16 +18,26 @@ import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
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.ecc.Curve;
|
import org.signal.libsignal.protocol.ecc.Curve;
|
||||||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||||
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse;
|
import org.signal.libsignal.zkgroup.auth.AuthCredentialResponse;
|
||||||
|
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
|
||||||
import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations;
|
import org.signal.libsignal.zkgroup.auth.ClientZkAuthOperations;
|
||||||
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||||
|
@ -56,6 +67,7 @@ class CertificateControllerTest {
|
||||||
private static final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
|
private static final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
|
||||||
private static final CertificateGenerator certificateGenerator;
|
private static final CertificateGenerator certificateGenerator;
|
||||||
private static final ServerZkAuthOperations serverZkAuthOperations;
|
private static final ServerZkAuthOperations serverZkAuthOperations;
|
||||||
|
private static final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
|
@ -73,7 +85,7 @@ class CertificateControllerTest {
|
||||||
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
||||||
.setMapper(SystemMapper.getMapper())
|
.setMapper(SystemMapper.getMapper())
|
||||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||||
.addResource(new CertificateController(certificateGenerator, serverZkAuthOperations))
|
.addResource(new CertificateController(certificateGenerator, serverZkAuthOperations, clock))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -289,4 +301,97 @@ class CertificateControllerTest {
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(401);
|
assertThat(response.getStatus()).isEqualTo(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSingleGroupCredential() {
|
||||||
|
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
||||||
|
|
||||||
|
final GroupCredentials credentials = resources.getJerseyTest()
|
||||||
|
.target("/v1/certificate/auth/group")
|
||||||
|
.queryParam("redemptionStartSeconds", startOfDay.getEpochSecond())
|
||||||
|
.queryParam("redemptionEndSeconds", startOfDay.getEpochSecond())
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.get(GroupCredentials.class);
|
||||||
|
|
||||||
|
assertEquals(1, credentials.getCredentials().size());
|
||||||
|
assertEquals(startOfDay.getEpochSecond(), credentials.getCredentials().get(0).getRedemptionTime());
|
||||||
|
|
||||||
|
final ClientZkAuthOperations clientZkAuthOperations =
|
||||||
|
new ClientZkAuthOperations(serverSecretParams.getPublicParams());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
clientZkAuthOperations.receiveAuthCredentialWithPni(
|
||||||
|
AuthHelper.VALID_UUID,
|
||||||
|
AuthHelper.VALID_PNI,
|
||||||
|
(int) startOfDay.getEpochSecond(),
|
||||||
|
new AuthCredentialWithPniResponse(credentials.getCredentials().get(0).getCredential()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetWeekLongGroupCredentials() {
|
||||||
|
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
||||||
|
|
||||||
|
final GroupCredentials credentials = resources.getJerseyTest()
|
||||||
|
.target("/v1/certificate/auth/group")
|
||||||
|
.queryParam("redemptionStartSeconds", startOfDay.getEpochSecond())
|
||||||
|
.queryParam("redemptionEndSeconds", startOfDay.plus(Duration.ofDays(7)).getEpochSecond())
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.get(GroupCredentials.class);
|
||||||
|
|
||||||
|
assertEquals(8, credentials.getCredentials().size());
|
||||||
|
|
||||||
|
final ClientZkAuthOperations clientZkAuthOperations =
|
||||||
|
new ClientZkAuthOperations(serverSecretParams.getPublicParams());
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
final Instant redemptionTime = startOfDay.plus(Duration.ofDays(i));
|
||||||
|
assertEquals(redemptionTime.getEpochSecond(), credentials.getCredentials().get(i).getRedemptionTime());
|
||||||
|
|
||||||
|
final int index = i;
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
clientZkAuthOperations.receiveAuthCredentialWithPni(
|
||||||
|
AuthHelper.VALID_UUID,
|
||||||
|
AuthHelper.VALID_PNI,
|
||||||
|
redemptionTime.getEpochSecond(),
|
||||||
|
new AuthCredentialWithPniResponse(credentials.getCredentials().get(index).getCredential()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void testBadRedemptionTimes(final Instant redemptionStart, final Instant redemptionEnd) {
|
||||||
|
final Response response = resources.getJerseyTest()
|
||||||
|
.target("/v1/certificate/auth/group")
|
||||||
|
.queryParam("redemptionStartSeconds", redemptionStart.getEpochSecond())
|
||||||
|
.queryParam("redemptionEndSeconds", redemptionEnd.getEpochSecond())
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertEquals(400, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> testBadRedemptionTimes() {
|
||||||
|
return Stream.of(
|
||||||
|
// Start is after end
|
||||||
|
Arguments.of(clock.instant().plus(Duration.ofDays(1)), clock.instant()),
|
||||||
|
|
||||||
|
// Start is in the past
|
||||||
|
Arguments.of(clock.instant().minus(Duration.ofDays(1)), clock.instant()),
|
||||||
|
|
||||||
|
// End is too far in the future
|
||||||
|
Arguments.of(clock.instant(), clock.instant().plus(CertificateController.MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1))),
|
||||||
|
|
||||||
|
// Start is not at a day boundary
|
||||||
|
Arguments.of(clock.instant().plusSeconds(17), clock.instant().plus(Duration.ofDays(1))),
|
||||||
|
|
||||||
|
// End is not at a day boundary
|
||||||
|
Arguments.of(clock.instant(), clock.instant().plusSeconds(17))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue