Assume that all devices have signed pre-keys
This commit is contained in:
parent
c29113d17a
commit
28a981f29f
|
@ -37,7 +37,6 @@ import javax.ws.rs.core.Response;
|
||||||
import org.signal.libsignal.protocol.IdentityKey;
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
import org.whispersystems.textsecuregcm.auth.Anonymous;
|
import org.whispersystems.textsecuregcm.auth.Anonymous;
|
||||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||||
import org.whispersystems.textsecuregcm.auth.ChangesDeviceEnabledState;
|
|
||||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
||||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||||
import org.whispersystems.textsecuregcm.entities.ECPreKey;
|
import org.whispersystems.textsecuregcm.entities.ECPreKey;
|
||||||
|
@ -96,7 +95,6 @@ public class KeysController {
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@ChangesDeviceEnabledState
|
|
||||||
@Operation(summary = "Upload new prekeys", description = "Upload new pre-keys for this device.")
|
@Operation(summary = "Upload new prekeys", description = "Upload new pre-keys for this device.")
|
||||||
@ApiResponse(responseCode = "200", description = "Indicates that new keys were successfully stored.")
|
@ApiResponse(responseCode = "200", description = "Indicates that new keys were successfully stored.")
|
||||||
@ApiResponse(responseCode = "401", description = "Account authentication check failed.")
|
@ApiResponse(responseCode = "401", description = "Account authentication check failed.")
|
||||||
|
@ -277,7 +275,6 @@ public class KeysController {
|
||||||
@PUT
|
@PUT
|
||||||
@Path("/signed")
|
@Path("/signed")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
@ChangesDeviceEnabledState
|
|
||||||
@Operation(summary = "Upload a new signed prekey",
|
@Operation(summary = "Upload a new signed prekey",
|
||||||
description = """
|
description = """
|
||||||
Upload a new signed elliptic-curve prekey for this device. Deprecated; use PUT /v2/keys instead.
|
Upload a new signed elliptic-curve prekey for this device. Deprecated; use PUT /v2/keys instead.
|
||||||
|
|
|
@ -207,8 +207,8 @@ public class Device {
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
boolean hasChannel = fetchesMessages || StringUtils.isNotEmpty(getApnId()) || StringUtils.isNotEmpty(getGcmId());
|
boolean hasChannel = fetchesMessages || StringUtils.isNotEmpty(getApnId()) || StringUtils.isNotEmpty(getGcmId());
|
||||||
|
|
||||||
return (id == PRIMARY_ID && hasChannel && signedPreKey != null) ||
|
return (id == PRIMARY_ID && hasChannel) ||
|
||||||
(id != PRIMARY_ID && hasChannel && signedPreKey != null && lastSeen > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30)));
|
(id != PRIMARY_ID && hasChannel && lastSeen > (System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getFetchesMessages() {
|
public boolean getFetchesMessages() {
|
||||||
|
|
|
@ -202,11 +202,9 @@ class AuthEnablementRefreshRequirementProviderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stream<Arguments> testDeviceEnabledChanged() {
|
static Stream<Arguments> testDeviceEnabledChanged() {
|
||||||
final byte deviceId1 = Device.PRIMARY_ID;
|
|
||||||
final byte deviceId2 = 2;
|
final byte deviceId2 = 2;
|
||||||
final byte deviceId3 = 3;
|
final byte deviceId3 = 3;
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(Map.of(deviceId1, false, deviceId2, false), Map.of(deviceId1, true, deviceId2, false)),
|
|
||||||
Arguments.of(Map.of(deviceId2, false, deviceId3, false), Map.of(deviceId2, true, deviceId3, true)),
|
Arguments.of(Map.of(deviceId2, false, deviceId3, false), Map.of(deviceId2, true, deviceId3, true)),
|
||||||
Arguments.of(Map.of(deviceId2, true, deviceId3, true), Map.of(deviceId2, false, deviceId3, false)),
|
Arguments.of(Map.of(deviceId2, true, deviceId3, true), Map.of(deviceId2, false, deviceId3, false)),
|
||||||
Arguments.of(Map.of(deviceId2, true, deviceId3, true), Map.of(deviceId2, true, deviceId3, true)),
|
Arguments.of(Map.of(deviceId2, true, deviceId3, true), Map.of(deviceId2, true, deviceId3, true)),
|
||||||
|
@ -274,32 +272,6 @@ class AuthEnablementRefreshRequirementProviderTest {
|
||||||
verifyNoMoreInteractions(clientPresenceManager);
|
verifyNoMoreInteractions(clientPresenceManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testPrimaryDeviceDisabledAndDeviceRemoved() {
|
|
||||||
assert account.getPrimaryDevice().isEnabled();
|
|
||||||
|
|
||||||
final Set<Byte> initialDeviceIds = account.getDevices().stream().map(Device::getId).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
final byte deletedDeviceId = 2;
|
|
||||||
assertTrue(initialDeviceIds.remove(deletedDeviceId));
|
|
||||||
|
|
||||||
final Response response = resources.getJerseyTest()
|
|
||||||
.target("/v1/test/account/disablePrimaryDeviceAndDeleteDevice/" + deletedDeviceId)
|
|
||||||
.request()
|
|
||||||
.header("Authorization",
|
|
||||||
"Basic " + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8)))
|
|
||||||
.post(Entity.entity("", MediaType.TEXT_PLAIN));
|
|
||||||
|
|
||||||
assertEquals(200, response.getStatus());
|
|
||||||
|
|
||||||
assertTrue(account.getDevice(deletedDeviceId).isEmpty());
|
|
||||||
|
|
||||||
initialDeviceIds.forEach(deviceId -> verify(clientPresenceManager).disconnectPresence(account.getUuid(), deviceId));
|
|
||||||
verify(clientPresenceManager).disconnectPresence(account.getUuid(), deletedDeviceId);
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(clientPresenceManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnEvent() {
|
void testOnEvent() {
|
||||||
Response response = resources.getJerseyTest()
|
Response response = resources.getJerseyTest()
|
||||||
|
|
|
@ -49,6 +49,7 @@ import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
@ -1351,6 +1352,10 @@ class AccountsManagerTest {
|
||||||
|
|
||||||
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
||||||
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
||||||
|
|
||||||
|
devices.forEach(device ->
|
||||||
|
device.setSignedPreKey(KeysHelper.signedECPreKey(ThreadLocalRandom.current().nextLong(), Curve.generateKeyPair())));
|
||||||
|
|
||||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
||||||
|
@ -1403,6 +1408,10 @@ class AccountsManagerTest {
|
||||||
|
|
||||||
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
||||||
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
||||||
|
|
||||||
|
devices.forEach(device ->
|
||||||
|
device.setSignedPreKey(KeysHelper.signedECPreKey(ThreadLocalRandom.current().nextLong(), Curve.generateKeyPair())));
|
||||||
|
|
||||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
final Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
final Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
||||||
|
@ -1463,6 +1472,10 @@ class AccountsManagerTest {
|
||||||
|
|
||||||
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
List<Device> devices = List.of(DevicesHelper.createDevice(Device.PRIMARY_ID, 0L, 101),
|
||||||
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
DevicesHelper.createDevice(deviceId2, 0L, 102));
|
||||||
|
|
||||||
|
devices.forEach(device ->
|
||||||
|
device.setSignedPreKey(KeysHelper.signedECPreKey(ThreadLocalRandom.current().nextLong(), Curve.generateKeyPair())));
|
||||||
|
|
||||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
final Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
final Map<Byte, ECSignedPreKey> newSignedKeys = Map.of(
|
||||||
|
@ -1528,12 +1541,6 @@ class AccountsManagerTest {
|
||||||
deviceId3, KeysHelper.signedECPreKey(2, identityKeyPair));
|
deviceId3, KeysHelper.signedECPreKey(2, identityKeyPair));
|
||||||
Map<Byte, Integer> newRegistrationIds = Map.of(Device.PRIMARY_ID, 201, deviceId2, 202);
|
Map<Byte, Integer> newRegistrationIds = Map.of(Device.PRIMARY_ID, 201, deviceId2, 202);
|
||||||
|
|
||||||
UUID oldUuid = account.getUuid();
|
|
||||||
UUID oldPni = account.getPhoneNumberIdentifier();
|
|
||||||
|
|
||||||
Map<Byte, ECSignedPreKey> oldSignedPreKeys = account.getDevices().stream()
|
|
||||||
.collect(Collectors.toMap(Device::getId, d -> d.getSignedPreKey(IdentityType.ACI)));
|
|
||||||
|
|
||||||
final IdentityKey pniIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
|
final IdentityKey pniIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
|
||||||
|
|
||||||
assertThrows(MismatchedDevicesException.class,
|
assertThrows(MismatchedDevicesException.class,
|
||||||
|
@ -1558,12 +1565,6 @@ class AccountsManagerTest {
|
||||||
Device.PRIMARY_ID, KeysHelper.signedKEMPreKey(3, identityKeyPair));
|
Device.PRIMARY_ID, KeysHelper.signedKEMPreKey(3, identityKeyPair));
|
||||||
Map<Byte, Integer> newRegistrationIds = Map.of(Device.PRIMARY_ID, 201, deviceId2, 202);
|
Map<Byte, Integer> newRegistrationIds = Map.of(Device.PRIMARY_ID, 201, deviceId2, 202);
|
||||||
|
|
||||||
UUID oldUuid = account.getUuid();
|
|
||||||
UUID oldPni = account.getPhoneNumberIdentifier();
|
|
||||||
|
|
||||||
Map<Byte, ECSignedPreKey> oldSignedPreKeys = account.getDevices().stream()
|
|
||||||
.collect(Collectors.toMap(Device::getId, d -> d.getSignedPreKey(IdentityType.ACI)));
|
|
||||||
|
|
||||||
final IdentityKey pniIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
|
final IdentityKey pniIdentityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
|
||||||
|
|
||||||
assertThrows(MismatchedDevicesException.class,
|
assertThrows(MismatchedDevicesException.class,
|
||||||
|
|
|
@ -20,7 +20,7 @@ class DeviceTest {
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource
|
@MethodSource
|
||||||
void testIsEnabled(final boolean primary, final boolean fetchesMessages, final String apnId, final String gcmId,
|
void testIsEnabled(final boolean primary, final boolean fetchesMessages, final String apnId, final String gcmId,
|
||||||
final ECSignedPreKey signedPreKey, final Duration timeSinceLastSeen, final boolean expectEnabled) {
|
final Duration timeSinceLastSeen, final boolean expectEnabled) {
|
||||||
|
|
||||||
final long lastSeen = System.currentTimeMillis() - timeSinceLastSeen.toMillis();
|
final long lastSeen = System.currentTimeMillis() - timeSinceLastSeen.toMillis();
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ class DeviceTest {
|
||||||
device.setFetchesMessages(fetchesMessages);
|
device.setFetchesMessages(fetchesMessages);
|
||||||
device.setApnId(apnId);
|
device.setApnId(apnId);
|
||||||
device.setGcmId(gcmId);
|
device.setGcmId(gcmId);
|
||||||
device.setSignedPreKey(signedPreKey);
|
|
||||||
device.setCreated(lastSeen);
|
device.setCreated(lastSeen);
|
||||||
device.setLastSeen(lastSeen);
|
device.setLastSeen(lastSeen);
|
||||||
|
|
||||||
|
@ -38,39 +37,23 @@ class DeviceTest {
|
||||||
|
|
||||||
private static Stream<Arguments> testIsEnabled() {
|
private static Stream<Arguments> testIsEnabled() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
// primary fetchesMessages apnId gcmId signedPreKey lastSeen expectEnabled
|
// primary fetchesMessages apnId gcmId lastSeen expectEnabled
|
||||||
Arguments.of(true, false, null, null, null, Duration.ofDays(60), false),
|
Arguments.of(true, false, null, null, Duration.ofDays(60), false),
|
||||||
Arguments.of(true, false, null, null, null, Duration.ofDays(1), false),
|
Arguments.of(true, false, null, null, Duration.ofDays(1), false),
|
||||||
Arguments.of(true, false, null, null, mock(ECSignedPreKey.class), Duration.ofDays(60), false),
|
Arguments.of(true, false, null, "gcm-id", Duration.ofDays(60), true),
|
||||||
Arguments.of(true, false, null, null, mock(ECSignedPreKey.class), Duration.ofDays(1), false),
|
Arguments.of(true, false, null, "gcm-id", Duration.ofDays(1), true),
|
||||||
Arguments.of(true, false, null, "gcm-id", null, Duration.ofDays(60), false),
|
Arguments.of(true, false, "apn-id", null, Duration.ofDays(60), true),
|
||||||
Arguments.of(true, false, null, "gcm-id", null, Duration.ofDays(1), false),
|
Arguments.of(true, false, "apn-id", null, Duration.ofDays(1), true),
|
||||||
Arguments.of(true, false, null, "gcm-id", mock(ECSignedPreKey.class), Duration.ofDays(60), true),
|
Arguments.of(true, true, null, null, Duration.ofDays(60), true),
|
||||||
Arguments.of(true, false, null, "gcm-id", mock(ECSignedPreKey.class), Duration.ofDays(1), true),
|
Arguments.of(true, true, null, null, Duration.ofDays(1), true),
|
||||||
Arguments.of(true, false, "apn-id", null, null, Duration.ofDays(60), false),
|
Arguments.of(false, false, null, null, Duration.ofDays(60), false),
|
||||||
Arguments.of(true, false, "apn-id", null, null, Duration.ofDays(1), false),
|
Arguments.of(false, false, null, null, Duration.ofDays(1), false),
|
||||||
Arguments.of(true, false, "apn-id", null, mock(ECSignedPreKey.class), Duration.ofDays(60), true),
|
Arguments.of(false, false, null, "gcm-id", Duration.ofDays(60), false),
|
||||||
Arguments.of(true, false, "apn-id", null, mock(ECSignedPreKey.class), Duration.ofDays(1), true),
|
Arguments.of(false, false, null, "gcm-id", Duration.ofDays(1), true),
|
||||||
Arguments.of(true, true, null, null, null, Duration.ofDays(60), false),
|
Arguments.of(false, false, "apn-id", null, Duration.ofDays(60), false),
|
||||||
Arguments.of(true, true, null, null, null, Duration.ofDays(1), false),
|
Arguments.of(false, false, "apn-id", null, Duration.ofDays(1), true),
|
||||||
Arguments.of(true, true, null, null, mock(ECSignedPreKey.class), Duration.ofDays(60), true),
|
Arguments.of(false, true, null, null, Duration.ofDays(60), false),
|
||||||
Arguments.of(true, true, null, null, mock(ECSignedPreKey.class), Duration.ofDays(1), true),
|
Arguments.of(false, true, null, null, Duration.ofDays(1), true)
|
||||||
Arguments.of(false, false, null, null, null, Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, null, null, null, Duration.ofDays(1), false),
|
|
||||||
Arguments.of(false, false, null, null, mock(ECSignedPreKey.class), Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, null, null, mock(ECSignedPreKey.class), Duration.ofDays(1), false),
|
|
||||||
Arguments.of(false, false, null, "gcm-id", null, Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, null, "gcm-id", null, Duration.ofDays(1), false),
|
|
||||||
Arguments.of(false, false, null, "gcm-id", mock(ECSignedPreKey.class), Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, null, "gcm-id", mock(ECSignedPreKey.class), Duration.ofDays(1), true),
|
|
||||||
Arguments.of(false, false, "apn-id", null, null, Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, "apn-id", null, null, Duration.ofDays(1), false),
|
|
||||||
Arguments.of(false, false, "apn-id", null, mock(ECSignedPreKey.class), Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, false, "apn-id", null, mock(ECSignedPreKey.class), Duration.ofDays(1), true),
|
|
||||||
Arguments.of(false, true, null, null, null, Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, true, null, null, null, Duration.ofDays(1), false),
|
|
||||||
Arguments.of(false, true, null, null, mock(ECSignedPreKey.class), Duration.ofDays(60), false),
|
|
||||||
Arguments.of(false, true, null, null, mock(ECSignedPreKey.class), Duration.ofDays(1), true)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,12 +47,11 @@ public class DevicesHelper {
|
||||||
|
|
||||||
public static void setEnabled(Device device, boolean enabled) {
|
public static void setEnabled(Device device, boolean enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
device.setSignedPreKey(KeysHelper.signedECPreKey(RANDOM.nextLong(), Curve.generateKeyPair()));
|
|
||||||
device.setPhoneNumberIdentitySignedPreKey(KeysHelper.signedECPreKey(RANDOM.nextLong(), Curve.generateKeyPair()));
|
device.setPhoneNumberIdentitySignedPreKey(KeysHelper.signedECPreKey(RANDOM.nextLong(), Curve.generateKeyPair()));
|
||||||
device.setGcmId("testGcmId" + RANDOM.nextLong());
|
device.setGcmId("testGcmId" + RANDOM.nextLong());
|
||||||
device.setLastSeen(Util.todayInMillis());
|
device.setLastSeen(Util.todayInMillis());
|
||||||
} else {
|
} else {
|
||||||
device.setSignedPreKey(null);
|
device.setLastSeen(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fail fast, to guard against a change to the isEnabled() implementation causing unexpected test behavior
|
// fail fast, to guard against a change to the isEnabled() implementation causing unexpected test behavior
|
||||||
|
|
Loading…
Reference in New Issue