Moving secret values out of the main configuration file

This commit is contained in:
Sergey Skrobotov
2023-05-17 11:14:04 -07:00
parent 8d1c26d07d
commit 287e2fa89a
57 changed files with 959 additions and 551 deletions

View File

@@ -5,6 +5,7 @@
package org.whispersystems.textsecuregcm;
import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
@@ -81,6 +82,8 @@ import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretStore;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
import org.whispersystems.textsecuregcm.controllers.ArtController;
@@ -236,8 +239,24 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
private static final Logger log = LoggerFactory.getLogger(WhisperServerService.class);
public static final String SECRETS_BUNDLE_FILE_NAME_PROPERTY = "secrets.bundle.filename";
private static final software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider AWSSDK_INSTANCE_PROFILE_CREDENTIALS_PROVIDER =
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create();
@Override
public void initialize(Bootstrap<WhisperServerConfiguration> bootstrap) {
public void initialize(final Bootstrap<WhisperServerConfiguration> bootstrap) {
// `SecretStore` needs to be initialized before Dropwizard reads the main application config file.
final String secretsBundleFileName = requireNonNull(
System.getProperty(SECRETS_BUNDLE_FILE_NAME_PROPERTY),
"Application requires property [%s] to be provided".formatted(SECRETS_BUNDLE_FILE_NAME_PROPERTY));
final SecretStore secretStore = SecretStore.fromYamlFileSecretsBundle(secretsBundleFileName);
SecretsModule.INSTANCE.setSecretStore(secretStore);
// Initializing SystemMapper here because parsing of the main application config happens before `run()` method is called.
SystemMapper.configureMapper(bootstrap.getObjectMapper());
bootstrap.addCommand(new DeleteUserCommand());
bootstrap.addCommand(new CertificateCommand());
bootstrap.addCommand(new ZkParamsCommand());
@@ -289,8 +308,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.lifecycle().manage(new MicrometerRegistryManager(Metrics.globalRegistry));
SystemMapper.configureMapper(environment.getObjectMapper());
HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup =
new HeaderControlledResourceBundleLookup();
ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter(
@@ -300,11 +317,11 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
AWSSDK_INSTANCE_PROFILE_CREDENTIALS_PROVIDER);
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
AWSSDK_INSTANCE_PROFILE_CREDENTIALS_PROVIDER);
AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard()
.withRegion(config.getDynamoDbClientConfiguration().getRegion())
@@ -446,11 +463,11 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getAdminEventLoggingConfiguration().projectId(),
config.getAdminEventLoggingConfiguration().logName());
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator(), config.getStripe().boostDescription(), config.getStripe()
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey().value(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator().value(), config.getStripe().boostDescription(), config.getStripe()
.supportedCurrencies());
BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(),
config.getBraintree().publicKey(), config.getBraintree().privateKey(), config.getBraintree().environment(),
config.getBraintree().publicKey(), config.getBraintree().privateKey().value(), config.getBraintree().environment(),
config.getBraintree().supportedCurrencies(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor);
@@ -506,9 +523,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
deletedAccountsManager, keys, messagesManager, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, secureValueRecovery2Client, clientPresenceManager,
experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials().value());
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster,
apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
@@ -553,7 +570,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getRecaptchaConfiguration().getCredentialConfigurationJson(),
dynamicConfigurationManager);
HttpClient hcaptchaHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
HCaptchaClient hCaptchaClient = new HCaptchaClient(config.getHCaptchaConfiguration().apiKey(), hcaptchaHttpClient, dynamicConfigurationManager);
HCaptchaClient hCaptchaClient = new HCaptchaClient(config.getHCaptchaConfiguration().apiKey().value(), hcaptchaHttpClient, dynamicConfigurationManager);
CaptchaChecker captchaChecker = new CaptchaChecker(List.of(recaptchaClient, hCaptchaClient));
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager, pushChallengeDynamoDb);
@@ -589,10 +606,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
);
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey());
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().getCoinMarketCapApiKey(), config.getPaymentsServiceConfiguration().getCoinMarketCapCurrencyIds());
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().fixerApiKey().value());
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().coinMarketCapApiKey().value(), config.getPaymentsServiceConfiguration().coinMarketCapCurrencyIds());
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient,
cacheCluster, config.getPaymentsServiceConfiguration().getPaymentCurrencies(), Clock.systemUTC());
cacheCluster, config.getPaymentsServiceConfiguration().paymentCurrencies(), Clock.systemUTC());
environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(apnPushNotificationScheduler);
@@ -610,19 +627,19 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
.create(AwsBasicCredentials.create(
config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret()));
config.getCdnConfiguration().accessKey().value(),
config.getCdnConfiguration().accessSecret().value()));
S3Client cdnS3Client = S3Client.builder()
.credentialsProvider(cdnCredentialsProvider)
.region(Region.of(config.getCdnConfiguration().getRegion()))
.region(Region.of(config.getCdnConfiguration().region()))
.build();
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().getRegion(),
config.getCdnConfiguration().getBucket(), config.getCdnConfiguration().getAccessKey());
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().getAccessSecret(),
config.getCdnConfiguration().getRegion());
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().region(),
config.getCdnConfiguration().bucket(), config.getCdnConfiguration().accessKey().value());
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().accessSecret().value(),
config.getCdnConfiguration().region());
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret());
GenericServerSecretParams genericZkSecretParams = new GenericServerSecretParams(config.getGenericZkConfig().serverSecret());
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().serverSecret().value());
GenericServerSecretParams genericZkSecretParams = new GenericServerSecretParams(config.getGenericZkConfig().serverSecret().value());
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams);
ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams);
ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams);
@@ -720,10 +737,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager,
registrationLockVerificationManager, rateLimiters),
new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().accessKey().value(), config.getAwsAttachmentsConfiguration().accessSecret().value(), config.getAwsAttachmentsConfiguration().region(), config.getAwsAttachmentsConfiguration().bucket()),
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().domain(), config.getGcpAttachmentsConfiguration().email(), config.getGcpAttachmentsConfiguration().maxSizeInBytes(), config.getGcpAttachmentsConfiguration().pathPrefix(), config.getGcpAttachmentsConfiguration().rsaSigningKey().value()),
new CallLinkController(rateLimiters, genericZkSecretParams),
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations, genericZkSecretParams, clock),
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().certificate().value(), config.getDeliveryCertificate().ecPrivateKey(), config.getDeliveryCertificate().expiresDays()), zkAuthOperations, genericZkSecretParams, clock),
new ChallengeController(rateLimitChallengeManager),
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
new DirectoryV2Controller(directoryV2CredentialsGenerator),
@@ -735,19 +752,19 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,
config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor),
config.getCdnConfiguration().bucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager),
new RegistrationController(accountsManager, phoneVerificationTokenManager, registrationLockVerificationManager,
rateLimiters),
new RemoteConfigController(remoteConfigsManager, adminEventLogger,
config.getRemoteConfigConfiguration().getAuthorizedTokens(),
config.getRemoteConfigConfiguration().getGlobalConfig()),
config.getRemoteConfigConfiguration().authorizedTokens().value(),
config.getRemoteConfigConfiguration().globalConfig()),
new SecureBackupController(backupCredentialsGenerator, accountsManager),
new SecureStorageController(storageCredentialsGenerator),
new SecureValueRecovery2Controller(svr2CredentialsGenerator, accountsManager, config.getSvr2Configuration()),
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(),
config.getCdnConfiguration().getBucket()),
new StickerController(rateLimiters, config.getCdnConfiguration().accessKey().value(),
config.getCdnConfiguration().accessSecret().value(), config.getCdnConfiguration().region(),
config.getCdnConfiguration().bucket()),
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,
accountsManager, clock)

