accept group send endorsements for multi-recipient sends
This commit is contained in:
parent
cdd2082b07
commit
2b652fe2a9
2
pom.xml
2
pom.xml
|
@ -278,7 +278,7 @@
|
|||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>libsignal-server</artifactId>
|
||||
<version>0.39.0</version>
|
||||
<version>0.44.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal.forks</groupId>
|
||||
|
|
|
@ -74,7 +74,7 @@ hCaptcha.apiKey: unset
|
|||
|
||||
storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
|
||||
zkConfig-libsignal-0.37.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==
|
||||
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
|
||||
|
||||
genericZkConfig.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==
|
||||
|
|
|
@ -319,8 +319,8 @@ storageService:
|
|||
-----END CERTIFICATE-----
|
||||
|
||||
zkConfig:
|
||||
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
|
||||
serverSecret: secret://zkConfig-libsignal-0.37.serverSecret
|
||||
serverPublic: 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+abcdefghijklmnopqrstuvwxyzAB==
|
||||
serverSecret: secret://zkConfig-libsignal-0.42.serverSecret
|
||||
|
||||
callingZkConfig:
|
||||
serverSecret: secret://callingZkConfig.serverSecret
|
||||
|
|
|
@ -959,7 +959,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
new MessageController(rateLimiters, messageByteLimitCardinalityEstimator, messageSender, receiptSender,
|
||||
accountsManager, messagesManager, pushNotificationManager, reportMessageManager,
|
||||
multiRecipientMessageExecutor, messageDeliveryScheduler, reportSpamTokenProvider, clientReleaseManager,
|
||||
dynamicConfigurationManager, zkSecretParams, spamChecker),
|
||||
dynamicConfigurationManager, zkSecretParams, spamChecker, Clock.systemUTC()),
|
||||
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
|
||||
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
|
||||
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.Base64;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendCredentialPresentation;
|
||||
|
||||
public record GroupSendCredentialHeader(GroupSendCredentialPresentation presentation) {
|
||||
|
||||
public static GroupSendCredentialHeader valueOf(String header) {
|
||||
try {
|
||||
return new GroupSendCredentialHeader(new GroupSendCredentialPresentation(Base64.getDecoder().decode(header)));
|
||||
} catch (InvalidInputException | IllegalArgumentException e) {
|
||||
// Base64 throws IllegalArgumentException; GroupSendCredentialPresentation ctor throws InvalidInputException
|
||||
throw new WebApplicationException(e, Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import java.util.Base64;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
|
||||
public record GroupSendTokenHeader(GroupSendFullToken token) {
|
||||
|
||||
public static GroupSendTokenHeader valueOf(String header) {
|
||||
try {
|
||||
return new GroupSendTokenHeader(new GroupSendFullToken(Base64.getDecoder().decode(header)));
|
||||
} catch (InvalidInputException | IllegalArgumentException e) {
|
||||
// Base64 throws IllegalArgumentException; GroupSendFullToken ctor throws InvalidInputException
|
||||
throw new WebApplicationException(e, Status.UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
|
@ -78,12 +79,14 @@ import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage.Recipient
|
|||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendDerivedKeyPair;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.Anonymous;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.CombinedUnidentifiedSenderAccessKeys;
|
||||
import org.whispersystems.textsecuregcm.auth.GroupSendCredentialHeader;
|
||||
import org.whispersystems.textsecuregcm.auth.GroupSendTokenHeader;
|
||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
|
@ -164,6 +167,7 @@ public class MessageController {
|
|||
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||
private final ServerSecretParams serverSecretParams;
|
||||
private final SpamChecker spamChecker;
|
||||
private final Clock clock;
|
||||
|
||||
private static final int MAX_FETCH_ACCOUNT_CONCURRENCY = 8;
|
||||
|
||||
|
@ -211,7 +215,8 @@ public class MessageController {
|
|||
final ClientReleaseManager clientReleaseManager,
|
||||
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||
final ServerSecretParams serverSecretParams,
|
||||
final SpamChecker spamChecker) {
|
||||
final SpamChecker spamChecker,
|
||||
final Clock clock) {
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.messageByteLimitEstimator = messageByteLimitEstimator;
|
||||
this.messageSender = messageSender;
|
||||
|
@ -227,6 +232,7 @@ public class MessageController {
|
|||
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||
this.serverSecretParams = serverSecretParams;
|
||||
this.spamChecker = spamChecker;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Timed
|
||||
|
@ -442,7 +448,7 @@ public class MessageController {
|
|||
@ApiResponse(responseCode="400", description="The envelope specified delivery to the same recipient device multiple times")
|
||||
@ApiResponse(
|
||||
responseCode="401",
|
||||
description="The message is not a story and the unauthorized access key or group send credential is missing or incorrect")
|
||||
description="The message is not a story and the unauthorized access key or group send endorsement token is missing or incorrect")
|
||||
@ApiResponse(
|
||||
responseCode="404",
|
||||
description="The message is not a story and some of the recipient service IDs do not correspond to registered Signal users")
|
||||
|
@ -454,12 +460,12 @@ public class MessageController {
|
|||
content = @Content(schema = @Schema(implementation = AccountStaleDevices[].class)))
|
||||
public Response sendMultiRecipientMessage(
|
||||
@Deprecated
|
||||
@Parameter(description="The bitwise xor of the unidentified access keys for every recipient of the message. Will be replaced with group send credentials")
|
||||
@Parameter(description="The bitwise xor of the unidentified access keys for every recipient of the message. Will be replaced with group send endorsements")
|
||||
@HeaderParam(HeaderUtils.UNIDENTIFIED_ACCESS_KEY) @Nullable CombinedUnidentifiedSenderAccessKeys accessKeys,
|
||||
|
||||
@Parameter(description="A group send credential covering all (included and excluded) recipients of the message. Must not be combined with `Unidentified-Access-Key` or set on a story message.")
|
||||
@HeaderParam(HeaderUtils.GROUP_SEND_CREDENTIAL)
|
||||
@Nullable GroupSendCredentialHeader groupSendCredential,
|
||||
@Parameter(description="A group send endorsement token covering recipients of this message. Must not be combined with `Unidentified-Access-Key` or set on a story message.")
|
||||
@HeaderParam(HeaderUtils.GROUP_SEND_TOKEN)
|
||||
@Nullable GroupSendTokenHeader groupSendToken,
|
||||
|
||||
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent,
|
||||
|
||||
|
@ -484,29 +490,29 @@ public class MessageController {
|
|||
return spamCheck.get();
|
||||
}
|
||||
|
||||
if (groupSendCredential == null && accessKeys == null && !isStory) {
|
||||
throw new NotAuthorizedException("A group send credential or unidentified access key is required for non-story messages");
|
||||
if (groupSendToken == null && accessKeys == null && !isStory) {
|
||||
throw new NotAuthorizedException("A group send endorsement token or unidentified access key is required for non-story messages");
|
||||
}
|
||||
if (groupSendCredential != null) {
|
||||
if (groupSendToken != null) {
|
||||
if (accessKeys != null) {
|
||||
throw new BadRequestException("Only one of group send credential and unidentified access key may be provided");
|
||||
throw new BadRequestException("Only one of group send endorsement token and unidentified access key may be provided");
|
||||
} else if (isStory) {
|
||||
throw new BadRequestException("Stories should not provide a group send credential");
|
||||
throw new BadRequestException("Stories should not provide a group send endorsement token");
|
||||
}
|
||||
}
|
||||
|
||||
if (groupSendCredential != null) {
|
||||
// Group send credentials are checked before we even attempt to resolve any accounts, since
|
||||
if (groupSendToken != null) {
|
||||
// Group send endorsements are checked before we even attempt to resolve any accounts, since
|
||||
// the lists of service IDs in the envelope are all that we need to check against
|
||||
checkGroupSendCredential(
|
||||
multiRecipientMessage.getRecipients().keySet(), multiRecipientMessage.getExcludedRecipients(), groupSendCredential);
|
||||
checkGroupSendToken(
|
||||
multiRecipientMessage.getRecipients().keySet(), groupSendToken);
|
||||
}
|
||||
|
||||
final Map<ServiceIdentifier, MultiRecipientDeliveryData> recipients = buildRecipientMap(multiRecipientMessage, isStory);
|
||||
|
||||
// Access keys are checked against the UAK in the resolved accounts, so we have to check after resolving accounts above.
|
||||
// Group send credentials are checked earlier; for stories, we don't check permissions at all because only clients check them
|
||||
if (groupSendCredential == null && !isStory) {
|
||||
// Group send endorsements are checked earlier; for stories, we don't check permissions at all because only clients check them
|
||||
if (groupSendToken == null && !isStory) {
|
||||
checkAccessKeys(accessKeys, recipients.values());
|
||||
}
|
||||
// We might filter out all the recipients of a story (if none exist).
|
||||
|
@ -623,20 +629,12 @@ public class MessageController {
|
|||
return Response.ok(new SendMultiRecipientMessageResponse(uuids404)).build();
|
||||
}
|
||||
|
||||
private void checkGroupSendCredential(
|
||||
private void checkGroupSendToken(
|
||||
final Collection<ServiceId> recipients,
|
||||
final Collection<ServiceId> excludedRecipients,
|
||||
final @NotNull GroupSendCredentialHeader groupSendCredential) {
|
||||
final @NotNull GroupSendTokenHeader groupSendToken) {
|
||||
try {
|
||||
// A group send credential covers *every* group member except the sender. However, clients
|
||||
// don't always want to actually send to every recipient in the same multi-send (most
|
||||
// commonly because a new member needs an SKDM first, but also could be because the sender
|
||||
// has blocked someone). So we check the group send credential against the combination of
|
||||
// the actual recipients and the supplied list of "excluded" recipients, accounts the
|
||||
// sender knows are part of the credential but doesn't want to send to right now.
|
||||
groupSendCredential.presentation().verify(
|
||||
Lists.newArrayList(Iterables.concat(recipients, excludedRecipients)),
|
||||
serverSecretParams);
|
||||
final GroupSendFullToken token = groupSendToken.token();
|
||||
token.verify(recipients, clock.instant(), GroupSendDerivedKeyPair.forExpiration(token.getExpiration(), serverSecretParams));
|
||||
} catch (VerificationFailedException e) {
|
||||
throw new NotAuthorizedException(e);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public final class HeaderUtils {
|
|||
|
||||
public static final String UNIDENTIFIED_ACCESS_KEY = "Unidentified-Access-Key";
|
||||
|
||||
public static final String GROUP_SEND_CREDENTIAL = "Group-Send-Credential";
|
||||
public static final String GROUP_SEND_TOKEN = "Group-Send-Token";
|
||||
|
||||
private HeaderUtils() {
|
||||
// utility class
|
||||
|
|
|
@ -43,6 +43,7 @@ import java.io.InputStream;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
@ -80,6 +81,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
import org.junitpioneer.jupiter.cartesian.ArgumentSets;
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.signal.libsignal.protocol.ServiceId;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.zkgroup.ServerPublicParams;
|
||||
|
@ -88,9 +90,11 @@ import org.signal.libsignal.zkgroup.groups.ClientZkGroupCipher;
|
|||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
|
||||
import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendCredential;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendCredentialResponse;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendDerivedKeyPair;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendFullToken;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse.ReceivedEndorsements;
|
||||
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
|
@ -134,6 +138,7 @@ import org.whispersystems.textsecuregcm.util.CompletableFutureTestUtil;
|
|||
import org.whispersystems.textsecuregcm.util.HeaderUtils;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
import org.whispersystems.textsecuregcm.util.UUIDUtil;
|
||||
import org.whispersystems.websocket.Stories;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -193,6 +198,8 @@ class MessageControllerTest {
|
|||
private static final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
private static final ServerSecretParams serverSecretParams = ServerSecretParams.generate();
|
||||
|
||||
private static final TestClock clock = TestClock.now();
|
||||
|
||||
private static final ResourceExtension resources = ResourceExtension.builder()
|
||||
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
|
@ -204,7 +211,7 @@ class MessageControllerTest {
|
|||
new MessageController(rateLimiters, cardinalityEstimator, messageSender, receiptSender, accountsManager,
|
||||
messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
|
||||
messageDeliveryScheduler, ReportSpamTokenProvider.noop(), mock(ClientReleaseManager.class), dynamicConfigurationManager,
|
||||
serverSecretParams, SpamChecker.noop()))
|
||||
serverSecretParams, SpamChecker.noop(), clock))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
|
@ -258,6 +265,8 @@ class MessageControllerTest {
|
|||
when(rateLimiters.getInboundMessageBytes()).thenReturn(rateLimiter);
|
||||
|
||||
when(rateLimiter.validateAsync(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
clock.unpin();
|
||||
}
|
||||
|
||||
private static Device generateTestDevice(final byte id, final int registrationId, final int pniRegistrationId,
|
||||
|
@ -980,25 +989,11 @@ class MessageControllerTest {
|
|||
bb.put(r.perRecipientKeyMaterial()); // key material (48 bytes)
|
||||
}
|
||||
|
||||
private static void writeMultiPayloadExcludedRecipient(final ByteBuffer bb, final ServiceIdentifier id, final boolean useExplicitIdentifier) {
|
||||
if (useExplicitIdentifier) {
|
||||
bb.put(id.toFixedWidthByteArray());
|
||||
} else {
|
||||
bb.put(UUIDUtil.toBytes(id.uuid()));
|
||||
}
|
||||
|
||||
bb.put((byte) 0);
|
||||
}
|
||||
|
||||
private static InputStream initializeMultiPayload(final List<Recipient> recipients, final byte[] buffer, final boolean explicitIdentifiers) {
|
||||
return initializeMultiPayload(recipients, List.of(), buffer, explicitIdentifiers, 39);
|
||||
return initializeMultiPayload(recipients, buffer, explicitIdentifiers, 39);
|
||||
}
|
||||
|
||||
private static InputStream initializeMultiPayload(final List<Recipient> recipients, final List<ServiceIdentifier> excludedRecipients, final byte[] buffer, final boolean explicitIdentifiers) {
|
||||
return initializeMultiPayload(recipients, excludedRecipients, buffer, explicitIdentifiers, 39);
|
||||
}
|
||||
|
||||
private static InputStream initializeMultiPayload(final List<Recipient> recipients, final List<ServiceIdentifier> excludedRecipients, final byte[] buffer, final boolean explicitIdentifiers, final int payloadSize) {
|
||||
private static InputStream initializeMultiPayload(final List<Recipient> recipients, final byte[] buffer, final boolean explicitIdentifiers, final int payloadSize) {
|
||||
// initialize a binary payload according to our wire format
|
||||
ByteBuffer bb = ByteBuffer.wrap(buffer);
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
|
@ -1007,10 +1002,9 @@ class MessageControllerTest {
|
|||
bb.put(explicitIdentifiers ? (byte) 0x23 : (byte) 0x22); // version byte
|
||||
|
||||
// count varint
|
||||
writeVarint(bb, recipients.size() + excludedRecipients.size());
|
||||
writeVarint(bb, recipients.size());
|
||||
|
||||
recipients.forEach(recipient -> writeMultiPayloadRecipient(bb, recipient, explicitIdentifiers));
|
||||
excludedRecipients.forEach(recipient -> writeMultiPayloadExcludedRecipient(bb, recipient, explicitIdentifiers));
|
||||
|
||||
// now write the actual message body (empty for now)
|
||||
assert(payloadSize >= 32);
|
||||
|
@ -1269,24 +1263,20 @@ class MessageControllerTest {
|
|||
.argumentsForNextParameter(false, true); // urgent
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testMultiRecipientMessageWithGroupSendCredential(
|
||||
List<ServiceIdentifier> includedRecipients,
|
||||
List<ServiceIdentifier> excludedRecipients,
|
||||
int expectedStatus,
|
||||
int expectedMessagesSent) throws Exception {
|
||||
final List<Recipient> recipients = new ArrayList<>();
|
||||
includedRecipients.forEach(
|
||||
serviceIdentifier -> multiRecipientTargetMap().get(serviceIdentifier).forEach(
|
||||
(deviceId, registrationId) ->
|
||||
recipients.add(new Recipient(serviceIdentifier, deviceId, registrationId, new byte[48]))));
|
||||
@Test
|
||||
void testMultiRecipientMessageWithGroupSendEndorsements() throws Exception {
|
||||
final List<Recipient> recipients = List.of(
|
||||
new Recipient(SINGLE_DEVICE_ACI_ID, SINGLE_DEVICE_ID1, SINGLE_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID1, MULTI_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID2, MULTI_DEVICE_REG_ID2, new byte[48]));
|
||||
|
||||
// initialize our binary payload and create an input stream
|
||||
byte[] buffer = new byte[2048];
|
||||
InputStream stream = initializeMultiPayload(recipients, excludedRecipients, buffer, true);
|
||||
InputStream stream = initializeMultiPayload(recipients, buffer, true);
|
||||
final AciServiceIdentifier senderId = new AciServiceIdentifier(UUID.randomUUID());
|
||||
|
||||
clock.pin(Instant.parse("2024-04-09T12:00:00.00Z"));
|
||||
|
||||
Response response = resources
|
||||
.getJerseyTest()
|
||||
.target("/v1/messages/multi_recipient")
|
||||
|
@ -1296,77 +1286,106 @@ class MessageControllerTest {
|
|||
.queryParam("urgent", false)
|
||||
.request()
|
||||
.header(HttpHeaders.USER_AGENT, "FIXME")
|
||||
.header(HeaderUtils.GROUP_SEND_CREDENTIAL, validGroupSendCredentialHeader(
|
||||
senderId,
|
||||
List.of(senderId, SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID)))
|
||||
.header(HeaderUtils.GROUP_SEND_TOKEN, validGroupSendTokenHeader(
|
||||
senderId, List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), Instant.parse("2024-04-10T00:00:00.00Z")))
|
||||
.put(Entity.entity(stream, MultiRecipientMessageProvider.MEDIA_TYPE));
|
||||
|
||||
assertThat("Unexpected response", response.getStatus(), is(equalTo(expectedStatus)));
|
||||
assertThat("Unexpected response", response.getStatus(), is(equalTo(200)));
|
||||
verify(messageSender,
|
||||
exactly(expectedMessagesSent))
|
||||
exactly(3))
|
||||
.sendMessage(
|
||||
any(),
|
||||
any(),
|
||||
argThat(env -> !env.hasSourceUuid() && !env.hasSourceDevice()),
|
||||
eq(true));
|
||||
if (expectedStatus == 200) {
|
||||
SendMultiRecipientMessageResponse smrmr = response.readEntity(SendMultiRecipientMessageResponse.class);
|
||||
assertThat(smrmr.uuids404(), is(empty()));
|
||||
}
|
||||
SendMultiRecipientMessageResponse smrmr = response.readEntity(SendMultiRecipientMessageResponse.class);
|
||||
assertThat(smrmr.uuids404(), is(empty()));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testMultiRecipientMessageWithGroupSendCredential() {
|
||||
return Stream.of(
|
||||
// All members present in included or excluded recipients: success, deliver to included recipients only
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), List.of(), 200, 3),
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID), List.of(MULTI_DEVICE_ACI_ID), 200, 1),
|
||||
Arguments.of(List.of(MULTI_DEVICE_ACI_ID), List.of(SINGLE_DEVICE_ACI_ID), 200, 2),
|
||||
@Test
|
||||
void testMultiRecipientMessageWithInvalidGroupSendEndorsements() throws Exception {
|
||||
final List<Recipient> recipients = List.of(
|
||||
new Recipient(NONEXISTENT_ACI_ID, SINGLE_DEVICE_ID1, SINGLE_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID1, MULTI_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID2, MULTI_DEVICE_REG_ID2, new byte[48]));
|
||||
|
||||
// No included recipients: request is bad
|
||||
Arguments.of(List.of(), List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), 400, 0),
|
||||
// initialize our binary payload and create an input stream
|
||||
byte[] buffer = new byte[2048];
|
||||
InputStream stream = initializeMultiPayload(recipients, buffer, true);
|
||||
final AciServiceIdentifier senderId = new AciServiceIdentifier(UUID.randomUUID());
|
||||
|
||||
// Some recipients both included and excluded: request is bad
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), List.of(SINGLE_DEVICE_ACI_ID), 400, 0),
|
||||
clock.pin(Instant.parse("2024-04-09T12:00:00.00Z"));
|
||||
|
||||
// Included recipient not covered by credential: forbid
|
||||
Arguments.of(List.of(NONEXISTENT_ACI_ID), List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), 401, 0),
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID, NONEXISTENT_ACI_ID), List.of(MULTI_DEVICE_ACI_ID), 401, 0),
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID, NONEXISTENT_ACI_ID), List.of(), 401, 0),
|
||||
Response response = resources
|
||||
.getJerseyTest()
|
||||
.target("/v1/messages/multi_recipient")
|
||||
.queryParam("online", true)
|
||||
.queryParam("ts", 1663798405641L)
|
||||
.queryParam("story", false)
|
||||
.queryParam("urgent", false)
|
||||
.request()
|
||||
.header(HttpHeaders.USER_AGENT, "FIXME")
|
||||
.header(HeaderUtils.GROUP_SEND_TOKEN, validGroupSendTokenHeader(
|
||||
senderId, List.of(MULTI_DEVICE_ACI_ID), Instant.parse("2024-04-10T00:00:00.00Z")))
|
||||
.put(Entity.entity(stream, MultiRecipientMessageProvider.MEDIA_TYPE));
|
||||
|
||||
// Excluded recipient not covered by credential: forbid
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), List.of(NONEXISTENT_ACI_ID), 401, 0),
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID), List.of(NONEXISTENT_ACI_ID, MULTI_DEVICE_ACI_ID), 401, 0),
|
||||
Arguments.of(List.of(MULTI_DEVICE_ACI_ID), List.of(NONEXISTENT_ACI_ID, SINGLE_DEVICE_ACI_ID), 401, 0),
|
||||
|
||||
// Some recipients not in included or excluded list: forbid
|
||||
Arguments.of(List.of(SINGLE_DEVICE_ACI_ID), List.of(), 401, 0),
|
||||
Arguments.of(List.of(MULTI_DEVICE_ACI_ID), List.of(), 401, 0),
|
||||
|
||||
// Substituting a PNI for an ACI is not allowed
|
||||
Arguments.of(List.of(SINGLE_DEVICE_PNI_ID, MULTI_DEVICE_ACI_ID), List.of(), 401, 0));
|
||||
assertThat("Unexpected response", response.getStatus(), is(equalTo(401)));
|
||||
verifyNoMoreInteractions(messageSender);
|
||||
}
|
||||
|
||||
private String validGroupSendCredentialHeader(AciServiceIdentifier sender, List<ServiceIdentifier> allGroupMembers) throws Exception {
|
||||
@Test
|
||||
void testMultiRecipientMessageWithExpiredGroupSendEndorsements() throws Exception {
|
||||
final List<Recipient> recipients = List.of(
|
||||
new Recipient(SINGLE_DEVICE_ACI_ID, SINGLE_DEVICE_ID1, SINGLE_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID1, MULTI_DEVICE_REG_ID1, new byte[48]),
|
||||
new Recipient(MULTI_DEVICE_ACI_ID, MULTI_DEVICE_ID2, MULTI_DEVICE_REG_ID2, new byte[48]));
|
||||
|
||||
// initialize our binary payload and create an input stream
|
||||
byte[] buffer = new byte[2048];
|
||||
InputStream stream = initializeMultiPayload(recipients, buffer, true);
|
||||
final AciServiceIdentifier senderId = new AciServiceIdentifier(UUID.randomUUID());
|
||||
|
||||
clock.pin(Instant.parse("2024-04-10T12:00:00.00Z"));
|
||||
|
||||
Response response = resources
|
||||
.getJerseyTest()
|
||||
.target("/v1/messages/multi_recipient")
|
||||
.queryParam("online", true)
|
||||
.queryParam("ts", 1663798405641L)
|
||||
.queryParam("story", false)
|
||||
.queryParam("urgent", false)
|
||||
.request()
|
||||
.header(HttpHeaders.USER_AGENT, "FIXME")
|
||||
.header(HeaderUtils.GROUP_SEND_TOKEN, validGroupSendTokenHeader(
|
||||
senderId, List.of(SINGLE_DEVICE_ACI_ID, MULTI_DEVICE_ACI_ID), Instant.parse("2024-04-10T00:00:00.00Z")))
|
||||
.put(Entity.entity(stream, MultiRecipientMessageProvider.MEDIA_TYPE));
|
||||
|
||||
assertThat("Unexpected response", response.getStatus(), is(equalTo(401)));
|
||||
verifyNoMoreInteractions(messageSender);
|
||||
}
|
||||
|
||||
private String validGroupSendTokenHeader(AciServiceIdentifier sender, List<ServiceIdentifier> recipients, Instant expiration) throws Exception {
|
||||
final ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
|
||||
final GroupMasterKey groupMasterKey = new GroupMasterKey(new byte[32]);
|
||||
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
||||
final ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
|
||||
|
||||
UuidCiphertext senderCiphertext = clientZkGroupCipher.encrypt(sender.toLibsignal());
|
||||
List<UuidCiphertext> groupCiphertexts = allGroupMembers.stream()
|
||||
.map(ServiceIdentifier::toLibsignal)
|
||||
List<ServiceId> groupPlaintexts = Stream.concat(Stream.of(sender), recipients.stream()).map(ServiceIdentifier::toLibsignal).toList();
|
||||
List<UuidCiphertext> groupCiphertexts = groupPlaintexts.stream()
|
||||
.map(clientZkGroupCipher::encrypt)
|
||||
.collect(Collectors.toList());
|
||||
GroupSendCredentialResponse credentialResponse =
|
||||
GroupSendCredentialResponse.issueCredential(groupCiphertexts, senderCiphertext, serverSecretParams);
|
||||
GroupSendCredential credential =
|
||||
credentialResponse.receive(
|
||||
allGroupMembers.stream().map(ServiceIdentifier::toLibsignal).collect(Collectors.toList()),
|
||||
.toList();
|
||||
GroupSendDerivedKeyPair keyPair = GroupSendDerivedKeyPair.forExpiration(expiration, serverSecretParams);
|
||||
GroupSendEndorsementsResponse endorsementsResponse =
|
||||
GroupSendEndorsementsResponse.issue(groupCiphertexts, keyPair);
|
||||
ReceivedEndorsements endorsements =
|
||||
endorsementsResponse.receive(
|
||||
groupPlaintexts,
|
||||
sender.toLibsignal(),
|
||||
serverPublicParams,
|
||||
groupSecretParams);
|
||||
GroupSendCredentialPresentation presentation = credential.present(serverPublicParams);
|
||||
return Base64.getEncoder().encodeToString(presentation.serialize());
|
||||
expiration.minus(Duration.ofDays(1)),
|
||||
groupSecretParams,
|
||||
serverPublicParams);
|
||||
GroupSendFullToken token = endorsements.combinedEndorsement().toFullToken(groupSecretParams, expiration);
|
||||
return Base64.getEncoder().encodeToString(token.serialize());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -1408,7 +1427,7 @@ class MessageControllerTest {
|
|||
.request()
|
||||
.header(HttpHeaders.USER_AGENT, "cluck cluck, i'm a parrot")
|
||||
.header(HeaderUtils.UNIDENTIFIED_ACCESS_KEY, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_BYTES))
|
||||
.put(Entity.entity(initializeMultiPayload(recipients, List.of(), new byte[257<<10], true, 256<<10), MultiRecipientMessageProvider.MEDIA_TYPE));
|
||||
.put(Entity.entity(initializeMultiPayload(recipients, new byte[257<<10], true, 256<<10), MultiRecipientMessageProvider.MEDIA_TYPE));
|
||||
|
||||
checkBadMultiRecipientResponse(response, 400);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 89e44cd170366326a400d95c596be1c5eb439c46
|
||||
Subproject commit fa14e55cdb88a7794c6d13bc459f8e2c646a4316
|
Loading…
Reference in New Issue