Generic credential auth endpoint for call links

This commit is contained in:
Katherine Yen
2023-04-04 10:28:35 -07:00
committed by GitHub
parent 48ebafa4e0
commit e4da59c236
8 changed files with 292 additions and 49 deletions

View File

@@ -8,9 +8,14 @@ package org.whispersystems.textsecuregcm.tests.auth;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
@@ -18,6 +23,7 @@ import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.MutableClock;
class ExternalServiceCredentialsGeneratorTest {
private static final String PREFIX = "prefix";
private static final String E164 = "+14152222222";
@@ -27,6 +33,42 @@ class ExternalServiceCredentialsGeneratorTest {
private static final String TIME_SECONDS_STRING = Long.toString(TIME_SECONDS);
private static final String USERNAME_TIMESTAMP = PREFIX + ":" + Instant.ofEpochSecond(TIME_SECONDS).truncatedTo(ChronoUnit.DAYS).getEpochSecond();
private static final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
private static final ExternalServiceCredentialsGenerator standardGenerator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withClock(clock)
.build();
private static final ExternalServiceCredentials standardCredentials = standardGenerator.generateFor(E164);
private static final ExternalServiceCredentialsGenerator usernameIsTimestampGenerator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withUsernameTimestampTruncatorAndPrefix(timestamp -> timestamp.truncatedTo(ChronoUnit.DAYS), PREFIX)
.withClock(clock)
.build();
private static final ExternalServiceCredentials usernameIsTimestampCredentials = usernameIsTimestampGenerator.generateWithTimestampAsUsername();
@BeforeEach
public void before() throws Exception {
clock.setTimeMillis(TIME_MILLIS);
}
@Test
void testInvalidConstructor() {
assertThrows(RuntimeException.class, () -> ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withUsernameTimestampTruncatorAndPrefix(null, PREFIX)
.build());
assertThrows(RuntimeException.class, () -> ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withUsernameTimestampTruncatorAndPrefix(timestamp -> timestamp.truncatedTo(ChronoUnit.DAYS), null)
.build());
}
@Test
void testGenerateDerivedUsername() {
@@ -42,18 +84,13 @@ class ExternalServiceCredentialsGeneratorTest {
@Test
void testGenerateNoDerivedUsername() {
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.build();
final ExternalServiceCredentials credentials = generator.generateFor(E164);
assertEquals(credentials.username(), E164);
assertTrue(credentials.password().startsWith(E164));
assertEquals(credentials.password().split(":").length, 3);
assertEquals(standardCredentials.username(), E164);
assertTrue(standardCredentials.password().startsWith(E164));
assertEquals(standardCredentials.password().split(":").length, 3);
}
@Test
public void testNotPrependUsername() throws Exception {
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.prependUsername(false)
@@ -65,52 +102,68 @@ class ExternalServiceCredentialsGeneratorTest {
assertEquals(credentials.password().split(":").length, 2);
}
@Test
public void testWithUsernameIsTimestamp() {
assertEquals(USERNAME_TIMESTAMP, usernameIsTimestampCredentials.username());
final String[] passwordComponents = usernameIsTimestampCredentials.password().split(":");
assertEquals(USERNAME_TIMESTAMP, passwordComponents[0] + ":" + passwordComponents[1]);
assertEquals(hmac256TruncatedToHexString(new byte[32], USERNAME_TIMESTAMP, 10), passwordComponents[2]);
}
@Test
public void testValidateValid() throws Exception {
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withClock(clock)
.build();
final ExternalServiceCredentials credentials = generator.generateFor(E164);
assertEquals(generator.validateAndGetTimestamp(credentials).orElseThrow(), TIME_SECONDS);
assertEquals(standardGenerator.validateAndGetTimestamp(standardCredentials).orElseThrow(), TIME_SECONDS);
}
@Test
public void testValidateValidWithUsernameIsTimestamp() {
final long expectedTimestamp = Instant.ofEpochSecond(TIME_SECONDS).truncatedTo(ChronoUnit.DAYS).getEpochSecond();
assertEquals(expectedTimestamp, usernameIsTimestampGenerator.validateAndGetTimestamp(usernameIsTimestampCredentials).orElseThrow());
}
@Test
public void testValidateInvalid() throws Exception {
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withClock(clock)
.build();
final ExternalServiceCredentials credentials = generator.generateFor(E164);
final ExternalServiceCredentials corruptedStandardUsername = new ExternalServiceCredentials(
standardCredentials.username(), standardCredentials.password().replace(E164, E164 + "0"));
final ExternalServiceCredentials corruptedStandardTimestamp = new ExternalServiceCredentials(
standardCredentials.username(), standardCredentials.password().replace(TIME_SECONDS_STRING, TIME_SECONDS_STRING + "0"));
final ExternalServiceCredentials corruptedStandardPassword = new ExternalServiceCredentials(
standardCredentials.username(), standardCredentials.password() + "0");
final ExternalServiceCredentials corruptedUsername = new ExternalServiceCredentials(
credentials.username(), credentials.password().replace(E164, E164 + "0"));
final ExternalServiceCredentials corruptedTimestamp = new ExternalServiceCredentials(
credentials.username(), credentials.password().replace(TIME_SECONDS_STRING, TIME_SECONDS_STRING + "0"));
final ExternalServiceCredentials corruptedPassword = new ExternalServiceCredentials(
credentials.username(), credentials.password() + "0");
final ExternalServiceCredentials corruptedUsernameTimestamp = new ExternalServiceCredentials(
usernameIsTimestampCredentials.username(), usernameIsTimestampCredentials.password().replace(USERNAME_TIMESTAMP, USERNAME_TIMESTAMP
+ "0"));
final ExternalServiceCredentials corruptedUsernameTimestampPassword = new ExternalServiceCredentials(
usernameIsTimestampCredentials.username(), usernameIsTimestampCredentials.password() + "0");
assertTrue(generator.validateAndGetTimestamp(corruptedUsername).isEmpty());
assertTrue(generator.validateAndGetTimestamp(corruptedTimestamp).isEmpty());
assertTrue(generator.validateAndGetTimestamp(corruptedPassword).isEmpty());
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardUsername).isEmpty());
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardTimestamp).isEmpty());
assertTrue(standardGenerator.validateAndGetTimestamp(corruptedStandardPassword).isEmpty());
assertTrue(usernameIsTimestampGenerator.validateAndGetTimestamp(corruptedUsernameTimestamp).isEmpty());
assertTrue(usernameIsTimestampGenerator.validateAndGetTimestamp(corruptedUsernameTimestampPassword).isEmpty());
}
@Test
public void testValidateWithExpiration() throws Exception {
final MutableClock clock = MockUtils.mutableClock(TIME_MILLIS);
final ExternalServiceCredentialsGenerator generator = ExternalServiceCredentialsGenerator
.builder(new byte[32])
.withClock(clock)
.build();
final ExternalServiceCredentials credentials = generator.generateFor(E164);
final long elapsedSeconds = 10000;
clock.incrementSeconds(elapsedSeconds);
assertEquals(generator.validateAndGetTimestamp(credentials, elapsedSeconds + 1).orElseThrow(), TIME_SECONDS);
assertTrue(generator.validateAndGetTimestamp(credentials, elapsedSeconds - 1).isEmpty());
assertEquals(standardGenerator.validateAndGetTimestamp(standardCredentials, elapsedSeconds + 1).orElseThrow(), TIME_SECONDS);
assertTrue(standardGenerator.validateAndGetTimestamp(standardCredentials, elapsedSeconds - 1).isEmpty());
}
@Test
public void testGetIdentityFromSignature() {
final String identity = standardGenerator.identityFromSignature(standardCredentials.password()).orElseThrow();
assertEquals(E164, identity);
}
@Test
public void testGetIdentityFromSignatureIsTimestamp() {
final String identity = usernameIsTimestampGenerator.identityFromSignature(usernameIsTimestampCredentials.password()).orElseThrow();
assertEquals(USERNAME_TIMESTAMP, identity);
}
@Test

View File

@@ -0,0 +1,44 @@
package org.whispersystems.textsecuregcm.tests.controllers;
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.mockito.Mockito;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.configuration.CallLinkConfiguration;
import org.whispersystems.textsecuregcm.controllers.CallLinkController;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@ExtendWith(DropwizardExtensionsSupport.class)
public class CallLinkControllerTest {
private static final CallLinkConfiguration callLinkConfiguration = MockUtils.buildMock(
CallLinkConfiguration.class,
cfg -> Mockito.when(cfg.userAuthenticationTokenSharedSecret()).thenReturn(new byte[32])
);
private static final ExternalServiceCredentialsGenerator callLinkCredentialsGenerator = CallLinkController.credentialsGenerator(callLinkConfiguration);
private static final ResourceExtension resources = ResourceExtension.builder()
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new CallLinkController(callLinkCredentialsGenerator))
.build();
@Test
void testGetAuth() {
ExternalServiceCredentials credentials = resources.getJerseyTest()
.target("/v1/call-link/auth")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.get(ExternalServiceCredentials.class);
assertThat(credentials.username()).isNotEmpty();
assertThat(credentials.password()).isNotEmpty();
}
}