Add UAK validator to AccountAttributes

This commit is contained in:
Chris Eager
2026-04-10 18:06:37 -05:00
committed by Chris Eager
parent b551e0cb34
commit 149de6c464
3 changed files with 54 additions and 6 deletions
@@ -139,9 +139,22 @@ public class AccountAttributes {
return this;
}
@VisibleForTesting
public AccountAttributes withUnrestrictedUnidentifiedAccess(final boolean unrestrictedUnidentifiedAccess) {
this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
return this;
}
@AssertTrue
@Schema(hidden = true)
public boolean isEachRegistrationIdValid() {
return validRegistrationId(registrationId) && validRegistrationId(phoneNumberIdentityRegistrationId);
}
@AssertTrue
@Schema(hidden = true)
public boolean isUakValid() {
return (unrestrictedUnidentifiedAccess && (unidentifiedAccessKey == null || unidentifiedAccessKey.length == 0))
|| (!unrestrictedUnidentifiedAccess && (unidentifiedAccessKey != null && unidentifiedAccessKey.length == 16));
}
}
@@ -36,6 +36,7 @@ import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HexFormat;
import java.util.List;
@@ -776,13 +777,40 @@ class AccountControllerTest {
}
}
@ParameterizedTest
@MethodSource
void testSetAccountAttributesUnrestrictedUnidentifiedAccess(final boolean unrestrictedUnidentifiedAccess, final byte[] unidentifiedAccessKey, final int expectedStatus) {
try (final Response response = resources.getJerseyTest()
.target("/v1/accounts/attributes/")
.request()
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)
.withUnidentifiedAccessKey(unidentifiedAccessKey)
.withUnrestrictedUnidentifiedAccess(unrestrictedUnidentifiedAccess)))) {
assertThat(response.getStatus()).isEqualTo(expectedStatus);
}
}
static Collection<Arguments> testSetAccountAttributesUnrestrictedUnidentifiedAccess() {
return List.of(
Arguments.argumentSet("restricted, non-empty UAK", false, new byte[16], 204),
Arguments.argumentSet("unrestricted, empty UAK", true, new byte[0], 204),
Arguments.argumentSet("restricted, empty UAK", false, new byte[0], 422),
Arguments.argumentSet("unrestricted, non-empty UAK", true, new byte[16], 422)
);
}
@Test
void testSetAccountAttributesNoDiscoverabilityChange() {
try (final Response response = resources.getJerseyTest()
.target("/v1/accounts/attributes/")
.request()
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)))) {
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)
.withUnidentifiedAccessKey(new byte[16])))) {
assertThat(response.getStatus()).isEqualTo(204);
}
@@ -794,7 +822,8 @@ class AccountControllerTest {
.target("/v1/accounts/attributes/")
.request()
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.UNDISCOVERABLE_UUID, AuthHelper.UNDISCOVERABLE_PASSWORD))
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)))) {
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)
.withUnidentifiedAccessKey(new byte[16])))) {
assertThat(response.getStatus()).isEqualTo(204);
}
@@ -811,6 +840,7 @@ class AccountControllerTest {
.request()
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.UNDISCOVERABLE_UUID, AuthHelper.UNDISCOVERABLE_PASSWORD))
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, true, null)
.withUnidentifiedAccessKey(new byte[16])
.withRecoveryPassword(recoveryPassword)))) {
assertThat(response.getStatus()).isEqualTo(204);
@@ -824,7 +854,8 @@ class AccountControllerTest {
.target("/v1/accounts/attributes/")
.request()
.header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, false, null)))) {
.put(Entity.json(new AccountAttributes(false, 2222, 3333, null, null, false, null)
.withUnidentifiedAccessKey(new byte[16])))) {
assertThat(response.getStatus()).isEqualTo(204);
}
@@ -89,6 +89,7 @@ import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
@ExtendWith(DropwizardExtensionsSupport.class)
class RegistrationControllerTest {
@@ -901,10 +902,12 @@ class RegistrationControllerTest {
final Set<DeviceCapability> deviceCapabilities = DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES;
final AccountAttributes fetchesMessagesAccountAttributes =
new AccountAttributes(true, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities);
new AccountAttributes(true, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities)
.withUnidentifiedAccessKey(TestRandomUtil.nextBytes(16));
final AccountAttributes pushAccountAttributes =
new AccountAttributes(false, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities);
new AccountAttributes(false, registrationId, pniRegistrationId, "test".getBytes(StandardCharsets.UTF_8), null, true, deviceCapabilities)
.withUnidentifiedAccessKey(TestRandomUtil.nextBytes(16));
final String apnsToken = "apns-token";
final String gcmToken = "gcm-token";
@@ -1017,7 +1020,8 @@ class RegistrationControllerTest {
final AccountAttributes accountAttributes = new AccountAttributes(true, registrationId, pniRegistrationId,
"name".getBytes(StandardCharsets.UTF_8), REGLOCK,
true, deviceCapabilities);
true, deviceCapabilities)
.withUnidentifiedAccessKey(TestRandomUtil.nextBytes(16));
return new RegistrationRequest(
Base64.getEncoder().encodeToString(sessionId.getBytes(StandardCharsets.UTF_8)),