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. # `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. # 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: adminEventLoggingConfiguration:
credentials: | credentials: |
Some credentials text {
blah blah blah "key": "value"
}
projectId: some-project-id projectId: some-project-id
logName: some-log-name logName: some-log-name
stripe: stripe:
apiKey: unset apiKey: secret://stripe.apiKey
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator
boostDescription: > boostDescription: >
Example Example
supportedCurrencies: supportedCurrencies:
@ -24,7 +61,7 @@ stripe:
braintree: braintree:
merchantId: unset merchantId: unset
publicKey: unset publicKey: unset
privateKey: unset privateKey: secret://braintree.privateKey
environment: unset environment: unset
graphqlUrl: unset graphqlUrl: unset
merchantAccounts: merchantAccounts:
@ -104,14 +141,14 @@ rateLimitersCluster: # Redis server configuration for rate limiters cluster
directoryV2: directoryV2:
client: # Configuration for interfacing with Contact Discovery Service v2 cluster 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 userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret
svr2: svr2:
enabled: false enabled: false
uri: svr2.example.com uri: svr2.example.com
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret
svrCaCertificates: svrCaCertificates:
- | - |
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
@ -146,8 +183,8 @@ metricsCluster:
configurationUri: redis://redis.example.com:6379/ configurationUri: redis://redis.example.com:6379/
awsAttachments: # AWS S3 configuration awsAttachments: # AWS S3 configuration
accessKey: test accessKey: secret://awsAttachments.accessKey
accessSecret: test accessSecret: secret://awsAttachments.accessSecret
bucket: aws-attachments bucket: aws-attachments
region: us-west-2 region: us-west-2
@ -156,35 +193,7 @@ gcpAttachments: # GCP Storage configuration
email: user@example.cocm email: user@example.cocm
maxSizeInBytes: 1024 maxSizeInBytes: 1024
pathPrefix: pathPrefix:
rsaSigningKey: | rsaSigningKey: secret://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-----
accountDatabaseCrawler: accountDatabaseCrawler:
chunkSize: 10 # accounts per run chunkSize: 10 # accounts per run
@ -194,31 +203,24 @@ apn: # Apple Push Notifications configuration
bundleId: com.example.textsecuregcm bundleId: com.example.textsecuregcm
keyId: unset keyId: unset
teamId: unset teamId: unset
signingKey: | signingKey: secret://apn.signingKey
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
fcm: # FCM configuration fcm: # FCM configuration
credentials: | credentials: secret://fcm.credentials
{ "json": true }
cdn: cdn:
accessKey: test # AWS Access Key ID accessKey: secret://cdn.accessKey
accessSecret: test # AWS Access Secret accessSecret: secret://cdn.accessSecret
bucket: cdn # S3 Bucket name bucket: cdn # S3 Bucket name
region: us-west-2 # AWS region region: us-west-2 # AWS region
datadog: datadog:
apiKey: unset apiKey: secret://datadog.apiKey
environment: dev environment: dev
unidentifiedDelivery: unidentifiedDelivery:
certificate: ABCD1234 certificate: secret://unidentifiedDelivery.certificate
privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA privateKey: secret://unidentifiedDelivery.privateKey
expiresDays: 7 expiresDays: 7
recaptcha: recaptcha:
@ -226,11 +228,11 @@ recaptcha:
credentialConfigurationJson: "{ }" # service account configuration for backend authentication credentialConfigurationJson: "{ }" # service account configuration for backend authentication
hCaptcha: hCaptcha:
apiKey: unset apiKey: secret://hCaptcha.apiKey
storageService: storageService:
uri: storage.example.com uri: storage.example.com
userAuthenticationTokenSharedSecret: 00000f userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret
storageCaCertificates: storageCaCertificates:
- | - |
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
@ -257,7 +259,7 @@ storageService:
backupService: backupService:
uri: backup.example.com uri: backup.example.com
userAuthenticationTokenSharedSecret: 00000f userAuthenticationTokenSharedSecret: secret://backupService.userAuthenticationTokenSharedSecret
backupCaCertificates: backupCaCertificates:
- | - |
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
@ -284,10 +286,10 @@ backupService:
zkConfig: zkConfig:
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz 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: 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: appConfig:
application: example application: example
@ -295,18 +297,14 @@ appConfig:
configuration: example configuration: example
remoteConfig: remoteConfig:
authorizedTokens: authorizedTokens: secret://remoteConfig.authorizedTokens
- # 1st authorized token
- # 2nd authorized token
- # ...
- # Nth authorized token
globalConfig: # keys and values that are given to clients on GET /v1/config globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE EXAMPLE_KEY: VALUE
paymentsService: paymentsService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret
fixerApiKey: unset fixerApiKey: secret://paymentsService.fixerApiKey
coinMarketCapApiKey: unset coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
coinMarketCapCurrencyIds: coinMarketCapCurrencyIds:
MOB: 7878 MOB: 7878
paymentCurrencies: paymentCurrencies:
@ -314,8 +312,8 @@ paymentsService:
- MOB - MOB
artService: artService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret not shared with any external service, but used in ArtController userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret to obscure user phone numbers from Sticker Creator userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret
badges: badges:
badges: badges:

View File

@ -5,6 +5,7 @@
package org.whispersystems.textsecuregcm; package org.whispersystems.textsecuregcm;
import static com.codahale.metrics.MetricRegistry.name; import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;
import com.amazonaws.ClientConfiguration; import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.InstanceProfileCredentialsProvider; 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.RecaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager; import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; 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.AccountController;
import org.whispersystems.textsecuregcm.controllers.AccountControllerV2; import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
import org.whispersystems.textsecuregcm.controllers.ArtController; 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); 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 @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 DeleteUserCommand());
bootstrap.addCommand(new CertificateCommand()); bootstrap.addCommand(new CertificateCommand());
bootstrap.addCommand(new ZkParamsCommand()); bootstrap.addCommand(new ZkParamsCommand());
@ -289,8 +308,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.lifecycle().manage(new MicrometerRegistryManager(Metrics.globalRegistry)); environment.lifecycle().manage(new MicrometerRegistryManager(Metrics.globalRegistry));
SystemMapper.configureMapper(environment.getObjectMapper());
HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup = HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup =
new HeaderControlledResourceBundleLookup(); new HeaderControlledResourceBundleLookup();
ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter( ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter(
@ -300,11 +317,11 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient( DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
config.getDynamoDbClientConfiguration(), config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); AWSSDK_INSTANCE_PROFILE_CREDENTIALS_PROVIDER);
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client( DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(
config.getDynamoDbClientConfiguration(), config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create()); AWSSDK_INSTANCE_PROFILE_CREDENTIALS_PROVIDER);
AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard() AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard()
.withRegion(config.getDynamoDbClientConfiguration().getRegion()) .withRegion(config.getDynamoDbClientConfiguration().getRegion())
@ -446,11 +463,11 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getAdminEventLoggingConfiguration().projectId(), config.getAdminEventLoggingConfiguration().projectId(),
config.getAdminEventLoggingConfiguration().logName()); config.getAdminEventLoggingConfiguration().logName());
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey(), subscriptionProcessorExecutor, StripeManager stripeManager = new StripeManager(config.getStripe().apiKey().value(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator(), config.getStripe().boostDescription(), config.getStripe() config.getStripe().idempotencyKeyGenerator().value(), config.getStripe().boostDescription(), config.getStripe()
.supportedCurrencies()); .supportedCurrencies());
BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(), 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().supportedCurrencies(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor); config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor);
@ -506,9 +523,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
deletedAccountsManager, keys, messagesManager, profilesManager, deletedAccountsManager, keys, messagesManager, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, secureValueRecovery2Client, clientPresenceManager, pendingAccountsManager, secureStorageClient, secureBackupClient, secureValueRecovery2Client, clientPresenceManager,
experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock); experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs); RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration()); APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials()); FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials().value());
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster,
apnSender, accountsManager); apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
@ -553,7 +570,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getRecaptchaConfiguration().getCredentialConfigurationJson(), config.getRecaptchaConfiguration().getCredentialConfigurationJson(),
dynamicConfigurationManager); dynamicConfigurationManager);
HttpClient hcaptchaHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build(); 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)); CaptchaChecker captchaChecker = new CaptchaChecker(List.of(recaptchaClient, hCaptchaClient));
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager, pushChallengeDynamoDb); 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(); HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey()); FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().fixerApiKey().value());
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().getCoinMarketCapApiKey(), config.getPaymentsServiceConfiguration().getCoinMarketCapCurrencyIds()); CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().coinMarketCapApiKey().value(), config.getPaymentsServiceConfiguration().coinMarketCapCurrencyIds());
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, 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(apnSender);
environment.lifecycle().manage(apnPushNotificationScheduler); environment.lifecycle().manage(apnPushNotificationScheduler);
@ -610,19 +627,19 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
.create(AwsBasicCredentials.create( .create(AwsBasicCredentials.create(
config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().accessKey().value(),
config.getCdnConfiguration().getAccessSecret())); config.getCdnConfiguration().accessSecret().value()));
S3Client cdnS3Client = S3Client.builder() S3Client cdnS3Client = S3Client.builder()
.credentialsProvider(cdnCredentialsProvider) .credentialsProvider(cdnCredentialsProvider)
.region(Region.of(config.getCdnConfiguration().getRegion())) .region(Region.of(config.getCdnConfiguration().region()))
.build(); .build();
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().getRegion(), PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().region(),
config.getCdnConfiguration().getBucket(), config.getCdnConfiguration().getAccessKey()); config.getCdnConfiguration().bucket(), config.getCdnConfiguration().accessKey().value());
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().getAccessSecret(), PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().accessSecret().value(),
config.getCdnConfiguration().getRegion()); config.getCdnConfiguration().region());
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret()); ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().serverSecret().value());
GenericServerSecretParams genericZkSecretParams = new GenericServerSecretParams(config.getGenericZkConfig().serverSecret()); GenericServerSecretParams genericZkSecretParams = new GenericServerSecretParams(config.getGenericZkConfig().serverSecret().value());
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams); ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams);
ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams); ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams);
ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams); ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams);
@ -720,10 +737,10 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager, new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager,
registrationLockVerificationManager, rateLimiters), registrationLockVerificationManager, rateLimiters),
new ArtController(rateLimiters, artCredentialsGenerator), new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()), new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().accessKey().value(), config.getAwsAttachmentsConfiguration().accessSecret().value(), config.getAwsAttachmentsConfiguration().region(), config.getAwsAttachmentsConfiguration().bucket()),
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()), new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().domain(), config.getGcpAttachmentsConfiguration().email(), config.getGcpAttachmentsConfiguration().maxSizeInBytes(), config.getGcpAttachmentsConfiguration().pathPrefix(), config.getGcpAttachmentsConfiguration().rsaSigningKey().value()),
new CallLinkController(rateLimiters, genericZkSecretParams), 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 ChallengeController(rateLimitChallengeManager),
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()), new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
new DirectoryV2Controller(directoryV2CredentialsGenerator), new DirectoryV2Controller(directoryV2CredentialsGenerator),
@ -735,19 +752,19 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new PaymentsController(currencyManager, paymentsCredentialsGenerator), new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,
config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor), config.getCdnConfiguration().bucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager), new ProvisioningController(rateLimiters, provisioningManager),
new RegistrationController(accountsManager, phoneVerificationTokenManager, registrationLockVerificationManager, new RegistrationController(accountsManager, phoneVerificationTokenManager, registrationLockVerificationManager,
rateLimiters), rateLimiters),
new RemoteConfigController(remoteConfigsManager, adminEventLogger, new RemoteConfigController(remoteConfigsManager, adminEventLogger,
config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().authorizedTokens().value(),
config.getRemoteConfigConfiguration().getGlobalConfig()), config.getRemoteConfigConfiguration().globalConfig()),
new SecureBackupController(backupCredentialsGenerator, accountsManager), new SecureBackupController(backupCredentialsGenerator, accountsManager),
new SecureStorageController(storageCredentialsGenerator), new SecureStorageController(storageCredentialsGenerator),
new SecureValueRecovery2Controller(svr2CredentialsGenerator, accountsManager, config.getSvr2Configuration()), new SecureValueRecovery2Controller(svr2CredentialsGenerator, accountsManager, config.getSvr2Configuration()),
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), new StickerController(rateLimiters, config.getCdnConfiguration().accessKey().value(),
config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().accessSecret().value(), config.getCdnConfiguration().region(),
config.getCdnConfiguration().getBucket()), config.getCdnConfiguration().bucket()),
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions), new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,
accountsManager, clock) 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.hmac256TruncatedToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmacHexStringsEqual; import static org.whispersystems.textsecuregcm.util.HmacUtils.hmacHexStringsEqual;
import com.google.common.annotations.VisibleForTesting;
import java.time.Clock; import java.time.Clock;
import java.time.Instant; import java.time.Instant;
import java.util.Optional; import java.util.Optional;
@ -17,6 +18,7 @@ import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ExternalServiceCredentialsGenerator { public class ExternalServiceCredentialsGenerator {
@ -40,6 +42,12 @@ public class ExternalServiceCredentialsGenerator {
private final int derivedUsernameTruncateLength; 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) { public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key); return new Builder(key);
} }
@ -240,6 +248,10 @@ public class ExternalServiceCredentialsGenerator {
this.key = requireNonNull(key); this.key = requireNonNull(key);
} }
public Builder withUserDerivationKey(final SecretBytes userDerivationKey) {
return withUserDerivationKey(userDerivationKey.value());
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) { public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty"); Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey; this.userDerivationKey = userDerivationKey;

View File

@ -5,10 +5,11 @@
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
public record AdminEventLoggingConfiguration( public record AdminEventLoggingConfiguration(
@NotEmpty String credentials, @NotBlank String credentials,
@NotEmpty String projectId, @NotEmpty String projectId,
@NotEmpty String logName) { @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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class ApnConfiguration { public record ApnConfiguration(@NotBlank String teamId,
@NotBlank String keyId,
@NotEmpty @NotNull SecretString signingKey,
@JsonProperty @NotBlank String bundleId,
private String teamId; boolean sandbox) {
@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;
}
} }

View File

@ -5,35 +5,17 @@
package org.whispersystems.textsecuregcm.configuration; 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.time.Duration;
import java.util.HexFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize;
public class ArtServiceConfiguration { public record ArtServiceConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretBytes userAuthenticationTokenUserIdSecret,
@NotEmpty @NotNull Duration tokenExpiration) {
@JsonProperty public ArtServiceConfiguration {
private String userAuthenticationTokenSharedSecret; tokenExpiration = firstNonNull(tokenExpiration, Duration.ofDays(1));
@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;
} }
} }