View File

@@ -10,6 +10,7 @@ import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256ToHexString
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmacHexStringsEqual;
import com.google.common.annotations.VisibleForTesting;
import java.time.Clock;
import java.time.Instant;
import java.util.Optional;
@@ -17,6 +18,7 @@ import java.util.UUID;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ExternalServiceCredentialsGenerator {
@@ -40,6 +42,12 @@ public class ExternalServiceCredentialsGenerator {
private final int derivedUsernameTruncateLength;
public static ExternalServiceCredentialsGenerator.Builder builder(final SecretBytes key) {
return builder(key.value());
}
@VisibleForTesting
public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key);
}
@@ -240,6 +248,10 @@ public class ExternalServiceCredentialsGenerator {
this.key = requireNonNull(key);
}
public Builder withUserDerivationKey(final SecretBytes userDerivationKey) {
return withUserDerivationKey(userDerivationKey.value());
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey;

View File

@@ -5,10 +5,11 @@
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
public record AdminEventLoggingConfiguration(
@NotEmpty String credentials,
@NotBlank String credentials,
@NotEmpty String projectId,
@NotEmpty String logName) {
}

View File

@@ -1,51 +1,17 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class ApnConfiguration {
@NotEmpty
@JsonProperty
private String teamId;
@NotEmpty
@JsonProperty
private String keyId;
@NotEmpty
@JsonProperty
private String signingKey;
@NotEmpty
@JsonProperty
private String bundleId;
@JsonProperty
private boolean sandbox = false;
public String getTeamId() {
return teamId;
}
public String getKeyId() {
return keyId;
}
public String getSigningKey() {
return signingKey;
}
public String getBundleId() {
return bundleId;
}
public boolean isSandboxEnabled() {
return sandbox;
}
public record ApnConfiguration(@NotBlank String teamId,
@NotBlank String keyId,
@NotNull SecretString signingKey,
@NotBlank String bundleId,
boolean sandbox) {
}

View File

@@ -5,35 +5,17 @@
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
import java.time.Duration;
import java.util.HexFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class ArtServiceConfiguration {
@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@NotEmpty
@JsonProperty
private String userAuthenticationTokenUserIdSecret;
@JsonProperty
@NotNull
private Duration tokenExpiration = Duration.ofDays(1);
public byte[] getUserAuthenticationTokenSharedSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret);
}
public byte[] getUserAuthenticationTokenUserIdSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenUserIdSecret);
}
public Duration getTokenExpiration() {
return tokenExpiration;
public record ArtServiceConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretBytes userAuthenticationTokenUserIdSecret,
@NotNull Duration tokenExpiration) {
public ArtServiceConfiguration {
tokenExpiration = firstNonNull(tokenExpiration, Duration.ofDays(1));
}
}

