From eb499833c6b80de25f2822e5eeb57d1db01ff523 Mon Sep 17 00:00:00 2001 From: Sergey Skrobotov Date: Wed, 25 Jan 2023 15:15:46 -0800 Subject: [PATCH] refactoring of ExternalServiceCredentialGenerator --- .../textsecuregcm/WhisperServerService.java | 42 +++---- .../ExternalServiceCredentialGenerator.java | 106 ---------------- .../ExternalServiceCredentialsGenerator.java | 114 ++++++++++++++++++ .../DirectoryV2ClientConfiguration.java | 28 +---- .../SecureStorageServiceConfiguration.java | 67 +++------- .../controllers/AccountController.java | 16 +-- .../controllers/ArtController.java | 27 ++++- .../controllers/AttachmentControllerV2.java | 4 +- .../controllers/DirectoryController.java | 18 ++- .../controllers/DirectoryV2Controller.java | 28 ++++- .../controllers/PaymentsController.java | 29 +++-- .../controllers/SecureBackupController.java | 22 +++- .../controllers/SecureStorageController.java | 25 +++- .../securebackup/SecureBackupClient.java | 23 ++-- .../securestorage/SecureStorageClient.java | 38 +++--- .../textsecuregcm/util/HeaderUtils.java | 17 ++- .../textsecuregcm/util/HmacUtils.java | 66 ++++++++++ .../textsecuregcm/util/HttpUtils.java | 17 +++ .../workers/AssignUsernameCommand.java | 16 +-- .../workers/DeleteUserCommand.java | 16 +-- .../SetUserDiscoverabilityCommand.java | 16 +-- .../controllers/AccountControllerTest.java | 16 ++- .../securebackup/SecureBackupClientTest.java | 16 +-- .../SecureStorageClientTest.java | 113 +++++++++-------- ...ternalServiceCredentialsGeneratorTest.java | 17 ++- .../tests/controllers/ArtControllerTest.java | 18 ++- .../controllers/DirectoryControllerTest.java | 6 +- .../DirectoryControllerV2Test.java | 14 ++- .../controllers/PaymentsControllerTest.java | 10 +- .../SecureStorageControllerTest.java | 18 ++- .../textsecuregcm/tests/util/AuthHelper.java | 15 +-- .../textsecuregcm/util/MockHelper.java | 31 +++++ 32 files changed, 594 insertions(+), 415 deletions(-) delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/auth/ExternalServiceCredentialGenerator.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/auth/ExternalServiceCredentialsGenerator.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/HmacUtils.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/HttpUtils.java create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/util/MockHelper.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 055b4c15f..a1fbad00c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm; @@ -80,16 +80,18 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.CertificateGenerator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener; import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter; import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator; import org.whispersystems.textsecuregcm.captcha.CaptchaChecker; import org.whispersystems.textsecuregcm.captcha.HCaptchaClient; +import org.whispersystems.textsecuregcm.captcha.RecaptchaClient; import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.controllers.AccountController; +import org.whispersystems.textsecuregcm.controllers.ArtController; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2; import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3; import org.whispersystems.textsecuregcm.controllers.CertificateController; @@ -108,15 +110,14 @@ import org.whispersystems.textsecuregcm.controllers.RemoteConfigController; import org.whispersystems.textsecuregcm.controllers.SecureBackupController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.StickerController; -import org.whispersystems.textsecuregcm.controllers.ArtController; import org.whispersystems.textsecuregcm.controllers.SubscriptionController; import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController; import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.currency.FixerClient; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; -import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter; import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter; +import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter; import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter; import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters; import org.whispersystems.textsecuregcm.limits.PushChallengeManager; @@ -158,7 +159,6 @@ import org.whispersystems.textsecuregcm.push.ProvisioningManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.ReceiptSender; -import org.whispersystems.textsecuregcm.captcha.RecaptchaClient; import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool; @@ -450,29 +450,23 @@ public class WhisperServerService extends Application 0 + ? hmac256TruncatedToHexString(userDerivationKey, identity, TRUNCATE_LENGTH) + : identity; + + final long currentTimeSeconds = TimeUnit.MILLISECONDS.toSeconds(clock.millis()); + + final String dataToSign = username + ":" + currentTimeSeconds; + + final String signature = truncateSignature + ? hmac256TruncatedToHexString(key, dataToSign, TRUNCATE_LENGTH) + : hmac256ToHexString(key, dataToSign); + + final String token = (prependUsername ? dataToSign : currentTimeSeconds) + ":" + signature; + + return new ExternalServiceCredentials(username, token); + } + + public static class Builder { + + private final byte[] key; + + private byte[] userDerivationKey = new byte[0]; + + private boolean prependUsername = true; + + private boolean truncateSignature = true; + + private Clock clock = Clock.systemUTC(); + + + private Builder(final byte[] key) { + this.key = requireNonNull(key); + } + + public Builder withUserDerivationKey(final byte[] userDerivationKey) { + Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty"); + this.userDerivationKey = userDerivationKey; + return this; + } + + public Builder withClock(final Clock clock) { + this.clock = requireNonNull(clock); + return this; + } + + public Builder prependUsername(final boolean prependUsername) { + this.prependUsername = prependUsername; + return this; + } + + public Builder truncateSignature(final boolean truncateSignature) { + this.truncateSignature = truncateSignature; + return this; + } + + public ExternalServiceCredentialsGenerator build() { + return new ExternalServiceCredentialsGenerator( + key, userDerivationKey, prependUsername, truncateSignature, clock); + } + } +} 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 9beba5d63..61965df5d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/DirectoryV2ClientConfiguration.java @@ -1,33 +1,11 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013-2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.configuration; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.textsecuregcm.util.ExactlySize; -public class DirectoryV2ClientConfiguration { - - private final byte[] userAuthenticationTokenSharedSecret; - private final byte[] userIdTokenSharedSecret; - - @JsonCreator - public DirectoryV2ClientConfiguration( - @JsonProperty("userAuthenticationTokenSharedSecret") final byte[] userAuthenticationTokenSharedSecret, - @JsonProperty("userIdTokenSharedSecret") final byte[] userIdTokenSharedSecret) { - this.userAuthenticationTokenSharedSecret = userAuthenticationTokenSharedSecret; - this.userIdTokenSharedSecret = userIdTokenSharedSecret; - } - - @ExactlySize({32}) - public byte[] getUserAuthenticationTokenSharedSecret() { - return userAuthenticationTokenSharedSecret; - } - - @ExactlySize({32}) - public byte[] getUserIdTokenSharedSecret() { - return userIdTokenSharedSecret; - } +public record DirectoryV2ClientConfiguration(@ExactlySize({32}) byte[] userAuthenticationTokenSharedSecret, + @ExactlySize({32}) byte[] userIdTokenSharedSecret) { } 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 9de901199..4a42bfbcd 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/SecureStorageServiceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -7,65 +7,28 @@ package org.whispersystems.textsecuregcm.configuration; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; +import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; -import java.util.List; -public class SecureStorageServiceConfiguration { +public record SecureStorageServiceConfiguration(@NotEmpty String userAuthenticationTokenSharedSecret, + @NotBlank String uri, + @NotEmpty List<@NotBlank String> storageCaCertificates, + @Valid @JsonProperty("circuitBreaker") CircuitBreakerConfiguration circuitBreakerConfig, + @Valid @JsonProperty("retry") RetryConfiguration retryConfig) { - @NotEmpty - @JsonProperty - private String userAuthenticationTokenSharedSecret; + @VisibleForTesting + public SecureStorageServiceConfiguration( + final @NotEmpty String userAuthenticationTokenSharedSecret, + final @NotBlank String uri, + final @NotEmpty List<@NotBlank String> storageCaCertificates) { + this(userAuthenticationTokenSharedSecret, uri, storageCaCertificates, new CircuitBreakerConfiguration(), new RetryConfiguration()); + } - @NotBlank - @JsonProperty - private String uri; - - @NotEmpty - @JsonProperty - private List<@NotBlank String> storageCaCertificates; - - @NotNull - @Valid - @JsonProperty - private CircuitBreakerConfiguration circuitBreaker = new CircuitBreakerConfiguration(); - - @NotNull - @Valid - @JsonProperty - private RetryConfiguration retry = new RetryConfiguration(); - - public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException { + public byte[] decodeUserAuthenticationTokenSharedSecret() throws DecoderException { return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray()); } - - @VisibleForTesting - public void setUri(final String uri) { - this.uri = uri; - } - - public String getUri() { - return uri; - } - - @VisibleForTesting - public void setStorageCaCertificates(final List certificatePem) { - this.storageCaCertificates = certificatePem; - } - - public List getStorageCaCertificates() { - return storageCaCertificates; - } - - public CircuitBreakerConfiguration getCircuitBreakerConfiguration() { - return circuitBreaker; - } - - public RetryConfiguration getRetryConfiguration() { - return retry; - } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 9092ece0d..dbc81563e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.controllers; @@ -61,8 +61,8 @@ import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.BasicAuthorizationHeader; import org.whispersystems.textsecuregcm.auth.ChangesDeviceEnabledState; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnToken; @@ -165,7 +165,7 @@ public class AccountController { private final Map testDevices; private final CaptchaChecker captchaChecker; private final PushNotificationManager pushNotificationManager; - private final ExternalServiceCredentialGenerator backupServiceCredentialGenerator; + private final ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator; private final ChangeNumberManager changeNumberManager; private final Clock clock; @@ -186,7 +186,7 @@ public class AccountController { CaptchaChecker captchaChecker, PushNotificationManager pushNotificationManager, ChangeNumberManager changeNumberManager, - ExternalServiceCredentialGenerator backupServiceCredentialGenerator, + ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator, ClientPresenceManager clientPresenceManager, Clock clock ) { @@ -199,7 +199,7 @@ public class AccountController { this.turnTokenGenerator = turnTokenGenerator; this.captchaChecker = captchaChecker; this.pushNotificationManager = pushNotificationManager; - this.backupServiceCredentialGenerator = backupServiceCredentialGenerator; + this.backupServiceCredentialsGenerator = backupServiceCredentialsGenerator; this.changeNumberManager = changeNumberManager; this.clientPresenceManager = clientPresenceManager; this.clock = clock; @@ -217,12 +217,12 @@ public class AccountController { CaptchaChecker captchaChecker, PushNotificationManager pushNotificationManager, ChangeNumberManager changeNumberManager, - ExternalServiceCredentialGenerator backupServiceCredentialGenerator + ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator ) { this(pendingAccounts, accounts, rateLimiters, registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, testDevices, captchaChecker, pushNotificationManager, changeNumberManager, - backupServiceCredentialGenerator, null, Clock.systemUTC()); + backupServiceCredentialsGenerator, null, Clock.systemUTC()); } @Timed @@ -832,7 +832,7 @@ public class AccountController { final StoredRegistrationLock existingRegistrationLock = existingAccount.getRegistrationLock(); final ExternalServiceCredentials existingBackupCredentials = - backupServiceCredentialGenerator.generateFor(existingAccount.getUuid().toString()); + backupServiceCredentialsGenerator.generateForUuid(existingAccount.getUuid()); if (existingRegistrationLock.requiresClientRegistrationLock()) { if (!Util.isEmpty(clientRegistrationLock)) { 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 8ae90c7a1..031998367 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArtController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -12,19 +12,34 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.apache.commons.codec.DecoderException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.limits.RateLimiters; @Path("/v1/art") public class ArtController { - private final ExternalServiceCredentialGenerator artServiceCredentialGenerator; + private final ExternalServiceCredentialsGenerator artServiceCredentialsGenerator; private final RateLimiters rateLimiters; + public static ExternalServiceCredentialsGenerator credentialsGenerator(final ArtServiceConfiguration cfg) { + try { + return ExternalServiceCredentialsGenerator + .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret()) + .prependUsername(false) + .truncateSignature(false) + .build(); + } catch (DecoderException e) { + throw new IllegalArgumentException(e); + } + } + public ArtController(RateLimiters rateLimiters, - ExternalServiceCredentialGenerator artServiceCredentialGenerator) { - this.artServiceCredentialGenerator = artServiceCredentialGenerator; + ExternalServiceCredentialsGenerator artServiceCredentialsGenerator) { + this.artServiceCredentialsGenerator = artServiceCredentialsGenerator; this.rateLimiters = rateLimiters; } @@ -36,6 +51,6 @@ public class ArtController { throws RateLimitExceededException { final UUID uuid = auth.getAccount().getUuid(); rateLimiters.getArtPackLimiter().validate(uuid); - return artServiceCredentialGenerator.generateFor(uuid.toString()); + return artServiceCredentialsGenerator.generateForUuid(uuid); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java index b0d5021af..fc8680c78 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AttachmentControllerV2.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -32,7 +32,7 @@ public class AttachmentControllerV2 { public AttachmentControllerV2(RateLimiters rateLimiters, String accessKey, String accessSecret, String region, String bucket) { - this.rateLimiter = rateLimiters.getAttachmentLimiter(); + this.rateLimiter = rateLimiters.getAttachmentLimiter(); this.policyGenerator = new PostPolicyGenerator(region, bucket, accessKey); this.policySigner = new PolicySigner(accessSecret, region); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java index 8953c545d..0c2801b0b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.controllers; @@ -13,15 +13,25 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.codec.DecoderException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.DirectoryClientConfiguration; @Path("/v1/directory") public class DirectoryController { - private final ExternalServiceCredentialGenerator directoryServiceTokenGenerator; + private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator; - public DirectoryController(ExternalServiceCredentialGenerator userTokenGenerator) { + public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryClientConfiguration cfg) + throws DecoderException { + return ExternalServiceCredentialsGenerator + .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .withUserDerivationKey(cfg.getUserAuthenticationTokenUserIdSecret()) + .build(); + } + + public DirectoryController(ExternalServiceCredentialsGenerator userTokenGenerator) { this.directoryServiceTokenGenerator = userTokenGenerator; } 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 8f16ee73d..ea221c195 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/DirectoryV2Controller.java @@ -1,11 +1,13 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.controllers; import com.codahale.metrics.annotation.Timed; +import com.google.common.annotations.VisibleForTesting; import io.dropwizard.auth.Auth; +import java.time.Clock; import java.util.UUID; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -13,15 +15,31 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration; @Path("/v2/directory") public class DirectoryV2Controller { - private final ExternalServiceCredentialGenerator directoryServiceTokenGenerator; + private final ExternalServiceCredentialsGenerator directoryServiceTokenGenerator; - public DirectoryV2Controller(ExternalServiceCredentialGenerator userTokenGenerator) { + @VisibleForTesting + public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryV2ClientConfiguration cfg, + final Clock clock) { + return ExternalServiceCredentialsGenerator + .builder(cfg.userAuthenticationTokenSharedSecret()) + .withUserDerivationKey(cfg.userIdTokenSharedSecret()) + .prependUsername(false) + .withClock(clock) + .build(); + } + + public static ExternalServiceCredentialsGenerator credentialsGenerator(final DirectoryV2ClientConfiguration cfg) { + return credentialsGenerator(cfg, Clock.systemUTC()); + } + + public DirectoryV2Controller(ExternalServiceCredentialsGenerator userTokenGenerator) { this.directoryServiceTokenGenerator = userTokenGenerator; } @@ -31,7 +49,7 @@ public class DirectoryV2Controller { @Produces(MediaType.APPLICATION_JSON) public Response getAuthToken(@Auth AuthenticatedAccount auth) { final UUID uuid = auth.getAccount().getUuid(); - final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateFor(uuid.toString()); + final ExternalServiceCredentials credentials = directoryServiceTokenGenerator.generateForUuid(uuid); return Response.ok().entity(credentials).build(); } } 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 f84f7a8c4..0e0ea530b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/PaymentsController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -11,36 +11,47 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.apache.commons.codec.DecoderException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList; @Path("/v1/payments") public class PaymentsController { - private final ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator; - private final CurrencyConversionManager currencyManager; + private final ExternalServiceCredentialsGenerator paymentsServiceCredentialsGenerator; + private final CurrencyConversionManager currencyManager; - public PaymentsController(CurrencyConversionManager currencyManager, ExternalServiceCredentialGenerator paymentsServiceCredentialGenerator) { + + public static ExternalServiceCredentialsGenerator credentialsGenerator(final PaymentsServiceConfiguration cfg) + throws DecoderException { + return ExternalServiceCredentialsGenerator + .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .prependUsername(true) + .build(); + } + + public PaymentsController(final CurrencyConversionManager currencyManager, final ExternalServiceCredentialsGenerator paymentsServiceCredentialsGenerator) { this.currencyManager = currencyManager; - this.paymentsServiceCredentialGenerator = paymentsServiceCredentialGenerator; + this.paymentsServiceCredentialsGenerator = paymentsServiceCredentialsGenerator; } @Timed @GET @Path("/auth") @Produces(MediaType.APPLICATION_JSON) - public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) { - return paymentsServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString()); + public ExternalServiceCredentials getAuth(final @Auth AuthenticatedAccount auth) { + return paymentsServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); } @Timed @GET @Path("/conversions") @Produces(MediaType.APPLICATION_JSON) - public CurrencyConversionEntityList getConversions(@Auth AuthenticatedAccount auth) { + public CurrencyConversionEntityList getConversions(final @Auth AuthenticatedAccount auth) { return currencyManager.getCurrencyConversions().orElseThrow(); } } 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 abc99c25a..a284576f9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureBackupController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -11,17 +11,27 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.apache.commons.codec.DecoderException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; @Path("/v1/backup") public class SecureBackupController { - private final ExternalServiceCredentialGenerator backupServiceCredentialGenerator; + private final ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator; - public SecureBackupController(ExternalServiceCredentialGenerator backupServiceCredentialGenerator) { - this.backupServiceCredentialGenerator = backupServiceCredentialGenerator; + public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureBackupServiceConfiguration cfg) + throws DecoderException { + return ExternalServiceCredentialsGenerator + .builder(cfg.getUserAuthenticationTokenSharedSecret()) + .prependUsername(true) + .build(); + } + + public SecureBackupController(ExternalServiceCredentialsGenerator backupServiceCredentialsGenerator) { + this.backupServiceCredentialsGenerator = backupServiceCredentialsGenerator; } @Timed @@ -29,6 +39,6 @@ public class SecureBackupController { @Path("/auth") @Produces(MediaType.APPLICATION_JSON) public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) { - return backupServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString()); + return backupServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); } } 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 af9a564f1..b83a93e5a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SecureStorageController.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -11,17 +11,30 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.apache.commons.codec.DecoderException; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; @Path("/v1/storage") public class SecureStorageController { - private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator; + private final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator; - public SecureStorageController(ExternalServiceCredentialGenerator storageServiceCredentialGenerator) { - this.storageServiceCredentialGenerator = storageServiceCredentialGenerator; + public static ExternalServiceCredentialsGenerator credentialsGenerator(final SecureStorageServiceConfiguration cfg) { + try { + return ExternalServiceCredentialsGenerator + .builder(cfg.decodeUserAuthenticationTokenSharedSecret()) + .prependUsername(true) + .build(); + } catch (DecoderException e) { + throw new IllegalArgumentException(e); + } + } + + public SecureStorageController(ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator) { + this.storageServiceCredentialsGenerator = storageServiceCredentialsGenerator; } @Timed @@ -29,6 +42,6 @@ public class SecureStorageController { @Path("/auth") @Produces(MediaType.APPLICATION_JSON) public ExternalServiceCredentials getAuth(@Auth AuthenticatedAccount auth) { - return storageServiceCredentialGenerator.generateFor(auth.getAccount().getUuid().toString()); + return storageServiceCredentialsGenerator.generateForUuid(auth.getAccount().getUuid()); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClient.java index 3d9db9c44..52a4628de 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClient.java @@ -1,41 +1,43 @@ /* - * Copyright 2021 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.securebackup; +import static org.whispersystems.textsecuregcm.util.HeaderUtils.basicAuthHeader; + import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.HttpHeaders; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.time.Duration; -import java.util.Base64; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; +import org.whispersystems.textsecuregcm.util.HttpUtils; /** * A client for sending requests to Signal's secure value recovery service on behalf of authenticated users. */ public class SecureBackupClient { - private final ExternalServiceCredentialGenerator secureBackupCredentialGenerator; + private final ExternalServiceCredentialsGenerator secureBackupCredentialsGenerator; private final URI deleteUri; private final FaultTolerantHttpClient httpClient; @VisibleForTesting static final String DELETE_PATH = "/v1/backup"; - public SecureBackupClient(final ExternalServiceCredentialGenerator secureBackupCredentialGenerator, final Executor executor, final SecureBackupServiceConfiguration configuration) throws CertificateException { - this.secureBackupCredentialGenerator = secureBackupCredentialGenerator; + public SecureBackupClient(final ExternalServiceCredentialsGenerator secureBackupCredentialsGenerator, final Executor executor, final SecureBackupServiceConfiguration configuration) throws CertificateException { + this.secureBackupCredentialsGenerator = secureBackupCredentialsGenerator; this.deleteUri = URI.create(configuration.getUri()).resolve(DELETE_PATH); this.httpClient = FaultTolerantHttpClient.newBuilder() .withCircuitBreaker(configuration.getCircuitBreakerConfiguration()) @@ -51,17 +53,16 @@ public class SecureBackupClient { } public CompletableFuture deleteBackups(final UUID accountUuid) { - final ExternalServiceCredentials credentials = secureBackupCredentialGenerator.generateFor(accountUuid.toString()); + final ExternalServiceCredentials credentials = secureBackupCredentialsGenerator.generateForUuid(accountUuid); final HttpRequest request = HttpRequest.newBuilder() .uri(deleteUri) .DELETE() - .header("Authorization", "Basic " + Base64.getEncoder().encodeToString( - (credentials.username() + ":" + credentials.password()).getBytes(StandardCharsets.UTF_8))) + .header(HttpHeaders.AUTHORIZATION, basicAuthHeader(credentials)) .build(); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> { - if (response.statusCode() >= 200 && response.statusCode() < 300) { + if (HttpUtils.isSuccessfulResponse(response.statusCode())) { return null; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java index cc6f12275..9e65b416f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java @@ -1,68 +1,68 @@ /* - * Copyright 2020 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.securestorage; -import com.google.common.annotations.VisibleForTesting; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; -import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; -import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; +import static org.whispersystems.textsecuregcm.util.HeaderUtils.basicAuthHeader; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.HttpHeaders; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.time.Duration; -import java.util.Base64; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; +import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; +import org.whispersystems.textsecuregcm.util.HttpUtils; /** * A client for sending requests to Signal's secure storage service on behalf of authenticated users. */ public class SecureStorageClient { - private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator; + private final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator; private final URI deleteUri; private final FaultTolerantHttpClient httpClient; @VisibleForTesting static final String DELETE_PATH = "/v1/storage"; - public SecureStorageClient(final ExternalServiceCredentialGenerator storageServiceCredentialGenerator, final Executor executor, final SecureStorageServiceConfiguration configuration) throws CertificateException { - this.storageServiceCredentialGenerator = storageServiceCredentialGenerator; - this.deleteUri = URI.create(configuration.getUri()).resolve(DELETE_PATH); + public SecureStorageClient(final ExternalServiceCredentialsGenerator storageServiceCredentialsGenerator, final Executor executor, final SecureStorageServiceConfiguration configuration) throws CertificateException { + this.storageServiceCredentialsGenerator = storageServiceCredentialsGenerator; + this.deleteUri = URI.create(configuration.uri()).resolve(DELETE_PATH); this.httpClient = FaultTolerantHttpClient.newBuilder() - .withCircuitBreaker(configuration.getCircuitBreakerConfiguration()) - .withRetry(configuration.getRetryConfiguration()) + .withCircuitBreaker(configuration.circuitBreakerConfig()) + .withRetry(configuration.retryConfig()) .withVersion(HttpClient.Version.HTTP_1_1) .withConnectTimeout(Duration.ofSeconds(10)) .withRedirect(HttpClient.Redirect.NEVER) .withExecutor(executor) .withName("secure-storage") .withSecurityProtocol(FaultTolerantHttpClient.SECURITY_PROTOCOL_TLS_1_3) - .withTrustedServerCertificates(configuration.getStorageCaCertificates().toArray(new String[0])) + .withTrustedServerCertificates(configuration.storageCaCertificates().toArray(new String[0])) .build(); } public CompletableFuture deleteStoredData(final UUID accountUuid) { - final ExternalServiceCredentials credentials = storageServiceCredentialGenerator.generateFor(accountUuid.toString()); + final ExternalServiceCredentials credentials = storageServiceCredentialsGenerator.generateForUuid(accountUuid); final HttpRequest request = HttpRequest.newBuilder() .uri(deleteUri) .DELETE() - .header("Authorization", "Basic " + Base64.getEncoder().encodeToString( - (credentials.username() + ":" + credentials.password()).getBytes(StandardCharsets.UTF_8))) + .header(HttpHeaders.AUTHORIZATION, basicAuthHeader(credentials)) .build(); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> { - if (response.statusCode() >= 200 && response.statusCode() < 300) { + if (HttpUtils.isSuccessfulResponse(response.statusCode())) { return null; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/HeaderUtils.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/HeaderUtils.java index 9fd7b61e1..85c3efc47 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/HeaderUtils.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/HeaderUtils.java @@ -1,14 +1,19 @@ /* - * Copyright 2022 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.util; +import static java.util.Objects.requireNonNull; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; public final class HeaderUtils { @@ -22,6 +27,16 @@ public final class HeaderUtils { // utility class } + public static String basicAuthHeader(final ExternalServiceCredentials credentials) { + return basicAuthHeader(credentials.username(), credentials.password()); + } + + public static String basicAuthHeader(final String username, final String password) { + requireNonNull(username); + requireNonNull(password); + return "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + } + @Nonnull public static String getTimestampHeader() { return TIMESTAMP_HEADER + ":" + System.currentTimeMillis(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/HmacUtils.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/HmacUtils.java new file mode 100644 index 000000000..89951f204 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/HmacUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import java.nio.charset.StandardCharsets; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +public final class HmacUtils { + + private static final HexFormat HEX = HexFormat.of(); + + private static final String HMAC_SHA_256 = "HmacSHA256"; + + private static final ThreadLocal THREAD_LOCAL_HMAC_SHA_256 = ThreadLocal.withInitial(() -> { + try { + return Mac.getInstance(HMAC_SHA_256); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + + public static byte[] hmac256(final byte[] key, final byte[] input) { + try { + final Mac mac = THREAD_LOCAL_HMAC_SHA_256.get(); + mac.init(new SecretKeySpec(key, HMAC_SHA_256)); + return mac.doFinal(input); + } catch (final InvalidKeyException e) { + throw new RuntimeException(e); + } + } + + public static byte[] hmac256(final byte[] key, final String input) { + return hmac256(key, input.getBytes(StandardCharsets.UTF_8)); + } + + public static String hmac256ToHexString(final byte[] key, final byte[] input) { + return HEX.formatHex(hmac256(key, input)); + } + + public static String hmac256ToHexString(final byte[] key, final String input) { + return hmac256ToHexString(key, input.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] hmac256Truncated(final byte[] key, final byte[] input, final int length) { + return Util.truncate(hmac256(key, input), length); + } + + public static byte[] hmac256Truncated(final byte[] key, final String input, final int length) { + return hmac256Truncated(key, input.getBytes(StandardCharsets.UTF_8), length); + } + + public static String hmac256TruncatedToHexString(final byte[] key, final byte[] input, final int length) { + return HEX.formatHex(Util.truncate(hmac256(key, input), length)); + } + + public static String hmac256TruncatedToHexString(final byte[] key, final String input, final int length) { + return hmac256TruncatedToHexString(key, input.getBytes(StandardCharsets.UTF_8), length); + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/HttpUtils.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/HttpUtils.java new file mode 100644 index 000000000..4f5169fd5 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/HttpUtils.java @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +public final class HttpUtils { + + private HttpUtils() { + // utility class + } + + public static boolean isSuccessfulResponse(final int statusCode) { + return statusCode >= 200 && statusCode < 300; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java index eeadff1a9..1e329f6df 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/AssignUsernameCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -23,8 +23,10 @@ import java.util.concurrent.Executors; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.controllers.SecureBackupController; +import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; @@ -103,11 +105,11 @@ public class AssignUsernameCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java index b41d459c5..7b7f9af1c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/DeleteUserCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -25,8 +25,10 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.controllers.SecureBackupController; +import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; @@ -105,11 +107,11 @@ public class DeleteUserCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java index d2b4d0608..8c6b9cc91 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/SetUserDiscoverabilityCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -24,8 +24,10 @@ import java.util.concurrent.Executors; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.controllers.SecureBackupController; +import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; @@ -108,11 +110,11 @@ public class SetUserDiscoverabilityCommand extends EnvironmentCommand dynamicConfigurationManager = new DynamicConfigurationManager<>( configuration.getAppConfig().getApplication(), configuration.getAppConfig().getEnvironment(), configuration.getAppConfig().getConfigurationName(), DynamicConfiguration.class); 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 be00c1578..0593296f3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -65,12 +65,13 @@ import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator; import org.whispersystems.textsecuregcm.captcha.AssessmentResult; import org.whispersystems.textsecuregcm.captcha.CaptchaChecker; +import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.entities.AccountAttributes; @@ -111,6 +112,7 @@ import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundExcep import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.Hex; +import org.whispersystems.textsecuregcm.util.MockHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.TestClock; @@ -169,7 +171,13 @@ class AccountControllerTest { private static DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); private byte[] registration_lock_key = new byte[32]; - private static ExternalServiceCredentialGenerator storageCredentialGenerator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false); + + private static final SecureStorageServiceConfiguration STORAGE_CFG = MockHelper.buildMock( + SecureStorageServiceConfiguration.class, + cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32])); + + private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController + .credentialsGenerator(STORAGE_CFG); private static final ResourceExtension resources = ResourceExtension.builder() .addProvider(AuthHelper.getAuthFilter()) @@ -192,7 +200,7 @@ class AccountControllerTest { captchaChecker, pushNotificationManager, changeNumberManager, - storageCredentialGenerator, + STORAGE_CREDENTIAL_GENERATOR, clientPresenceManager, testClock)) .build(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClientTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClientTest.java index 7f3f0a00d..aaa6df37f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securebackup/SecureBackupClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -23,18 +23,18 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration; class SecureBackupClientTest { private UUID accountUuid; - private ExternalServiceCredentialGenerator credentialGenerator; + private ExternalServiceCredentialsGenerator credentialsGenerator; private ExecutorService httpExecutor; private SecureBackupClient secureStorageClient; @@ -47,7 +47,7 @@ class SecureBackupClientTest { @BeforeEach void setUp() throws CertificateException { accountUuid = UUID.randomUUID(); - credentialGenerator = mock(ExternalServiceCredentialGenerator.class); + credentialsGenerator = mock(ExternalServiceCredentialsGenerator.class); httpExecutor = Executors.newSingleThreadExecutor(); final SecureBackupServiceConfiguration config = new SecureBackupServiceConfiguration(); @@ -101,7 +101,7 @@ class SecureBackupClientTest { -----END CERTIFICATE----- """)); - secureStorageClient = new SecureBackupClient(credentialGenerator, httpExecutor, config); + secureStorageClient = new SecureBackupClient(credentialsGenerator, httpExecutor, config); } @AfterEach @@ -115,7 +115,7 @@ class SecureBackupClientTest { final String username = RandomStringUtils.randomAlphabetic(16); final String password = RandomStringUtils.randomAlphanumeric(32); - when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password)); wireMock.stubFor(delete(urlEqualTo(SecureBackupClient.DELETE_PATH)) .withBasicAuth(username, password) @@ -130,7 +130,7 @@ class SecureBackupClientTest { final String username = RandomStringUtils.randomAlphabetic(16); final String password = RandomStringUtils.randomAlphanumeric(32); - when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password)); wireMock.stubFor(delete(urlEqualTo(SecureBackupClient.DELETE_PATH)) .withBasicAuth(username, password) 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 ce6feddad..b632bba6f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -27,14 +27,14 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; class SecureStorageClientTest { private UUID accountUuid; - private ExternalServiceCredentialGenerator credentialGenerator; + private ExternalServiceCredentialsGenerator credentialsGenerator; private ExecutorService httpExecutor; private SecureStorageClient secureStorageClient; @@ -47,61 +47,60 @@ class SecureStorageClientTest { @BeforeEach void setUp() throws CertificateException { accountUuid = UUID.randomUUID(); - credentialGenerator = mock(ExternalServiceCredentialGenerator.class); + credentialsGenerator = mock(ExternalServiceCredentialsGenerator.class); httpExecutor = Executors.newSingleThreadExecutor(); - final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration(); - config.setUri("http://localhost:" + wireMock.getPort()); + final SecureStorageServiceConfiguration config = new SecureStorageServiceConfiguration( + "not_used", + "http://localhost:" + wireMock.getPort(), + List.of(""" + -----BEGIN CERTIFICATE----- + MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEL + MAkGA1UECAwCVVMxHjAcBgNVBAoMFVNpZ25hbCBNZXNzZW5nZXIsIExMQzETMBEG + A1UEAwwKc2lnbmFsLm9yZzAeFw0yMDEyMjMyMjQ3NTlaFw0zMDEyMjEyMjQ3NTla + ME8xCzAJBgNVBAYTAnVzMQswCQYDVQQIDAJVUzEeMBwGA1UECgwVU2lnbmFsIE1l + c3NlbmdlciwgTExDMRMwEQYDVQQDDApzaWduYWwub3JnMIGfMA0GCSqGSIb3DQEB + AQUAA4GNADCBiQKBgQCfSLcZNHYqbxSsgWp4JvbPRHjQTrlsrKrgD2q7f/OY6O3Y + /X0QNcNSOJpliN8rmzwslfsrXHO3q1diGRw4xHogUJZ/7NQrHiP/zhN0VTDh49pD + ZpjXVyUbayLS/6qM5arKxBspzEFBb5v8cF6bPr76SO/rpGXiI0j6yJKX6fRiKwID + AQABo1AwTjAdBgNVHQ4EFgQU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwHwYDVR0jBBgw + FoAU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B + AQ0FAAOBgQB+5d5+NtzLILfrc9QmJdIO1YeDP64JmFwTER0kEUouRsb9UwknVWZa + y7MTM4NoBV1k0zb5LAk89SIDPr/maW5AsLtEomzjnEiomjoMBUdNe3YCgQReoLnr + R/QaUNbrCjTGYfBsjGbIzmkWPUyTec2ZdRyJ8JiVl386+6CZkxnndQ== + -----END CERTIFICATE----- + """, + """ + -----BEGIN CERTIFICATE----- + MIIEpDCCAowCCQC43PUTWSADVjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls + b2NhbGhvc3QwHhcNMjIxMDE3MjA0NTM0WhcNMjMxMDE3MjA0NTM0WjAUMRIwEAYD + VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDV + x1cdEd2ffQTlTXWRiCHGcrlYf4RJnctt9sw/BuHWTLXBu5LhyJSGn5LRszO/NCXK + Z/cmGR7pLj366RtiwL+Qo3nhvDCK7T9xZeNIusM6XMcMK9D/DGCYPqtjQz8NXd9V + ajBBe6nwTDTa+oqX8Mt89foWNkg5Il/lY62u9Dr18LRZ2W9zzYi3Q9/K0CbIX6pM + yVlPIO5rITOR2IsbeyqsO9jufgX5lP4ZKLLBAP1b7usjC4YdvWacjQg/rK5aay1x + jC2HCDgo/4N30QVXzSA9nFfSe6AE/xkStK4819JqOkY5JsJCbef1P3hOOdSLEjbp + xq3MjOs6G6dOgteaAGs10vx7dHxDWETTIiD7BIZ9zRYgOF5bkCaIUO+JfySE1MHD + KBAFLoRuvmRev5Ln5R0MCHpUMSmMNgJqz+RWZV3g/gpYbuWiHgJOwL1393eK50Bg + W7SXQ8EjJj2yXZSH+1gPzN0DRoJZiaBoTPnCL2qUgvwFpW1PJsM5FDyUJFUoK5kK + HLBBSKAPt6ZlSrUe2nBgJv7EF1GK+fTU08LXgW33OpLceGPa0zTShkukQUMtUtZ8 + GqhO12ohMzEupIu5Xurthq4VVUrzHUdj1ZZRMhAbfLU36sd03MMyL/xBqTN6dzCa + GDGIPGpYjAllZ5xMRt2kZdv+Kr6oo3u2nLUIsqI7KQIDAQABMA0GCSqGSIb3DQEB + CwUAA4ICAQCB5s43YF35ssf5YONW5iAaifGpi1o0866xfeOybtohFGvQ7V2W34i9 + TYBCt8+0hgatMcvZ08f0vqig1i7nrvYcE1hnhL7JNkU8qm0s9ytHZt6j62nB0kd/ + uqE2hOEQalTf/2TGPV0CCgiqLyd8lEUQvQeA38wktwUeZpVnErlzHeMR2CvV3K8R + u4vV6SnBcf+TAt56RKYZkPyvZj5llQPo14Glyoo8qZES7Ky1SHmM0GL+baPRBjRW + 3KgSt98Wyu4yr9qu21JpnbAnLhBfzfSKjSeCRgFElUE1GIaFGRZ7ypA74dUKeLnb + /VUWrszmUhGaEjV9dpI6x6B/kSpQMtIQqBaKRY2ALUeEujS/rURi4iMDwSU+GkSH + cyEvZKS97OA/dWeXfLXdo4beDBRG93bI4rQnDg5+VdlBOkQSLueb8x6/VThMoC5d + vZiotFQHseljQAdTkNa6tBu6c4XDYPCKB3CfkMYOlCfTS7Acn5G6dxTPKBtLGBnL + nQfYyzuwYkN09+2PVzt6auBHr3To7uoclkxX+hxyvPIwIZ0N6b4tQR1FCAkvg29Q + WIOjZOKGW690ESKCKOnFjUHVO0HpuWnT81URTuY62FXsYdVc2wE4v0E04mEbqQ0P + lY6ZKNA81Lm3YADYtObmK1IUrOPo9BeIaPy0UM08SmN880Vunqa91Q== + -----END CERTIFICATE----- + """)); - // This is a randomly-generated, throwaway certificate that's not actually connected to anything - config.setStorageCaCertificates(List.of(""" - -----BEGIN CERTIFICATE----- - MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEL - MAkGA1UECAwCVVMxHjAcBgNVBAoMFVNpZ25hbCBNZXNzZW5nZXIsIExMQzETMBEG - A1UEAwwKc2lnbmFsLm9yZzAeFw0yMDEyMjMyMjQ3NTlaFw0zMDEyMjEyMjQ3NTla - ME8xCzAJBgNVBAYTAnVzMQswCQYDVQQIDAJVUzEeMBwGA1UECgwVU2lnbmFsIE1l - c3NlbmdlciwgTExDMRMwEQYDVQQDDApzaWduYWwub3JnMIGfMA0GCSqGSIb3DQEB - AQUAA4GNADCBiQKBgQCfSLcZNHYqbxSsgWp4JvbPRHjQTrlsrKrgD2q7f/OY6O3Y - /X0QNcNSOJpliN8rmzwslfsrXHO3q1diGRw4xHogUJZ/7NQrHiP/zhN0VTDh49pD - ZpjXVyUbayLS/6qM5arKxBspzEFBb5v8cF6bPr76SO/rpGXiI0j6yJKX6fRiKwID - AQABo1AwTjAdBgNVHQ4EFgQU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwHwYDVR0jBBgw - FoAU6Jrs/Fmj0z4dA3wvdq/WqA4P49IwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B - AQ0FAAOBgQB+5d5+NtzLILfrc9QmJdIO1YeDP64JmFwTER0kEUouRsb9UwknVWZa - y7MTM4NoBV1k0zb5LAk89SIDPr/maW5AsLtEomzjnEiomjoMBUdNe3YCgQReoLnr - R/QaUNbrCjTGYfBsjGbIzmkWPUyTec2ZdRyJ8JiVl386+6CZkxnndQ== - -----END CERTIFICATE----- - """, - """ - -----BEGIN CERTIFICATE----- - MIIEpDCCAowCCQC43PUTWSADVjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls - b2NhbGhvc3QwHhcNMjIxMDE3MjA0NTM0WhcNMjMxMDE3MjA0NTM0WjAUMRIwEAYD - VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDV - x1cdEd2ffQTlTXWRiCHGcrlYf4RJnctt9sw/BuHWTLXBu5LhyJSGn5LRszO/NCXK - Z/cmGR7pLj366RtiwL+Qo3nhvDCK7T9xZeNIusM6XMcMK9D/DGCYPqtjQz8NXd9V - ajBBe6nwTDTa+oqX8Mt89foWNkg5Il/lY62u9Dr18LRZ2W9zzYi3Q9/K0CbIX6pM - yVlPIO5rITOR2IsbeyqsO9jufgX5lP4ZKLLBAP1b7usjC4YdvWacjQg/rK5aay1x - jC2HCDgo/4N30QVXzSA9nFfSe6AE/xkStK4819JqOkY5JsJCbef1P3hOOdSLEjbp - xq3MjOs6G6dOgteaAGs10vx7dHxDWETTIiD7BIZ9zRYgOF5bkCaIUO+JfySE1MHD - KBAFLoRuvmRev5Ln5R0MCHpUMSmMNgJqz+RWZV3g/gpYbuWiHgJOwL1393eK50Bg - W7SXQ8EjJj2yXZSH+1gPzN0DRoJZiaBoTPnCL2qUgvwFpW1PJsM5FDyUJFUoK5kK - HLBBSKAPt6ZlSrUe2nBgJv7EF1GK+fTU08LXgW33OpLceGPa0zTShkukQUMtUtZ8 - GqhO12ohMzEupIu5Xurthq4VVUrzHUdj1ZZRMhAbfLU36sd03MMyL/xBqTN6dzCa - GDGIPGpYjAllZ5xMRt2kZdv+Kr6oo3u2nLUIsqI7KQIDAQABMA0GCSqGSIb3DQEB - CwUAA4ICAQCB5s43YF35ssf5YONW5iAaifGpi1o0866xfeOybtohFGvQ7V2W34i9 - TYBCt8+0hgatMcvZ08f0vqig1i7nrvYcE1hnhL7JNkU8qm0s9ytHZt6j62nB0kd/ - uqE2hOEQalTf/2TGPV0CCgiqLyd8lEUQvQeA38wktwUeZpVnErlzHeMR2CvV3K8R - u4vV6SnBcf+TAt56RKYZkPyvZj5llQPo14Glyoo8qZES7Ky1SHmM0GL+baPRBjRW - 3KgSt98Wyu4yr9qu21JpnbAnLhBfzfSKjSeCRgFElUE1GIaFGRZ7ypA74dUKeLnb - /VUWrszmUhGaEjV9dpI6x6B/kSpQMtIQqBaKRY2ALUeEujS/rURi4iMDwSU+GkSH - cyEvZKS97OA/dWeXfLXdo4beDBRG93bI4rQnDg5+VdlBOkQSLueb8x6/VThMoC5d - vZiotFQHseljQAdTkNa6tBu6c4XDYPCKB3CfkMYOlCfTS7Acn5G6dxTPKBtLGBnL - nQfYyzuwYkN09+2PVzt6auBHr3To7uoclkxX+hxyvPIwIZ0N6b4tQR1FCAkvg29Q - WIOjZOKGW690ESKCKOnFjUHVO0HpuWnT81URTuY62FXsYdVc2wE4v0E04mEbqQ0P - lY6ZKNA81Lm3YADYtObmK1IUrOPo9BeIaPy0UM08SmN880Vunqa91Q== - -----END CERTIFICATE----- - """)); - - secureStorageClient = new SecureStorageClient(credentialGenerator, httpExecutor, config); + secureStorageClient = new SecureStorageClient(credentialsGenerator, httpExecutor, config); } @AfterEach @@ -115,7 +114,7 @@ class SecureStorageClientTest { final String username = RandomStringUtils.randomAlphabetic(16); final String password = RandomStringUtils.randomAlphanumeric(32); - when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password)); wireMock.stubFor(delete(urlEqualTo(SecureStorageClient.DELETE_PATH)) .withBasicAuth(username, password) @@ -130,7 +129,7 @@ class SecureStorageClientTest { final String username = RandomStringUtils.randomAlphabetic(16); final String password = RandomStringUtils.randomAlphanumeric(32); - when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(new ExternalServiceCredentials(username, password)); wireMock.stubFor(delete(urlEqualTo(SecureStorageClient.DELETE_PATH)) .withBasicAuth(username, password) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/ExternalServiceCredentialsGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/ExternalServiceCredentialsGeneratorTest.java index a2d7d0773..a993ee88b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/ExternalServiceCredentialsGeneratorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/auth/ExternalServiceCredentialsGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -8,15 +8,18 @@ package org.whispersystems.textsecuregcm.tests.auth; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import org.junit.jupiter.api.Test; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; class ExternalServiceCredentialsGeneratorTest { @Test void testGenerateDerivedUsername() { - ExternalServiceCredentialGenerator generator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32]); - ExternalServiceCredentials credentials = generator.generateFor("+14152222222"); + final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator + .builder(new byte[32]) + .withUserDerivationKey(new byte[32]) + .build(); + final ExternalServiceCredentials credentials = generator.generateFor("+14152222222"); assertThat(credentials.username()).isNotEqualTo("+14152222222"); assertThat(credentials.password().startsWith("+14152222222")).isFalse(); @@ -24,8 +27,10 @@ class ExternalServiceCredentialsGeneratorTest { @Test void testGenerateNoDerivedUsername() { - ExternalServiceCredentialGenerator generator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false); - ExternalServiceCredentials credentials = generator.generateFor("+14152222222"); + final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator + .builder(new byte[32]) + .build(); + final ExternalServiceCredentials credentials = generator.generateFor("+14152222222"); assertThat(credentials.username()).isEqualTo("+14152222222"); assertThat(credentials.password().startsWith("+14152222222")).isTrue(); 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 33bb97b63..7136657ff 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -16,21 +16,29 @@ import io.dropwizard.testing.junit5.ResourceExtension; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration; import org.whispersystems.textsecuregcm.controllers.ArtController; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.MockHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; @ExtendWith(DropwizardExtensionsSupport.class) class ArtControllerTest { - private static final ExternalServiceCredentialGenerator artCredentialGenerator = new ExternalServiceCredentialGenerator( - new byte[32], new byte[32], true, false, false); + private static final ArtServiceConfiguration ART_SERVICE_CONFIGURATION = MockHelper.buildMock( + ArtServiceConfiguration.class, + cfg -> { + Mockito.when(cfg.getUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32]); + Mockito.when(cfg.getUserAuthenticationTokenUserIdSecret()).thenReturn(new byte[32]); + }); + private static final ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(ART_SERVICE_CONFIGURATION); private static final RateLimiter rateLimiter = mock(RateLimiter.class); private static final RateLimiters rateLimiters = mock(RateLimiters.class); @@ -40,7 +48,7 @@ class ArtControllerTest { ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new ArtController(rateLimiters, artCredentialGenerator)) + .addResource(new ArtController(rateLimiters, artCredentialsGenerator)) .build(); @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java index 8556bd294..a0a08055a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DirectoryControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -26,15 +26,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.controllers.DirectoryController; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; @ExtendWith(DropwizardExtensionsSupport.class) class DirectoryControllerTest { - private static final ExternalServiceCredentialGenerator directoryCredentialsGenerator = mock(ExternalServiceCredentialGenerator.class); + private static final ExternalServiceCredentialsGenerator directoryCredentialsGenerator = mock(ExternalServiceCredentialsGenerator.class); private static final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password"); private static final ResourceExtension resources = ResourceExtension.builder() 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 e938a68b1..85ce0fa14 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 @@ -1,5 +1,5 @@ /* - * Copyright 2021 Signal Messenger, LLC + * Copyright 2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -15,8 +15,9 @@ import java.time.ZoneId; import java.util.UUID; import org.junit.jupiter.api.Test; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.DirectoryV2ClientConfiguration; import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; @@ -26,11 +27,12 @@ class DirectoryControllerV2Test { @Test void testAuthToken() { - final ExternalServiceCredentialGenerator credentialGenerator = new ExternalServiceCredentialGenerator( - new byte[]{0x1}, new byte[]{0x2}, true, false, - Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC"))); + final ExternalServiceCredentialsGenerator credentialsGenerator = DirectoryV2Controller.credentialsGenerator( + new DirectoryV2ClientConfiguration(new byte[]{0x1}, new byte[]{0x2}), + Clock.fixed(Instant.ofEpochSecond(1633738643L), ZoneId.of("Etc/UTC")) + ); - final DirectoryV2Controller controller = new DirectoryV2Controller(credentialGenerator); + final DirectoryV2Controller controller = new DirectoryV2Controller(credentialsGenerator); final Account account = mock(Account.class); final UUID uuid = UUID.fromString("11111111-1111-1111-1111-111111111111"); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java index e8afb3d10..45a6a3cf0 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/PaymentsControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -25,8 +25,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; import org.whispersystems.textsecuregcm.controllers.PaymentsController; import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager; import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntity; @@ -36,7 +36,7 @@ import org.whispersystems.textsecuregcm.tests.util.AuthHelper; @ExtendWith(DropwizardExtensionsSupport.class) class PaymentsControllerTest { - private static final ExternalServiceCredentialGenerator paymentsCredentialGenerator = mock(ExternalServiceCredentialGenerator.class); + private static final ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = mock(ExternalServiceCredentialsGenerator.class); private static final CurrencyConversionManager currencyManager = mock(CurrencyConversionManager.class); private final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password"); @@ -46,13 +46,13 @@ class PaymentsControllerTest { .addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>( ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new PaymentsController(currencyManager, paymentsCredentialGenerator)) + .addResource(new PaymentsController(currencyManager, paymentsCredentialsGenerator)) .build(); @BeforeEach void setup() { - when(paymentsCredentialGenerator.generateFor(eq(AuthHelper.VALID_UUID.toString()))).thenReturn(validCredentials); + when(paymentsCredentialsGenerator.generateForUuid(eq(AuthHelper.VALID_UUID))).thenReturn(validCredentials); when(currencyManager.getCurrencyConversions()).thenReturn(Optional.of( new CurrencyConversionEntityList(List.of( new CurrencyConversionEntity("FOO", Map.of( 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 fb08105bc..c95330788 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 @@ -1,11 +1,12 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ package org.whispersystems.textsecuregcm.tests.controllers; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableSet; import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider; @@ -17,16 +18,23 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.MockHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; @ExtendWith(DropwizardExtensionsSupport.class) class SecureStorageControllerTest { - private static final ExternalServiceCredentialGenerator storageCredentialGenerator = new ExternalServiceCredentialGenerator(new byte[32], new byte[32], false); + private static final SecureStorageServiceConfiguration STORAGE_CFG = MockHelper.buildMock( + SecureStorageServiceConfiguration.class, + cfg -> when(cfg.decodeUserAuthenticationTokenSharedSecret()).thenReturn(new byte[32])); + + private static final ExternalServiceCredentialsGenerator STORAGE_CREDENTIAL_GENERATOR = SecureStorageController + .credentialsGenerator(STORAGE_CFG); private static final ResourceExtension resources = ResourceExtension.builder() .addProvider(AuthHelper.getAuthFilter()) @@ -34,7 +42,7 @@ class SecureStorageControllerTest { ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addResource(new SecureStorageController(storageCredentialGenerator)) + .addResource(new SecureStorageController(STORAGE_CREDENTIAL_GENERATOR)) .build(); @@ -60,6 +68,4 @@ class SecureStorageControllerTest { assertThat(response.getStatus()).isEqualTo(401); } - - } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index 8ea5d23ab..4d75d06f6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 Signal Messenger, LLC + * Copyright 2013-2023 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ @@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.tests.util; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; @@ -26,10 +25,10 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; -import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.Device; +import org.whispersystems.textsecuregcm.util.HeaderUtils; public class AuthHelper { // Static seed to ensure reproducible tests. @@ -201,19 +200,15 @@ public class AuthHelper { } public static String getAuthHeader(UUID uuid, long deviceId, String password) { - return getAuthHeader(uuid.toString() + "." + deviceId, password); + return HeaderUtils.basicAuthHeader(uuid.toString() + "." + deviceId, password); } public static String getAuthHeader(UUID uuid, String password) { - return getAuthHeader(uuid.toString(), password); + return HeaderUtils.basicAuthHeader(uuid.toString(), password); } public static String getProvisioningAuthHeader(String number, String password) { - return getAuthHeader(number, password); - } - - private static String getAuthHeader(String identifier, String password) { - return "Basic " + Base64.getEncoder().encodeToString((identifier + ":" + password).getBytes()); + return HeaderUtils.basicAuthHeader(number, password); } public static String getUnidentifiedAccessHeader(byte[] key) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/MockHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/MockHelper.java new file mode 100644 index 000000000..53c3eb0ea --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/MockHelper.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import org.mockito.Mockito; + +public final class MockHelper { + + private MockHelper() { + // utility class + } + + @FunctionalInterface + public interface MockInitializer { + + void init(T mock) throws Exception; + } + + public static T buildMock(final Class clazz, final MockInitializer initializer) throws RuntimeException { + final T mock = Mockito.mock(clazz); + try { + initializer.init(mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + return mock; + } +}