From f10f772e94809ce699b68df116675e6108275b40 Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Tue, 28 Nov 2023 22:03:10 -0500 Subject: [PATCH] Convert `PreKeyState` to a record --- .../controllers/KeysController.java | 18 +-- .../textsecuregcm/entities/PreKeyState.java | 122 ++++++------------ .../controllers/KeysControllerTest.java | 18 +-- 3 files changed, 57 insertions(+), 101 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 4d09208d1..04d280687 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -138,15 +138,15 @@ public class KeysController { final boolean usePhoneNumberIdentity = usePhoneNumberIdentity(identityType); - if (preKeys.getSignedPreKey() != null && - !preKeys.getSignedPreKey().equals(usePhoneNumberIdentity ? device.getSignedPreKey(IdentityType.PNI) + if (preKeys.signedPreKey() != null && + !preKeys.signedPreKey().equals(usePhoneNumberIdentity ? device.getSignedPreKey(IdentityType.PNI) : device.getSignedPreKey(IdentityType.ACI))) { updateAccount = true; } final IdentityKey oldIdentityKey = usePhoneNumberIdentity ? account.getIdentityKey(IdentityType.PNI) : account.getIdentityKey(IdentityType.ACI); - if (!Objects.equals(preKeys.getIdentityKey(), oldIdentityKey)) { + if (!Objects.equals(preKeys.identityKey(), oldIdentityKey)) { updateAccount = true; final boolean hasIdentityKey = oldIdentityKey != null; @@ -170,26 +170,26 @@ public class KeysController { if (updateAccount) { account = accounts.update(account, a -> { - if (preKeys.getSignedPreKey() != null) { + if (preKeys.signedPreKey() != null) { a.getDevice(device.getId()).ifPresent(d -> { if (usePhoneNumberIdentity) { - d.setPhoneNumberIdentitySignedPreKey(preKeys.getSignedPreKey()); + d.setPhoneNumberIdentitySignedPreKey(preKeys.signedPreKey()); } else { - d.setSignedPreKey(preKeys.getSignedPreKey()); + d.setSignedPreKey(preKeys.signedPreKey()); } }); } if (usePhoneNumberIdentity) { - a.setPhoneNumberIdentityKey(preKeys.getIdentityKey()); + a.setPhoneNumberIdentityKey(preKeys.identityKey()); } else { - a.setIdentityKey(preKeys.getIdentityKey()); + a.setIdentityKey(preKeys.identityKey()); } }); } return keys.store(getIdentifier(account, identityType), device.getId(), - preKeys.getPreKeys(), preKeys.getPqPreKeys(), preKeys.getSignedPreKey(), preKeys.getPqLastResortPreKey()) + preKeys.preKeys(), preKeys.pqPreKeys(), preKeys.signedPreKey(), preKeys.pqLastResortPreKey()) .thenApply(Util.ASYNC_EMPTY_RESPONSE); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java index dfd25e703..f4825f871 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java @@ -4,105 +4,59 @@ */ package org.whispersystems.textsecuregcm.entities; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.annotations.VisibleForTesting; import io.swagger.v3.oas.annotations.media.Schema; import org.signal.libsignal.protocol.IdentityKey; import org.whispersystems.textsecuregcm.util.IdentityKeyAdapter; - import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; -public class PreKeyState { +public record PreKeyState( + @Valid + @Schema(description = """ + A list of unsigned elliptic-curve prekeys to use for this device. If present and not empty, replaces all stored + unsigned EC prekeys for the device; if absent or empty, any stored unsigned EC prekeys for the device are not + deleted. + """) + List<@Valid ECPreKey> preKeys, - @JsonProperty - @Valid - @Schema(description = """ - A list of unsigned elliptic-curve prekeys to use for this device. If present and not empty, replaces all stored - unsigned EC prekeys for the device; if absent or empty, any stored unsigned EC prekeys for the device are not - deleted. - """) - private List<@Valid ECPreKey> preKeys; + @Valid + @Schema(description = """ + An optional signed elliptic-curve prekey to use for this device. If present, replaces the stored signed + elliptic-curve prekey for the device; if absent, the stored signed prekey is not deleted. If present, must have + a valid signature from the identity key in this request. + """) + ECSignedPreKey signedPreKey, - @JsonProperty - @Valid - @Schema(description = """ - An optional signed elliptic-curve prekey to use for this device. If present, replaces the stored signed - elliptic-curve prekey for the device; if absent, the stored signed prekey is not deleted. If present, must have a - valid signature from the identity key in this request. - """) - private ECSignedPreKey signedPreKey; + @Valid + @Schema(description = """ + A list of signed post-quantum one-time prekeys to use for this device. Each key must have a valid signature from + the identity key in this request. If present and not empty, replaces all stored unsigned PQ prekeys for the + device; if absent or empty, any stored unsigned PQ prekeys for the device are not deleted. + """) + List<@Valid KEMSignedPreKey> pqPreKeys, - @JsonProperty - @Valid - @Schema(description = """ - A list of signed post-quantum one-time prekeys to use for this device. Each key must have a valid signature from - the identity key in this request. If present and not empty, replaces all stored unsigned PQ prekeys for the - device; if absent or empty, any stored unsigned PQ prekeys for the device are not deleted. - """) - private List<@Valid KEMSignedPreKey> pqPreKeys; + @Valid + @Schema(description = """ + An optional signed last-resort post-quantum prekey to use for this device. If present, replaces the stored + signed post-quantum last-resort prekey for the device; if absent, a stored last-resort prekey will *not* be + deleted. If present, must have a valid signature from the identity key in this request. + """) + KEMSignedPreKey pqLastResortPreKey, - @JsonProperty - @Valid - @Schema(description = """ - An optional signed last-resort post-quantum prekey to use for this device. If present, replaces the stored signed - post-quantum last-resort prekey for the device; if absent, a stored last-resort prekey will *not* be deleted. If - present, must have a valid signature from the identity key in this request. - """) - private KEMSignedPreKey pqLastResortPreKey; - - @JsonProperty - @JsonSerialize(using = IdentityKeyAdapter.Serializer.class) - @JsonDeserialize(using = IdentityKeyAdapter.Deserializer.class) - @NotNull - @Schema(description = """ - Required. The public identity key for this identity (account or phone-number identity). If this device is not the - primary device for the account, must match the existing stored identity key for this identity. - """) - private IdentityKey identityKey; - - public PreKeyState() { - } - - @VisibleForTesting - public PreKeyState(IdentityKey identityKey, ECSignedPreKey signedPreKey, List keys) { - this(identityKey, signedPreKey, keys, null, null); - } - - @VisibleForTesting - public PreKeyState(IdentityKey identityKey, ECSignedPreKey signedPreKey, List keys, - List pqKeys, KEMSignedPreKey pqLastResortKey) { - this.identityKey = identityKey; - this.signedPreKey = signedPreKey; - this.preKeys = keys; - this.pqPreKeys = pqKeys; - this.pqLastResortPreKey = pqLastResortKey; - } - - public List getPreKeys() { - return preKeys; - } - - public ECSignedPreKey getSignedPreKey() { - return signedPreKey; - } - - public List getPqPreKeys() { - return pqPreKeys; - } - - public KEMSignedPreKey getPqLastResortPreKey() { - return pqLastResortPreKey; - } - - public IdentityKey getIdentityKey() { - return identityKey; - } + @JsonSerialize(using = IdentityKeyAdapter.Serializer.class) + @JsonDeserialize(using = IdentityKeyAdapter.Deserializer.class) + @NotNull + @Schema(description = """ + Required. The public identity key for this identity (account or phone-number identity). If this device is not + the primary device for the account, must match the existing stored identity key for this identity. + """) + IdentityKey identityKey +) { @AssertTrue public boolean isSignatureValidOnEachSignedKey() { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeysControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeysControllerTest.java index fad2aa397..73e9b25ca 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeysControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/KeysControllerTest.java @@ -732,7 +732,7 @@ class KeysControllerTest { final ECSignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey()); - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey)); + final PreKeyState preKeyState = new PreKeyState(List.of(preKey), signedPreKey, null, null, identityKey); Response response = resources.getJerseyTest() @@ -763,7 +763,8 @@ class KeysControllerTest { final KEMSignedPreKey pqLastResortPreKey = KeysHelper.signedKEMPreKey(31340, identityKeyPair); final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey()); - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey); + final PreKeyState preKeyState = + new PreKeyState(List.of(preKey), signedPreKey, List.of(pqPreKey), pqLastResortPreKey, identityKey); Response response = resources.getJerseyTest() @@ -866,7 +867,7 @@ class KeysControllerTest { final ECSignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey()); - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey)); + final PreKeyState preKeyState = new PreKeyState(List.of(preKey), signedPreKey, null, null, identityKey); Response response = resources.getJerseyTest() @@ -898,7 +899,8 @@ class KeysControllerTest { final KEMSignedPreKey pqLastResortPreKey = KeysHelper.signedKEMPreKey(31340, identityKeyPair); final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey()); - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey); + final PreKeyState preKeyState = + new PreKeyState(List.of(preKey), signedPreKey, List.of(pqPreKey), pqLastResortPreKey, identityKey); Response response = resources.getJerseyTest() @@ -926,7 +928,7 @@ class KeysControllerTest { @Test void putPrekeyWithInvalidSignature() { final ECSignedPreKey badSignedPreKey = KeysHelper.signedECPreKey(1, Curve.generateKeyPair()); - PreKeyState preKeyState = new PreKeyState(IDENTITY_KEY, badSignedPreKey, List.of()); + final PreKeyState preKeyState = new PreKeyState(List.of(), badSignedPreKey, null, null, IDENTITY_KEY); Response response = resources.getJerseyTest() .target("/v2/keys") @@ -945,7 +947,7 @@ class KeysControllerTest { final ECSignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final IdentityKey identityKey = new IdentityKey(identityKeyPair.getPublicKey()); - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey)); + final PreKeyState preKeyState = new PreKeyState(List.of(preKey), signedPreKey, null, null, identityKey); Response response = resources.getJerseyTest() @@ -975,9 +977,9 @@ class KeysControllerTest { final ECPreKey preKey = KeysHelper.ecPreKey(31337); final ECSignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, IDENTITY_KEY_PAIR); - List preKeys = List.of(preKey); + final List preKeys = List.of(preKey); - PreKeyState preKeyState = new PreKeyState(IDENTITY_KEY, signedPreKey, preKeys); + final PreKeyState preKeyState = new PreKeyState(preKeys, signedPreKey, null, null, IDENTITY_KEY); Response response = resources.getJerseyTest()