View File

@@ -1,43 +1,15 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class AwsAttachmentsConfiguration {
@NotEmpty
@JsonProperty
private String accessKey;
@NotEmpty
@JsonProperty
private String accessSecret;
@NotEmpty
@JsonProperty
private String bucket;
@NotEmpty
@JsonProperty
private String region;
public String getAccessKey() {
return accessKey;
}
public String getAccessSecret() {
return accessSecret;
}
public String getBucket() {
return bucket;
}
public String getRegion() {
return region;
}
public record AwsAttachmentsConfiguration(@NotNull SecretString accessKey,
@NotNull SecretString accessSecret,
@NotBlank String bucket,
@NotBlank String region) {
}

View File

@@ -11,6 +11,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
/**
* @param merchantId the Braintree merchant ID
@@ -24,7 +25,7 @@ import javax.validation.constraints.NotNull;
*/
public record BraintreeConfiguration(@NotBlank String merchantId,
@NotBlank String publicKey,
@NotBlank String privateKey,
@NotNull SecretString privateKey,
@NotBlank String environment,
@NotEmpty Set<@NotBlank String> supportedCurrencies,
@NotBlank String graphqlUrl,

View File

@@ -1,44 +1,16 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotEmpty;
public class CdnConfiguration {
@NotEmpty
@JsonProperty
private String accessKey;
@NotEmpty
@JsonProperty
private String accessSecret;
@NotEmpty
@JsonProperty
private String bucket;
@NotEmpty
@JsonProperty
private String region;
public String getAccessKey() {
return accessKey;
}
public String getAccessSecret() {
return accessSecret;
}
public String getBucket() {
return bucket;
}
public String getRegion() {
return region;
}
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record CdnConfiguration(@NotNull SecretString accessKey,
@NotNull SecretString accessSecret,
@NotBlank String bucket,
@NotBlank String 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
*/
@@ -7,16 +7,17 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.micrometer.datadog.DatadogConfig;
import java.time.Duration;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class DatadogConfiguration implements DatadogConfig {
@JsonProperty
@NotBlank
private String apiKey;
@NotNull
private SecretString apiKey;
@JsonProperty
@NotNull
@@ -32,7 +33,7 @@ public class DatadogConfiguration implements DatadogConfig {
@Override
public String apiKey() {
return apiKey;
return apiKey.value();
}
@Override

View File

@@ -1,11 +1,12 @@
/*
* Copyright 2013-2023 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public record DirectoryV2ClientConfiguration(@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret,
@ExactlySize({32}) byte[] userIdTokenSharedSecret) {
public record DirectoryV2ClientConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@ExactlySize(32) SecretBytes userIdTokenSharedSecret) {
}

View File

@@ -1,11 +1,12 @@
/*
* Copyright 2013-2022 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record FcmConfiguration(@NotBlank String credentials) {
public record FcmConfiguration(@NotNull SecretString credentials) {
}

View File

@@ -1,57 +1,22 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.util.Strings;
import io.dropwizard.validation.ValidationMethod;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
public class GcpAttachmentsConfiguration {
@NotEmpty
@JsonProperty
private String domain;
@NotEmpty
@JsonProperty
private String email;
@JsonProperty
@Min(1)
private int maxSizeInBytes;
@JsonProperty
private String pathPrefix;
@NotEmpty
@JsonProperty
private String rsaSigningKey;
public String getDomain() {
return domain;
}
public String getEmail() {
return email;
}
public int getMaxSizeInBytes() {
return maxSizeInBytes;
}
public String getPathPrefix() {
return pathPrefix;
}
public String getRsaSigningKey() {
return rsaSigningKey;
}
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record GcpAttachmentsConfiguration(@NotBlank String domain,
@NotBlank String email,
@Min(1) int maxSizeInBytes,
String pathPrefix,
@NotNull SecretString rsaSigningKey) {
@SuppressWarnings("unused")
@ValidationMethod(message = "pathPrefix must be empty or start with /")
public boolean isPathPrefixValid() {

View File

@@ -1,15 +1,12 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public record GenericZkConfig (
@JsonProperty
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
byte[] serverSecret
) {}
public record GenericZkConfig(@NotNull SecretBytes serverSecret) {
}

View File

@@ -1,11 +1,12 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record HCaptchaConfiguration(@NotBlank String apiKey) {
public record HCaptchaConfiguration(@NotNull SecretString apiKey) {
}

View File

@@ -1,56 +1,21 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class PaymentsServiceConfiguration {
@NotEmpty
@JsonProperty
private String userAuthenticationTokenSharedSecret;
@NotBlank
@JsonProperty
private String coinMarketCapApiKey;
@JsonProperty
@NotEmpty
private Map<@NotBlank String, Integer> coinMarketCapCurrencyIds;
@NotEmpty
@JsonProperty
private String fixerApiKey;
@NotEmpty
@JsonProperty
private List<String> paymentCurrencies;
public byte[] getUserAuthenticationTokenSharedSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret);
}
public String getCoinMarketCapApiKey() {
return coinMarketCapApiKey;
}
public Map<String, Integer> getCoinMarketCapCurrencyIds() {
return coinMarketCapCurrencyIds;
}
public String getFixerApiKey() {
return fixerApiKey;
}
public List<String> getPaymentCurrencies() {
return paymentCurrencies;
}
public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretString coinMarketCapApiKey,
@NotNull SecretString fixerApiKey,
@NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds,
@NotEmpty List<String> paymentCurrencies) {
}

View File

@@ -1,33 +1,14 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretStringList;
public class RemoteConfigConfiguration {
@JsonProperty
@NotNull
private List<String> authorizedTokens = new LinkedList<>();
@NotNull
@JsonProperty
private Map<String, String> globalConfig = new HashMap<>();
public List<String> getAuthorizedTokens() {
return authorizedTokens;
}
public Map<String, String> getGlobalConfig() {
return globalConfig;
}
public record RemoteConfigConfiguration(@NotNull SecretStringList authorizedTokens,
@NotNull Map<String, String> globalConfig) {
}

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,18 +7,18 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.util.HexFormat;
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.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class SecureBackupServiceConfiguration {
@NotEmpty
@NotNull
@JsonProperty
private String userAuthenticationTokenSharedSecret;
private SecretBytes userAuthenticationTokenSharedSecret;
@NotBlank
@JsonProperty
@@ -38,8 +38,8 @@ public class SecureBackupServiceConfiguration {
@JsonProperty
private RetryConfiguration retry = new RetryConfiguration();
public byte[] getUserAuthenticationTokenSharedSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret);
public SecretBytes userAuthenticationTokenSharedSecret() {
return userAuthenticationTokenSharedSecret;
}
@VisibleForTesting

View File

@@ -5,18 +5,18 @@
package org.whispersystems.textsecuregcm.configuration;
import java.util.HexFormat;
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.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticationTokenSharedSecret,
public record SecureStorageServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret,
@NotBlank String uri,
@NotEmpty List<@NotBlank String> storageCaCertificates,
@Valid CircuitBreakerConfiguration circuitBreaker,
@Valid RetryConfiguration retry) {
public SecureStorageServiceConfiguration {
if (circuitBreaker == null) {
circuitBreaker = new CircuitBreakerConfiguration();
@@ -25,8 +25,4 @@ public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticat
retry = new RetryConfiguration();
}
}
public byte[] decodeUserAuthenticationTokenSharedSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret);
}
}

View File

@@ -9,13 +9,14 @@ import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public record SecureValueRecovery2Configuration(
boolean enabled,
@NotBlank String uri,
@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret,
@ExactlySize({32}) byte[] userIdTokenSharedSecret,
@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@ExactlySize(32) SecretBytes userIdTokenSharedSecret,
@NotEmpty List<@NotBlank String> svrCaCertificates,
@NotNull @Valid CircuitBreakerConfiguration circuitBreaker,
@NotNull @Valid RetryConfiguration retry) {

View File

@@ -8,10 +8,12 @@ package org.whispersystems.textsecuregcm.configuration;
import java.util.Set;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public record StripeConfiguration(@NotBlank String apiKey,
@NotEmpty byte[] idempotencyKeyGenerator,
public record StripeConfiguration(@NotNull SecretString apiKey,
@NotNull SecretBytes idempotencyKeyGenerator,
@NotBlank String boostDescription,
@NotEmpty Set<@NotBlank String> supportedCurrencies) {
}

View File

@@ -1,48 +1,21 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import javax.validation.constraints.NotNull;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class UnidentifiedDeliveryConfiguration {
@JsonProperty
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
private byte[] certificate;
@JsonProperty
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
@Size(min = 32, max = 32)
private byte[] privateKey;
@NotNull
private int expiresDays;
public byte[] getCertificate() {
return certificate;
}
public ECPrivateKey getPrivateKey() throws InvalidKeyException {
return Curve.decodePrivatePoint(privateKey);
}
public int getExpiresDays() {
return expiresDays;
public record UnidentifiedDeliveryConfiguration(@NotNull SecretBytes certificate,
@ExactlySize(32) SecretBytes privateKey,
int expiresDays) {
public ECPrivateKey ecPrivateKey() throws InvalidKeyException {
return Curve.decodePrivatePoint(privateKey.value());
}
}

View File

@@ -1,36 +1,14 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ZkConfig {
@JsonProperty
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
private byte[] serverSecret;
@JsonProperty
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
private byte[] serverPublic;
public byte[] getServerSecret() {
return serverSecret;
}
public byte[] getServerPublic() {
return serverPublic;
}
public record ZkConfig(@NotNull SecretBytes serverSecret,
@NotEmpty byte[] serverPublic) {
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import static java.util.Objects.requireNonNull;
import java.lang.annotation.Annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public abstract class BaseSecretValidator<A extends Annotation, T, S extends Secret<? extends T>> implements ConstraintValidator<A, S> {
private final ConstraintValidator<A, T> validator;
protected BaseSecretValidator(final ConstraintValidator<A, T> validator) {
this.validator = requireNonNull(validator);
}
@Override
public void initialize(final A constraintAnnotation) {
validator.initialize(constraintAnnotation);
}
@Override
public boolean isValid(final S value, final ConstraintValidatorContext context) {
return validator.isValid(value.value(), context);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
public class Secret<T> {
private final T value;
public Secret(final T value) {
this.value = value;
}
public T value() {
return value;
}
@Override
public String toString() {
return "[REDACTED]";
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import org.apache.commons.lang3.Validate;
public class SecretBytes extends Secret<byte[]> {
public SecretBytes(final byte[] value) {
super(requireNotEmpty(value));
}
private static byte[] requireNotEmpty(final byte[] value) {
Validate.isTrue(value.length > 0, "SecretBytes value must not be empty");
return value;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import org.hibernate.validator.internal.constraintvalidators.bv.notempty.NotEmptyValidatorForCollection;
public class SecretBytesList extends Secret<List<byte[]>> {
@SuppressWarnings("rawtypes")
public static class ValidatorNotEmpty extends BaseSecretValidator<NotEmpty, Collection, SecretBytesList> {
public ValidatorNotEmpty() {
super(new NotEmptyValidatorForCollection());
}
}
public SecretBytesList(final List<byte[]> value) {
super(ImmutableList.copyOf(value));
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.whispersystems.textsecuregcm.util.SystemMapper;
public class SecretStore {
private final Map<String, Secret<?>> secrets;
public static SecretStore fromYamlFileSecretsBundle(final String filename) {
try {
@SuppressWarnings("unchecked")
final Map<String, Object> secretsBundle = SystemMapper.yamlMapper().readValue(new File(filename), Map.class);
return fromSecretsBundle(secretsBundle);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to parse YAML file [%s]".formatted(filename), e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public SecretStore(final Map<String, Secret<?>> secrets) {
this.secrets = Map.copyOf(secrets);
}
public SecretString secretString(final String reference) {
return fromStore(reference, SecretString.class);
}
public SecretBytes secretBytesFromBase64String(final String reference) {
final SecretString secret = fromStore(reference, SecretString.class);
return new SecretBytes(decodeBase64(secret.value()));
}
public SecretStringList secretStringList(final String reference) {
return fromStore(reference, SecretStringList.class);
}
public SecretBytesList secretBytesListFromBase64Strings(final String reference) {
final List<String> secrets = secretStringList(reference).value();
final List<byte[]> byteSecrets = secrets.stream().map(SecretStore::decodeBase64).toList();
return new SecretBytesList(byteSecrets);
}
private <T extends Secret<?>> T fromStore(final String name, final Class<T> expected) {
final Secret<?> secret = secrets.get(name);
if (secret == null) {
throw new IllegalArgumentException("Secret [%s] is not present in the secrets bundle".formatted(name));
}
if (!expected.isInstance(secret)) {
throw new IllegalArgumentException("Secret [%s] is of type [%s] but caller expects type [%s]".formatted(
name, secret.getClass().getSimpleName(), expected.getSimpleName()));
}
return expected.cast(secret);
}
@VisibleForTesting
public static SecretStore fromYamlStringSecretsBundle(final String secretsBundleYaml) {
try {
@SuppressWarnings("unchecked")
final Map<String, Object> secretsBundle = SystemMapper.yamlMapper().readValue(secretsBundleYaml, Map.class);
return fromSecretsBundle(secretsBundle);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to parse JSON", e);
}
}
private static SecretStore fromSecretsBundle(final Map<String, Object> secretsBundle) {
final Map<String, Secret<?>> store = new HashMap<>();
secretsBundle.forEach((k, v) -> {
if (v instanceof final String str) {
store.put(k, new SecretString(str));
return;
}
if (v instanceof final List<?> list) {
final List<String> secrets = list.stream().map(o -> {
if (o instanceof final String s) {
return s;
}
throw new IllegalArgumentException("Secrets bundle JSON object is only supposed to have values of types String and list of Strings");
}).toList();
store.put(k, new SecretStringList(secrets));
return;
}
throw new IllegalArgumentException("Secrets bundle JSON object is only supposed to have values of types String and list of Strings");
});
return new SecretStore(store);
}
private static byte[] decodeBase64(final String str) {
return Base64.getDecoder().decode(str);
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import org.apache.commons.lang3.Validate;
public class SecretString extends Secret<String> {
public SecretString(final String value) {
super(Validate.notBlank(value, "SecretString value must not be blank"));
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import org.hibernate.validator.internal.constraintvalidators.bv.notempty.NotEmptyValidatorForCollection;
public class SecretStringList extends Secret<List<String>> {
@SuppressWarnings("rawtypes")
public static class ValidatorNotEmpty extends BaseSecretValidator<NotEmpty, Collection, SecretStringList> {
public ValidatorNotEmpty() {
super(new NotEmptyValidatorForCollection());
}
}
public SecretStringList(final List<String> value) {
super(ImmutableList.copyOf(value));
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import static java.util.Objects.requireNonNull;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
public class SecretsModule extends SimpleModule {
public static final SecretsModule INSTANCE = new SecretsModule();
public static final String PREFIX = "secret://";
private final AtomicReference<SecretStore> secretStoreHolder = new AtomicReference<>(null);
private SecretsModule() {
addDeserializer(SecretString.class, createDeserializer(SecretStore::secretString));
addDeserializer(SecretBytes.class, createDeserializer(SecretStore::secretBytesFromBase64String));
addDeserializer(SecretStringList.class, createDeserializer(SecretStore::secretStringList));
addDeserializer(SecretBytesList.class, createDeserializer(SecretStore::secretBytesListFromBase64Strings));
}
public void setSecretStore(final SecretStore secretStore) {
this.secretStoreHolder.set(requireNonNull(secretStore));
}
private <T> JsonDeserializer<T> createDeserializer(final BiFunction<SecretStore, String, T> constructor) {
return new JsonDeserializer<>() {
@Override
public T deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JacksonException {
final SecretStore secretStore = secretStoreHolder.get();
if (secretStore == null) {
throw new IllegalStateException(
"An instance of a SecretStore must be set for the SecretsModule via setSecretStore() method");
}
final String reference = p.getValueAsString();
if (!reference.startsWith(PREFIX) || reference.length() <= PREFIX.length()) {
throw new IllegalArgumentException(
"Value of a secret field must start with a [%s] prefix and refer to an entry in a secrets bundle".formatted(PREFIX));
}
return constructor.apply(secretStore, reference.substring(PREFIX.length()));
}
};
}
}

View File

@@ -27,15 +27,15 @@ public class ArtController {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final ArtServiceConfiguration cfg) {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret())
.builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userAuthenticationTokenUserIdSecret())
.prependUsername(false)
.truncateSignature(false)
.build();
}
public ArtController(RateLimiters rateLimiters,
ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) {
public ArtController(final RateLimiters rateLimiters,
final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) {
this.artServiceCredentialsGenerator = artServiceCredentialsGenerator;
this.rateLimiters = rateLimiters;
}
@@ -44,7 +44,7 @@ public class ArtController {
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth)
public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth)
throws RateLimitExceededException {
final UUID uuid = auth.getAccount().getUuid();
rateLimiters.getArtPackLimiter().validate(uuid);

View File

@@ -41,7 +41,7 @@ public class DirectoryV2Controller {
return credentialsGenerator(cfg, Clock.systemUTC());
}
public DirectoryV2Controller(ExternalServiceCredentialsGenerator userTokenGenerator) {
public DirectoryV2Controller(final ExternalServiceCredentialsGenerator userTokenGenerator) {
this.directoryServiceTokenGenerator = userTokenGenerator;
}
@@ -49,7 +49,7 @@ public class DirectoryV2Controller {
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthToken(@Auth AuthenticatedAccount auth) {
public Response getAuthToken(final @Auth AuthenticatedAccount auth) {
final UUID uuid = auth.getAccount().getUuid();
final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid);
return Response.ok().entity(credentials).build();

View File

@@ -29,7 +29,7 @@ public class PaymentsController {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final PaymentsServiceConfiguration cfg) {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.builder(cfg.userAuthenticationTokenSharedSecret())
.prependUsername(true)
.build();
}

View File

@@ -28,8 +28,8 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsSelector;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsSelector;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.entities.AuthCheckRequest;
import org.whispersystems.textsecuregcm.entities.AuthCheckResponse;
@@ -56,7 +56,7 @@ public class SecureBackupController {
final SecureBackupServiceConfiguration cfg,
final Clock clock) {
return ExternalServiceCredentialsGenerator
.builder(cfg.getUserAuthenticationTokenSharedSecret())
.builder(cfg.userAuthenticationTokenSharedSecret())
.prependUsername(true)
.withClock(clock)
.build();

View File

@@ -25,7 +25,7 @@ public class SecureStorageController {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) {
return ExternalServiceCredentialsGenerator
.builder(cfg.decodeUserAuthenticationTokenSharedSecret())
.builder(cfg.userAuthenticationTokenSharedSecret())
.prependUsername(true)
.build();
}

View File

@@ -10,6 +10,11 @@ import io.dropwizard.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.Clock;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
@@ -31,14 +36,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.UUIDUtil;
import java.time.Clock;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Path("/v2/backup")
@Tag(name = "Secure Value Recovery")
@@ -54,7 +51,7 @@ public class SecureValueRecovery2Controller {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureValueRecovery2Configuration cfg, final Clock clock) {
return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userIdTokenSharedSecret())
.withUserDerivationKey(cfg.userIdTokenSharedSecret().value())
.prependUsername(false)
.withDerivedUsernameTruncateLength(16)
.withClock(clock)

View File

@@ -22,17 +22,27 @@ import io.dropwizard.logging.filter.LevelFilterFactory;
import io.dropwizard.logging.layout.LayoutFactory;
import java.time.Duration;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import net.logstash.logback.appender.LogstashTcpSocketAppender;
import net.logstash.logback.encoder.LogstashEncoder;
import org.whispersystems.textsecuregcm.WhisperServerVersion;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.util.HostnameUtil;
@JsonTypeName("logstashtcpsocket")
public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<ILoggingEvent> {
@JsonProperty
private String destination;
@JsonProperty
private Duration keepAlive = Duration.ofSeconds(20);
private String apiKey;
@JsonProperty
@NotNull
private SecretString apiKey;
@JsonProperty
private String environment;
@JsonProperty
@@ -47,8 +57,7 @@ public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<IL
}
@JsonProperty
@NotEmpty
public String getApiKey() {
public SecretString getApiKey() {
return apiKey;
}
@@ -84,7 +93,7 @@ public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<IL
encoder.setCustomFields(customFieldsNode.toString());
final LayoutWrappingEncoder<ILoggingEvent> prefix = new LayoutWrappingEncoder<>();
final PatternLayout layout = new PatternLayout();
layout.setPattern(String.format("%s ", apiKey));
layout.setPattern(String.format("%s ", apiKey.value()));
prefix.setLayout(layout);
encoder.setPrefix(prefix);
appender.setEncoder(encoder);

View File

@@ -1,3 +1,8 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
/*
* This is derived from Coursera's dropwizard datadog reporter.
* https://github.com/coursera/metrics-datadog
@@ -10,6 +15,7 @@ import com.codahale.metrics.ScheduledReporter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.metrics.BaseReporterFactory;
import io.dropwizard.util.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@@ -20,8 +26,9 @@ import org.coursera.metrics.datadog.DatadogReporter.Expansion;
import org.coursera.metrics.datadog.DefaultMetricNameFormatterFactory;
import org.coursera.metrics.datadog.DynamicTagsCallbackFactory;
import org.coursera.metrics.datadog.MetricNameFormatterFactory;
import org.coursera.metrics.datadog.transport.AbstractTransportFactory;
import org.coursera.metrics.datadog.transport.HttpTransport;
import org.whispersystems.textsecuregcm.WhisperServerVersion;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.util.HostnameUtil;
@JsonTypeName("signal-datadog")
@@ -44,8 +51,8 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
@Valid
@NotNull
@JsonProperty
private AbstractTransportFactory transport = null;
@JsonProperty("transport")
private HttpTransportConfig httpTransportConfig;
private static final EnumSet<Expansion> EXPANSIONS = EnumSet.of(
Expansion.COUNT,
@@ -59,7 +66,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
Expansion.P999
);
public ScheduledReporter build(MetricRegistry registry) {
public ScheduledReporter build(final MetricRegistry registry) {
final List<String> tagsWithVersion;
{
@@ -74,7 +81,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
}
return DatadogReporter.forRegistry(registry)
.withTransport(transport.build())
.withTransport(httpTransportConfig.httpTransport())
.withHost(HostnameUtil.getLocalHostname())
.withTags(tagsWithVersion)
.withPrefix(prefix)
@@ -86,4 +93,26 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
.convertRatesTo(getRateUnit())
.build();
}
public static class HttpTransportConfig {
@JsonProperty
@NotNull
private SecretString apiKey;
@JsonProperty
private Duration connectTimeout = Duration.seconds(5);
@JsonProperty
private Duration socketTimeout = Duration.seconds(5);
public HttpTransport httpTransport() {
return new HttpTransport.Builder()
.withApiKey(apiKey.value())
.withConnectTimeout((int) connectTimeout.toMilliseconds())
.withSocketTimeout((int) socketTimeout.toMilliseconds())
.build();
}
}
}

View File

@@ -1,9 +1,11 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.push;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.eatthepath.pushy.apns.ApnsClient;
import com.eatthepath.pushy.apns.ApnsClientBuilder;
import com.eatthepath.pushy.apns.DeliveryPriority;
@@ -13,6 +15,8 @@ import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
@@ -21,12 +25,8 @@ import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class APNSender implements Managed, PushNotificationSender {
private final ExecutorService executor;
@@ -61,12 +61,12 @@ public class APNSender implements Managed, PushNotificationSender {
throws IOException, NoSuchAlgorithmException, InvalidKeyException
{
this.executor = executor;
this.bundleId = configuration.getBundleId();
this.bundleId = configuration.bundleId();
this.apnsClient = new ApnsClientBuilder().setSigningKey(
ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.getSigningKey().getBytes()),
configuration.getTeamId(), configuration.getKeyId()))
ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.signingKey().value().getBytes()),
configuration.teamId(), configuration.keyId()))
.setTrustedServerCertificateChain(getClass().getResourceAsStream(APNS_CA_FILENAME))
.setApnsServer(configuration.isSandboxEnabled() ? ApnsClientBuilder.DEVELOPMENT_APNS_HOST : ApnsClientBuilder.PRODUCTION_APNS_HOST)
.setApnsServer(configuration.sandbox() ? ApnsClientBuilder.DEVELOPMENT_APNS_HOST : ApnsClientBuilder.PRODUCTION_APNS_HOST)
.build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -25,12 +25,12 @@ import javax.validation.Payload;
ExactlySizeValidatorForString.class,
ExactlySizeValidatorForArraysOfByte.class,
ExactlySizeValidatorForCollection.class,
ExactlySizeValidatorForSecretBytes.class,
})
@Documented
public @interface ExactlySize {
String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize." +
"message}";
String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize.message}";
Class<?>[] groups() default { };

View File

@@ -0,0 +1,15 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.util;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ExactlySizeValidatorForSecretBytes extends ExactlySizeValidator<SecretBytes> {
@Override
protected int size(final SecretBytes value) {
return value == null ? 0 : value.value().length;
}
}

View File

@@ -13,6 +13,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.annotation.Nonnull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule;
public class SystemMapper {
@@ -37,6 +38,7 @@ public class SystemMapper {
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.registerModules(
SecretsModule.INSTANCE,
new JavaTimeModule(),
new Jdk8Module());
}