diff --git a/service/config/sample-secrets-bundle.yml b/service/config/sample-secrets-bundle.yml index a7e608d9c..2f14cc6d1 100644 --- a/service/config/sample-secrets-bundle.yml +++ b/service/config/sample-secrets-bundle.yml @@ -16,6 +16,9 @@ directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789 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 +svrb.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVRB to generate auth tokens for Signal users +svrb.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVRB to generate auth identity tokens for Signal users + tus.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= gcpAttachments.rsaSigningKey: | diff --git a/service/config/sample.yml b/service/config/sample.yml index 3a461cb83..28a0e3903 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -225,6 +225,34 @@ svr2: AAAAAAAAAAAAAAAAAAAA -----END CERTIFICATE----- +svrb: + uri: svrb.example.com + userAuthenticationTokenSharedSecret: secret://svrb.userAuthenticationTokenSharedSecret + userIdTokenSharedSecret: secret://svrb.userIdTokenSharedSecret + svrCaCertificates: + - | + -----BEGIN CERTIFICATE----- + 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 + AAAAAAAAAAAAAAAAAAAA + -----END CERTIFICATE----- + messageCache: # Redis server configuration for message store cache persistDelayMinutes: 1 cluster: diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index b7cc1f66b..3b434eaab 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -52,7 +52,7 @@ import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration; import org.whispersystems.textsecuregcm.configuration.S3ObjectMonitorFactory; import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration; -import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; +import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration; import org.whispersystems.textsecuregcm.configuration.ShortCodeExpanderConfiguration; import org.whispersystems.textsecuregcm.configuration.SpamFilterConfiguration; import org.whispersystems.textsecuregcm.configuration.StripeConfiguration; @@ -156,7 +156,12 @@ public class WhisperServerConfiguration extends Configuration { @NotNull @Valid @JsonProperty - private SecureValueRecovery2Configuration svr2; + private SecureValueRecoveryConfiguration svr2; + + @NotNull + @Valid + @JsonProperty + private SecureValueRecoveryConfiguration svrb; @NotNull @Valid @@ -389,10 +394,14 @@ public class WhisperServerConfiguration extends Configuration { return pubsub; } - public SecureValueRecovery2Configuration getSvr2Configuration() { + public SecureValueRecoveryConfiguration getSvr2Configuration() { return svr2; } + public SecureValueRecoveryConfiguration getSvrbConfiguration() { + return svrb; + } + public DirectoryV2Configuration getDirectoryV2Configuration() { return directoryV2; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 41b5aa1dc..4e7336f5c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -124,6 +124,7 @@ import org.whispersystems.textsecuregcm.controllers.RegistrationController; import org.whispersystems.textsecuregcm.controllers.RemoteConfigController; import org.whispersystems.textsecuregcm.controllers.SecureStorageController; import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controller; +import org.whispersystems.textsecuregcm.controllers.SecureValueRecoveryBController; import org.whispersystems.textsecuregcm.controllers.StickerController; import org.whispersystems.textsecuregcm.controllers.SubscriptionController; import org.whispersystems.textsecuregcm.controllers.VerificationController; @@ -608,6 +609,8 @@ public class WhisperServerService extends Application { @@ -38,7 +38,17 @@ enum ExternalServiceDefinitions { .build(); }), SVR(ExternalServiceType.EXTERNAL_SERVICE_TYPE_SVR, (chatConfig, clock) -> { - final SecureValueRecovery2Configuration cfg = chatConfig.getSvr2Configuration(); + final SecureValueRecoveryConfiguration cfg = chatConfig.getSvr2Configuration(); + return ExternalServiceCredentialsGenerator + .builder(cfg.userAuthenticationTokenSharedSecret()) + .withUserDerivationKey(cfg.userIdTokenSharedSecret().value()) + .prependUsername(false) + .withDerivedUsernameTruncateLength(16) + .withClock(clock) + .build(); + }), + SVRB(ExternalServiceType.EXTERNAL_SERVICE_TYPE_SVRB, (chatConfig, clock) -> { + final SecureValueRecoveryConfiguration cfg = chatConfig.getSvrbConfiguration(); return ExternalServiceCredentialsGenerator .builder(cfg.userAuthenticationTokenSharedSecret()) .withUserDerivationKey(cfg.userIdTokenSharedSecret().value()) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2Client.java b/service/src/main/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2Client.java index 21ce5ecd7..26b9eb77d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2Client.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2Client.java @@ -21,7 +21,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; +import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration; import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; import org.whispersystems.textsecuregcm.util.HttpUtils; @@ -39,7 +39,7 @@ public class SecureValueRecovery2Client { public SecureValueRecovery2Client(final ExternalServiceCredentialsGenerator secureValueRecoveryCredentialsGenerator, final Executor executor, final ScheduledExecutorService retryExecutor, - final SecureValueRecovery2Configuration configuration) + final SecureValueRecoveryConfiguration configuration) throws CertificateException { this.secureValueRecoveryCredentialsGenerator = secureValueRecoveryCredentialsGenerator; this.deleteUri = URI.create(configuration.uri()).resolve(DELETE_PATH); diff --git a/service/src/main/proto/org/signal/chat/credentials.proto b/service/src/main/proto/org/signal/chat/credentials.proto index d35086026..0a8cbbce1 100644 --- a/service/src/main/proto/org/signal/chat/credentials.proto +++ b/service/src/main/proto/org/signal/chat/credentials.proto @@ -53,6 +53,7 @@ enum ExternalServiceType { EXTERNAL_SERVICE_TYPE_PAYMENTS = 2; EXTERNAL_SERVICE_TYPE_STORAGE = 3; EXTERNAL_SERVICE_TYPE_SVR = 4; + EXTERNAL_SERVICE_TYPE_SVRB = 5; } message GetExternalServiceCredentialsRequest { 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 b73a87555..b551f48de 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecovery2ControllerTest.java @@ -23,7 +23,7 @@ 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.SecureValueRecovery2Configuration; +import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration; import org.whispersystems.textsecuregcm.entities.AuthCheckRequest; import org.whispersystems.textsecuregcm.entities.AuthCheckResponseV2; import org.whispersystems.textsecuregcm.storage.Account; @@ -42,7 +42,7 @@ import java.util.stream.Collectors; @ExtendWith(DropwizardExtensionsSupport.class) public class SecureValueRecovery2ControllerTest { - private static final SecureValueRecovery2Configuration CFG = new SecureValueRecovery2Configuration( + private static final SecureValueRecoveryConfiguration CFG = new SecureValueRecoveryConfiguration( "", randomSecretBytes(32), randomSecretBytes(32), diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecoveryBControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecoveryBControllerTest.java new file mode 100644 index 000000000..3a5e03272 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SecureValueRecoveryBControllerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.controllers; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes; + +import io.dropwizard.auth.AuthValueFactoryProvider; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +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.whispersystems.textsecuregcm.auth.AuthenticatedDevice; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; +import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; +import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration; +import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.MutableClock; +import org.whispersystems.textsecuregcm.util.SystemMapper; +import java.time.Instant; +import java.util.HexFormat; + +@ExtendWith(DropwizardExtensionsSupport.class) +public class SecureValueRecoveryBControllerTest { + + private static final SecureValueRecoveryConfiguration CFG = new SecureValueRecoveryConfiguration( + "", + randomSecretBytes(32), + randomSecretBytes(32), + null, + null, + null + ); + + private static final MutableClock CLOCK = new MutableClock(); + + private static final ExternalServiceCredentialsGenerator CREDENTIAL_GENERATOR = + SecureValueRecoveryBController.credentialsGenerator(CFG, CLOCK); + + private static final SecureValueRecoveryBController CONTROLLER = + new SecureValueRecoveryBController(CREDENTIAL_GENERATOR); + + private static final ResourceExtension RESOURCES = ResourceExtension.builder() + .addProvider(AuthHelper.getAuthFilter()) + .addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class)) + .setMapper(SystemMapper.jsonMapper()) + .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) + .addResource(CONTROLLER) + .build(); + + @Test + public void testGetCredentials() { + CLOCK.setTimeInstant(Instant.ofEpochSecond(123)); + final ExternalServiceCredentials creds = RESOURCES.getJerseyTest() + .target("/v1/svrb/auth") + .request() + .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) + .get(ExternalServiceCredentials.class); + + assertThat(HexFormat.of().parseHex(creds.username())).hasSize(16); + System.out.println(creds.password()); + final String[] split = creds.password().split(":", 2); + assertThat(Long.parseLong(split[0])).isEqualTo(123); + } +} 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 29097956d..f839a22fe 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securevaluerecovery/SecureValueRecovery2ClientTest.java @@ -32,7 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator; -import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration; +import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration; class SecureValueRecovery2ClientTest { @@ -55,7 +55,7 @@ class SecureValueRecovery2ClientTest { httpExecutor = Executors.newSingleThreadExecutor(); retryExecutor = Executors.newSingleThreadScheduledExecutor(); - final SecureValueRecovery2Configuration config = new SecureValueRecovery2Configuration( + final SecureValueRecoveryConfiguration config = new SecureValueRecoveryConfiguration( "http://localhost:" + wireMock.getPort(), randomSecretBytes(32), randomSecretBytes(32), diff --git a/service/src/test/resources/config/test-secrets-bundle.yml b/service/src/test/resources/config/test-secrets-bundle.yml index dc2d640c6..af91cd1a9 100644 --- a/service/src/test/resources/config/test-secrets-bundle.yml +++ b/service/src/test/resources/config/test-secrets-bundle.yml @@ -53,6 +53,9 @@ directoryV2.client.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789 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 +svrb.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVRB to generate auth tokens for Signal users +svrb.userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVRB to generate auth identity tokens for Signal users + tus.userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # The below private key was key generated exclusively for testing purposes. Do not use it in any other context. diff --git a/service/src/test/resources/config/test.yml b/service/src/test/resources/config/test.yml index fd353d5ad..1efbbd5ca 100644 --- a/service/src/test/resources/config/test.yml +++ b/service/src/test/resources/config/test.yml @@ -223,6 +223,35 @@ svr2: 9Kxq0DY7RCEpdHMCKcOL -----END CERTIFICATE----- +svrb: + uri: svrb.example.com + userAuthenticationTokenSharedSecret: secret://svrb.userAuthenticationTokenSharedSecret + userIdTokenSharedSecret: secret://svrb.userIdTokenSharedSecret + svrCaCertificates: + # this is a randomly generated test certificate + - | + -----BEGIN CERTIFICATE----- + MIIDazCCAlOgAwIBAgIUW5lcNWkuynRVc8Rq5pO6mHQBuZAwDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAzMjUwMzE4MTNaFw0yOTAz + MjQwMzE4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB + AQUAA4IBDwAwggEKAoIBAQCfH4Um+fv2r4KudhD37/UXp8duRLTmp4XvpBTpDHpD + 2HF8p2yThVKlJnMkP/9Ey1Rb0vhxO7DCltLdW8IYcxJuHoyMvyhGUEtxxkOZbrk8 + ciUR9jTZ37x7vXRGj/RxcdlS6iD0MeF0D/LAkImt4T/kiKwDbENrVEnYWJmipCKP + ribxWky7HqxDCoYMQr0zatxB3A9mx5stH+H3kbw3CZcm+ugF9ZIKDEVHb0lf28gq + llmD120q/vs9YV3rzVL7sBGDqf6olkulvHQJKElZg2rdcHWFcngSlU2BjR04oyuH + c/SSiLSB3YB0tdFGta5uorXyV1y7RElPeBfOfvEjsG3TAgMBAAGjUzBRMB0GA1Ud + DgQWBBQX+xlgSWWbDjv0SrJ+h67xauJ80zAfBgNVHSMEGDAWgBQX+xlgSWWbDjv0 + SrJ+h67xauJ80zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw + ZG2MCCjscn6h/QOoJU+IDfa68OqLq0I37gMnLMde4yEhAmm//miePIq4Uz9GRJ+h + rAmdEnspKgyQ93PjF7Xpk/JdJA4B1bIrsOl/cSwqx2sFhRt8Kt1DHGlGWXqOaHRP + UkZ86MyRL3sXly6WkxEYxZJeQaOzMy2XmQh7grzrlTBuSI+0xf7vsRRDipxr6LVQ + 6qGWyGODLLc2JD1IXj/1HpRVT2LoGGlKMuyxACQAm4oak1vvJ9mGxgfd9AU+eo58 + O/esB2Eaf+QqMPELdFSZQfG2jvp+3WQTZK8fDKHyLr076G3UetEMy867F6fzTSZd + 9Kxq0DY7RCEpdHMCKcOL + -----END CERTIFICATE----- + messageCache: # Redis server configuration for message store cache persistDelayMinutes: 1 cluster: