Increase DeviceCheck key limit

This commit is contained in:
Ravi Khadiwala
2026-08-12 09:10:04 -04:00
committed by Jon Chambers
parent 7d39f2952b
commit e7ec47aa25
3 changed files with 35 additions and 1 deletions
@@ -14,6 +14,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
@@ -35,6 +36,7 @@ import org.whispersystems.textsecuregcm.backup.BackupAuthManager;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.devicecheck.AppleDeviceCheckManager;
import org.whispersystems.textsecuregcm.storage.devicecheck.ChallengeNotFoundException;
import org.whispersystems.textsecuregcm.storage.devicecheck.DeviceCheckKeyIdNotFoundException;
@@ -139,6 +141,10 @@ public class DeviceCheckController {
@RequestBody(description = "The attestation data, created by [attestKey](https://developer.apple.com/documentation/devicecheck/dcappattestservice/attestkey(_:clientdatahash:completionhandler:))")
@NotNull final byte[] attestation) {
if (authenticatedDevice.deviceId() != Device.PRIMARY_ID) {
throw new ForbiddenException("Only primary devices may register attestations");
}
final Account account = accountsManager.getByAccountIdentifier(authenticatedDevice.accountIdentifier())
.orElseThrow(() -> new WebApplicationException(Response.Status.UNAUTHORIZED));
@@ -30,8 +30,11 @@ import java.util.Base64;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.util.ResilienceUtil;
@@ -56,7 +59,9 @@ public class AppleDeviceCheckManager {
// How many distinct device keys we're willing to accept for a single Account
@VisibleForTesting
static final int MAX_DEVICE_KEYS = 100;
static final int MAX_DEVICE_KEYS = 200;
private static final String KEY_COUNT_DISTRIBUTION_NAME = MetricsUtil.name(AppleDeviceCheckManager.class, "keyCount");
private final AppleDeviceChecks appleDeviceChecks;
private final FaultTolerantRedisClusterClient redisClient;
@@ -109,6 +114,10 @@ public class AppleDeviceCheckManager {
return;
}
DistributionSummary.builder(KEY_COUNT_DISTRIBUTION_NAME)
.register(Metrics.globalRegistry)
.record(existingKeys.size());
if (existingKeys.size() >= MAX_DEVICE_KEYS) {
// This is best-effort, since we don't check the number of keys transactionally. We just don't want to allow
// the keys for an account to grow arbitrarily large
@@ -12,6 +12,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import io.dropwizard.auth.AuthValueFactoryProvider;
@@ -193,6 +194,24 @@ class DeviceCheckControllerTest {
.registerAttestation(any(), eq(keyId), eq(attestation));
}
@Test
public void registerKeyNonPrimary() {
final byte[] keyId = TestRandomUtil.nextBytes(16);
final byte[] attestation = TestRandomUtil.nextBytes(32);
final Response response = resources.getJerseyTest()
.target("v1/devicecheck/attest")
.queryParam("keyId", Base64.getUrlEncoder().encodeToString(keyId))
.request()
.header("Authorization", AuthHelper.getAuthHeader(
AuthHelper.VALID_UUID_3,
AuthHelper.VALID_DEVICE_3_LINKED_ID,
AuthHelper.VALID_PASSWORD_3_LINKED))
.put(Entity.entity(attestation, MediaType.APPLICATION_OCTET_STREAM));
assertThat(response.getStatus()).isEqualTo(403);
verifyNoInteractions(appleDeviceCheckManager);
}
@Test
public void checkAssertion()
throws DeviceCheckKeyIdNotFoundException, DeviceCheckVerificationFailedException, ChallengeNotFoundException, RequestReuseException {