refactoring of ExternalServiceCredentialGenerator

This commit is contained in:
Sergey Skrobotov 2023-01-25 15:15:46 -08:00
parent dd98f7f043
commit eb499833c6
32 changed files with 594 additions and 415 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
@ -80,16 +80,18 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener;
import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter;
import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator;
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.ArtController;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3;
import org.whispersystems.textsecuregcm.controllers.CertificateController;
@ -108,15 +110,14 @@ import org.whispersystems.textsecuregcm.controllers.RemoteConfigController;
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.controllers.StickerController;
import org.whispersystems.textsecuregcm.controllers.ArtController;
import org.whispersystems.textsecuregcm.controllers.SubscriptionController;
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.currency.FixerClient;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters;
import org.whispersystems.textsecuregcm.limits.PushChallengeManager;
@ -158,7 +159,6 @@ import org.whispersystems.textsecuregcm.push.ProvisioningManager;
import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
@ -450,29 +450,23 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getBraintree().supportedCurrencies(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor);
ExternalServiceCredentialGenerator directoryCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryConfiguration().getDirectoryClientConfiguration().getUserAuthenticationTokenUserIdSecret());
ExternalServiceCredentialGenerator directoryV2CredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration().getUserIdTokenSharedSecret(),
true, false);
ExternalServiceCredentialsGenerator directoryCredentialsGenerator = DirectoryController.credentialsGenerator(
config.getDirectoryConfiguration().getDirectoryClientConfiguration());
ExternalServiceCredentialsGenerator directoryV2CredentialsGenerator = DirectoryV2Controller.credentialsGenerator(
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration());
ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator(
config.getSecureStorageServiceConfiguration());
ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
config.getSecureBackupServiceConfiguration());
ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = PaymentsController.credentialsGenerator(
config.getPaymentsServiceConfiguration());
ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(
config.getArtServiceConfiguration());
dynamicConfigurationManager.start();
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator paymentsCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getPaymentsServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator artCredentialsGenerator = new ExternalServiceCredentialGenerator(
config.getArtServiceConfiguration().getUserAuthenticationTokenSharedSecret(),
config.getArtServiceConfiguration().getUserAuthenticationTokenUserIdSecret(),
true, false, false);
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor);
SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration());

View File

