From 287e2fa89a10c6ff96fe6f10d2de7b7016df10a3 Mon Sep 17 00:00:00 2001 From: Sergey Skrobotov Date: Wed, 17 May 2023 11:14:04 -0700 Subject: [PATCH] Moving secret values out of the main configuration file --- service/config/sample-secrets-bundle.yml | 86 ++++++++++ service/config/sample.yml | 136 ++++++++-------- .../textsecuregcm/WhisperServerService.java | 83 ++++++---- .../ExternalServiceCredentialsGenerator.java | 12 ++ .../AdminEventLoggingConfiguration.java | 3 +- .../configuration/ApnConfiguration.java | 52 +----- .../ArtServiceConfiguration.java | 36 +--- .../AwsAttachmentsConfiguration.java | 44 +---- .../configuration/BraintreeConfiguration.java | 3 +- .../configuration/CdnConfiguration.java | 44 +---- .../configuration/DatadogConfiguration.java | 11 +- .../DirectoryV2ClientConfiguration.java | 7 +- .../configuration/FcmConfiguration.java | 7 +- .../GcpAttachmentsConfiguration.java | 53 +----- .../configuration/GenericZkConfig.java | 19 +-- .../configuration/HCaptchaConfiguration.java | 7 +- .../PaymentsServiceConfiguration.java | 53 +----- .../RemoteConfigConfiguration.java | 29 +--- .../SecureBackupServiceConfiguration.java | 12 +- .../SecureStorageServiceConfiguration.java | 10 +- .../SecureValueRecovery2Configuration.java | 5 +- .../configuration/StripeConfiguration.java | 8 +- .../UnidentifiedDeliveryConfiguration.java | 45 +---- .../textsecuregcm/configuration/ZkConfig.java | 32 +--- .../secrets/BaseSecretValidator.java | 32 ++++ .../configuration/secrets/Secret.java | 25 +++ .../configuration/secrets/SecretBytes.java | 20 +++ .../secrets/SecretBytesList.java | 26 +++ .../configuration/secrets/SecretStore.java | 106 ++++++++++++ .../configuration/secrets/SecretString.java | 14 ++ .../secrets/SecretStringList.java | 26 +++ .../configuration/secrets/SecretsModule.java | 57 +++++++ .../controllers/ArtController.java | 10 +- .../controllers/DirectoryV2Controller.java | 4 +- .../controllers/PaymentsController.java | 2 +- .../controllers/SecureBackupController.java | 4 +- .../controllers/SecureStorageController.java | 2 +- .../SecureValueRecovery2Controller.java | 15 +- .../LogstashTcpSocketAppenderFactory.java | 17 +- .../metrics/SignalDatadogReporterFactory.java | 39 ++++- .../textsecuregcm/push/APNSender.java | 18 +- .../textsecuregcm/util/ExactlySize.java | 6 +- .../ExactlySizeValidatorForSecretBytes.java | 15 ++ .../textsecuregcm/util/SystemMapper.java | 2 + .../main/resources/META-INF/validation.xml | 8 + .../validation/constraints-custom.xml | 15 ++ .../CheckServiceConfigurations.java | 18 +- .../configuration/secrets/SecretsTest.java | 154 ++++++++++++++++++ .../controllers/AccountControllerTest.java | 9 +- .../SecureBackupControllerTest.java | 23 +-- .../SecureValueRecovery2ControllerTest.java | 6 +- .../SecureStorageClientTest.java | 3 +- .../SecureValueRecovery2ClientTest.java | 4 +- .../tests/controllers/ArtControllerTest.java | 13 +- .../DirectoryControllerV2Test.java | 3 +- .../SecureStorageControllerTest.java | 3 +- .../textsecuregcm/util/MockUtils.java | 14 ++ 57 files changed, 959 insertions(+), 551 deletions(-) create mode 100644 service/config/sample-secrets-bundle.yml create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/BaseSecretValidator.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/Secret.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytes.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytesList.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStore.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretString.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStringList.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsModule.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySizeValidatorForSecretBytes.java create mode 100644 service/src/main/resources/META-INF/validation.xml create mode 100644 service/src/main/resources/META-INF/validation/constraints-custom.xml create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsTest.java diff --git a/service/config/sample-secrets-bundle.yml b/service/config/sample-secrets-bundle.yml new file mode 100644 index 000000000..035584704 --- /dev/null +++ b/service/config/sample-secrets-bundle.yml @@ -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= diff --git a/service/config/sample.yml b/service/config/sample.yml index 32e03572a..0931034fa 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -3,16 +3,53 @@ # `unset` values will need to be set to work properly. # Most other values are technically valid for a local/demonstration environment, but are probably not production-ready. +logging: + level: INFO + appenders: + - type: console + threshold: ALL + timeZone: UTC + target: stdout + - type: logstashtcpsocket + destination: example.com:10516 + apiKey: secret://datadog.apiKey + environment: staging + +metrics: + reporters: + - type: signal-datadog + frequency: 10 seconds + tags: + - "env:staging" + - "service:chat" + transport: + apiKey: secret://datadog.apiKey + excludesAttributes: + - m1_rate + - m5_rate + - m15_rate + - mean_rate + - stddev + useRegexFilters: true + excludes: + - ^.+\.total$ + - ^.+\.request\.filtering$ + - ^.+\.response\.filtering$ + - ^executor\..+$ + - ^lettuce\..+$ + reportOnStop: true + adminEventLoggingConfiguration: credentials: | - Some credentials text - blah blah blah + { + "key": "value" + } projectId: some-project-id logName: some-log-name stripe: - apiKey: unset - idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash + apiKey: secret://stripe.apiKey + idempotencyKeyGenerator: secret://stripe.idempotencyKeyGenerator boostDescription: > Example supportedCurrencies: @@ -24,7 +61,7 @@ stripe: braintree: merchantId: unset publicKey: unset - privateKey: unset + privateKey: secret://braintree.privateKey environment: unset graphqlUrl: unset merchantAccounts: @@ -104,14 +141,14 @@ rateLimitersCluster: # Redis server configuration for rate limiters cluster directoryV2: client: # Configuration for interfacing with Contact Discovery Service v2 cluster - userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users - userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users + userAuthenticationTokenSharedSecret: secret://directoryV2.client.userAuthenticationTokenSharedSecret + userIdTokenSharedSecret: secret://directoryV2.client.userIdTokenSharedSecret svr2: enabled: false uri: svr2.example.com - userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users - userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users + userAuthenticationTokenSharedSecret: secret://svr2.userAuthenticationTokenSharedSecret + userIdTokenSharedSecret: secret://svr2.userIdTokenSharedSecret svrCaCertificates: - | -----BEGIN CERTIFICATE----- @@ -146,8 +183,8 @@ metricsCluster: configurationUri: redis://redis.example.com:6379/ awsAttachments: # AWS S3 configuration - accessKey: test - accessSecret: test + accessKey: secret://awsAttachments.accessKey + accessSecret: secret://awsAttachments.accessSecret bucket: aws-attachments region: us-west-2 @@ -156,35 +193,7 @@ gcpAttachments: # GCP Storage configuration email: user@example.cocm maxSizeInBytes: 1024 pathPrefix: - rsaSigningKey: | - -----BEGIN PRIVATE KEY----- - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - AAAAAAAA - -----END PRIVATE KEY----- + rsaSigningKey: secret://gcpAttachments.rsaSigningKey accountDatabaseCrawler: chunkSize: 10 # accounts per run @@ -194,31 +203,24 @@ apn: # Apple Push Notifications configuration bundleId: com.example.textsecuregcm keyId: unset teamId: unset - signingKey: | - -----BEGIN PRIVATE KEY----- - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - AAAAAAAA - -----END PRIVATE KEY----- + signingKey: secret://apn.signingKey fcm: # FCM configuration - credentials: | - { "json": true } + credentials: secret://fcm.credentials cdn: - accessKey: test # AWS Access Key ID - accessSecret: test # AWS Access Secret + accessKey: secret://cdn.accessKey + accessSecret: secret://cdn.accessSecret bucket: cdn # S3 Bucket name region: us-west-2 # AWS region datadog: - apiKey: unset + apiKey: secret://datadog.apiKey environment: dev unidentifiedDelivery: - certificate: ABCD1234 - privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA + certificate: secret://unidentifiedDelivery.certificate + privateKey: secret://unidentifiedDelivery.privateKey expiresDays: 7 recaptcha: @@ -226,11 +228,11 @@ recaptcha: credentialConfigurationJson: "{ }" # service account configuration for backend authentication hCaptcha: - apiKey: unset + apiKey: secret://hCaptcha.apiKey storageService: uri: storage.example.com - userAuthenticationTokenSharedSecret: 00000f + userAuthenticationTokenSharedSecret: secret://storageService.userAuthenticationTokenSharedSecret storageCaCertificates: - | -----BEGIN CERTIFICATE----- @@ -257,7 +259,7 @@ storageService: backupService: uri: backup.example.com - userAuthenticationTokenSharedSecret: 00000f + userAuthenticationTokenSharedSecret: secret://backupService.userAuthenticationTokenSharedSecret backupCaCertificates: - | -----BEGIN CERTIFICATE----- @@ -284,10 +286,10 @@ backupService: zkConfig: serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz - serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA== + serverSecret: secret://zkConfig.serverSecret genericZkConfig: - serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA== + serverSecret: secret://genericZkConfig.serverSecret appConfig: application: example @@ -295,18 +297,14 @@ appConfig: configuration: example remoteConfig: - authorizedTokens: - - # 1st authorized token - - # 2nd authorized token - - # ... - - # Nth authorized token + authorizedTokens: secret://remoteConfig.authorizedTokens globalConfig: # keys and values that are given to clients on GET /v1/config EXAMPLE_KEY: VALUE paymentsService: - userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users - fixerApiKey: unset - coinMarketCapApiKey: unset + userAuthenticationTokenSharedSecret: secret://paymentsService.userAuthenticationTokenSharedSecret + fixerApiKey: secret://paymentsService.fixerApiKey + coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey coinMarketCapCurrencyIds: MOB: 7878 paymentCurrencies: @@ -314,8 +312,8 @@ paymentsService: - MOB artService: - userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret not shared with any external service, but used in ArtController - userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret to obscure user phone numbers from Sticker Creator + userAuthenticationTokenSharedSecret: secret://artService.userAuthenticationTokenSharedSecret + userAuthenticationTokenUserIdSecret: secret://artService.userAuthenticationTokenUserIdSecret badges: badges: diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 300c08417..87c8aa3b4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -5,6 +5,7 @@ package org.whispersystems.textsecuregcm; import static com.codahale.metrics.MetricRegistry.name; +import static java.util.Objects.requireNonNull; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.InstanceProfileCredentialsProvider; @@ -81,6 +82,8 @@ import org.whispersystems.textsecuregcm.captcha.HCaptchaClient; import org.whispersystems.textsecuregcm.captcha.RecaptchaClient; import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretStore; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AccountControllerV2; import org.whispersystems.textsecuregcm.controllers.ArtController; @@ -236,8 +239,24 @@ public class WhisperServerService extends Application bootstrap) { + public void initialize(final Bootstrap bootstrap) { + // `SecretStore` needs to be initialized before Dropwizard reads the main application config file. + final String secretsBundleFileName = requireNonNull( + System.getProperty(SECRETS_BUNDLE_FILE_NAME_PROPERTY), + "Application requires property [%s] to be provided".formatted(SECRETS_BUNDLE_FILE_NAME_PROPERTY)); + final SecretStore secretStore = SecretStore.fromYamlFileSecretsBundle(secretsBundleFileName); + SecretsModule.INSTANCE.setSecretStore(secretStore); + + // Initializing SystemMapper here because parsing of the main application config happens before `run()` method is called. + SystemMapper.configureMapper(bootstrap.getObjectMapper()); + bootstrap.addCommand(new DeleteUserCommand()); bootstrap.addCommand(new CertificateCommand()); bootstrap.addCommand(new ZkParamsCommand()); @@ -289,8 +308,6 @@ public class WhisperServerService extends Application 0, "userDerivationKey must not be empty"); this.userDerivationKey = userDerivationKey; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AdminEventLoggingConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AdminEventLoggingConfiguration.java index 18fca6dc6..7a13fa971 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AdminEventLoggingConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AdminEventLoggingConfiguration.java @@ -5,10 +5,11 @@ package org.whispersystems.textsecuregcm.configuration; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; public record AdminEventLoggingConfiguration( - @NotEmpty String credentials, + @NotBlank String credentials, @NotEmpty String projectId, @NotEmpty String logName) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ApnConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ApnConfiguration.java index 131ad053b..3a996d6ba 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ApnConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ApnConfiguration.java @@ -1,51 +1,17 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public class ApnConfiguration { - - @NotEmpty - @JsonProperty - private String teamId; - - @NotEmpty - @JsonProperty - private String keyId; - - @NotEmpty - @JsonProperty - private String signingKey; - - @NotEmpty - @JsonProperty - private String bundleId; - - @JsonProperty - private boolean sandbox = false; - - public String getTeamId() { - return teamId; - } - - public String getKeyId() { - return keyId; - } - - public String getSigningKey() { - return signingKey; - } - - public String getBundleId() { - return bundleId; - } - - public boolean isSandboxEnabled() { - return sandbox; - } +public record ApnConfiguration(@NotBlank String teamId, + @NotBlank String keyId, + @NotNull SecretString signingKey, + @NotBlank String bundleId, + boolean sandbox) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ArtServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ArtServiceConfiguration.java index f0cea1d9f..ce2c79b47 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ArtServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ArtServiceConfiguration.java @@ -5,35 +5,17 @@ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; +import static org.apache.commons.lang3.ObjectUtils.firstNonNull; + import java.time.Duration; -import java.util.HexFormat; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; +import org.whispersystems.textsecuregcm.util.ExactlySize; -public class ArtServiceConfiguration { - - @NotEmpty - @JsonProperty - private String userAuthenticationTokenSharedSecret; - - @NotEmpty - @JsonProperty - private String userAuthenticationTokenUserIdSecret; - - @JsonProperty - @NotNull - private Duration tokenExpiration = Duration.ofDays(1); - - public byte[] getUserAuthenticationTokenSharedSecret() { - return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret); - } - - public byte[] getUserAuthenticationTokenUserIdSecret() { - return HexFormat.of().parseHex(userAuthenticationTokenUserIdSecret); - } - - public Duration getTokenExpiration() { - return tokenExpiration; +public record ArtServiceConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret, + @NotNull SecretBytes userAuthenticationTokenUserIdSecret, + @NotNull Duration tokenExpiration) { + public ArtServiceConfiguration { + tokenExpiration = firstNonNull(tokenExpiration, Duration.ofDays(1)); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java index b1d98271e..3bf3f3af1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/AwsAttachmentsConfiguration.java @@ -1,43 +1,15 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public class AwsAttachmentsConfiguration { - - @NotEmpty - @JsonProperty - private String accessKey; - - @NotEmpty - @JsonProperty - private String accessSecret; - - @NotEmpty - @JsonProperty - private String bucket; - - @NotEmpty - @JsonProperty - private String region; - - public String getAccessKey() { - return accessKey; - } - - public String getAccessSecret() { - return accessSecret; - } - - public String getBucket() { - return bucket; - } - - public String getRegion() { - return region; - } +public record AwsAttachmentsConfiguration(@NotNull SecretString accessKey, + @NotNull SecretString accessSecret, + @NotBlank String bucket, + @NotBlank String region) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java index 388dcacbd..1e8aa85b2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/BraintreeConfiguration.java @@ -11,6 +11,7 @@ import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; /** * @param merchantId the Braintree merchant ID @@ -24,7 +25,7 @@ import javax.validation.constraints.NotNull; */ public record BraintreeConfiguration(@NotBlank String merchantId, @NotBlank String publicKey, - @NotBlank String privateKey, + @NotNull SecretString privateKey, @NotBlank String environment, @NotEmpty Set<@NotBlank String> supportedCurrencies, @NotBlank String graphqlUrl, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java index 910d59fdd..e3bb81ccc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/CdnConfiguration.java @@ -1,44 +1,16 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import javax.validation.constraints.NotEmpty; - -public class CdnConfiguration { - @NotEmpty - @JsonProperty - private String accessKey; - - @NotEmpty - @JsonProperty - private String accessSecret; - - @NotEmpty - @JsonProperty - private String bucket; - - @NotEmpty - @JsonProperty - private String region; - - public String getAccessKey() { - return accessKey; - } - - public String getAccessSecret() { - return accessSecret; - } - - public String getBucket() { - return bucket; - } - - public String getRegion() { - return region; - } +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; +public record CdnConfiguration(@NotNull SecretString accessKey, + @NotNull SecretString accessSecret, + @NotBlank String bucket, + @NotBlank String region) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java index bed91c501..4c2c7ac17 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DatadogConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -7,16 +7,17 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import io.micrometer.datadog.DatadogConfig; +import java.time.Duration; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import java.time.Duration; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; public class DatadogConfiguration implements DatadogConfig { @JsonProperty - @NotBlank - private String apiKey; + @NotNull + private SecretString apiKey; @JsonProperty @NotNull @@ -32,7 +33,7 @@ public class DatadogConfiguration implements DatadogConfig { @Override public String apiKey() { - return apiKey; + return apiKey.value(); } @Override diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java index 61965df5d..58991a71e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java @@ -1,11 +1,12 @@ /* - * Copyright 2013-2023 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; import org.whispersystems.textsecuregcm.util.ExactlySize; -public record DirectoryV2ClientConfiguration(@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret, - @ExactlySize({32}) byte[] userIdTokenSharedSecret) { +public record DirectoryV2ClientConfiguration(@ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret, + @ExactlySize(32) SecretBytes userIdTokenSharedSecret) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FcmConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FcmConfiguration.java index 494d23532..bd6b3e5e0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FcmConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/FcmConfiguration.java @@ -1,11 +1,12 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public record FcmConfiguration(@NotBlank String credentials) { +public record FcmConfiguration(@NotNull SecretString credentials) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GcpAttachmentsConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GcpAttachmentsConfiguration.java index 6970cc96f..fba253e63 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GcpAttachmentsConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GcpAttachmentsConfiguration.java @@ -1,57 +1,22 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; import io.dropwizard.util.Strings; import io.dropwizard.validation.ValidationMethod; import javax.validation.constraints.Min; -import javax.validation.constraints.NotEmpty; - -public class GcpAttachmentsConfiguration { - - @NotEmpty - @JsonProperty - private String domain; - - @NotEmpty - @JsonProperty - private String email; - - @JsonProperty - @Min(1) - private int maxSizeInBytes; - - @JsonProperty - private String pathPrefix; - - @NotEmpty - @JsonProperty - private String rsaSigningKey; - - public String getDomain() { - return domain; - } - - public String getEmail() { - return email; - } - - public int getMaxSizeInBytes() { - return maxSizeInBytes; - } - - public String getPathPrefix() { - return pathPrefix; - } - - public String getRsaSigningKey() { - return rsaSigningKey; - } +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; +public record GcpAttachmentsConfiguration(@NotBlank String domain, + @NotBlank String email, + @Min(1) int maxSizeInBytes, + String pathPrefix, + @NotNull SecretString rsaSigningKey) { @SuppressWarnings("unused") @ValidationMethod(message = "pathPrefix must be empty or start with /") public boolean isPathPrefixValid() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GenericZkConfig.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GenericZkConfig.java index 295939e33..2ebcace88 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GenericZkConfig.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/GenericZkConfig.java @@ -1,15 +1,12 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; -public record GenericZkConfig ( - @JsonProperty - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - @NotNull - byte[] serverSecret -) {} +public record GenericZkConfig(@NotNull SecretBytes serverSecret) { +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java index 1156b253d..6c2ea26bf 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/HCaptchaConfiguration.java @@ -1,11 +1,12 @@ /* - * Copyright 2021-2022 Signal Messenger, LLC + * Copyright 2021 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public record HCaptchaConfiguration(@NotBlank String apiKey) { +public record HCaptchaConfiguration(@NotNull SecretString apiKey) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java index 474474a0f..06dc78e7a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/PaymentsServiceConfiguration.java @@ -1,56 +1,21 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.HexFormat; import java.util.List; import java.util.Map; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public class PaymentsServiceConfiguration { - - @NotEmpty - @JsonProperty - private String userAuthenticationTokenSharedSecret; - - @NotBlank - @JsonProperty - private String coinMarketCapApiKey; - - @JsonProperty - @NotEmpty - private Map<@NotBlank String, Integer> coinMarketCapCurrencyIds; - - @NotEmpty - @JsonProperty - private String fixerApiKey; - - @NotEmpty - @JsonProperty - private List paymentCurrencies; - - public byte[] getUserAuthenticationTokenSharedSecret() { - return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret); - } - - public String getCoinMarketCapApiKey() { - return coinMarketCapApiKey; - } - - public Map getCoinMarketCapCurrencyIds() { - return coinMarketCapCurrencyIds; - } - - public String getFixerApiKey() { - return fixerApiKey; - } - - public List getPaymentCurrencies() { - return paymentCurrencies; - } +public record PaymentsServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret, + @NotNull SecretString coinMarketCapApiKey, + @NotNull SecretString fixerApiKey, + @NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds, + @NotEmpty List paymentCurrencies) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RemoteConfigConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RemoteConfigConfiguration.java index b235d176f..1e70573a6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RemoteConfigConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/RemoteConfigConfiguration.java @@ -1,33 +1,14 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; - -import javax.validation.constraints.NotNull; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretStringList; -public class RemoteConfigConfiguration { - - @JsonProperty - @NotNull - private List authorizedTokens = new LinkedList<>(); - - @NotNull - @JsonProperty - private Map globalConfig = new HashMap<>(); - - public List getAuthorizedTokens() { - return authorizedTokens; - } - - public Map getGlobalConfig() { - return globalConfig; - } +public record RemoteConfigConfiguration(@NotNull SecretStringList authorizedTokens, + @NotNull Map globalConfig) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureBackupServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureBackupServiceConfiguration.java index 020172c5a..b3702aaf6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureBackupServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureBackupServiceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -7,18 +7,18 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; -import java.util.HexFormat; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; public class SecureBackupServiceConfiguration { - @NotEmpty + @NotNull @JsonProperty - private String userAuthenticationTokenSharedSecret; + private SecretBytes userAuthenticationTokenSharedSecret; @NotBlank @JsonProperty @@ -38,8 +38,8 @@ public class SecureBackupServiceConfiguration { @JsonProperty private RetryConfiguration retry = new RetryConfiguration(); - public byte[] getUserAuthenticationTokenSharedSecret() { - return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret); + public SecretBytes userAuthenticationTokenSharedSecret() { + return userAuthenticationTokenSharedSecret; } @VisibleForTesting diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java index 4c9bf35aa..ebf6c2349 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java @@ -5,18 +5,18 @@ package org.whispersystems.textsecuregcm.configuration; -import java.util.HexFormat; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; -public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticationTokenSharedSecret, +public record SecureStorageServiceConfiguration(@NotNull SecretBytes userAuthenticationTokenSharedSecret, @NotBlank String uri, @NotEmpty List<@NotBlank String> storageCaCertificates, @Valid CircuitBreakerConfiguration circuitBreaker, @Valid RetryConfiguration retry) { - public SecureStorageServiceConfiguration { if (circuitBreaker == null) { circuitBreaker = new CircuitBreakerConfiguration(); @@ -25,8 +25,4 @@ public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticat retry = new RetryConfiguration(); } } - - public byte[] decodeUserAuthenticationTokenSharedSecret() { - return HexFormat.of().parseHex(userAuthenticationTokenSharedSecret); - } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureValueRecovery2Configuration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureValueRecovery2Configuration.java index 179888604..6e6b5470a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureValueRecovery2Configuration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureValueRecovery2Configuration.java @@ -9,13 +9,14 @@ import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; import org.whispersystems.textsecuregcm.util.ExactlySize; public record SecureValueRecovery2Configuration( boolean enabled, @NotBlank String uri, - @ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret, - @ExactlySize({32}) byte[] userIdTokenSharedSecret, + @ExactlySize(32) SecretBytes userAuthenticationTokenSharedSecret, + @ExactlySize(32) SecretBytes userIdTokenSharedSecret, @NotEmpty List<@NotBlank String> svrCaCertificates, @NotNull @Valid CircuitBreakerConfiguration circuitBreaker, @NotNull @Valid RetryConfiguration retry) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StripeConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StripeConfiguration.java index 4ff28adce..4682b0cde 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StripeConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/StripeConfiguration.java @@ -8,10 +8,12 @@ package org.whispersystems.textsecuregcm.configuration; import java.util.Set; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; -public record StripeConfiguration(@NotBlank String apiKey, - @NotEmpty byte[] idempotencyKeyGenerator, +public record StripeConfiguration(@NotNull SecretString apiKey, + @NotNull SecretBytes idempotencyKeyGenerator, @NotBlank String boostDescription, @NotEmpty Set<@NotBlank String> supportedCurrencies) { - } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java index 9b8968972..18971349e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/UnidentifiedDeliveryConfiguration.java @@ -1,48 +1,21 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import javax.validation.constraints.NotNull; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPrivateKey; -import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; +import org.whispersystems.textsecuregcm.util.ExactlySize; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - -public class UnidentifiedDeliveryConfiguration { - - @JsonProperty - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - @NotNull - private byte[] certificate; - - @JsonProperty - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - @NotNull - @Size(min = 32, max = 32) - private byte[] privateKey; - - @NotNull - private int expiresDays; - - public byte[] getCertificate() { - return certificate; - } - - public ECPrivateKey getPrivateKey() throws InvalidKeyException { - return Curve.decodePrivatePoint(privateKey); - } - - public int getExpiresDays() { - return expiresDays; +public record UnidentifiedDeliveryConfiguration(@NotNull SecretBytes certificate, + @ExactlySize(32) SecretBytes privateKey, + int expiresDays) { + public ECPrivateKey ecPrivateKey() throws InvalidKeyException { + return Curve.decodePrivatePoint(privateKey.value()); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ZkConfig.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ZkConfig.java index 044dcc1b5..a0aebf91f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ZkConfig.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/ZkConfig.java @@ -1,36 +1,14 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; - +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; -public class ZkConfig { - - @JsonProperty - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - @NotNull - private byte[] serverSecret; - - @JsonProperty - @JsonSerialize(using = ByteArrayAdapter.Serializing.class) - @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) - @NotNull - private byte[] serverPublic; - - public byte[] getServerSecret() { - return serverSecret; - } - - public byte[] getServerPublic() { - return serverPublic; - } +public record ZkConfig(@NotNull SecretBytes serverSecret, + @NotEmpty byte[] serverPublic) { } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/BaseSecretValidator.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/BaseSecretValidator.java new file mode 100644 index 000000000..d7887535e --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/BaseSecretValidator.java @@ -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> implements ConstraintValidator { + + private final ConstraintValidator validator; + + + protected BaseSecretValidator(final ConstraintValidator 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); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/Secret.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/Secret.java new file mode 100644 index 000000000..5f0b82921 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/Secret.java @@ -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 { + + private final T value; + + + public Secret(final T value) { + this.value = value; + } + + public T value() { + return value; + } + + @Override + public String toString() { + return "[REDACTED]"; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytes.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytes.java new file mode 100644 index 000000000..46205c43b --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytes.java @@ -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 { + + 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; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytesList.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytesList.java new file mode 100644 index 000000000..88dec09b2 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretBytesList.java @@ -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> { + + @SuppressWarnings("rawtypes") + public static class ValidatorNotEmpty extends BaseSecretValidator { + public ValidatorNotEmpty() { + super(new NotEmptyValidatorForCollection()); + } + } + + public SecretBytesList(final List value) { + super(ImmutableList.copyOf(value)); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStore.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStore.java new file mode 100644 index 000000000..03f3c0788 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStore.java @@ -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> secrets; + + + public static SecretStore fromYamlFileSecretsBundle(final String filename) { + try { + @SuppressWarnings("unchecked") + final Map 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> 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 secrets = secretStringList(reference).value(); + final List byteSecrets = secrets.stream().map(SecretStore::decodeBase64).toList(); + return new SecretBytesList(byteSecrets); + } + + private > T fromStore(final String name, final Class 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 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 secretsBundle) { + final Map> 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 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); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretString.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretString.java new file mode 100644 index 000000000..55591092b --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretString.java @@ -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 { + public SecretString(final String value) { + super(Validate.notBlank(value, "SecretString value must not be blank")); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStringList.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStringList.java new file mode 100644 index 000000000..e08fe5b54 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretStringList.java @@ -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> { + + @SuppressWarnings("rawtypes") + public static class ValidatorNotEmpty extends BaseSecretValidator { + public ValidatorNotEmpty() { + super(new NotEmptyValidatorForCollection()); + } + } + + public SecretStringList(final List value) { + super(ImmutableList.copyOf(value)); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsModule.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsModule.java new file mode 100644 index 000000000..e395b6573 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsModule.java @@ -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 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 JsonDeserializer createDeserializer(final BiFunction 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())); + } + }; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java index d8f4fa22e..86f99d079 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java @@ -27,15 +27,15 @@ public class ArtController { public static ExternalServiceCredentialsGenerator credentialsGenerator(final ArtServiceConfiguration cfg) { return ExternalServiceCredentialsGenerator - .builder(cfg.getUserAuthenticationTokenSharedSecret()) - .withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret()) + .builder(cfg.userAuthenticationTokenSharedSecret()) + .withUserDerivationKey(cfg.userAuthenticationTokenUserIdSecret()) .prependUsername(false) .truncateSignature(false) .build(); } - public ArtController(RateLimiters rateLimiters, - ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) { + public ArtController(final RateLimiters rateLimiters, + final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) { this.artServiceCredentialsGenerator = artServiceCredentialsGenerator; this.rateLimiters = rateLimiters; } @@ -44,7 +44,7 @@ public class ArtController { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) + public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) throws RateLimitExceededException { final UUID uuid = auth.getAccount().getUuid(); rateLimiters.getArtPackLimiter().validate(uuid); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java index 489c8ab3f..537e74500 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java @@ -41,7 +41,7 @@ public class DirectoryV2Controller { return credentialsGenerator(cfg, Clock.systemUTC()); } - public DirectoryV2Controller(ExternalServiceCredentialsGenerator userTokenGenerator) { + public DirectoryV2Controller(final ExternalServiceCredentialsGenerator userTokenGenerator) { this.directoryServiceTokenGenerator = userTokenGenerator; } @@ -49,7 +49,7 @@ public class DirectoryV2Controller { @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public Response getAuthToken(@Auth AuthenticatedAccount auth) { + public Response getAuthToken(final @Auth AuthenticatedAccount auth) { final UUID uuid = auth.getAccount().getUuid(); final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid); return Response.ok().entity(credentials).build(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java index 1d33d090d..7fb9a2b8c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java @@ -29,7 +29,7 @@ public class PaymentsController { public static ExternalServiceCredentialsGenerator credentialsGenerator(final PaymentsServiceConfiguration cfg) { return ExternalServiceCredentialsGenerator - .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .builder(cfg.userAuthenticationTokenSharedSecret()) .prependUsername(true) .build(); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java index e04ee41e5..5ada330bb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java @@ -28,8 +28,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsSelector; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsSelector; import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; import org.whispersystems.textsecuregcm.entities.AuthCheckRequest; import org.whispersystems.textsecuregcm.entities.AuthCheckResponse; @@ -56,7 +56,7 @@ public class SecureBackupController { final SecureBackupServiceConfiguration cfg, final Clock clock) { return ExternalServiceCredentialsGenerator - .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .builder(cfg.userAuthenticationTokenSharedSecret()) .prependUsername(true) .withClock(clock) .build(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java index b93126d9f..6f0d75bad 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java @@ -25,7 +25,7 @@ public class SecureStorageController { public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) { return ExternalServiceCredentialsGenerator - .builder(cfg.decodeUserAuthenticationTokenSharedSecret()) + .builder(cfg.userAuthenticationTokenSharedSecret()) .prependUsername(true) .build(); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java index f5c8b0764..858fe1cdb 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2Controller.java @@ -10,6 +10,11 @@ import io.dropwizard.auth.Auth; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import java.time.Clock; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; @@ -31,14 +36,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimitedByIp; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; -import org.whispersystems.textsecuregcm.util.UUIDUtil; -import java.time.Clock; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; @Path("/v2/backup") @Tag(name = "Secure Value Recovery") @@ -54,7 +51,7 @@ public class SecureValueRecovery2Controller { public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureValueRecovery2Configuration cfg, final Clock clock) { return ExternalServiceCredentialsGenerator .builder(cfg.userAuthenticationTokenSharedSecret()) - .withUserDerivationKey(cfg.userIdTokenSharedSecret()) + .withUserDerivationKey(cfg.userIdTokenSharedSecret().value()) .prependUsername(false) .withDerivedUsernameTruncateLength(16) .withClock(clock) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/LogstashTcpSocketAppenderFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/LogstashTcpSocketAppenderFactory.java index e199bab73..4c4d975fc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/LogstashTcpSocketAppenderFactory.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/LogstashTcpSocketAppenderFactory.java @@ -22,17 +22,27 @@ import io.dropwizard.logging.filter.LevelFilterFactory; import io.dropwizard.logging.layout.LayoutFactory; import java.time.Duration; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import net.logstash.logback.appender.LogstashTcpSocketAppender; import net.logstash.logback.encoder.LogstashEncoder; import org.whispersystems.textsecuregcm.WhisperServerVersion; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; import org.whispersystems.textsecuregcm.util.HostnameUtil; @JsonTypeName("logstashtcpsocket") public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory { + @JsonProperty private String destination; + + @JsonProperty private Duration keepAlive = Duration.ofSeconds(20); - private String apiKey; + + @JsonProperty + @NotNull + private SecretString apiKey; + + @JsonProperty private String environment; @JsonProperty @@ -47,8 +57,7 @@ public class LogstashTcpSocketAppenderFactory extends AbstractAppenderFactory prefix = new LayoutWrappingEncoder<>(); final PatternLayout layout = new PatternLayout(); - layout.setPattern(String.format("%s ", apiKey)); + layout.setPattern(String.format("%s ", apiKey.value())); prefix.setLayout(layout); encoder.setPrefix(prefix); appender.setEncoder(encoder); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/SignalDatadogReporterFactory.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/SignalDatadogReporterFactory.java index 3338aef59..a00247396 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/SignalDatadogReporterFactory.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/SignalDatadogReporterFactory.java @@ -1,3 +1,8 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* * This is derived from Coursera's dropwizard datadog reporter. * https://github.com/coursera/metrics-datadog @@ -10,6 +15,7 @@ import com.codahale.metrics.ScheduledReporter; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import io.dropwizard.metrics.BaseReporterFactory; +import io.dropwizard.util.Duration; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -20,8 +26,9 @@ import org.coursera.metrics.datadog.DatadogReporter.Expansion; import org.coursera.metrics.datadog.DefaultMetricNameFormatterFactory; import org.coursera.metrics.datadog.DynamicTagsCallbackFactory; import org.coursera.metrics.datadog.MetricNameFormatterFactory; -import org.coursera.metrics.datadog.transport.AbstractTransportFactory; +import org.coursera.metrics.datadog.transport.HttpTransport; import org.whispersystems.textsecuregcm.WhisperServerVersion; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretString; import org.whispersystems.textsecuregcm.util.HostnameUtil; @JsonTypeName("signal-datadog") @@ -44,8 +51,8 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory { @Valid @NotNull - @JsonProperty - private AbstractTransportFactory transport = null; + @JsonProperty("transport") + private HttpTransportConfig httpTransportConfig; private static final EnumSet EXPANSIONS = EnumSet.of( Expansion.COUNT, @@ -59,7 +66,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory { Expansion.P999 ); - public ScheduledReporter build(MetricRegistry registry) { + public ScheduledReporter build(final MetricRegistry registry) { final List tagsWithVersion; { @@ -74,7 +81,7 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory { } return DatadogReporter.forRegistry(registry) - .withTransport(transport.build()) + .withTransport(httpTransportConfig.httpTransport()) .withHost(HostnameUtil.getLocalHostname()) .withTags(tagsWithVersion) .withPrefix(prefix) @@ -86,4 +93,26 @@ public class SignalDatadogReporterFactory extends BaseReporterFactory { .convertRatesTo(getRateUnit()) .build(); } + + public static class HttpTransportConfig { + + @JsonProperty + @NotNull + private SecretString apiKey; + + @JsonProperty + private Duration connectTimeout = Duration.seconds(5); + + @JsonProperty + private Duration socketTimeout = Duration.seconds(5); + + + public HttpTransport httpTransport() { + return new HttpTransport.Builder() + .withApiKey(apiKey.value()) + .withConnectTimeout((int) connectTimeout.toMilliseconds()) + .withSocketTimeout((int) socketTimeout.toMilliseconds()) + .build(); + } + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java index 78c1bee76..15482ac0d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/APNSender.java @@ -1,9 +1,11 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.push; +import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; + import com.eatthepath.pushy.apns.ApnsClient; import com.eatthepath.pushy.apns.ApnsClientBuilder; import com.eatthepath.pushy.apns.DeliveryPriority; @@ -13,6 +15,8 @@ import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder; import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification; import com.google.common.annotations.VisibleForTesting; import io.dropwizard.lifecycle.Managed; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; import java.io.ByteArrayInputStream; import java.io.IOException; import java.security.InvalidKeyException; @@ -21,12 +25,8 @@ import java.time.Duration; import java.time.Instant; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; -import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; - public class APNSender implements Managed, PushNotificationSender { private final ExecutorService executor; @@ -61,12 +61,12 @@ public class APNSender implements Managed, PushNotificationSender { throws IOException, NoSuchAlgorithmException, InvalidKeyException { this.executor = executor; - this.bundleId = configuration.getBundleId(); + this.bundleId = configuration.bundleId(); this.apnsClient = new ApnsClientBuilder().setSigningKey( - ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.getSigningKey().getBytes()), - configuration.getTeamId(), configuration.getKeyId())) + ApnsSigningKey.loadFromInputStream(new ByteArrayInputStream(configuration.signingKey().value().getBytes()), + configuration.teamId(), configuration.keyId())) .setTrustedServerCertificateChain(getClass().getResourceAsStream(APNS_CA_FILENAME)) - .setApnsServer(configuration.isSandboxEnabled() ? ApnsClientBuilder.DEVELOPMENT_APNS_HOST : ApnsClientBuilder.PRODUCTION_APNS_HOST) + .setApnsServer(configuration.sandbox() ? ApnsClientBuilder.DEVELOPMENT_APNS_HOST : ApnsClientBuilder.PRODUCTION_APNS_HOST) .build(); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySize.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySize.java index d77a32ebd..0bb9416f8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySize.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySize.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -25,12 +25,12 @@ import javax.validation.Payload; ExactlySizeValidatorForString.class, ExactlySizeValidatorForArraysOfByte.class, ExactlySizeValidatorForCollection.class, + ExactlySizeValidatorForSecretBytes.class, }) @Documented public @interface ExactlySize { - String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize." + - "message}"; + String message() default "{org.whispersystems.textsecuregcm.util.ExactlySize.message}"; Class[] groups() default { }; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySizeValidatorForSecretBytes.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySizeValidatorForSecretBytes.java new file mode 100644 index 000000000..723237e06 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ExactlySizeValidatorForSecretBytes.java @@ -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 { + @Override + protected int size(final SecretBytes value) { + return value == null ? 0 : value.value().length; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java index 5578ba3b8..52bec59d6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/SystemMapper.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import javax.annotation.Nonnull; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretsModule; public class SystemMapper { @@ -37,6 +38,7 @@ public class SystemMapper { .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .setVisibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.PUBLIC_ONLY) .registerModules( + SecretsModule.INSTANCE, new JavaTimeModule(), new Jdk8Module()); } diff --git a/service/src/main/resources/META-INF/validation.xml b/service/src/main/resources/META-INF/validation.xml new file mode 100644 index 000000000..2a285fd33 --- /dev/null +++ b/service/src/main/resources/META-INF/validation.xml @@ -0,0 +1,8 @@ + + + META-INF/validation/constraints-custom.xml + diff --git a/service/src/main/resources/META-INF/validation/constraints-custom.xml b/service/src/main/resources/META-INF/validation/constraints-custom.xml new file mode 100644 index 000000000..e1d56701c --- /dev/null +++ b/service/src/main/resources/META-INF/validation/constraints-custom.xml @@ -0,0 +1,15 @@ + + + + + org.whispersystems.textsecuregcm.configuration.secrets.SecretStringList$ValidatorNotEmpty + org.whispersystems.textsecuregcm.configuration.secrets.SecretBytesList$ValidatorNotEmpty + + + + diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/CheckServiceConfigurations.java b/service/src/test/java/org/whispersystems/textsecuregcm/CheckServiceConfigurations.java index 08dac0247..e4fa8ac7e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/CheckServiceConfigurations.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/CheckServiceConfigurations.java @@ -15,19 +15,27 @@ import java.util.Arrays; */ public class CheckServiceConfigurations { + private static final String SECRETS_BUNDLE_FILENAME = "sample-secrets-bundle.yml"; + private void checkConfiguration(final File configDirectory) { final File[] configFiles = configDirectory.listFiles(f -> !f.isDirectory() - && f.getPath().endsWith(".yml")); + && f.getPath().endsWith(".yml") + && !f.getPath().endsWith(SECRETS_BUNDLE_FILENAME)); if (configFiles == null || configFiles.length == 0) { throw new IllegalArgumentException("No .yml configuration files found at " + configDirectory.getPath()); } - for (File configFile : configFiles) { - String[] args = new String[]{"check", configFile.getAbsolutePath()}; + final File[] secretsBundle = configDirectory.listFiles(f -> !f.isDirectory() && f.getName().equals(SECRETS_BUNDLE_FILENAME)); + if (secretsBundle == null || secretsBundle.length != 1) { + throw new IllegalArgumentException("No [%s] file found at %s".formatted(SECRETS_BUNDLE_FILENAME, configDirectory.getPath())); + } + System.setProperty(WhisperServerService.SECRETS_BUNDLE_FILE_NAME_PROPERTY, secretsBundle[0].getAbsolutePath()); + for (final File configFile : configFiles) { + final String[] args = new String[]{"check", configFile.getAbsolutePath()}; try { new WhisperServerService().run(args); } catch (final Exception e) { @@ -38,8 +46,7 @@ public class CheckServiceConfigurations { } } - public static void main(String[] args) { - + public static void main(final String[] args) { if (args.length != 1) { throw new IllegalArgumentException("Expected single argument with config directory: " + Arrays.toString(args)); } @@ -52,5 +59,4 @@ public class CheckServiceConfigurations { new CheckServiceConfigurations().checkConfiguration(configDirectory); } - } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsTest.java new file mode 100644 index 000000000..e5b645ff1 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/secrets/SecretsTest.java @@ -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 secretStringList = List.of("secret1", "secret2", "secret3"); + final List secretBytesList = List.of(RandomUtils.nextBytes(16), RandomUtils.nextBytes(16), RandomUtils.nextBytes(16)); + final List secretBytesListBase64 = secretBytesList.stream().map(Base64.getEncoder()::encodeToString).toList(); + final Map> 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()); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index 6e08109db..d0dc29e9a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; @@ -72,7 +73,6 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.usernames.BaseUsernameException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; @@ -99,7 +99,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationLock; import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure; import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashRequest; import org.whispersystems.textsecuregcm.entities.ReserveUsernameHashResponse; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.entities.UsernameHashResponse; import org.whispersystems.textsecuregcm.limits.RateLimitByIpFilter; import org.whispersystems.textsecuregcm.limits.RateLimiter; @@ -203,13 +202,13 @@ class AccountControllerTest { private static final SecureBackupServiceConfiguration SVR1_CFG = MockUtils.buildMock( SecureBackupServiceConfiguration.class, - cfg -> when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32])); + cfg -> when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32))); private static final SecureValueRecovery2Configuration SVR2_CFG = MockUtils.buildMock( SecureValueRecovery2Configuration.class, cfg -> { - when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(new byte[32]); - when(cfg.userIdTokenSharedSecret()).thenReturn(new byte[32]); + when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32)); + when(cfg.userIdTokenSharedSecret()).thenReturn(randomSecretBytes(32)); }); private static final ExternalServiceCredentialsGenerator svr1CredentialsGenerator = SecureBackupController.credentialsGenerator( diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java index 76e607eb2..4620a2b97 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureBackupControllerTest.java @@ -5,32 +5,17 @@ package org.whispersystems.textsecuregcm.controllers; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.ResourceExtension; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import org.apache.commons.lang3.RandomUtils; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; -import org.whispersystems.textsecuregcm.entities.AuthCheckRequest; -import org.whispersystems.textsecuregcm.entities.AuthCheckResponse; -import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.MockUtils; @@ -40,11 +25,11 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; @ExtendWith(DropwizardExtensionsSupport.class) class SecureBackupControllerTest extends SecureValueRecoveryControllerBaseTest { - private static final byte[] SECRET = RandomUtils.nextBytes(32); + private static final SecretBytes SECRET = randomSecretBytes(32); private static final SecureBackupServiceConfiguration CFG = MockUtils.buildMock( SecureBackupServiceConfiguration.class, - cfg -> Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(SECRET) + cfg -> Mockito.when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(SECRET) ); private static final MutableClock CLOCK = new MutableClock(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java index d8f469646..b1f094c35 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java @@ -7,10 +7,10 @@ package org.whispersystems.textsecuregcm.controllers; import static org.mockito.Mockito.mock; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.ResourceExtension; -import org.apache.commons.lang3.RandomUtils; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.jupiter.api.extension.ExtendWith; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; @@ -26,8 +26,8 @@ public class SecureValueRecovery2ControllerTest extends SecureValueRecoveryContr private static final SecureValueRecovery2Configuration CFG = new SecureValueRecovery2Configuration( true, "", - RandomUtils.nextBytes(32), - RandomUtils.nextBytes(32), + randomSecretBytes(32), + randomSecretBytes(32), null, null, null diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java index 81a6282a9..f76ff3f73 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import java.security.cert.CertificateException; @@ -53,7 +54,7 @@ class SecureStorageClientTest { httpExecutor = Executors.newSingleThreadExecutor(); final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration( - "not_used", + randomSecretBytes(32), "http://localhost:" + wireMock.getPort(), List.of(""" -----BEGIN CERTIFICATE----- diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java index 6e8b9cdd7..528a63e57 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import java.security.cert.CertificateException; @@ -53,7 +54,8 @@ class SecureValueRecovery2ClientTest { final SecureValueRecovery2Configuration config = new SecureValueRecovery2Configuration(true, "http://localhost:" + wireMock.getPort(), - new byte[0], new byte[0], + randomSecretBytes(32), + randomSecretBytes(32), // This is a randomly-generated, throwaway certificate that's not actually connected to anything List.of(""" -----BEGIN CERTIFICATE----- diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java index d4a9aadeb..cf5b5385a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ArtControllerTest.java @@ -8,15 +8,16 @@ package org.whispersystems.textsecuregcm.tests.controllers; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import com.google.common.collect.ImmutableSet; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.ResourceExtension; +import java.time.Duration; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; @@ -26,18 +27,12 @@ import org.whispersystems.textsecuregcm.controllers.ArtController; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; -import org.whispersystems.textsecuregcm.util.MockUtils; import org.whispersystems.textsecuregcm.util.SystemMapper; @ExtendWith(DropwizardExtensionsSupport.class) class ArtControllerTest { - - private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = MockUtils.buildMock( - ArtServiceConfiguration.class, - cfg -> { - Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]); - Mockito.when(cfg.getUserAuthenticationTokenUserIdSecret()).thenReturn(new byte[32]); - }); + private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = new ArtServiceConfiguration( + randomSecretBytes(32), randomSecretBytes(32), Duration.ofDays(1)); private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION); private static final RateLimiter rateLimiter = mock(RateLimiter.class); private static final RateLimiters rateLimiters = mock(RateLimiters.class); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerV2Test.java index 85ce0fa14..ad87358ae 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerV2Test.java @@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.tests.controllers; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.secretBytesOf; import java.time.Clock; import java.time.Instant; @@ -28,7 +29,7 @@ class DirectoryControllerV2Test { @Test void testAuthToken() { final ExternalServiceCredentialsGenerator credentialsGenerator = DirectoryV2Controller.credentialsGenerator( - new DirectoryV2ClientConfiguration(new byte[]{0x1}, new byte[]{0x2}), + new DirectoryV2ClientConfiguration(secretBytesOf(0x01), secretBytesOf(0x02)), Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC")) ); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java index 45238727a..8740c023a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/SecureStorageControllerTest.java @@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.tests.controllers; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.Mockito.when; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; import com.google.common.collect.ImmutableSet; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; @@ -31,7 +32,7 @@ class SecureStorageControllerTest { private static final SecureStorageServiceConfiguration STORAGE_CFG = MockUtils.buildMock( SecureStorageServiceConfiguration.class, - cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32])); + cfg -> when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(randomSecretBytes(32))); private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController .credentialsGenerator(STORAGE_CFG); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/MockUtils.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/MockUtils.java index 80eda4af6..c3161821f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/util/MockUtils.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/MockUtils.java @@ -12,7 +12,9 @@ import static org.mockito.Mockito.doThrow; import java.time.Duration; import java.util.Optional; +import org.apache.commons.lang3.RandomUtils; import org.mockito.Mockito; +import org.whispersystems.textsecuregcm.configuration.secrets.SecretBytes; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; @@ -70,4 +72,16 @@ public final class MockUtils { throw new RuntimeException(e); } } + + public static SecretBytes randomSecretBytes(final int size) { + return new SecretBytes(RandomUtils.nextBytes(size)); + } + + public static SecretBytes secretBytesOf(final int... byteVals) { + final byte[] bytes = new byte[byteVals.length]; + for (int i = 0; i < byteVals.length; i++) { + bytes[i] = (byte) byteVals[i]; + } + return new SecretBytes(bytes); + } }