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

@ -0,0 +1,86 @@
datadog.apiKey: unset
stripe.apiKey: unset
stripe.idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
braintree.privateKey: unset
directoryV2.client.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
svr2.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
svr2.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
awsAttachments.accessKey: test
awsAttachments.accessSecret: test
gcpAttachments.rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
apn.signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
fcm.credentials: |
{ "json": true }
cdn.accessKey: test # AWS Access Key ID
cdn.accessSecret: test # AWS Access Secret
unidentifiedDelivery.certificate: ABCD1234
unidentifiedDelivery.privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
hCaptcha.apiKey: unset
storageService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
backupService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
zkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
genericZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
remoteConfig.authorizedTokens:
- token1 # 1st authorized token
- token2 # 2nd authorized token
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
paymentsService.fixerApiKey: unset
paymentsService.coinMarketCapApiKey: unset
artService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret not shared with any external service, but used in ArtController
artService.userAuthenticationTokenUserIdSecret: AAAAAAAAAAA= # base64-encoded secret to obscure user phone numbers from Sticker Creator
currentReportingKey.secret: AAAAAAAAAAA=
currentReportingKey.salt: AAAAAAAAAAA=

View File

@ -3,16 +3,53 @@
# `unset` values will need to be set to work properly.
# Most other values are technically valid for a local/demonstration environment, but are probably not production-ready.
logging:
level: INFO
appenders:
- type: console
threshold: ALL
timeZone: UTC
target: stdout
- type: logstashtcpsocket
destination: example.com:10516
apiKey: secret://datadog.apiKey
environment: staging
metrics:
reporters:
- type: signal-datadog
frequency: 10 seconds
tags:
- "env:staging"
- "service:chat"
transport:
apiKey: secret://datadog.apiKey
excludesAttributes:
- m1_rate
- m5_rate
- m15_rate
- mean_rate
- stddev
useRegexFilters: true
excludes:
- ^.+\.total$
- ^.+\.request\.filtering$
- ^.+\.response\.filtering$
- ^executor\..+$
- ^lettuce\..+$
reportOnStop: true
adminEventLoggingConfiguration:
credentials: |
Some credentials text
blah blah blah
{
"key": "value"
}
projectId: some-project-id
logName: some-log-name
stripe:
apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
apiKey: secret://stripe.apiKey
idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator
boostDescription: >
Example
supportedCurrencies:
@ -24,7 +61,7 @@ stripe:
braintree:
merchantId: unset
publicKey: unset
privateKey: unset
privateKey: secret://braintree.privateKey
environment: unset
graphqlUrl: unset
merchantAccounts:
@ -104,14 +141,14 @@ rateLimitersCluster: # Redis server configuration for rate limiters cluster
directoryV2:
client: # Configuration for interfacing with Contact Discovery Service v2 cluster
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret
svr2:
enabled: false
uri: svr2.example.com
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret
svrCaCertificates:
- |
-----BEGIN CERTIFICATE-----
@ -146,8 +183,8 @@ metricsCluster:
configurationUri: redis://redis.example.com:6379/
awsAttachments: # AWS S3 configuration
accessKey: test
accessSecret: test
accessKey: secret://awsAttachments.accessKey
accessSecret: secret://awsAttachments.accessSecret
bucket: aws-attachments
region: us-west-2
@ -156,35 +193,7 @@ gcpAttachments: # GCP Storage configuration
email: user@example.cocm
maxSizeInBytes: 1024
pathPrefix:
rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
rsaSigningKey: secret://gcpAttachments.rsaSigningKey
accountDatabaseCrawler:
chunkSize: 10 # accounts per run
@ -194,31 +203,24 @@ apn: # Apple Push Notifications configuration
bundleId: com.example.textsecuregcm
keyId: unset
teamId: unset
signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
signingKey: secret://apn.signingKey
fcm: # FCM configuration
credentials: |
{ "json": true }
credentials: secret://fcm.credentials
cdn:
accessKey: test # AWS Access Key ID
accessSecret: test # AWS Access Secret
accessKey: secret://cdn.accessKey
accessSecret: secret://cdn.accessSecret
bucket: cdn # S3 Bucket name
region: us-west-2 # AWS region
datadog:
apiKey: unset
apiKey: secret://datadog.apiKey
environment: dev
unidentifiedDelivery:
certificate: ABCD1234
privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
certificate: secret://unidentifiedDelivery.certificate
privateKey: secret://unidentifiedDelivery.privateKey
expiresDays: 7
recaptcha:
@ -226,11 +228,11 @@ recaptcha:
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
hCaptcha:
apiKey: unset
apiKey: secret://hCaptcha.apiKey
storageService:
uri: storage.example.com
userAuthenticationTokenSharedSecret: 00000f
userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret
storageCaCertificates:
- |
-----BEGIN CERTIFICATE-----
@ -257,7 +259,7 @@ storageService:
backupService:
uri: backup.example.com
userAuthenticationTokenSharedSecret: 00000f
userAuthenticationTokenSharedSecret: secret://backupService.userAuthenticationTokenSharedSecret
backupCaCertificates:
- |
-----BEGIN CERTIFICATE-----
@ -284,10 +286,10 @@ backupService:
zkConfig:
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
serverSecret: secret://zkConfig.serverSecret
genericZkConfig:
serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
serverSecret: secret://genericZkConfig.serverSecret
appConfig:
application: example
@ -295,18 +297,14 @@ appConfig:
configuration: example
remoteConfig:
authorizedTokens:
- # 1st authorized token
- # 2nd authorized token
- # ...
- # Nth authorized token
authorizedTokens: secret://remoteConfig.authorizedTokens
globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE
paymentsService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
fixerApiKey: unset
coinMarketCapApiKey: unset
userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
fixerApiKey: secret://paymentsService.fixerApiKey
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
coinMarketCapCurrencyIds:
MOB: 7878
paymentCurrencies:
@ -314,8 +312,8 @@ paymentsService:
- MOB
artService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret not shared with any external service, but used in ArtController
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret to obscure user phone numbers from Sticker Creator
userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges:
badges:

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());
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd"
version="2.0">
<constraint-mapping>META-INF/validation/constraints-custom.xml</constraint-mapping>
</validation-config>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mappings
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd"
version="2.0">
<constraint-definition annotation="javax.validation.constraints.NotEmpty">
<validated-by include-existing-validators="true">
<value>org.whispersystems.textsecuregcm.configuration.secrets.SecretStringList$ValidatorNotEmpty</value>
<value>org.whispersystems.textsecuregcm.configuration.secrets.SecretBytesList$ValidatorNotEmpty</value>
</validated-by>
</constraint-definition>
</constraint-mappings>