@ -1,106 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.whispersystems.textsecuregcm.util.Util;
public class ExternalServiceCredentialGenerator {
private final byte[] key;
private final byte[] userIdKey;
private final boolean usernameDerivation;
private final boolean prependUsername;
private final boolean truncateKey;
private final Clock clock;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey) {
this(key, userIdKey, true, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername) {
this(key, prependUsername, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername, boolean truncateKey) {
this(key, new byte[0], false, prependUsername, truncateKey);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this(key, userIdKey, usernameDerivation, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername) {
this(key, userIdKey, usernameDerivation, prependUsername, true, Clock.systemUTC());
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey) {
this(key, userIdKey, usernameDerivation, prependUsername, truncateKey, Clock.systemUTC());
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, Clock clock) {
this(key, userIdKey, usernameDerivation, prependUsername, true, clock);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey, Clock clock) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
this.prependUsername = prependUsername;
this.truncateKey = truncateKey;
this.clock = clock;
}
public ExternalServiceCredentials generateFor(String identity) {
Mac mac = getMacInstance();
String username = getUserId(identity, mac, usernameDerivation);
long currentTimeSeconds = clock.millis() / 1000;
String prefix = username + ":" + currentTimeSeconds;
byte[] prefixMac = getHmac(key, prefix.getBytes(), mac);
final HexFormat hex = HexFormat.of();
String output = hex.formatHex(truncateKey ? Util.truncate(prefixMac, 10) : prefixMac);
String token = (prependUsername ? prefix : currentTimeSeconds) + ":" + output;
return new ExternalServiceCredentials(username, token);
}
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
final HexFormat hex = HexFormat.of();
if (usernameDerivation) return hex.formatHex(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
else return number;
}
private Mac getMacInstance() {
try {
return Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private byte[] getHmac(byte[] key, byte[] input, Mac mac) {
try {
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(input);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static java.util.Objects.requireNonNull;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256ToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import java.time.Clock;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.Validate;
public class ExternalServiceCredentialsGenerator {
private static final int TRUNCATE_LENGTH = 10;
private final byte[] key;
private final byte[] userDerivationKey;
private final boolean prependUsername;
private final boolean truncateSignature;
private final Clock clock;
public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key);
}
private ExternalServiceCredentialsGenerator(
final byte[] key,
final byte[] userDerivationKey,
final boolean prependUsername,
final boolean truncateSignature,
final Clock clock) {
this.key = requireNonNull(key);
this.userDerivationKey = requireNonNull(userDerivationKey);
this.prependUsername = prependUsername;
this.truncateSignature = truncateSignature;
this.clock = requireNonNull(clock);
}
public ExternalServiceCredentials generateForUuid(final UUID uuid) {
return generateFor(uuid.toString());
}
public ExternalServiceCredentials generateFor(final String identity) {
final String username = userDerivationKey.length > 0
? hmac256TruncatedToHexString(userDerivationKey, identity, TRUNCATE_LENGTH)
: identity;
final long currentTimeSeconds = TimeUnit.MILLISECONDS.toSeconds(clock.millis());
final String dataToSign = username + ":" + currentTimeSeconds;
final String signature = truncateSignature
? hmac256TruncatedToHexString(key, dataToSign, TRUNCATE_LENGTH)
: hmac256ToHexString(key, dataToSign);
final String token = (prependUsername ? dataToSign : currentTimeSeconds) + ":" + signature;
return new ExternalServiceCredentials(username, token);
}
public static class Builder {
private final byte[] key;
private byte[] userDerivationKey = new byte[0];
private boolean prependUsername = true;
private boolean truncateSignature = true;
private Clock clock = Clock.systemUTC();
private Builder(final byte[] key) {
this.key = requireNonNull(key);
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey;
return this;
}
public Builder withClock(final Clock clock) {
this.clock = requireNonNull(clock);
return this;
}
public Builder prependUsername(final boolean prependUsername) {
this.prependUsername = prependUsername;
return this;
}
public Builder truncateSignature(final boolean truncateSignature) {
this.truncateSignature = truncateSignature;
return this;
}
public ExternalServiceCredentialsGenerator build() {
return new ExternalServiceCredentialsGenerator(
key, userDerivationKey, prependUsername, truncateSignature, clock);
}
}
}

View File

@ -1,33 +1,11 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013-2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class DirectoryV2ClientConfiguration {
private final byte[] userAuthenticationTokenSharedSecret;
private final byte[] userIdTokenSharedSecret;
@JsonCreator
public DirectoryV2ClientConfiguration(
@JsonProperty("userAuthenticationTokenSharedSecret") final byte[] userAuthenticationTokenSharedSecret,
@JsonProperty("userIdTokenSharedSecret") final byte[] userIdTokenSharedSecret) {
this.userAuthenticationTokenSharedSecret = userAuthenticationTokenSharedSecret;
this.userIdTokenSharedSecret = userIdTokenSharedSecret;
}
@ExactlySize({32})
public byte[] getUserAuthenticationTokenSharedSecret() {
return userAuthenticationTokenSharedSecret;
}
@ExactlySize({32})
public byte[] getUserIdTokenSharedSecret() {
return userIdTokenSharedSecret;
}
public record DirectoryV2ClientConfiguration(@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret,
@ExactlySize({32}) byte[] userIdTokenSharedSecret) {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -7,65 +7,28 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.util.List;
public class SecureStorageServiceConfiguration {
public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticationTokenSharedSecret,
@NotBlank String uri,
@NotEmpty List<@NotBlank String> storageCaCertificates,
@Valid @JsonProperty("circuitBreaker") CircuitBreakerConfiguration circuitBreakerConfig,
@Valid @JsonProperty("retry") RetryConfiguration retryConfig) {
@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@VisibleForTesting
public SecureStorageServiceConfiguration(
final @NotEmpty String userAuthenticationTokenSharedSecret,
final @NotBlank String uri,
final @NotEmpty List<@NotBlank String> storageCaCertificates) {
this(userAuthenticationTokenSharedSecret, uri, storageCaCertificates, new CircuitBreakerConfiguration(), new RetryConfiguration());
}
@NotBlank
@JsonProperty
private String uri;
@NotEmpty
@JsonProperty
private List<@NotBlank String> storageCaCertificates;
@NotNull
@Valid
@JsonProperty
private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration();
@NotNull
@Valid
@JsonProperty
private RetryConfiguration retry = new RetryConfiguration();
public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException {
public byte[] decodeUserAuthenticationTokenSharedSecret() throws DecoderException {
return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
}
@VisibleForTesting
public void setUri(final String uri) {
this.uri = uri;
}
public String getUri() {
return uri;
}
@VisibleForTesting
public void setStorageCaCertificates(final List<String> certificatePem) {
this.storageCaCertificates = certificatePem;
}
public List<String> getStorageCaCertificates() {
return storageCaCertificates;
}
public CircuitBreakerConfiguration getCircuitBreakerConfiguration() {
return circuitBreaker;
}
public RetryConfiguration getRetryConfiguration() {
return retry;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
@ -61,8 +61,8 @@ import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.BasicAuthorizationHeader;
import org.whispersystems.textsecuregcm.auth.ChangesDeviceEnabledState;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnToken;
@ -165,7 +165,7 @@ public class AccountController {
private final Map<String, Integer> testDevices;
private final CaptchaChecker captchaChecker;
private final PushNotificationManager pushNotificationManager;
private final ExternalServiceCredentialGenerator backupServiceCredentialGenerator;
private final ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator;
private final ChangeNumberManager changeNumberManager;
private final Clock clock;
@ -186,7 +186,7 @@ public class AccountController {
CaptchaChecker captchaChecker,
PushNotificationManager pushNotificationManager,
ChangeNumberManager changeNumberManager,
ExternalServiceCredentialGenerator backupServiceCredentialGenerator,
ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator,
ClientPresenceManager clientPresenceManager,
Clock clock
) {
@ -199,7 +199,7 @@ public class AccountController {
this.turnTokenGenerator = turnTokenGenerator;
this.captchaChecker = captchaChecker;
this.pushNotificationManager = pushNotificationManager;
this.backupServiceCredentialGenerator = backupServiceCredentialGenerator;
this.backupServiceCredentialsGenerator = backupServiceCredentialsGenerator;
this.changeNumberManager = changeNumberManager;
this.clientPresenceManager = clientPresenceManager;
this.clock = clock;
@ -217,12 +217,12 @@ public class AccountController {
CaptchaChecker captchaChecker,
PushNotificationManager pushNotificationManager,
ChangeNumberManager changeNumberManager,
ExternalServiceCredentialGenerator backupServiceCredentialGenerator
ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator
) {
this(pendingAccounts, accounts, rateLimiters,
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, testDevices, captchaChecker,
pushNotificationManager, changeNumberManager,
backupServiceCredentialGenerator, null, Clock.systemUTC());
backupServiceCredentialsGenerator, null, Clock.systemUTC());
}
@Timed
@ -832,7 +832,7 @@ public class AccountController {
final StoredRegistrationLock existingRegistrationLock = existingAccount.getRegistrationLock();
final ExternalServiceCredentials existingBackupCredentials =
backupServiceCredentialGenerator.generateFor(existingAccount.getUuid().toString());
backupServiceCredentialsGenerator.generateForUuid(existingAccount.getUuid());
if (existingRegistrationLock.requiresClientRegistrationLock()) {
if (!Util.isEmpty(clientRegistrationLock)) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -12,19 +12,34 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.DecoderException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@Path("/v1/art")
public class ArtController {
private final ExternalServiceCredentialGenerator artServiceCredentialGenerator;
private final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator;
private final RateLimiters rateLimiters;
public static ExternalServiceCredentialsGenerator credentialsGenerator(final ArtServiceConfiguration cfg) {
try {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret())
.prependUsername(false)
.truncateSignature(false)
.build();
} catch (DecoderException e) {
throw new IllegalArgumentException(e);
}
}
public ArtController(RateLimiters rateLimiters,
ExternalServiceCredentialGenerator artServiceCredentialGenerator) {
this.artServiceCredentialGenerator = artServiceCredentialGenerator;
ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) {
this.artServiceCredentialsGenerator = artServiceCredentialsGenerator;
this.rateLimiters = rateLimiters;
}
@ -36,6 +51,6 @@ public class ArtController {
throws RateLimitExceededException {
final UUID uuid = auth.getAccount().getUuid();
rateLimiters.getArtPackLimiter().validate(uuid);
return artServiceCredentialGenerator.generateFor(uuid.toString());
return artServiceCredentialsGenerator.generateForUuid(uuid);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -32,7 +32,7 @@ public class AttachmentControllerV2 {
public AttachmentControllerV2(RateLimiters rateLimiters, String accessKey, String accessSecret, String region,
String bucket) {
this.rateLimiter = rateLimiters.getAttachmentLimiter();
this.rateLimiter = rateLimiters.getAttachmentLimiter();
this.policyGenerator = new PostPolicyGenerator(region, bucket, accessKey);
this.policySigner = new PolicySigner(accessSecret, region);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
@ -13,15 +13,25 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.codec.DecoderException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.DirectoryClientConfiguration;
@Path("/v1/directory")
public class DirectoryController {
private final ExternalServiceCredentialGenerator directoryServiceTokenGenerator;
private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator;
public DirectoryController(ExternalServiceCredentialGenerator userTokenGenerator) {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryClientConfiguration cfg)
throws DecoderException {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret())
.build();
}
public DirectoryController(ExternalServiceCredentialsGenerator userTokenGenerator) {
this.directoryServiceTokenGenerator = userTokenGenerator;
}

View File

@ -1,11 +1,13 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 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 java.time.Clock;
import java.util.UUID;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@ -13,15 +15,31 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
@Path("/v2/directory")
public class DirectoryV2Controller {
private final ExternalServiceCredentialGenerator directoryServiceTokenGenerator;
private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator;
public DirectoryV2Controller(ExternalServiceCredentialGenerator userTokenGenerator) {
@VisibleForTesting
public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryV2ClientConfiguration cfg,
final Clock clock) {
return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userIdTokenSharedSecret())
.prependUsername(false)
.withClock(clock)
.build();
}
public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryV2ClientConfiguration cfg) {
return credentialsGenerator(cfg, Clock.systemUTC());
}
public DirectoryV2Controller(ExternalServiceCredentialsGenerator userTokenGenerator) {
this.directoryServiceTokenGenerator = userTokenGenerator;
}
@ -31,7 +49,7 @@ public class DirectoryV2Controller {
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthToken(@Auth AuthenticatedAccount auth) {
final UUID uuid = auth.getAccount().getUuid();
final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateFor(uuid.toString());
final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid);
return Response.ok().entity(credentials).build();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,36 +11,47 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.DecoderException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
@Path("/v1/payments")
public class PaymentsController {
private final ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator;
private final CurrencyConversionManager currencyManager;
private final ExternalServiceCredentialsGenerator paymentsServiceCredentialsGenerator;
private final CurrencyConversionManager currencyManager;
public PaymentsController(CurrencyConversionManager currencyManager, ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator) {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final PaymentsServiceConfiguration cfg)
throws DecoderException {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.prependUsername(true)
.build();
}
public PaymentsController(final CurrencyConversionManager currencyManager, final ExternalServiceCredentialsGenerator paymentsServiceCredentialsGenerator) {
this.currencyManager = currencyManager;
this.paymentsServiceCredentialGenerator = paymentsServiceCredentialGenerator;
this.paymentsServiceCredentialsGenerator = paymentsServiceCredentialsGenerator;
}
@Timed
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) {
return paymentsServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) {
return paymentsServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid());
}
@Timed
@GET
@Path("/conversions")
@Produces(MediaType.APPLICATION_JSON)
public CurrencyConversionEntityList getConversions(@Auth AuthenticatedAccount auth) {
public CurrencyConversionEntityList getConversions(final @Auth AuthenticatedAccount auth) {
return currencyManager.getCurrencyConversions().orElseThrow();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,17 +11,27 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.DecoderException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
@Path("/v1/backup")
public class SecureBackupController {
private final ExternalServiceCredentialGenerator backupServiceCredentialGenerator;
private final ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator;
public SecureBackupController(ExternalServiceCredentialGenerator backupServiceCredentialGenerator) {
this.backupServiceCredentialGenerator = backupServiceCredentialGenerator;
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureBackupServiceConfiguration cfg)
throws DecoderException {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.prependUsername(true)
.build();
}
public SecureBackupController(ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator) {
this.backupServiceCredentialsGenerator = backupServiceCredentialsGenerator;
}
@Timed
@ -29,6 +39,6 @@ public class SecureBackupController {
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) {
return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
return backupServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,17 +11,30 @@ import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.DecoderException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
@Path("/v1/storage")
public class SecureStorageController {
private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator;
private final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator;
public SecureStorageController(ExternalServiceCredentialGenerator storageServiceCredentialGenerator) {
this.storageServiceCredentialGenerator = storageServiceCredentialGenerator;
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) {
try {
return ExternalServiceCredentialsGenerator
.builder(cfg.decodeUserAuthenticationTokenSharedSecret())
.prependUsername(true)
.build();
} catch (DecoderException e) {
throw new IllegalArgumentException(e);
}
}
public SecureStorageController(ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator) {
this.storageServiceCredentialsGenerator = storageServiceCredentialsGenerator;
}
@Timed
@ -29,6 +42,6 @@ public class SecureStorageController {
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) {
return storageServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString());
return storageServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid());
}
}

View File

@ -1,41 +1,43 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.securebackup;
import static org.whispersystems.textsecuregcm.util.HeaderUtils.basicAuthHeader;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HttpHeaders;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.time.Duration;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.util.HttpUtils;
/**
* A client for sending requests to Signal's secure value recovery service on behalf of authenticated users.
*/
public class SecureBackupClient {
private final ExternalServiceCredentialGenerator secureBackupCredentialGenerator;
private final ExternalServiceCredentialsGenerator secureBackupCredentialsGenerator;
private final URI deleteUri;
private final FaultTolerantHttpClient httpClient;
@VisibleForTesting
static final String DELETE_PATH = "/v1/backup";
public SecureBackupClient(final ExternalServiceCredentialGenerator secureBackupCredentialGenerator, final Executor executor, final SecureBackupServiceConfiguration configuration) throws CertificateException {
this.secureBackupCredentialGenerator = secureBackupCredentialGenerator;
public SecureBackupClient(final ExternalServiceCredentialsGenerator secureBackupCredentialsGenerator, final Executor executor, final SecureBackupServiceConfiguration configuration) throws CertificateException {
this.secureBackupCredentialsGenerator = secureBackupCredentialsGenerator;
this.deleteUri = URI.create(configuration.getUri()).resolve(DELETE_PATH);
this.httpClient = FaultTolerantHttpClient.newBuilder()
.withCircuitBreaker(configuration.getCircuitBreakerConfiguration())
@ -51,17 +53,16 @@ public class SecureBackupClient {
}
public CompletableFuture<Void> deleteBackups(final UUID accountUuid) {
final ExternalServiceCredentials credentials = secureBackupCredentialGenerator.generateFor(accountUuid.toString());
final ExternalServiceCredentials credentials = secureBackupCredentialsGenerator.generateForUuid(accountUuid);
final HttpRequest request = HttpRequest.newBuilder()
.uri(deleteUri)
.DELETE()
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(
(credentials.username() + ":" + credentials.password()).getBytes(StandardCharsets.UTF_8)))
.header(HttpHeaders.AUTHORIZATION, basicAuthHeader(credentials))
.build();
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> {
if (response.statusCode() >= 200 && response.statusCode() < 300) {
if (HttpUtils.isSuccessfulResponse(response.statusCode())) {
return null;
}

View File

@ -1,68 +1,68 @@
/*
* Copyright 2020 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.securestorage;
import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import static org.whispersystems.textsecuregcm.util.HeaderUtils.basicAuthHeader;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HttpHeaders;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.time.Duration;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.util.HttpUtils;
/**
* A client for sending requests to Signal's secure storage service on behalf of authenticated users.
*/
public class SecureStorageClient {
private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator;
private final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator;
private final URI deleteUri;
private final FaultTolerantHttpClient httpClient;
@VisibleForTesting
static final String DELETE_PATH = "/v1/storage";
public SecureStorageClient(final ExternalServiceCredentialGenerator storageServiceCredentialGenerator, final Executor executor, final SecureStorageServiceConfiguration configuration) throws CertificateException {
this.storageServiceCredentialGenerator = storageServiceCredentialGenerator;
this.deleteUri = URI.create(configuration.getUri()).resolve(DELETE_PATH);
public SecureStorageClient(final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator, final Executor executor, final SecureStorageServiceConfiguration configuration) throws CertificateException {
this.storageServiceCredentialsGenerator = storageServiceCredentialsGenerator;
this.deleteUri = URI.create(configuration.uri()).resolve(DELETE_PATH);
this.httpClient = FaultTolerantHttpClient.newBuilder()
.withCircuitBreaker(configuration.getCircuitBreakerConfiguration())
.withRetry(configuration.getRetryConfiguration())
.withCircuitBreaker(configuration.circuitBreakerConfig())
.withRetry(configuration.retryConfig())
.withVersion(HttpClient.Version.HTTP_1_1)
.withConnectTimeout(Duration.ofSeconds(10))
.withRedirect(HttpClient.Redirect.NEVER)
.withExecutor(executor)
.withName("secure-storage")
.withSecurityProtocol(FaultTolerantHttpClient.SECURITY_PROTOCOL_TLS_1_3)
.withTrustedServerCertificates(configuration.getStorageCaCertificates().toArray(new String[0]))
.withTrustedServerCertificates(configuration.storageCaCertificates().toArray(new String[0]))
.build();
}
public CompletableFuture<Void> deleteStoredData(final UUID accountUuid) {
final ExternalServiceCredentials credentials = storageServiceCredentialGenerator.generateFor(accountUuid.toString());
final ExternalServiceCredentials credentials = storageServiceCredentialsGenerator.generateForUuid(accountUuid);
final HttpRequest request = HttpRequest.newBuilder()
.uri(deleteUri)
.DELETE()
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(
(credentials.username() + ":" + credentials.password()).getBytes(StandardCharsets.UTF_8)))
.header(HttpHeaders.AUTHORIZATION, basicAuthHeader(credentials))
.build();
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> {
if (response.statusCode() >= 200 && response.statusCode() < 300) {
if (HttpUtils.isSuccessfulResponse(response.statusCode())) {
return null;
}

View File

@ -1,14 +1,19 @@
/*
* Copyright 2022 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import static java.util.Objects.requireNonNull;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
public final class HeaderUtils {
@ -22,6 +27,16 @@ public final class HeaderUtils {
// utility class
}
public static String basicAuthHeader(final ExternalServiceCredentials credentials) {
return basicAuthHeader(credentials.username(), credentials.password());
}
public static String basicAuthHeader(final String username, final String password) {
requireNonNull(username);
requireNonNull(password);
return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
}
@Nonnull
public static String getTimestampHeader() {
return TIMESTAMP_HEADER + ":" + System.currentTimeMillis();

View File

@ -0,0 +1,66 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import java.nio.charset.StandardCharsets;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public final class HmacUtils {
private static final HexFormat HEX = HexFormat.of();
private static final String HMAC_SHA_256 = "HmacSHA256";
private static final ThreadLocal<Mac> THREAD_LOCAL_HMAC_SHA_256 = ThreadLocal.withInitial(() -> {
try {
return Mac.getInstance(HMAC_SHA_256);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
public static byte[] hmac256(final byte[] key, final byte[] input) {
try {
final Mac mac = THREAD_LOCAL_HMAC_SHA_256.get();
mac.init(new SecretKeySpec(key, HMAC_SHA_256));
return mac.doFinal(input);
} catch (final InvalidKeyException e) {
throw new RuntimeException(e);
}
}
public static byte[] hmac256(final byte[] key, final String input) {
return hmac256(key, input.getBytes(StandardCharsets.UTF_8));
}
public static String hmac256ToHexString(final byte[] key, final byte[] input) {
return HEX.formatHex(hmac256(key, input));
}
public static String hmac256ToHexString(final byte[] key, final String input) {
return hmac256ToHexString(key, input.getBytes(StandardCharsets.UTF_8));
}
public static byte[] hmac256Truncated(final byte[] key, final byte[] input, final int length) {
return Util.truncate(hmac256(key, input), length);
}
public static byte[] hmac256Truncated(final byte[] key, final String input, final int length) {
return hmac256Truncated(key, input.getBytes(StandardCharsets.UTF_8), length);
}
public static String hmac256TruncatedToHexString(final byte[] key, final byte[] input, final int length) {
return HEX.formatHex(Util.truncate(hmac256(key, input), length));
}
public static String hmac256TruncatedToHexString(final byte[] key, final String input, final int length) {
return hmac256TruncatedToHexString(key, input.getBytes(StandardCharsets.UTF_8), length);
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
public final class HttpUtils {
private HttpUtils() {
// utility class
}
public static boolean isSuccessfulResponse(final int statusCode) {
return statusCode >= 200 && statusCode < 300;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -23,8 +23,10 @@ import java.util.concurrent.Executors;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
@ -103,11 +105,11 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
ExecutorService storageServiceExecutor = environment.lifecycle()
.executorService(name(getClass(), "storageService-%d")).maxThreads(8).minThreads(1).build();
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
configuration.getSecureBackupServiceConfiguration());
ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator(
configuration.getSecureStorageServiceConfiguration());
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(),
configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -25,8 +25,10 @@ import net.sourceforge.argparse4j.inf.Subparser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
@ -105,11 +107,11 @@ public class DeleteUserCommand extends EnvironmentCommand<WhisperServerConfigura
ExecutorService storageServiceExecutor = environment.lifecycle()
.executorService(name(getClass(), "storageService-%d")).maxThreads(8).minThreads(1).build();
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
configuration.getSecureBackupServiceConfiguration());
ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator(
configuration.getSecureStorageServiceConfiguration());
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(),
configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -24,8 +24,10 @@ import java.util.concurrent.Executors;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
@ -108,11 +110,11 @@ public class SetUserDiscoverabilityCommand extends EnvironmentCommand<WhisperSer
ExecutorService storageServiceExecutor = environment.lifecycle()
.executorService(name(getClass(), "storageService-%d")).maxThreads(8).minThreads(1).build();
ExternalServiceCredentialGenerator backupCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureBackupServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialGenerator storageCredentialsGenerator = new ExternalServiceCredentialGenerator(
configuration.getSecureStorageServiceConfiguration().getUserAuthenticationTokenSharedSecret(), true);
ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
configuration.getSecureBackupServiceConfiguration());
ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator(
configuration.getSecureStorageServiceConfiguration());
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = new DynamicConfigurationManager<>(
configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(),
configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -65,12 +65,13 @@ import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
@ -111,6 +112,7 @@ import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundExcep
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.Hex;
import org.whispersystems.textsecuregcm.util.MockHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestClock;
@ -169,7 +171,13 @@ class AccountControllerTest {
private static DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
private byte[] registration_lock_key = new byte[32];
private static ExternalServiceCredentialGenerator storageCredentialGenerator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false);
private static final SecureStorageServiceConfiguration STORAGE_CFG = MockHelper.buildMock(
SecureStorageServiceConfiguration.class,
cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController
.credentialsGenerator(STORAGE_CFG);
private static final ResourceExtension resources = ResourceExtension.builder()
.addProvider(AuthHelper.getAuthFilter())
@ -192,7 +200,7 @@ class AccountControllerTest {
captchaChecker,
pushNotificationManager,
changeNumberManager,
storageCredentialGenerator,
STORAGE_CREDENTIAL_GENERATOR,
clientPresenceManager,
testClock))
.build();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -23,18 +23,18 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
class SecureBackupClientTest {
private UUID accountUuid;
private ExternalServiceCredentialGenerator credentialGenerator;
private ExternalServiceCredentialsGenerator credentialsGenerator;
private ExecutorService httpExecutor;
private SecureBackupClient secureStorageClient;
@ -47,7 +47,7 @@ class SecureBackupClientTest {
@BeforeEach
void setUp() throws CertificateException {
accountUuid = UUID.randomUUID();
credentialGenerator = mock(ExternalServiceCredentialGenerator.class);
credentialsGenerator = mock(ExternalServiceCredentialsGenerator.class);
httpExecutor = Executors.newSingleThreadExecutor();
final SecureBackupServiceConfiguration config = new SecureBackupServiceConfiguration();
@ -101,7 +101,7 @@ class SecureBackupClientTest {
-----END CERTIFICATE-----
"""));
secureStorageClient = new SecureBackupClient(credentialGenerator, httpExecutor, config);
secureStorageClient = new SecureBackupClient(credentialsGenerator, httpExecutor, config);
}
@AfterEach
@ -115,7 +115,7 @@ class SecureBackupClientTest {
final String username = RandomStringUtils.randomAlphabetic(16);
final String password = RandomStringUtils.randomAlphanumeric(32);
when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password));
when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password));
wireMock.stubFor(delete(urlEqualTo(SecureBackupClient.DELETE_PATH))
.withBasicAuth(username, password)
@ -130,7 +130,7 @@ class SecureBackupClientTest {
final String username = RandomStringUtils.randomAlphabetic(16);
final String password = RandomStringUtils.randomAlphanumeric(32);
when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password));
when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password));
wireMock.stubFor(delete(urlEqualTo(SecureBackupClient.DELETE_PATH))
.withBasicAuth(username, password)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -27,14 +27,14 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
class SecureStorageClientTest {
private UUID accountUuid;
private ExternalServiceCredentialGenerator credentialGenerator;
private ExternalServiceCredentialsGenerator credentialsGenerator;
private ExecutorService httpExecutor;
private SecureStorageClient secureStorageClient;
@ -47,61 +47,60 @@ class SecureStorageClientTest {
@BeforeEach
void setUp() throws CertificateException {
accountUuid = UUID.randomUUID();
credentialGenerator = mock(ExternalServiceCredentialGenerator.class);
credentialsGenerator = mock(ExternalServiceCredentialsGenerator.class);
httpExecutor = Executors.newSingleThreadExecutor();
final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration();
config.setUri("http://localhost:" + wireMock.getPort());
final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration(
"not_used",
"http://localhost:" + wireMock.getPort(),
List.of("""
-----BEGIN CERTIFICATE-----
MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEL
MAkGA1UECAwCVVMxHjAcBgNVBAoMFVNpZ25hbCBNZXNzZW5nZXIsIExMQzETMBEG
A1UEAwwKc2lnbmFsLm9yZzAeFw0yMDEyMjMyMjQ3NTlaFw0zMDEyMjEyMjQ3NTla
ME8xCzAJBgNVBAYTAnVzMQswCQYDVQQIDAJVUzEeMBwGA1UECgwVU2lnbmFsIE1l
c3NlbmdlciwgTExDMRMwEQYDVQQDDApzaWduYWwub3JnMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCfSLcZNHYqbxSsgWp4JvbPRHjQTrlsrKrgD2q7f/OY6O3Y
/X0QNcNSOJpliN8rmzwslfsrXHO3q1diGRw4xHogUJZ/7NQrHiP/zhN0VTDh49pD
ZpjXVyUbayLS/6qM5arKxBspzEFBb5v8cF6bPr76SO/rpGXiI0j6yJKX6fRiKwID
AQABo1AwTjAdBgNVHQ4EFgQU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwHwYDVR0jBBgw
FoAU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
AQ0FAAOBgQB+5d5+NtzLILfrc9QmJdIO1YeDP64JmFwTER0kEUouRsb9UwknVWZa
y7MTM4NoBV1k0zb5LAk89SIDPr/maW5AsLtEomzjnEiomjoMBUdNe3YCgQReoLnr
R/QaUNbrCjTGYfBsjGbIzmkWPUyTec2ZdRyJ8JiVl386+6CZkxnndQ==
-----END CERTIFICATE-----
""",
"""
-----BEGIN CERTIFICATE-----
MIIEpDCCAowCCQC43PUTWSADVjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjIxMDE3MjA0NTM0WhcNMjMxMDE3MjA0NTM0WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDV
x1cdEd2ffQTlTXWRiCHGcrlYf4RJnctt9sw/BuHWTLXBu5LhyJSGn5LRszO/NCXK
Z/cmGR7pLj366RtiwL+Qo3nhvDCK7T9xZeNIusM6XMcMK9D/DGCYPqtjQz8NXd9V
ajBBe6nwTDTa+oqX8Mt89foWNkg5Il/lY62u9Dr18LRZ2W9zzYi3Q9/K0CbIX6pM
yVlPIO5rITOR2IsbeyqsO9jufgX5lP4ZKLLBAP1b7usjC4YdvWacjQg/rK5aay1x
jC2HCDgo/4N30QVXzSA9nFfSe6AE/xkStK4819JqOkY5JsJCbef1P3hOOdSLEjbp
xq3MjOs6G6dOgteaAGs10vx7dHxDWETTIiD7BIZ9zRYgOF5bkCaIUO+JfySE1MHD
KBAFLoRuvmRev5Ln5R0MCHpUMSmMNgJqz+RWZV3g/gpYbuWiHgJOwL1393eK50Bg
W7SXQ8EjJj2yXZSH+1gPzN0DRoJZiaBoTPnCL2qUgvwFpW1PJsM5FDyUJFUoK5kK
HLBBSKAPt6ZlSrUe2nBgJv7EF1GK+fTU08LXgW33OpLceGPa0zTShkukQUMtUtZ8
GqhO12ohMzEupIu5Xurthq4VVUrzHUdj1ZZRMhAbfLU36sd03MMyL/xBqTN6dzCa
GDGIPGpYjAllZ5xMRt2kZdv+Kr6oo3u2nLUIsqI7KQIDAQABMA0GCSqGSIb3DQEB
CwUAA4ICAQCB5s43YF35ssf5YONW5iAaifGpi1o0866xfeOybtohFGvQ7V2W34i9
TYBCt8+0hgatMcvZ08f0vqig1i7nrvYcE1hnhL7JNkU8qm0s9ytHZt6j62nB0kd/
uqE2hOEQalTf/2TGPV0CCgiqLyd8lEUQvQeA38wktwUeZpVnErlzHeMR2CvV3K8R
u4vV6SnBcf+TAt56RKYZkPyvZj5llQPo14Glyoo8qZES7Ky1SHmM0GL+baPRBjRW
3KgSt98Wyu4yr9qu21JpnbAnLhBfzfSKjSeCRgFElUE1GIaFGRZ7ypA74dUKeLnb
/VUWrszmUhGaEjV9dpI6x6B/kSpQMtIQqBaKRY2ALUeEujS/rURi4iMDwSU+GkSH
cyEvZKS97OA/dWeXfLXdo4beDBRG93bI4rQnDg5+VdlBOkQSLueb8x6/VThMoC5d
vZiotFQHseljQAdTkNa6tBu6c4XDYPCKB3CfkMYOlCfTS7Acn5G6dxTPKBtLGBnL
nQfYyzuwYkN09+2PVzt6auBHr3To7uoclkxX+hxyvPIwIZ0N6b4tQR1FCAkvg29Q
WIOjZOKGW690ESKCKOnFjUHVO0HpuWnT81URTuY62FXsYdVc2wE4v0E04mEbqQ0P
lY6ZKNA81Lm3YADYtObmK1IUrOPo9BeIaPy0UM08SmN880Vunqa91Q==
-----END CERTIFICATE-----
"""));
// This is a randomly-generated, throwaway certificate that's not actually connected to anything
config.setStorageCaCertificates(List.of("""
-----BEGIN CERTIFICATE-----
MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEL
MAkGA1UECAwCVVMxHjAcBgNVBAoMFVNpZ25hbCBNZXNzZW5nZXIsIExMQzETMBEG
A1UEAwwKc2lnbmFsLm9yZzAeFw0yMDEyMjMyMjQ3NTlaFw0zMDEyMjEyMjQ3NTla
ME8xCzAJBgNVBAYTAnVzMQswCQYDVQQIDAJVUzEeMBwGA1UECgwVU2lnbmFsIE1l
c3NlbmdlciwgTExDMRMwEQYDVQQDDApzaWduYWwub3JnMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCfSLcZNHYqbxSsgWp4JvbPRHjQTrlsrKrgD2q7f/OY6O3Y
/X0QNcNSOJpliN8rmzwslfsrXHO3q1diGRw4xHogUJZ/7NQrHiP/zhN0VTDh49pD
ZpjXVyUbayLS/6qM5arKxBspzEFBb5v8cF6bPr76SO/rpGXiI0j6yJKX6fRiKwID
AQABo1AwTjAdBgNVHQ4EFgQU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwHwYDVR0jBBgw
FoAU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
AQ0FAAOBgQB+5d5+NtzLILfrc9QmJdIO1YeDP64JmFwTER0kEUouRsb9UwknVWZa
y7MTM4NoBV1k0zb5LAk89SIDPr/maW5AsLtEomzjnEiomjoMBUdNe3YCgQReoLnr
R/QaUNbrCjTGYfBsjGbIzmkWPUyTec2ZdRyJ8JiVl386+6CZkxnndQ==
-----END CERTIFICATE-----
""",
"""
-----BEGIN CERTIFICATE-----
MIIEpDCCAowCCQC43PUTWSADVjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjIxMDE3MjA0NTM0WhcNMjMxMDE3MjA0NTM0WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDV
x1cdEd2ffQTlTXWRiCHGcrlYf4RJnctt9sw/BuHWTLXBu5LhyJSGn5LRszO/NCXK
Z/cmGR7pLj366RtiwL+Qo3nhvDCK7T9xZeNIusM6XMcMK9D/DGCYPqtjQz8NXd9V
ajBBe6nwTDTa+oqX8Mt89foWNkg5Il/lY62u9Dr18LRZ2W9zzYi3Q9/K0CbIX6pM
yVlPIO5rITOR2IsbeyqsO9jufgX5lP4ZKLLBAP1b7usjC4YdvWacjQg/rK5aay1x
jC2HCDgo/4N30QVXzSA9nFfSe6AE/xkStK4819JqOkY5JsJCbef1P3hOOdSLEjbp
xq3MjOs6G6dOgteaAGs10vx7dHxDWETTIiD7BIZ9zRYgOF5bkCaIUO+JfySE1MHD
KBAFLoRuvmRev5Ln5R0MCHpUMSmMNgJqz+RWZV3g/gpYbuWiHgJOwL1393eK50Bg
W7SXQ8EjJj2yXZSH+1gPzN0DRoJZiaBoTPnCL2qUgvwFpW1PJsM5FDyUJFUoK5kK
HLBBSKAPt6ZlSrUe2nBgJv7EF1GK+fTU08LXgW33OpLceGPa0zTShkukQUMtUtZ8
GqhO12ohMzEupIu5Xurthq4VVUrzHUdj1ZZRMhAbfLU36sd03MMyL/xBqTN6dzCa
GDGIPGpYjAllZ5xMRt2kZdv+Kr6oo3u2nLUIsqI7KQIDAQABMA0GCSqGSIb3DQEB
CwUAA4ICAQCB5s43YF35ssf5YONW5iAaifGpi1o0866xfeOybtohFGvQ7V2W34i9
TYBCt8+0hgatMcvZ08f0vqig1i7nrvYcE1hnhL7JNkU8qm0s9ytHZt6j62nB0kd/
uqE2hOEQalTf/2TGPV0CCgiqLyd8lEUQvQeA38wktwUeZpVnErlzHeMR2CvV3K8R
u4vV6SnBcf+TAt56RKYZkPyvZj5llQPo14Glyoo8qZES7Ky1SHmM0GL+baPRBjRW
3KgSt98Wyu4yr9qu21JpnbAnLhBfzfSKjSeCRgFElUE1GIaFGRZ7ypA74dUKeLnb
/VUWrszmUhGaEjV9dpI6x6B/kSpQMtIQqBaKRY2ALUeEujS/rURi4iMDwSU+GkSH
cyEvZKS97OA/dWeXfLXdo4beDBRG93bI4rQnDg5+VdlBOkQSLueb8x6/VThMoC5d
vZiotFQHseljQAdTkNa6tBu6c4XDYPCKB3CfkMYOlCfTS7Acn5G6dxTPKBtLGBnL
nQfYyzuwYkN09+2PVzt6auBHr3To7uoclkxX+hxyvPIwIZ0N6b4tQR1FCAkvg29Q
WIOjZOKGW690ESKCKOnFjUHVO0HpuWnT81URTuY62FXsYdVc2wE4v0E04mEbqQ0P
lY6ZKNA81Lm3YADYtObmK1IUrOPo9BeIaPy0UM08SmN880Vunqa91Q==
-----END CERTIFICATE-----
"""));
secureStorageClient = new SecureStorageClient(credentialGenerator, httpExecutor, config);
secureStorageClient = new SecureStorageClient(credentialsGenerator, httpExecutor, config);
}
@AfterEach
@ -115,7 +114,7 @@ class SecureStorageClientTest {
final String username = RandomStringUtils.randomAlphabetic(16);
final String password = RandomStringUtils.randomAlphanumeric(32);
when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password));
when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password));
wireMock.stubFor(delete(urlEqualTo(SecureStorageClient.DELETE_PATH))
.withBasicAuth(username, password)
@ -130,7 +129,7 @@ class SecureStorageClientTest {
final String username = RandomStringUtils.randomAlphabetic(16);
final String password = RandomStringUtils.randomAlphanumeric(32);
when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password));
when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password));
wireMock.stubFor(delete(urlEqualTo(SecureStorageClient.DELETE_PATH))
.withBasicAuth(username, password)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -8,15 +8,18 @@ package org.whispersystems.textsecuregcm.tests.auth;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
class ExternalServiceCredentialsGeneratorTest {
@Test
void testGenerateDerivedUsername() {
ExternalServiceCredentialGenerator generator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32]);
ExternalServiceCredentials credentials = generator.generateFor("+14152222222");
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withUserDerivationKey(new byte[32])
.build();
final ExternalServiceCredentials credentials = generator.generateFor("+14152222222");
assertThat(credentials.username()).isNotEqualTo("+14152222222");
assertThat(credentials.password().startsWith("+14152222222")).isFalse();
@ -24,8 +27,10 @@ class ExternalServiceCredentialsGeneratorTest {
@Test
void testGenerateNoDerivedUsername() {
ExternalServiceCredentialGenerator generator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false);
ExternalServiceCredentials credentials = generator.generateFor("+14152222222");
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.build();
final ExternalServiceCredentials credentials = generator.generateFor("+14152222222");
assertThat(credentials.username()).isEqualTo("+14152222222");
assertThat(credentials.password().startsWith("+14152222222")).isTrue();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -16,21 +16,29 @@ import io.dropwizard.testing.junit5.ResourceExtension;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.controllers.ArtController;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class ArtControllerTest {
private static final ExternalServiceCredentialGenerator artCredentialGenerator = new ExternalServiceCredentialGenerator(
new byte[32], new byte[32], true, false, false);
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = MockHelper.buildMock(
ArtServiceConfiguration.class,
cfg -> {
Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]);
Mockito.when(cfg.getUserAuthenticationTokenUserIdSecret()).thenReturn(new byte[32]);
});
private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION);
private static final RateLimiter rateLimiter = mock(RateLimiter.class);
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
@ -40,7 +48,7 @@ class ArtControllerTest {
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
.setMapper(SystemMapper.getMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new ArtController(rateLimiters, artCredentialGenerator))
.addResource(new ArtController(rateLimiters, artCredentialsGenerator))
.build();
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -26,15 +26,15 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
@ExtendWith(DropwizardExtensionsSupport.class)
class DirectoryControllerTest {
private static final ExternalServiceCredentialGenerator directoryCredentialsGenerator = mock(ExternalServiceCredentialGenerator.class);
private static final ExternalServiceCredentialsGenerator directoryCredentialsGenerator = mock(ExternalServiceCredentialsGenerator.class);
private static final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password");
private static final ResourceExtension resources = ResourceExtension.builder()

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -15,8 +15,9 @@ import java.time.ZoneId;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration;
import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
@ -26,11 +27,12 @@ class DirectoryControllerV2Test {
@Test
void testAuthToken() {
final ExternalServiceCredentialGenerator credentialGenerator = new ExternalServiceCredentialGenerator(
new byte[]{0x1}, new byte[]{0x2}, true, false,
Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC")));
final ExternalServiceCredentialsGenerator credentialsGenerator = DirectoryV2Controller.credentialsGenerator(
new DirectoryV2ClientConfiguration(new byte[]{0x1}, new byte[]{0x2}),
Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC"))
);
final DirectoryV2Controller controller = new DirectoryV2Controller(credentialGenerator);
final DirectoryV2Controller controller = new DirectoryV2Controller(credentialsGenerator);
final Account account = mock(Account.class);
final UUID uuid = UUID.fromString("11111111-1111-1111-1111-111111111111");

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -25,8 +25,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.controllers.PaymentsController;
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntity;
@ -36,7 +36,7 @@ import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
@ExtendWith(DropwizardExtensionsSupport.class)
class PaymentsControllerTest {
private static final ExternalServiceCredentialGenerator paymentsCredentialGenerator = mock(ExternalServiceCredentialGenerator.class);
private static final ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = mock(ExternalServiceCredentialsGenerator.class);
private static final CurrencyConversionManager currencyManager = mock(CurrencyConversionManager.class);
private final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password");
@ -46,13 +46,13 @@ class PaymentsControllerTest {
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new PaymentsController(currencyManager, paymentsCredentialGenerator))
.addResource(new PaymentsController(currencyManager, paymentsCredentialsGenerator))
.build();
@BeforeEach
void setup() {
when(paymentsCredentialGenerator.generateFor(eq(AuthHelper.VALID_UUID.toString()))).thenReturn(validCredentials);
when(paymentsCredentialsGenerator.generateForUuid(eq(AuthHelper.VALID_UUID))).thenReturn(validCredentials);
when(currencyManager.getCurrencyConversions()).thenReturn(Optional.of(
new CurrencyConversionEntityList(List.of(
new CurrencyConversionEntity("FOO", Map.of(

View File

@ -1,11 +1,12 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.tests.controllers;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
@ -17,16 +18,23 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class SecureStorageControllerTest {
private static final ExternalServiceCredentialGenerator storageCredentialGenerator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false);
private static final SecureStorageServiceConfiguration STORAGE_CFG = MockHelper.buildMock(
SecureStorageServiceConfiguration.class,
cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController
.credentialsGenerator(STORAGE_CFG);
private static final ResourceExtension resources = ResourceExtension.builder()
.addProvider(AuthHelper.getAuthFilter())
@ -34,7 +42,7 @@ class SecureStorageControllerTest {
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
.setMapper(SystemMapper.getMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new SecureStorageController(storageCredentialGenerator))
.addResource(new SecureStorageController(STORAGE_CREDENTIAL_GENERATOR))
.build();
@ -60,6 +68,4 @@ class SecureStorageControllerTest {
assertThat(response.getStatus()).isEqualTo(401);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013-2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.tests.util;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
@ -26,10 +25,10 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.HeaderUtils;
public class AuthHelper {
// Static seed to ensure reproducible tests.
@ -201,19 +200,15 @@ public class AuthHelper {
}
public static String getAuthHeader(UUID uuid, long deviceId, String password) {
return getAuthHeader(uuid.toString() + "." + deviceId, password);
return HeaderUtils.basicAuthHeader(uuid.toString() + "." + deviceId, password);
}
public static String getAuthHeader(UUID uuid, String password) {
return getAuthHeader(uuid.toString(), password);
return HeaderUtils.basicAuthHeader(uuid.toString(), password);
}
public static String getProvisioningAuthHeader(String number, String password) {
return getAuthHeader(number, password);
}
private static String getAuthHeader(String identifier, String password) {
return "Basic " + Base64.getEncoder().encodeToString((identifier + ":" + password).getBytes());
return HeaderUtils.basicAuthHeader(number, password);
}
public static String getUnidentifiedAccessHeader(byte[] key) {

View File

@ -0,0 +1,31 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import org.mockito.Mockito;
public final class MockHelper {
private MockHelper() {
// utility class
}
@FunctionalInterface
public interface MockInitializer<T> {
void init(T mock) throws Exception;
}
public static <T> T buildMock(final Class<T> clazz, final MockInitializer<T> initializer) throws RuntimeException {
final T mock = Mockito.mock(clazz);
try {
initializer.init(mock);
} catch (Exception e) {
throw new RuntimeException(e);
}
return mock;
}
}