Add support for issuing libsignal v0.101.0+ call link credentials

This commit is contained in:
Chris Eager
2026-08-27 17:39:03 -04:00
committed by Chris Eager
parent 3dde05de8a
commit 355d95c6c6
10 changed files with 99 additions and 17 deletions
+1
View File
@@ -79,6 +79,7 @@ storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAA
zkConfig-libsignal-0.42.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdef
callingZkConfigPreV101.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
callingZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
chatZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
+3
View File
@@ -315,6 +315,9 @@ storageService:
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
callingZkConfigPreV101:
serverSecret: secret://callingZkConfigPreV101.serverSecret
callingZkConfig:
serverSecret: secret://callingZkConfig.serverSecret
@@ -228,6 +228,11 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private PaymentsServiceConfiguration paymentsService;
@Valid
@NotNull
@JsonProperty
private GenericZkConfig callingZkConfigPreV101;
@Valid
@NotNull
@JsonProperty
@@ -514,6 +519,11 @@ public class WhisperServerConfiguration extends Configuration {
return chatZkConfig;
}
/// ZK secret shared with Calling Service
public GenericZkConfig getCallingZkConfigPreV101() {
return callingZkConfigPreV101;
}
/// ZK secret shared with Calling Service
public GenericZkConfig getCallingZkConfig() {
return callingZkConfig;
@@ -940,6 +940,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getCdnConfiguration().credentials().secretAccessKey().value());
ServerSecretParams groupZkSecretParams = new ServerSecretParams(config.getGroupsZkConfig().serverSecret().value());
GenericServerSecretParams callingPreV101GenericZkSecretParams = new GenericServerSecretParams(config.getCallingZkConfigPreV101().serverSecret().value());
GenericServerSecretParams callingGenericZkSecretParams = new GenericServerSecretParams(config.getCallingZkConfig().serverSecret().value());
GenericServerSecretParams chatGenericZkSecretParams = new GenericServerSecretParams(config.getChatZkConfig().serverSecret().value());
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(groupZkSecretParams);
@@ -1268,9 +1269,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
experimentEnrollmentManager, config.getAttachments().maxAttachmentUploadSizeInBytes()),
new ArchiveController(accountsManager, backupAuthManager, backupManager, backupMetrics, config.getAttachments().maxAttachmentUploadSizeInBytes(), config.getAttachments().maxMessageBackupUploadSizeInBytes()),
new CallRoutingControllerV2(rateLimiters, cloudflareTurnCredentialsManager),
new CallLinkController(rateLimiters, callingGenericZkSecretParams),
new CallLinkController(rateLimiters, callingGenericZkSecretParams, callingPreV101GenericZkSecretParams),
new CallQualitySurveyController(callQualitySurveyManager),
new CertificateController(accountsManager, certificateGenerator, zkAuthOperations, callingGenericZkSecretParams, clock),
new CertificateController(accountsManager, certificateGenerator, zkAuthOperations, callingGenericZkSecretParams, callingPreV101GenericZkSecretParams, clock),
new ChallengeController(accountsManager, rateLimitChallengeManager, challengeConstraintChecker),
new DeviceController(accountsManager, rateLimiters, persistentTimer),
new DeviceCheckController(clock, accountsManager, backupAuthManager, appleDeviceCheckManager, rateLimiters,
@@ -2,6 +2,7 @@ package org.whispersystems.textsecuregcm.controllers;
import io.dropwizard.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
@@ -9,6 +10,7 @@ import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -26,13 +28,16 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters;
public class CallLinkController {
private final RateLimiters rateLimiters;
private final GenericServerSecretParams genericServerSecretParams;
private final GenericServerSecretParams genericServerSecretParamsPreV101;
public CallLinkController(
final RateLimiters rateLimiters,
final GenericServerSecretParams genericServerSecretParams
final GenericServerSecretParams genericServerSecretParams,
final GenericServerSecretParams genericServerSecretParamsPreV101
) {
this.rateLimiters = rateLimiters;
this.genericServerSecretParams = genericServerSecretParams;
this.genericServerSecretParamsPreV101 = genericServerSecretParamsPreV101;
}
@POST
@@ -52,7 +57,9 @@ public class CallLinkController {
@ApiResponse(responseCode = "429", description = "Ratelimited.")
public CreateCallLinkCredential getCreateAuth(
final @Auth AuthenticatedDevice auth,
final @NotNull @Valid GetCreateCallLinkCredentialsRequest request
final @NotNull @Valid GetCreateCallLinkCredentialsRequest request,
@Parameter(description = "Whether to use libsignal v0.101.0+ secret params")
final @QueryParam("v101") boolean v101
) throws RateLimitExceededException {
rateLimiters.getCreateCallLinkLimiter().validate(auth.accountIdentifier());
@@ -67,7 +74,7 @@ public class CallLinkController {
}
return new CreateCallLinkCredential(
createCallLinkCredentialRequest.issueCredential(new ServiceId.Aci(auth.accountIdentifier()), truncatedDayTimestamp, genericServerSecretParams).serialize(),
createCallLinkCredentialRequest.issueCredential(new ServiceId.Aci(auth.accountIdentifier()), truncatedDayTimestamp, v101 ? genericServerSecretParams : genericServerSecretParamsPreV101).serialize(),
truncatedDayTimestamp.getEpochSecond()
);
}
@@ -10,6 +10,7 @@ import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.auth.Auth;
import io.micrometer.core.instrument.Metrics;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.DefaultValue;
@@ -49,6 +50,7 @@ public class CertificateController {
private final CertificateGenerator certificateGenerator;
private final ServerZkAuthOperations serverZkAuthOperations;
private final GenericServerSecretParams genericServerSecretParams;
private final GenericServerSecretParams genericServerSecretParamsPreV101;
private final Clock clock;
@VisibleForTesting
@@ -61,12 +63,14 @@ public class CertificateController {
@Nonnull CertificateGenerator certificateGenerator,
@Nonnull ServerZkAuthOperations serverZkAuthOperations,
@Nonnull GenericServerSecretParams genericServerSecretParams,
@Nonnull GenericServerSecretParams genericServerSecretParamsPreV101,
@Nonnull Clock clock) {
this.accountsManager = accountsManager;
this.certificateGenerator = Objects.requireNonNull(certificateGenerator);
this.serverZkAuthOperations = Objects.requireNonNull(serverZkAuthOperations);
this.genericServerSecretParams = genericServerSecretParams;
this.genericServerSecretParamsPreV101 = genericServerSecretParamsPreV101;
this.clock = Objects.requireNonNull(clock);
}
@@ -95,7 +99,9 @@ public class CertificateController {
public GroupCredentials getGroupAuthenticationCredentials(
@Auth AuthenticatedDevice auth,
@QueryParam("redemptionStartSeconds") long startSeconds,
@QueryParam("redemptionEndSeconds") long endSeconds) {
@QueryParam("redemptionEndSeconds") long endSeconds,
@Parameter(description = "Whether to use libsignal v0.101.0+ secret params")
@QueryParam("v101") boolean v101) {
final RedemptionRange redemptionRange;
try {
@@ -127,7 +133,7 @@ public class CertificateController {
(int) redemption.getEpochSecond()));
callLinkAuthCredentials.add(new GroupCredentials.CallLinkAuthCredential(
CallLinkAuthCredentialResponse.issueCredential(aci, redemption, genericServerSecretParams).serialize(),
CallLinkAuthCredentialResponse.issueCredential(aci, redemption, v101 ? genericServerSecretParams : genericServerSecretParamsPreV101).serialize(),
redemption.getEpochSecond()));
}
@@ -19,9 +19,17 @@ import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.BeforeEach;
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.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.signal.libsignal.protocol.ServiceId;
import org.signal.libsignal.protocol.util.Hex;
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
import org.signal.libsignal.zkgroup.calllinks.CallLinkSecretParams;
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredential;
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialPresentation;
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialRequestContext;
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialResponse;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.entities.GetCreateCallLinkCredentialsRequest;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
@@ -29,10 +37,12 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
@ExtendWith(DropwizardExtensionsSupport.class)
public class CallLinkControllerTest {
private static final GenericServerSecretParams genericServerSecretParams = GenericServerSecretParams.generate();
private static final GenericServerSecretParams genericServerSecretParamsPreV101 = GenericServerSecretParams.generate();
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
private static final RateLimiter createCallLinkLimiter = mock(RateLimiter.class);
private static final byte[] roomId = Hex.fromStringCondensedAssert("c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7");
@@ -45,7 +55,7 @@ public class CallLinkControllerTest {
.addProvider(new RateLimitExceededExceptionMapper())
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new CallLinkController(rateLimiters, genericServerSecretParams))
.addResource(new CallLinkController(rateLimiters, genericServerSecretParams, genericServerSecretParamsPreV101))
.build();
@BeforeEach
@@ -53,14 +63,36 @@ public class CallLinkControllerTest {
when(rateLimiters.getCreateCallLinkLimiter()).thenReturn(createCallLinkLimiter);
}
@Test
void testGetCreateAuth() {
@ParameterizedTest
@NullSource
@ValueSource(booleans = {true, false})
void testGetCreateAuth(final Boolean v101) throws Exception {
try (Response response = resources.getJerseyTest()
.target("/v1/call-link/create-auth")
// a null value will exclude the parameter altogether, rather than sending `?v101=`
.queryParam("v101", v101)
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.post(Entity.json(new GetCreateCallLinkCredentialsRequest(createCallLinkRequestSerialized)))) {
assertThat(response.getStatus()).isEqualTo(200);
final GenericServerSecretParams verificationParams = v101 != null && v101
? genericServerSecretParams
: genericServerSecretParamsPreV101;
final byte[] serializedCredential = response.readEntity(
org.whispersystems.textsecuregcm.entities.CreateCallLinkCredential.class).credential();
final CreateCallLinkCredentialResponse credentialResponse = new CreateCallLinkCredentialResponse(serializedCredential);
final ServiceId.Aci aci = new ServiceId.Aci(AuthHelper.VALID_UUID);
final CreateCallLinkCredential credential = createCallLinkRequestContext.receiveResponse(credentialResponse, aci,
verificationParams.getPublicParams());
final CallLinkSecretParams callLinkSecretParams = CallLinkSecretParams.deriveFromRootKey(TestRandomUtil.nextBytes(16));
final CreateCallLinkCredentialPresentation presentation = credential.present(roomId,
aci, verificationParams.getPublicParams(), callLinkSecretParams);
presentation.verify(roomId, verificationParams, callLinkSecretParams.getPublicParams());
}
}
@@ -68,6 +68,7 @@ class CertificateControllerTest {
private static final ServerSecretParams SERVER_SECRET_PARAMS = ServerSecretParams.generate();
private static final GenericServerSecretParams genericServerSecretParams = GenericServerSecretParams.generate();
private static final GenericServerSecretParams genericServerSecretParamsPreV101 = GenericServerSecretParams.generate();
private static final ServerZkAuthOperations SERVER_ZK_AUTH_OPERATIONS;
private static final Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
@@ -96,7 +97,7 @@ class CertificateControllerTest {
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new CertificateController(AuthHelper.ACCOUNTS_MANAGER, CERTIFICATE_GENERATOR, SERVER_ZK_AUTH_OPERATIONS, genericServerSecretParams, clock))
.addResource(new CertificateController(AuthHelper.ACCOUNTS_MANAGER, CERTIFICATE_GENERATOR, SERVER_ZK_AUTH_OPERATIONS, genericServerSecretParams, genericServerSecretParamsPreV101, clock))
.build();
@Test
@@ -237,7 +238,8 @@ class CertificateControllerTest {
final UUID aci,
final String password,
@Nullable UUID pni,
@Nullable byte[] authCredentialSalt) {
@Nullable byte[] authCredentialSalt,
@Nullable Boolean v101) {
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
@@ -245,6 +247,8 @@ class CertificateControllerTest {
.target("/v1/certificate/auth/group")
.queryParam("redemptionStartSeconds", startOfDay.getEpochSecond())
.queryParam("redemptionEndSeconds", startOfDay.getEpochSecond())
// a null value will exclude the parameter altogether, rather than sending `?v101=`
.queryParam("v101", v101)
.request()
.header("Authorization", AuthHelper.getAuthHeader(aci, password))
.get(GroupCredentials.class);
@@ -276,16 +280,24 @@ class CertificateControllerTest {
}
final GenericServerSecretParams verificationParams = v101 != null && v101
? genericServerSecretParams
: genericServerSecretParamsPreV101;
assertDoesNotThrow(() -> {
new CallLinkAuthCredentialResponse(credentials.callLinkAuthCredentials().getFirst().credential())
.receive(new ServiceId.Aci(aci), startOfDay, genericServerSecretParams.getPublicParams());
.receive(new ServiceId.Aci(aci), startOfDay, verificationParams.getPublicParams());
});
}
static Collection<Arguments> getGroupCredentials() {
return List.of(
Arguments.argumentSet("account with phone number", true, AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD, AuthHelper.VALID_PNI, null),
Arguments.argumentSet("account without phone number", false, AuthHelper.NUMBERLESS_UUID, AuthHelper.NUMBERLESS_PASSWORD, null, AuthHelper.NUMBERLESS_AUTH_CREDENTIAL_SALT)
Arguments.argumentSet("account with phone number", true, AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD, AuthHelper.VALID_PNI, null, null),
Arguments.argumentSet("account with phone number - v101 = false", true, AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD, AuthHelper.VALID_PNI, null, false),
Arguments.argumentSet("account with phone number - v101 = true", true, AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD, AuthHelper.VALID_PNI, null, true),
Arguments.argumentSet("account without phone number", false, AuthHelper.NUMBERLESS_UUID, AuthHelper.NUMBERLESS_PASSWORD, null, AuthHelper.NUMBERLESS_AUTH_CREDENTIAL_SALT, null),
Arguments.argumentSet("account without phone number - v101 = false", false, AuthHelper.NUMBERLESS_UUID, AuthHelper.NUMBERLESS_PASSWORD, null, AuthHelper.NUMBERLESS_AUTH_CREDENTIAL_SALT, false),
Arguments.argumentSet("account without phone number - v101 = true", false, AuthHelper.NUMBERLESS_UUID, AuthHelper.NUMBERLESS_PASSWORD, null, AuthHelper.NUMBERLESS_AUTH_CREDENTIAL_SALT, true)
);
}
@@ -295,13 +307,15 @@ class CertificateControllerTest {
final UUID aci,
final String password,
@Nullable UUID pni,
@Nullable byte[] authCredentialSalt) {
@Nullable byte[] authCredentialSalt,
@Nullable Boolean v101) {
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())
.queryParam("v101", v101)
.request()
.header("Authorization", AuthHelper.getAuthHeader(aci, password))
.get(GroupCredentials.class);
@@ -336,9 +350,13 @@ class CertificateControllerTest {
new AuthCredentialWithPniResponse(credentials.credentials().getFirst().credential())));
}
final GenericServerSecretParams verificationParams = v101 != null && v101
? genericServerSecretParams
: genericServerSecretParamsPreV101;
assertDoesNotThrow(() -> {
new CallLinkAuthCredentialResponse(credentials.callLinkAuthCredentials().get(index).credential())
.receive(new ServiceId.Aci(aci), redemptionTime, genericServerSecretParams.getPublicParams());
.receive(new ServiceId.Aci(aci), redemptionTime, verificationParams.getPublicParams());
});
}
}
@@ -155,6 +155,7 @@ storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAA
# The below secrets were generated exclusively for testing purposes. Do not use them in any other context.
zkConfig-libsignal-0.42.serverSecret: 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
callingZkConfigPreV101.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
callingZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
chatZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
@@ -324,6 +324,9 @@ storageService:
9Kxq0DY7RCEpdHMCKcOL
-----END CERTIFICATE-----
callingZkConfigPreV101:
serverSecret: secret://callingZkConfigPreV101.serverSecret
callingZkConfig:
serverSecret: secret://callingZkConfig.serverSecret