View File

@ -15,19 +15,27 @@ import java.util.Arrays;
*/
public class CheckServiceConfigurations {
private static final String SECRETS_BUNDLE_FILENAME = "sample-secrets-bundle.yml";
private void checkConfiguration(final File configDirectory) {
final File[] configFiles = configDirectory.listFiles(f ->
!f.isDirectory()
&& f.getPath().endsWith(".yml"));
&& f.getPath().endsWith(".yml")
&& !f.getPath().endsWith(SECRETS_BUNDLE_FILENAME));
if (configFiles == null || configFiles.length == 0) {
throw new IllegalArgumentException("No .yml configuration files found at " + configDirectory.getPath());
}
for (File configFile : configFiles) {
String[] args = new String[]{"check", configFile.getAbsolutePath()};
final File[] secretsBundle = configDirectory.listFiles(f -> !f.isDirectory() && f.getName().equals(SECRETS_BUNDLE_FILENAME));
if (secretsBundle == null || secretsBundle.length != 1) {
throw new IllegalArgumentException("No [%s] file found at %s".formatted(SECRETS_BUNDLE_FILENAME, configDirectory.getPath()));
}
System.setProperty(WhisperServerService.SECRETS_BUNDLE_FILE_NAME_PROPERTY, secretsBundle[0].getAbsolutePath());
for (final File configFile : configFiles) {
final String[] args = new String[]{"check", configFile.getAbsolutePath()};
try {
new WhisperServerService().run(args);
} catch (final Exception e) {
@ -38,8 +46,7 @@ public class CheckServiceConfigurations {
}
}
public static void main(String[] args) {
public static void main(final String[] args) {
if (args.length != 1) {
throw new IllegalArgumentException("Expected single argument with config directory: " + Arrays.toString(args));
}
@ -52,5 +59,4 @@ public class CheckServiceConfigurations {
new CheckServiceConfigurations().checkConfiguration(configDirectory);
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.JsonMappingException;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotEmpty;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.util.ExactlySize;
import org.whispersystems.textsecuregcm.util.SystemMapper;
public class SecretsTest {
private static final String SECRET_REF = "secret_string";
private static final String SECRET_LIST_REF = "secret_string_list";
private static final String SECRET_BYTES_REF = "secret_bytes";
private static final String SECRET_BYTES_LIST_REF = "secret_bytes_list";
public record TestData(SecretString secret,
SecretBytes secretBytes,
SecretStringList secretList,
SecretBytesList secretBytesList) {
}
private static final String VALID_CONFIG_YAML = """
secret: secret://%s
secretBytes: secret://%s
secretList: secret://%s
secretBytesList: secret://%s
""".formatted(SECRET_REF, SECRET_BYTES_REF, SECRET_LIST_REF, SECRET_BYTES_LIST_REF);
@Test
public void testDeserialization() throws Exception {
final String secretString = "secret_string";
final byte[] secretBytes = RandomUtils.nextBytes(16);
final String secretBytesBase64 = Base64.getEncoder().encodeToString(secretBytes);
final List<String> secretStringList = List.of("secret1", "secret2", "secret3");
final List<byte[]> secretBytesList = List.of(RandomUtils.nextBytes(16), RandomUtils.nextBytes(16), RandomUtils.nextBytes(16));
final List<String> secretBytesListBase64 = secretBytesList.stream().map(Base64.getEncoder()::encodeToString).toList();
final Map<String, Secret<?>> storeMap = Map.of(
SECRET_REF, new SecretString(secretString),
SECRET_BYTES_REF, new SecretString(secretBytesBase64),
SECRET_LIST_REF, new SecretStringList(secretStringList),
SECRET_BYTES_LIST_REF, new SecretStringList(secretBytesListBase64)
);
SecretsModule.INSTANCE.setSecretStore(new SecretStore(storeMap));
final TestData result = SystemMapper.yamlMapper().readValue(VALID_CONFIG_YAML, TestData.class);
assertEquals(secretString, result.secret().value());
assertEquals(secretStringList, result.secretList().value());
assertArrayEquals(secretBytes, result.secretBytes().value());
for (int i = 0; i < secretBytesList.size(); i++) {
assertArrayEquals(secretBytesList.get(i), result.secretBytesList().value().get(i));
}
}
@Test
public void testValueWithoutPrefix() throws Exception {
final String config = """
secret: ref
""";
SecretsModule.INSTANCE.setSecretStore(new SecretStore(Collections.emptyMap()));
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(config, TestData.class));
}
@Test
public void testNoSecretInTheStore() throws Exception {
final String config = """
secret: secret://missing
secretBytes: secret://missing
secretList: secret://missing
secretBytesList: secret://missing
""";
SecretsModule.INSTANCE.setSecretStore(new SecretStore(Collections.emptyMap()));
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(config, TestData.class));
}
@Test
public void testSecretStoreNotSet() throws Exception {
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(VALID_CONFIG_YAML, TestData.class));
}
@Test
public void testReadFromJson() throws Exception {
// checking that valid json secrets bundle is read correctly
final SecretStore secretStore = SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
secret_string_list:
- value1
- value2
- value3
""");
assertEquals("value", secretStore.secretString("secret_string").value());
assertEquals(List.of("value1", "value2", "value3"), secretStore.secretStringList("secret_string_list").value());
// checking that secrets bundle can't have objects as values
assertThrows(IllegalArgumentException.class, () -> SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
not_a_string_or_list:
k: v
"""));
// checking that secrets bundle can't have numbers as values
assertThrows(IllegalArgumentException.class, () -> SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
not_a_string_or_list: 42
"""));
}
record NotEmptySecretStringList(@NotEmpty SecretStringList secret) {
}
record NotEmptySecretBytesList(@NotEmpty SecretBytesList secret) {
}
record ExactlySizeBytesSecret(@ExactlySize(32) SecretBytes secret) {
}
@Test
public void testValidators() throws Exception {
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// @NotEmpty SecretStringList
assertFalse(validator.validate(new NotEmptySecretStringList(new SecretStringList(List.of()))).isEmpty());
assertTrue(validator.validate(new NotEmptySecretStringList(new SecretStringList(List.of("smth")))).isEmpty());
// @NotEmpty SecretBytesList
assertFalse(validator.validate(new NotEmptySecretBytesList(new SecretBytesList(List.of()))).isEmpty());
assertTrue(validator.validate(new NotEmptySecretBytesList(new SecretBytesList(List.of(new byte[4])))).isEmpty());
// @ExactlySize SecretBytes
assertFalse(validator.validate(new ExactlySizeBytesSecret(new SecretBytes(new byte[16]))).isEmpty());
assertTrue(validator.validate(new ExactlySizeBytesSecret(new SecretBytes(new byte[32]))).isEmpty());
}
}

View File

@ -24,6 +24,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.HttpHeaders;
@ -72,7 +73,6 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
@ -99,7 +99,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationLock;
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
import org.whispersystems.textsecuregcm.limits.RateLimitByIpFilter;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
@ -203,13 +202,13 @@ class AccountControllerTest {
private static final SecureBackupServiceConfiguration SVR1_CFG = MockUtils.buildMock(
SecureBackupServiceConfiguration.class,
cfg -> when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
cfg -> when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32)));
private static final SecureValueRecovery2Configuration SVR2_CFG = MockUtils.buildMock(
SecureValueRecovery2Configuration.class,
cfg -> {
when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(new byte[32]);
when(cfg.userIdTokenSharedSecret()).thenReturn(new byte[32]);
when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32));
when(cfg.userIdTokenSharedSecret()).thenReturn(randomSecretBytes(32));
});
private static final ExternalServiceCredentialsGenerator svr1CredentialsGenerator = SecureBackupController.credentialsGenerator(

View File

@ -5,32 +5,17 @@
package org.whispersystems.textsecuregcm.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.RandomUtils;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.entities.AuthCheckRequest;
import org.whispersystems.textsecuregcm.entities.AuthCheckResponse;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockUtils;
@ -40,11 +25,11 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class SecureBackupControllerTest extends SecureValueRecoveryControllerBaseTest {
private static final byte[] SECRET = RandomUtils.nextBytes(32);
private static final SecretBytes SECRET = randomSecretBytes(32);
private static final SecureBackupServiceConfiguration CFG = MockUtils.buildMock(
SecureBackupServiceConfiguration.class,
cfg -> Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(SECRET)
cfg -> Mockito.when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(SECRET)
);
private static final MutableClock CLOCK = new MutableClock();

View File

@ -7,10 +7,10 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.mockito.Mockito.mock;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import org.apache.commons.lang3.RandomUtils;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
@ -26,8 +26,8 @@ public class SecureValueRecovery2ControllerTest extends SecureValueRecoveryContr
private static final SecureValueRecovery2Configuration CFG = new SecureValueRecovery2Configuration(
true,
"",
RandomUtils.nextBytes(32),
RandomUtils.nextBytes(32),
randomSecretBytes(32),
randomSecretBytes(32),
null,
null,
null

View File

@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.security.cert.CertificateException;
@ -53,7 +54,7 @@ class SecureStorageClientTest {
httpExecutor = Executors.newSingleThreadExecutor();
final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration(
"not_used",
randomSecretBytes(32),
"http://localhost:" + wireMock.getPort(),
List.of("""
-----BEGIN CERTIFICATE-----

View File

@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.security.cert.CertificateException;
@ -53,7 +54,8 @@ class SecureValueRecovery2ClientTest {
final SecureValueRecovery2Configuration config = new SecureValueRecovery2Configuration(true,
"http://localhost:" + wireMock.getPort(),
new byte[0], new byte[0],
randomSecretBytes(32),
randomSecretBytes(32),
// This is a randomly-generated, throwaway certificate that's not actually connected to anything
List.of("""
-----BEGIN CERTIFICATE-----

View File

@ -8,15 +8,16 @@ package org.whispersystems.textsecuregcm.tests.controllers;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.time.Duration;
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.ExternalServiceCredentials;
@ -26,18 +27,12 @@ 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.MockUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class)
class ArtControllerTest {
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = MockUtils.buildMock(
ArtServiceConfiguration.class,
cfg -> {
Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]);
Mockito.when(cfg.getUserAuthenticationTokenUserIdSecret()).thenReturn(new byte[32]);
});
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = new ArtServiceConfiguration(
randomSecretBytes(32), randomSecretBytes(32), Duration.ofDays(1));
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);

View File

@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.tests.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.secretBytesOf;
import java.time.Clock;
import java.time.Instant;
@ -28,7 +29,7 @@ class DirectoryControllerV2Test {
@Test
void testAuthToken() {
final ExternalServiceCredentialsGenerator credentialsGenerator = DirectoryV2Controller.credentialsGenerator(
new DirectoryV2ClientConfiguration(new byte[]{0x1}, new byte[]{0x2}),
new DirectoryV2ClientConfiguration(secretBytesOf(0x01), secretBytesOf(0x02)),
Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC"))
);

View File

@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.tests.controllers;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
@ -31,7 +32,7 @@ class SecureStorageControllerTest {
private static final SecureStorageServiceConfiguration STORAGE_CFG = MockUtils.buildMock(
SecureStorageServiceConfiguration.class,
cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]));
cfg -> when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32)));
private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController
.credentialsGenerator(STORAGE_CFG);

View File

@ -12,7 +12,9 @@ import static org.mockito.Mockito.doThrow;
import java.time.Duration;
import java.util.Optional;
import org.apache.commons.lang3.RandomUtils;
import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
@ -70,4 +72,16 @@ public final class MockUtils {
throw new RuntimeException(e);
}
}
public static SecretBytes randomSecretBytes(final int size) {
return new SecretBytes(RandomUtils.nextBytes(size));
}
public static SecretBytes secretBytesOf(final int... byteVals) {
final byte[] bytes = new byte[byteVals.length];
for (int i = 0; i < byteVals.length; i++) {
bytes[i] = (byte) byteVals[i];
}
return new SecretBytes(bytes);
}
}