diff --git a/integration-tests/src/main/java/org/signal/integration/Operations.java b/integration-tests/src/main/java/org/signal/integration/Operations.java index 2433fc984..399a061bb 100644 --- a/integration-tests/src/main/java/org/signal/integration/Operations.java +++ b/integration-tests/src/main/java/org/signal/integration/Operations.java @@ -318,12 +318,12 @@ public final class Operations { private static SignedPreKey generateSignedECPreKey(long id, final ECKeyPair identityKeyPair) { final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize(); final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey); - return new SignedPreKey(id, Base64.getEncoder().encodeToString(pubKey), Base64.getEncoder().encodeToString(sig)); + return new SignedPreKey(id, pubKey, sig); } private static SignedPreKey generateSignedKEMPreKey(long id, final ECKeyPair identityKeyPair) { final byte[] pubKey = KEMKeyPair.generate(KEMKeyType.KYBER_1024).getPublicKey().serialize(); final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey); - return new SignedPreKey(id, Base64.getEncoder().encodeToString(pubKey), Base64.getEncoder().encodeToString(sig)); + return new SignedPreKey(id, pubKey, sig); } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKey.java index 2d8335504..40d5e50a7 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKey.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKey.java @@ -6,8 +6,14 @@ 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 org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.Objects; public class PreKey { @@ -16,22 +22,24 @@ public class PreKey { private long keyId; @JsonProperty + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) @NotEmpty - private String publicKey; + private byte[] publicKey; public PreKey() {} - public PreKey(long keyId, String publicKey) + public PreKey(long keyId, byte[] publicKey) { this.keyId = keyId; this.publicKey = publicKey; } - public String getPublicKey() { + public byte[] getPublicKey() { return publicKey; } - public void setPublicKey(String publicKey) { + public void setPublicKey(byte[] publicKey) { this.publicKey = publicKey; } @@ -44,23 +52,17 @@ public class PreKey { } @Override - public boolean equals(Object object) { - if (object == null || !(object instanceof PreKey)) return false; - PreKey that = (PreKey)object; - - if (publicKey == null) { - return this.keyId == that.keyId && that.publicKey == null; - } else { - return this.keyId == that.keyId && this.publicKey.equals(that.publicKey); - } + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PreKey preKey = (PreKey) o; + return keyId == preKey.keyId && Arrays.equals(publicKey, preKey.publicKey); } @Override public int hashCode() { - if (publicKey == null) { - return (int)this.keyId; - } else { - return ((int)this.keyId) ^ publicKey.hashCode(); - } + int result = Objects.hash(keyId); + result = 31 * result + Arrays.hashCode(publicKey); + return result; } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java index ae75a19ad..8a1922b3d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java @@ -18,17 +18,13 @@ public abstract class PreKeySignatureValidator { public static final Counter INVALID_SIGNATURE_COUNTER = Metrics.counter(name(PreKeySignatureValidator.class, "invalidPreKeySignature")); - public static final boolean validatePreKeySignatures(final String identityKeyB64, final Collection spks) { + public static boolean validatePreKeySignatures(final String identityKeyB64, final Collection spks) { try { final byte[] identityKeyBytes = Base64.getDecoder().decode(identityKeyB64); final ECPublicKey identityKey = Curve.decodePoint(identityKeyBytes, 0); - final boolean success = spks.stream().allMatch(spk -> { - final byte[] prekeyBytes = Base64.getDecoder().decode(spk.getPublicKey()); - final byte[] prekeySignatureBytes = Base64.getDecoder().decode(spk.getSignature()); - - return identityKey.verifySignature(prekeyBytes, prekeySignatureBytes); - }); + final boolean success = spks.stream() + .allMatch(spk -> identityKey.verifySignature(spk.getPublicKey(), spk.getSignature())); if (!success) { INVALID_SIGNATURE_COUNTER.increment(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/SignedPreKey.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/SignedPreKey.java index cb63f2d40..21b5c3d5d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/SignedPreKey.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/SignedPreKey.java @@ -6,43 +6,45 @@ 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 org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + import javax.validation.constraints.NotEmpty; +import java.util.Arrays; public class SignedPreKey extends PreKey { @JsonProperty + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) @NotEmpty - private String signature; + private byte[] signature; public SignedPreKey() {} - public SignedPreKey(long keyId, String publicKey, String signature) { + public SignedPreKey(long keyId, byte[] publicKey, byte[] signature) { super(keyId, publicKey); this.signature = signature; } - public String getSignature() { + public byte[] getSignature() { return signature; } @Override - public boolean equals(Object object) { - if (object == null || !(object instanceof SignedPreKey)) return false; - SignedPreKey that = (SignedPreKey) object; - - if (signature == null) { - return super.equals(object) && that.signature == null; - } else { - return super.equals(object) && this.signature.equals(that.signature); - } + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SignedPreKey that = (SignedPreKey) o; + return Arrays.equals(signature, that.signature); } @Override public int hashCode() { - if (signature == null) { - return super.hashCode(); - } else { - return super.hashCode() ^ signature.hashCode(); - } + int result = super.hashCode(); + result = 31 * result + Arrays.hashCode(signature); + return result; } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java index f9c0242b1..43fbcf69b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java @@ -374,24 +374,23 @@ public class Keys extends AbstractDynamoDbStore { return Map.of( KEY_ACCOUNT_UUID, getPartitionKey(accountUuid), KEY_DEVICE_ID_KEY_ID, getSortKey(deviceId, spk.getKeyId()), - KEY_PUBLIC_KEY, AttributeValues.fromByteArray(Base64.getDecoder().decode(spk.getPublicKey())), - KEY_SIGNATURE, AttributeValues.fromByteArray(Base64.getDecoder().decode(spk.getSignature()))); + KEY_PUBLIC_KEY, AttributeValues.fromByteArray(spk.getPublicKey()), + KEY_SIGNATURE, AttributeValues.fromByteArray(spk.getSignature())); } return Map.of( KEY_ACCOUNT_UUID, getPartitionKey(accountUuid), KEY_DEVICE_ID_KEY_ID, getSortKey(deviceId, preKey.getKeyId()), - KEY_PUBLIC_KEY, AttributeValues.fromByteArray(Base64.getDecoder().decode(preKey.getPublicKey()))); + KEY_PUBLIC_KEY, AttributeValues.fromByteArray(preKey.getPublicKey())); } private PreKey getPreKeyFromItem(Map item) { final long keyId = item.get(KEY_DEVICE_ID_KEY_ID).b().asByteBuffer().getLong(8); - final String publicKey = Base64.getEncoder().encodeToString(extractByteArray(item.get(KEY_PUBLIC_KEY))); + final byte[] publicKey = extractByteArray(item.get(KEY_PUBLIC_KEY)); if (item.containsKey(KEY_SIGNATURE)) { // All PQ prekeys are signed, and therefore have this attribute. Signed EC prekeys are stored // in the Accounts table, so EC prekeys retrieved by this class are never SignedPreKeys. - final String signature = Base64.getEncoder().encodeToString(extractByteArray(item.get(KEY_SIGNATURE))); - return new SignedPreKey(keyId, publicKey, signature); + return new SignedPreKey(keyId, publicKey, extractByteArray(item.get(KEY_SIGNATURE))); } return new PreKey(keyId, publicKey); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java index ac47d4c25..3e84f2db6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java @@ -61,6 +61,8 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager; @@ -72,7 +74,6 @@ import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse; import org.whispersystems.textsecuregcm.entities.ChangeNumberRequest; import org.whispersystems.textsecuregcm.entities.PhoneNumberDiscoverabilityRequest; import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper; @@ -87,6 +88,7 @@ import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.tests.util.KeysHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.Util; @@ -786,12 +788,18 @@ class AccountControllerV2Test { static Account buildTestAccountForDataReport(final UUID aci, final String number, final boolean unrestrictedUnidentifiedAccess, final boolean discoverableByPhoneNumber, List badges, List devices) { + + final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair(); + final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair(); + final Account account = new Account(); account.setUuid(aci); account.setNumber(number, UUID.randomUUID()); account.setUnrestrictedUnidentifiedAccess(unrestrictedUnidentifiedAccess); account.setDiscoverableByPhoneNumber(discoverableByPhoneNumber); account.setBadges(Clock.systemUTC(), new ArrayList<>(badges)); + account.setIdentityKey(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); + account.setPhoneNumberIdentityKey(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); assert !devices.isEmpty(); @@ -802,7 +810,8 @@ class AccountControllerV2Test { device.setId(deviceData.id); device.setAuthTokenHash(passwordTokenHash); device.setFetchesMessages(true); - device.setSignedPreKey(new SignedPreKey(1, "publicKey", "signature")); + device.setSignedPreKey(KeysHelper.signedECPreKey(1, aciIdentityKeyPair)); + device.setPhoneNumberIdentitySignedPreKey(KeysHelper.signedECPreKey(2, pniIdentityKeyPair)); device.setLastSeen(deviceData.lastSeen().toEpochMilli()); device.setCreated(deviceData.created().toEpochMilli()); device.setUserAgent(deviceData.userAgent()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java index dbbe6d416..309d406bc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -69,6 +69,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; @@ -101,6 +103,7 @@ import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.ReportMessageManager; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.tests.util.KeysHelper; import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.websocket.Stories; @@ -163,13 +166,17 @@ class MessageControllerTest { @BeforeEach void setup() { + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); + + + final List singleDeviceList = List.of( - generateTestDevice(SINGLE_DEVICE_ID1, SINGLE_DEVICE_REG_ID1, 1111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis()) + generateTestDevice(SINGLE_DEVICE_ID1, SINGLE_DEVICE_REG_ID1, 1111, KeysHelper.signedECPreKey(333, identityKeyPair), System.currentTimeMillis(), System.currentTimeMillis()) ); final List multiDeviceList = List.of( - generateTestDevice(MULTI_DEVICE_ID1, MULTI_DEVICE_REG_ID1, 2222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis()), - generateTestDevice(MULTI_DEVICE_ID2, MULTI_DEVICE_REG_ID2, 3333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis()), + generateTestDevice(MULTI_DEVICE_ID1, MULTI_DEVICE_REG_ID1, 2222, KeysHelper.signedECPreKey(111, identityKeyPair), System.currentTimeMillis(), System.currentTimeMillis()), + generateTestDevice(MULTI_DEVICE_ID2, MULTI_DEVICE_REG_ID2, 3333, KeysHelper.signedECPreKey(222, identityKeyPair), System.currentTimeMillis(), System.currentTimeMillis()), generateTestDevice(MULTI_DEVICE_ID3, MULTI_DEVICE_REG_ID3, 4444, null, System.currentTimeMillis(), System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31)) ); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java index 3bb3a428c..7531a4fb5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java @@ -134,11 +134,6 @@ class AccountsManagerConcurrentModificationIntegrationTest { accountsManager.create("+14155551212", "password", null, new AccountAttributes(), new ArrayList<>()), a -> { a.setUnidentifiedAccessKey(new byte[16]); - - final Random random = new Random(); - final SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), - "testSignature-" + random.nextInt()); - a.removeDevice(1); a.addDevice(DevicesHelper.createDevice(1)); }); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java index 7b4f18da1..a3c7645a7 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java @@ -49,6 +49,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.stubbing.Answer; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; @@ -61,6 +63,7 @@ import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2 import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.DevicesHelper; +import org.whispersystems.textsecuregcm.tests.util.KeysHelper; import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper; class AccountsManagerTest { @@ -506,7 +509,7 @@ class AccountsManagerTest { Device enabledDevice = new Device(); enabledDevice.setFetchesMessages(true); - enabledDevice.setSignedPreKey(new SignedPreKey(1L, "key", "signature")); + enabledDevice.setSignedPreKey(KeysHelper.signedECPreKey(1, Curve.generateKeyPair())); enabledDevice.setLastSeen(System.currentTimeMillis()); final long deviceId = account.getNextDeviceId(); enabledDevice.setId(deviceId); @@ -720,12 +723,13 @@ class AccountsManagerTest { final UUID uuid = UUID.randomUUID(); final UUID originalPni = UUID.randomUUID(); final UUID targetPni = UUID.randomUUID(); + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final Map newSignedKeys = Map.of( - 1L, new SignedPreKey(1L, "pub1", "sig1"), - 2L, new SignedPreKey(2L, "pub2", "sig2")); + 1L, KeysHelper.signedECPreKey(1, identityKeyPair), + 2L, KeysHelper.signedECPreKey(2, identityKeyPair)); final Map newSignedPqKeys = Map.of( - 1L, new SignedPreKey(3L, "pub3", "sig3"), - 2L, new SignedPreKey(4L, "pub4", "sig4")); + 1L, KeysHelper.signedKEMPreKey(3, identityKeyPair), + 2L, KeysHelper.signedKEMPreKey(4, identityKeyPair)); final Map newRegistrationIds = Map.of(1L, 201, 2L, 202); final Account existingAccount = AccountsHelper.generateTestAccount(targetNumber, existingAccountUuid, targetPni, new ArrayList<>(), new byte[16]); @@ -747,7 +751,7 @@ class AccountsManagerTest { verify(keys).delete(newPni); verify(keys).delete(originalPni); verify(keys).getPqEnabledDevices(uuid); - verify(keys).storePqLastResort(eq(newPni), eq(Map.of(1L, new SignedPreKey(3L, "pub3", "sig3")))); + verify(keys).storePqLastResort(eq(newPni), eq(Map.of(1L, newSignedPqKeys.get(1L)))); verifyNoMoreInteractions(keys); } @@ -768,9 +772,10 @@ class AccountsManagerTest { List devices = List.of(DevicesHelper.createDevice(1L, 0L, 101), DevicesHelper.createDevice(2L, 0L, 102)); Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[16]); + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); Map newSignedKeys = Map.of( - 1L, new SignedPreKey(1L, "pub1", "sig1"), - 2L, new SignedPreKey(2L, "pub2", "sig2")); + 1L, KeysHelper.signedECPreKey(1, identityKeyPair), + 2L, KeysHelper.signedECPreKey(2, identityKeyPair)); Map newRegistrationIds = Map.of(1L, 201, 2L, 202); UUID oldUuid = account.getUuid(); @@ -807,12 +812,13 @@ class AccountsManagerTest { List devices = List.of(DevicesHelper.createDevice(1L, 0L, 101), DevicesHelper.createDevice(2L, 0L, 102)); Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), devices, new byte[16]); - Map newSignedKeys = Map.of( - 1L, new SignedPreKey(1L, "pub1", "sig1"), - 2L, new SignedPreKey(2L, "pub2", "sig2")); - Map newSignedPqKeys = Map.of( - 1L, new SignedPreKey(3L, "pub3", "sig3"), - 2L, new SignedPreKey(4L, "pub4", "sig4")); + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); + final Map newSignedKeys = Map.of( + 1L, KeysHelper.signedECPreKey(1, identityKeyPair), + 2L, KeysHelper.signedECPreKey(2, identityKeyPair)); + final Map newSignedPqKeys = Map.of( + 1L, KeysHelper.signedKEMPreKey(3, identityKeyPair), + 2L, KeysHelper.signedKEMPreKey(4, identityKeyPair)); Map newRegistrationIds = Map.of(1L, 201, 2L, 202); UUID oldUuid = account.getUuid(); @@ -947,7 +953,7 @@ class AccountsManagerTest { final Device device = new Device(); device.setId(Device.MASTER_ID); device.setFetchesMessages(true); - device.setSignedPreKey(new SignedPreKey(1, "key", "sig")); + device.setSignedPreKey(KeysHelper.signedECPreKey(1, Curve.generateKeyPair())); device.setLastSeen(lastSeen); return device; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java index c6f84d179..79282732b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java @@ -20,9 +20,12 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; +import org.whispersystems.textsecuregcm.tests.util.KeysHelper; import org.whispersystems.textsecuregcm.util.AttributeValues; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -211,9 +214,11 @@ class KeysTest { void testStorePqLastResort() { assertEquals(0, getLastResortCount(ACCOUNT_UUID)); + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); + keys.storePqLastResort( ACCOUNT_UUID, - Map.of(1L, new SignedPreKey(1L, "pub1", "sig1"), 2L, new SignedPreKey(2L, "pub2", "sig2"))); + Map.of(1L, KeysHelper.signedKEMPreKey(1, identityKeyPair), 2L, KeysHelper.signedKEMPreKey(2, identityKeyPair))); assertEquals(2, getLastResortCount(ACCOUNT_UUID)); assertEquals(1L, keys.getLastResort(ACCOUNT_UUID, 1L).get().getKeyId()); assertEquals(2L, keys.getLastResort(ACCOUNT_UUID, 2L).get().getKeyId()); @@ -221,7 +226,7 @@ class KeysTest { keys.storePqLastResort( ACCOUNT_UUID, - Map.of(1L, new SignedPreKey(3L, "pub3", "sig3"), 3L, new SignedPreKey(4L, "pub4", "sig4"))); + Map.of(1L, KeysHelper.signedKEMPreKey(3, identityKeyPair), 3L, KeysHelper.signedKEMPreKey(4, identityKeyPair))); assertEquals(3, getLastResortCount(ACCOUNT_UUID), "storing new last-resort keys should not create duplicates"); assertEquals(3L, keys.getLastResort(ACCOUNT_UUID, 1L).get().getKeyId(), "storing new last-resort keys should overwrite old ones"); assertEquals(2L, keys.getLastResort(ACCOUNT_UUID, 2L).get().getKeyId(), "storing new last-resort keys should leave untouched ones alone"); @@ -242,9 +247,11 @@ class KeysTest { @Test void testGetPqEnabledDevices() { - keys.store(ACCOUNT_UUID, DEVICE_ID, null, List.of(new SignedPreKey(1L, "pub1", "sig1")), null); - keys.store(ACCOUNT_UUID, DEVICE_ID + 1, null, null, new SignedPreKey(2L, "pub2", "sig2")); - keys.store(ACCOUNT_UUID, DEVICE_ID + 2, null, List.of(new SignedPreKey(3L, "pub3", "sig3")), new SignedPreKey(4L, "pub4", "sig4")); + final ECKeyPair identityKeyPair = Curve.generateKeyPair(); + + keys.store(ACCOUNT_UUID, DEVICE_ID, null, List.of(KeysHelper.signedKEMPreKey(1, identityKeyPair)), null); + keys.store(ACCOUNT_UUID, DEVICE_ID + 1, null, null, KeysHelper.signedKEMPreKey(2, identityKeyPair)); + keys.store(ACCOUNT_UUID, DEVICE_ID + 2, null, List.of(KeysHelper.signedKEMPreKey(3, identityKeyPair)), KeysHelper.signedKEMPreKey(4, identityKeyPair)); keys.store(ACCOUNT_UUID, DEVICE_ID + 3, null, null, null); assertIterableEquals( Set.of(DEVICE_ID + 1, DEVICE_ID + 2), @@ -291,7 +298,7 @@ class KeysTest { final byte[] key = new byte[32]; new SecureRandom().nextBytes(key); - return new PreKey(keyId, Base64.getEncoder().encodeToString(key)); + return new PreKey(keyId, key); } private static SignedPreKey generateTestSignedPreKey(final long keyId) { @@ -302,6 +309,6 @@ class KeysTest { secureRandom.nextBytes(key); secureRandom.nextBytes(signature); - return new SignedPreKey(keyId, Base64.getEncoder().encodeToString(key), Base64.getEncoder().encodeToString(signature)); + return new SignedPreKey(keyId, key, signature); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index c7e1417cb..0e3b0ff3d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -514,7 +514,7 @@ class DeviceControllerTest { private static SignedPreKey signedPreKeyWithBadSignature(final SignedPreKey signedPreKey) { return new SignedPreKey(signedPreKey.getKeyId(), signedPreKey.getPublicKey(), - Base64.getEncoder().encodeToString("incorrect-signature".getBytes(StandardCharsets.UTF_8))); + "incorrect-signature".getBytes(StandardCharsets.UTF_8)); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java index fba6158e4..a1c71668c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java @@ -26,7 +26,6 @@ import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import io.dropwizard.testing.junit5.ResourceExtension; import java.time.Duration; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -86,18 +85,18 @@ class KeysControllerTest { private final ECKeyPair PNI_IDENTITY_KEY_PAIR = Curve.generateKeyPair(); private final String PNI_IDENTITY_KEY = KeysHelper.serializeIdentityKey(PNI_IDENTITY_KEY_PAIR); - private final PreKey SAMPLE_KEY = new PreKey(1234, "test1"); - private final PreKey SAMPLE_KEY2 = new PreKey(5667, "test3"); - private final PreKey SAMPLE_KEY3 = new PreKey(334, "test5"); - private final PreKey SAMPLE_KEY4 = new PreKey(336, "test6"); + private final PreKey SAMPLE_KEY = KeysHelper.ecPreKey(1234); + private final PreKey SAMPLE_KEY2 = KeysHelper.ecPreKey(5667); + private final PreKey SAMPLE_KEY3 = KeysHelper.ecPreKey(334); + private final PreKey SAMPLE_KEY4 = KeysHelper.ecPreKey(336); - private final PreKey SAMPLE_KEY_PNI = new PreKey(7777, "test7"); + private final PreKey SAMPLE_KEY_PNI = KeysHelper.ecPreKey(7777); - private final SignedPreKey SAMPLE_PQ_KEY = new SignedPreKey(2424, "test1", "sig"); - private final SignedPreKey SAMPLE_PQ_KEY2 = new SignedPreKey(6868, "test3", "sig"); - private final SignedPreKey SAMPLE_PQ_KEY3 = new SignedPreKey(1313, "test5", "sig"); + private final SignedPreKey SAMPLE_PQ_KEY = KeysHelper.signedKEMPreKey(2424, Curve.generateKeyPair()); + private final SignedPreKey SAMPLE_PQ_KEY2 = KeysHelper.signedKEMPreKey(6868, Curve.generateKeyPair()); + private final SignedPreKey SAMPLE_PQ_KEY3 = KeysHelper.signedKEMPreKey(1313, Curve.generateKeyPair()); - private final SignedPreKey SAMPLE_PQ_KEY_PNI = new SignedPreKey(8888, "test7", "sig"); + private final SignedPreKey SAMPLE_PQ_KEY_PNI = KeysHelper.signedKEMPreKey(8888, Curve.generateKeyPair()); private final SignedPreKey SAMPLE_SIGNED_KEY = KeysHelper.signedECPreKey(1111, IDENTITY_KEY_PAIR); private final SignedPreKey SAMPLE_SIGNED_KEY2 = KeysHelper.signedECPreKey(2222, IDENTITY_KEY_PAIR); @@ -656,7 +655,7 @@ class KeysControllerTest { @Test void putKeysTestV2() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair); @@ -684,7 +683,7 @@ class KeysControllerTest { @Test void putKeysPqTestV2() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair); @@ -716,7 +715,7 @@ class KeysControllerTest { @Test void putKeysByPhoneNumberIdentifierTestV2() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair); @@ -745,7 +744,7 @@ class KeysControllerTest { @Test void putKeysByPhoneNumberIdentifierPqTestV2() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair); @@ -778,7 +777,7 @@ class KeysControllerTest { @Test void putPrekeyWithInvalidSignature() { - final SignedPreKey badSignedPreKey = new SignedPreKey(1L, "foo", "bar"); + final SignedPreKey badSignedPreKey = KeysHelper.signedECPreKey(1, Curve.generateKeyPair()); PreKeyState preKeyState = new PreKeyState(IDENTITY_KEY, badSignedPreKey, List.of()); Response response = resources.getJerseyTest() @@ -793,16 +792,12 @@ class KeysControllerTest { @Test void disabledPutKeysTestV2() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair); - List preKeys = new LinkedList() {{ - add(preKey); - }}; - - PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, preKeys); + PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey)); Response response = resources.getJerseyTest() @@ -819,7 +814,7 @@ class KeysControllerTest { List capturedList = listCaptor.getValue(); assertThat(capturedList.size()).isEqualTo(1); assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337); - assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar"); + assertThat(capturedList.get(0).getPublicKey()).isEqualTo(preKey.getPublicKey()); verify(AuthHelper.DISABLED_ACCOUNT).setIdentityKey(eq(identityKey)); verify(AuthHelper.DISABLED_DEVICE).setSignedPreKey(eq(signedPreKey)); @@ -828,7 +823,7 @@ class KeysControllerTest { @Test void putIdentityKeyNonPrimary() { - final PreKey preKey = new PreKey(31337, "foobar"); + final PreKey preKey = KeysHelper.ecPreKey(31337); final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, IDENTITY_KEY_PAIR); List preKeys = List.of(preKey); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java index 74fa9ae87..87c3ea109 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java @@ -14,11 +14,15 @@ import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixtur import org.junit.jupiter.api.Test; import org.whispersystems.textsecuregcm.entities.PreKey; +import java.util.Base64; + class PreKeyTest { + private static final byte[] PUBLIC_KEY = Base64.getDecoder().decode("BQ+NbroQtVKyFaCSfqzSw8Wy72Ff22RSa5ERKTv5DIk2"); + @Test void serializeToJSONV2() throws Exception { - PreKey preKey = new PreKey(1234, "test"); + PreKey preKey = new PreKey(1234, PUBLIC_KEY); assertThat("PreKeyV2 Serialization works", asJson(preKey), diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/DevicesHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/DevicesHelper.java index ea424e236..86601867c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/DevicesHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/DevicesHelper.java @@ -6,6 +6,8 @@ package org.whispersystems.textsecuregcm.tests.util; import java.util.Random; + +import org.signal.libsignal.protocol.ecc.Curve; import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.util.Util; @@ -36,8 +38,8 @@ public class DevicesHelper { public static void setEnabled(Device device, boolean enabled) { if (enabled) { - device.setSignedPreKey(new SignedPreKey(RANDOM.nextLong(), "testPublicKey-" + RANDOM.nextLong(), - "testSignature-" + RANDOM.nextLong())); + device.setSignedPreKey(KeysHelper.signedECPreKey(RANDOM.nextLong(), Curve.generateKeyPair())); + device.setPhoneNumberIdentitySignedPreKey(KeysHelper.signedECPreKey(RANDOM.nextLong(), Curve.generateKeyPair())); device.setGcmId("testGcmId" + RANDOM.nextLong()); device.setLastSeen(Util.todayInMillis()); } else { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java index 6a8ac92f5..224e8962d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java @@ -10,22 +10,27 @@ import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyType; +import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.entities.SignedPreKey; public final class KeysHelper { public static String serializeIdentityKey(ECKeyPair keyPair) { return Base64.getEncoder().encodeToString(keyPair.getPublicKey().serialize()); } + + public static PreKey ecPreKey(final long id) { + return new PreKey(id, Curve.generateKeyPair().getPublicKey().serialize()); + } public static SignedPreKey signedECPreKey(long id, final ECKeyPair identityKeyPair) { final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize(); final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey); - return new SignedPreKey(id, Base64.getEncoder().encodeToString(pubKey), Base64.getEncoder().encodeToString(sig)); + return new SignedPreKey(id, pubKey, sig); } public static SignedPreKey signedKEMPreKey(long id, final ECKeyPair identityKeyPair) { final byte[] pubKey = KEMKeyPair.generate(KEMKeyType.KYBER_1024).getPublicKey().serialize(); final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey); - return new SignedPreKey(id, Base64.getEncoder().encodeToString(pubKey), Base64.getEncoder().encodeToString(sig)); + return new SignedPreKey(id, pubKey, sig); } } diff --git a/service/src/test/resources/fixtures/prekey_v2.json b/service/src/test/resources/fixtures/prekey_v2.json index 1feafdaea..fb1b2829e 100644 --- a/service/src/test/resources/fixtures/prekey_v2.json +++ b/service/src/test/resources/fixtures/prekey_v2.json @@ -1,4 +1,4 @@ { "keyId" : 1234, - "publicKey" : "test" + "publicKey" : "BQ+NbroQtVKyFaCSfqzSw8Wy72Ff22RSa5ERKTv5DIk2" } \ No newline at end of file