View File

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

View File

@ -11,6 +11,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
/** /**
* @param merchantId the Braintree merchant ID * @param merchantId the Braintree merchant ID
@ -24,7 +25,7 @@ import javax.validation.constraints.NotNull;
*/ */
public record BraintreeConfiguration(@NotBlank String merchantId, public record BraintreeConfiguration(@NotBlank String merchantId,
@NotBlank String publicKey, @NotBlank String publicKey,
@NotBlank String privateKey, @NotNull SecretString privateKey,
@NotBlank String environment, @NotBlank String environment,
@NotEmpty Set<@NotBlank String> supportedCurrencies, @NotEmpty Set<@NotBlank String> supportedCurrencies,
@NotBlank String graphqlUrl, @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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
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;
}
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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -7,16 +7,17 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.micrometer.datadog.DatadogConfig; import io.micrometer.datadog.DatadogConfig;
import java.time.Duration;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.Duration; import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
public class DatadogConfiguration implements DatadogConfig { public class DatadogConfiguration implements DatadogConfig {
@JsonProperty @JsonProperty
@NotBlank @NotNull
private String apiKey; private SecretString apiKey;
@JsonProperty @JsonProperty
@NotNull @NotNull
@ -32,7 +33,7 @@ public class DatadogConfiguration implements DatadogConfig {
@Override @Override
public String apiKey() { public String apiKey() {
return apiKey; return apiKey.value();
} }
@Override @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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.util.ExactlySize; import org.whispersystems.textsecuregcm.util.ExactlySize;
public record DirectoryV2ClientConfiguration(@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret, public record DirectoryV2ClientConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret,
@ExactlySize({32}) byte[] userIdTokenSharedSecret) { @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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.util.Strings; import io.dropwizard.util.Strings;
import io.dropwizard.validation.ValidationMethod; import io.dropwizard.validation.ValidationMethod;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class GcpAttachmentsConfiguration { import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
@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;
}
public record GcpAttachmentsConfiguration(@NotBlank String domain,
@NotBlank String email,
@Min(1) int maxSizeInBytes,
String pathPrefix,
@NotNull SecretString rsaSigningKey) {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ValidationMethod(message = "pathPrefix must be empty or start with /") @ValidationMethod(message = "pathPrefix must be empty or start with /")
public boolean isPathPrefixValid() { 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; 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 javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public record GenericZkConfig ( public record GenericZkConfig(@NotNull SecretBytes serverSecret) {
@JsonProperty }
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
@NotNull
byte[] 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HexFormat;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; 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 { public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret,
@NotNull SecretString coinMarketCapApiKey,
@NotEmpty @NotNull SecretString fixerApiKey,
@JsonProperty @NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds,
private String userAuthenticationTokenSharedSecret; @NotEmpty List<String> paymentCurrencies) {
@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;
}
} }

View File

@ -1,33 +1,14 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; 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 java.util.Map;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretStringList;
public class RemoteConfigConfiguration { public record RemoteConfigConfiguration(@NotNull SecretStringList authorizedTokens,
@NotNull Map<String, String> globalConfig) {
@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;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -7,18 +7,18 @@ package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import java.util.HexFormat;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class SecureBackupServiceConfiguration { public class SecureBackupServiceConfiguration {
@NotEmpty @NotNull
@JsonProperty @JsonProperty
private String userAuthenticationTokenSharedSecret; private SecretBytes userAuthenticationTokenSharedSecret;
@NotBlank @NotBlank
@JsonProperty @JsonProperty
@ -38,8 +38,8 @@ public class SecureBackupServiceConfiguration {
@JsonProperty @JsonProperty
private RetryConfiguration retry = new RetryConfiguration(); private RetryConfiguration retry = new RetryConfiguration();
public byte[] getUserAuthenticationTokenSharedSecret() { public SecretBytes userAuthenticationTokenSharedSecret() {
return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret); return userAuthenticationTokenSharedSecret;
} }
@VisibleForTesting @VisibleForTesting

View File

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

View File

@ -8,10 +8,12 @@ package org.whispersystems.textsecuregcm.configuration;
import java.util.Set; import java.util.Set;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; 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, public record StripeConfiguration(@NotNull SecretString apiKey,
@NotEmpty byte[] idempotencyKeyGenerator, @NotNull SecretBytes idempotencyKeyGenerator,
@NotBlank String boostDescription, @NotBlank String boostDescription,
@NotEmpty Set<@NotBlank String> supportedCurrencies) { @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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPrivateKey; 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; public record UnidentifiedDeliveryConfiguration(@NotNull SecretBytes certificate,
import javax.validation.constraints.Size; @ExactlySize(32) SecretBytes privateKey,
int expiresDays) {
public class UnidentifiedDeliveryConfiguration { public ECPrivateKey ecPrivateKey() throws InvalidKeyException {
return Curve.decodePrivatePoint(privateKey.value());
@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;
} }
} }

View File

@ -1,36 +1,14 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.configuration; package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty; import javax.validation.constraints.NotEmpty;
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 javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
public class ZkConfig { public record ZkConfig(@NotNull SecretBytes serverSecret,
@NotEmpty byte[] serverPublic) {
@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;
}
} }

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ public class SecureStorageController {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) { public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) {
return ExternalServiceCredentialsGenerator return ExternalServiceCredentialsGenerator
.builder(cfg.decodeUserAuthenticationTokenSharedSecret()) .builder(cfg.userAuthenticationTokenSharedSecret())
.prependUsername(true) .prependUsername(true)
.build(); .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.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag; 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.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes; 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.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; 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") @Path("/v2/backup")
@Tag(name = "Secure Value Recovery") @Tag(name = "Secure Value Recovery")
@ -54,7 +51,7 @@ public class SecureValueRecovery2Controller {
public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureValueRecovery2Configuration cfg, final Clock clock) { public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureValueRecovery2Configuration cfg, final Clock clock) {
return ExternalServiceCredentialsGenerator return ExternalServiceCredentialsGenerator
.builder(cfg.userAuthenticationTokenSharedSecret()) .builder(cfg.userAuthenticationTokenSharedSecret())
.withUserDerivationKey(cfg.userIdTokenSharedSecret()) .withUserDerivationKey(cfg.userIdTokenSharedSecret().value())
.prependUsername(false) .prependUsername(false)
.withDerivedUsernameTruncateLength(16) .withDerivedUsernameTruncateLength(16)
.withClock(clock) .withClock(clock)

View File

@ -22,17 +22,27 @@ import io.dropwizard.logging.filter.LevelFilterFactory;
import io.dropwizard.logging.layout.LayoutFactory; import io.dropwizard.logging.layout.LayoutFactory;
import java.time.Duration; import java.time.Duration;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import net.logstash.logback.appender.LogstashTcpSocketAppender; import net.logstash.logback.appender.LogstashTcpSocketAppender;
import net.logstash.logback.encoder.LogstashEncoder; import net.logstash.logback.encoder.LogstashEncoder;
import org.whispersystems.textsecuregcm.WhisperServerVersion; import org.whispersystems.textsecuregcm.WhisperServerVersion;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.util.HostnameUtil; import org.whispersystems.textsecuregcm.util.HostnameUtil;
@JsonTypeName("logstashtcpsocket") @JsonTypeName("logstashtcpsocket")
public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<ILoggingEvent> { public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<ILoggingEvent> {
@JsonProperty
private String destination; private String destination;
@JsonProperty
private Duration keepAlive = Duration.ofSeconds(20); private Duration keepAlive = Duration.ofSeconds(20);
private String apiKey;
@JsonProperty
@NotNull
private SecretString apiKey;
@JsonProperty
private String environment; private String environment;
@JsonProperty @JsonProperty
@ -47,8 +57,7 @@ public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<IL
} }
@JsonProperty @JsonProperty
@NotEmpty public SecretString getApiKey() {
public String getApiKey() {
return apiKey; return apiKey;
} }
@ -84,7 +93,7 @@ public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory<IL
encoder.setCustomFields(customFieldsNode.toString()); encoder.setCustomFields(customFieldsNode.toString());
final LayoutWrappingEncoder<ILoggingEvent> prefix = new LayoutWrappingEncoder<>(); final LayoutWrappingEncoder<ILoggingEvent> prefix = new LayoutWrappingEncoder<>();
final PatternLayout layout = new PatternLayout(); final PatternLayout layout = new PatternLayout();
layout.setPattern(String.format("%s ", apiKey)); layout.setPattern(String.format("%s ", apiKey.value()));
prefix.setLayout(layout); prefix.setLayout(layout);
encoder.setPrefix(prefix); encoder.setPrefix(prefix);
appender.setEncoder(encoder); 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. * This is derived from Coursera's dropwizard datadog reporter.
* https://github.com/coursera/metrics-datadog * 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.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.metrics.BaseReporterFactory; import io.dropwizard.metrics.BaseReporterFactory;
import io.dropwizard.util.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; 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.DefaultMetricNameFormatterFactory;
import org.coursera.metrics.datadog.DynamicTagsCallbackFactory; import org.coursera.metrics.datadog.DynamicTagsCallbackFactory;
import org.coursera.metrics.datadog.MetricNameFormatterFactory; 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.WhisperServerVersion;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
import org.whispersystems.textsecuregcm.util.HostnameUtil; import org.whispersystems.textsecuregcm.util.HostnameUtil;
@JsonTypeName("signal-datadog") @JsonTypeName("signal-datadog")
@ -44,8 +51,8 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
@Valid @Valid
@NotNull @NotNull
@JsonProperty @JsonProperty("transport")
private AbstractTransportFactory transport = null; private HttpTransportConfig httpTransportConfig;
private static final EnumSet<Expansion> EXPANSIONS = EnumSet.of( private static final EnumSet<Expansion> EXPANSIONS = EnumSet.of(
Expansion.COUNT, Expansion.COUNT,
@ -59,7 +66,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
Expansion.P999 Expansion.P999
); );
public ScheduledReporter build(MetricRegistry registry) { public ScheduledReporter build(final MetricRegistry registry) {
final List<String> tagsWithVersion; final List<String> tagsWithVersion;
{ {
@ -74,7 +81,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
} }
return DatadogReporter.forRegistry(registry) return DatadogReporter.forRegistry(registry)
.withTransport(transport.build()) .withTransport(httpTransportConfig.httpTransport())
.withHost(HostnameUtil.getLocalHostname()) .withHost(HostnameUtil.getLocalHostname())
.withTags(tagsWithVersion) .withTags(tagsWithVersion)
.withPrefix(prefix) .withPrefix(prefix)
@ -86,4 +93,26 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory {
.convertRatesTo(getRateUnit()) .convertRatesTo(getRateUnit())
.build(); .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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.push; package org.whispersystems.textsecuregcm.push;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.eatthepath.pushy.apns.ApnsClient; import com.eatthepath.pushy.apns.ApnsClient;
import com.eatthepath.pushy.apns.ApnsClientBuilder; import com.eatthepath.pushy.apns.ApnsClientBuilder;
import com.eatthepath.pushy.apns.DeliveryPriority; 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.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
@ -21,12 +25,8 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class APNSender implements Managed, PushNotificationSender { public class APNSender implements Managed, PushNotificationSender {
private final ExecutorService executor; private final ExecutorService executor;
@ -61,12 +61,12 @@ public class APNSender implements Managed, PushNotificationSender {
throws IOException, NoSuchAlgorithmException, InvalidKeyException throws IOException, NoSuchAlgorithmException, InvalidKeyException
{ {
this.executor = executor; this.executor = executor;
this.bundleId = configuration.getBundleId(); this.bundleId = configuration.bundleId();
this.apnsClient = new ApnsClientBuilder().setSigningKey( this.apnsClient = new ApnsClientBuilder().setSigningKey(
ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.getSigningKey().getBytes()), ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.signingKey().value().getBytes()),
configuration.getTeamId(), configuration.getKeyId())) configuration.teamId(), configuration.keyId()))
.setTrustedServerCertificateChain(getClass().getResourceAsStream(APNS_CA_FILENAME)) .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(); .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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -25,12 +25,12 @@ import javax.validation.Payload;
ExactlySizeValidatorForString.class, ExactlySizeValidatorForString.class,
ExactlySizeValidatorForArraysOfByte.class, ExactlySizeValidatorForArraysOfByte.class,
ExactlySizeValidatorForCollection.class, ExactlySizeValidatorForCollection.class,
ExactlySizeValidatorForSecretBytes.class,
}) })
@Documented @Documented
public @interface ExactlySize { public @interface ExactlySize {
String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize." + String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize.message}";
"message}";
Class<?>[] groups() default { }; 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.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule;
public class SystemMapper { public class SystemMapper {
@ -37,6 +38,7 @@ public class SystemMapper {
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY) .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY)
.registerModules( .registerModules(
SecretsModule.INSTANCE,
new JavaTimeModule(), new JavaTimeModule(),
new Jdk8Module()); 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 { public class CheckServiceConfigurations {
private static final String SECRETS_BUNDLE_FILENAME = "sample-secrets-bundle.yml";
private void checkConfiguration(final File configDirectory) { private void checkConfiguration(final File configDirectory) {
final File[] configFiles = configDirectory.listFiles(f -> final File[] configFiles = configDirectory.listFiles(f ->
!f.isDirectory() !f.isDirectory()
&& f.getPath().endsWith(".yml")); && f.getPath().endsWith(".yml")
&& !f.getPath().endsWith(SECRETS_BUNDLE_FILENAME));
if (configFiles == null || configFiles.length == 0) { if (configFiles == null || configFiles.length == 0) {
throw new IllegalArgumentException("No .yml configuration files found at " + configDirectory.getPath()); throw new IllegalArgumentException("No .yml configuration files found at " + configDirectory.getPath());
} }
for (File configFile : configFiles) { final File[] secretsBundle = configDirectory.listFiles(f -> !f.isDirectory() && f.getName().equals(SECRETS_BUNDLE_FILENAME));
String[] args = new String[]{"check", configFile.getAbsolutePath()}; 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 { try {
new WhisperServerService().run(args); new WhisperServerService().run(args);
} catch (final Exception e) { } 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) { if (args.length != 1) {
throw new IllegalArgumentException("Expected single argument with config directory: " + Arrays.toString(args)); throw new IllegalArgumentException("Expected single argument with config directory: " + Arrays.toString(args));
} }
@ -52,5 +59,4 @@ public class CheckServiceConfigurations {
new CheckServiceConfigurations().checkConfiguration(configDirectory); 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.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.net.HttpHeaders; 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.signal.libsignal.usernames.BaseUsernameException;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager; import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; 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.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest; import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest;
import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse; import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.entities.UsernameHashResponse; import org.whispersystems.textsecuregcm.entities.UsernameHashResponse;
import org.whispersystems.textsecuregcm.limits.RateLimitByIpFilter; import org.whispersystems.textsecuregcm.limits.RateLimitByIpFilter;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
@ -203,13 +202,13 @@ class AccountControllerTest {
private static final SecureBackupServiceConfiguration SVR1_CFG = MockUtils.buildMock( private static final SecureBackupServiceConfiguration SVR1_CFG = MockUtils.buildMock(
SecureBackupServiceConfiguration.class, 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( private static final SecureValueRecovery2Configuration SVR2_CFG = MockUtils.buildMock(
SecureValueRecovery2Configuration.class, SecureValueRecovery2Configuration.class,
cfg -> { cfg -> {
when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(new byte[32]); when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32));
when(cfg.userIdTokenSharedSecret()).thenReturn(new byte[32]); when(cfg.userIdTokenSharedSecret()).thenReturn(randomSecretBytes(32));
}); });
private static final ExternalServiceCredentialsGenerator svr1CredentialsGenerator = SecureBackupController.credentialsGenerator( private static final ExternalServiceCredentialsGenerator svr1CredentialsGenerator = SecureBackupController.credentialsGenerator(

View File

@ -5,32 +5,17 @@
package org.whispersystems.textsecuregcm.controllers; package org.whispersystems.textsecuregcm.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock; 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.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension; 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.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.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.entities.AuthCheckRequest; import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.entities.AuthCheckResponse;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockUtils; import org.whispersystems.textsecuregcm.util.MockUtils;
@ -40,11 +25,11 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class) @ExtendWith(DropwizardExtensionsSupport.class)
class SecureBackupControllerTest extends SecureValueRecoveryControllerBaseTest { 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( private static final SecureBackupServiceConfiguration CFG = MockUtils.buildMock(
SecureBackupServiceConfiguration.class, SecureBackupServiceConfiguration.class,
cfg -> Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(SECRET) cfg -> Mockito.when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(SECRET)
); );
private static final MutableClock CLOCK = new MutableClock(); 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.mockito.Mockito.mock;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension; import io.dropwizard.testing.junit5.ResourceExtension;
import org.apache.commons.lang3.RandomUtils;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
@ -26,8 +26,8 @@ public class SecureValueRecovery2ControllerTest extends SecureValueRecoveryContr
private static final SecureValueRecovery2Configuration CFG = new SecureValueRecovery2Configuration( private static final SecureValueRecovery2Configuration CFG = new SecureValueRecovery2Configuration(
true, true,
"", "",
RandomUtils.nextBytes(32), randomSecretBytes(32),
RandomUtils.nextBytes(32), randomSecretBytes(32),
null, null,
null, 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.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -53,7 +54,7 @@ class SecureStorageClientTest {
httpExecutor = Executors.newSingleThreadExecutor(); httpExecutor = Executors.newSingleThreadExecutor();
final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration( final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration(
"not_used", randomSecretBytes(32),
"http://localhost:" + wireMock.getPort(), "http://localhost:" + wireMock.getPort(),
List.of(""" List.of("""
-----BEGIN CERTIFICATE----- -----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.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
@ -53,7 +54,8 @@ class SecureValueRecovery2ClientTest {
final SecureValueRecovery2Configuration config = new SecureValueRecovery2Configuration(true, final SecureValueRecovery2Configuration config = new SecureValueRecovery2Configuration(true,
"http://localhost:" + wireMock.getPort(), "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 // This is a randomly-generated, throwaway certificate that's not actually connected to anything
List.of(""" List.of("""
-----BEGIN CERTIFICATE----- -----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.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension; import io.dropwizard.testing.junit5.ResourceExtension;
import java.time.Duration;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; 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.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
@ExtendWith(DropwizardExtensionsSupport.class) @ExtendWith(DropwizardExtensionsSupport.class)
class ArtControllerTest { class ArtControllerTest {
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = new ArtServiceConfiguration(
private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = MockUtils.buildMock( randomSecretBytes(32), randomSecretBytes(32), Duration.ofDays(1));
ArtServiceConfiguration.class,
cfg -> {
Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]);
Mockito.when(cfg.getUserAuthenticationTokenUserIdSecret()).thenReturn(new byte[32]);
});
private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION); private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION);
private static final RateLimiter rateLimiter = mock(RateLimiter.class); private static final RateLimiter rateLimiter = mock(RateLimiter.class);
private static final RateLimiters rateLimiters = mock(RateLimiters.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.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.secretBytesOf;
import java.time.Clock; import java.time.Clock;
import java.time.Instant; import java.time.Instant;
@ -28,7 +29,7 @@ class DirectoryControllerV2Test {
@Test @Test
void testAuthToken() { void testAuthToken() {
final ExternalServiceCredentialsGenerator credentialsGenerator = DirectoryV2Controller.credentialsGenerator( 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")) 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.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
@ -31,7 +32,7 @@ class SecureStorageControllerTest {
private static final SecureStorageServiceConfiguration STORAGE_CFG = MockUtils.buildMock( private static final SecureStorageServiceConfiguration STORAGE_CFG = MockUtils.buildMock(
SecureStorageServiceConfiguration.class, 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 private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController
.credentialsGenerator(STORAGE_CFG); .credentialsGenerator(STORAGE_CFG);

View File

@ -12,7 +12,9 @@ import static org.mockito.Mockito.doThrow;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
import org.apache.commons.lang3.RandomUtils;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
@ -70,4 +72,16 @@ public final class MockUtils {
throw new RuntimeException(e); 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);
}
} }