Always check prekey signatures when new prekeys are uploaded
This commit is contained in:
parent
bc68b67cdf
commit
e38911b2c5
|
@ -61,6 +61,7 @@ public class KeysController {
|
|||
private final Keys keys;
|
||||
private final AccountsManager accounts;
|
||||
|
||||
private static final String IDENTITY_KEY_CHANGE_COUNTER_NAME = name(KeysController.class, "identityKeyChange");
|
||||
private static final String IDENTITY_KEY_CHANGE_FORBIDDEN_COUNTER_NAME = name(KeysController.class, "identityKeyChangeForbidden");
|
||||
|
||||
private static final String IDENTITY_TYPE_TAG_NAME = "identityType";
|
||||
|
@ -85,6 +86,7 @@ public class KeysController {
|
|||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@ChangesDeviceEnabledState
|
||||
public void setKeys(@Auth final DisabledPermittedAuthenticatedAccount disabledPermittedAuth,
|
||||
@NotNull @Valid final PreKeyState preKeys,
|
||||
|
@ -100,21 +102,21 @@ public class KeysController {
|
|||
updateAccount = true;
|
||||
}
|
||||
|
||||
if (!preKeys.getIdentityKey().equals(usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey())) {
|
||||
final String oldIdentityKey = usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey();
|
||||
if (!preKeys.getIdentityKey().equals(oldIdentityKey)) {
|
||||
updateAccount = true;
|
||||
|
||||
final boolean hasIdentityKey = StringUtils.isNotBlank(oldIdentityKey);
|
||||
final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent))
|
||||
.and(HAS_IDENTITY_KEY_TAG_NAME, String.valueOf(hasIdentityKey))
|
||||
.and(IDENTITY_TYPE_TAG_NAME, usePhoneNumberIdentity ? "pni" : "aci");
|
||||
|
||||
if (!device.isMaster()) {
|
||||
final boolean hasIdentityKey = usePhoneNumberIdentity ?
|
||||
StringUtils.isNotBlank(account.getPhoneNumberIdentityKey()) :
|
||||
StringUtils.isNotBlank(account.getIdentityKey());
|
||||
|
||||
final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent))
|
||||
.and(HAS_IDENTITY_KEY_TAG_NAME, String.valueOf(hasIdentityKey))
|
||||
.and(IDENTITY_TYPE_TAG_NAME, usePhoneNumberIdentity ? "pni" : "aci");
|
||||
|
||||
Metrics.counter(IDENTITY_KEY_CHANGE_FORBIDDEN_COUNTER_NAME, tags).increment();
|
||||
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
Metrics.counter(IDENTITY_KEY_CHANGE_COUNTER_NAME, tags).increment();
|
||||
}
|
||||
|
||||
if (updateAccount) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
|
@ -24,4 +25,12 @@ public record ChangeNumberRequest(String sessionId,
|
|||
@NotNull @Valid Map<Long, @NotNull @Valid SignedPreKey> devicePniSignedPrekeys,
|
||||
@NotNull Map<Long, Integer> pniRegistrationIds) implements PhoneVerificationRequest {
|
||||
|
||||
@AssertTrue
|
||||
public boolean isSignatureValidOnEachSignedPreKey() {
|
||||
if (devicePniSignedPrekeys == null) {
|
||||
return true;
|
||||
}
|
||||
return devicePniSignedPrekeys.values().parallelStream()
|
||||
.allMatch(spk -> PreKeySignatureValidator.validatePreKeySignature(pniIdentityKey, spk));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.entities;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
|
@ -18,4 +19,14 @@ public record ChangePhoneNumberRequest(@NotBlank String number,
|
|||
@Nullable List<IncomingMessage> deviceMessages,
|
||||
@Nullable Map<Long, SignedPreKey> devicePniSignedPrekeys,
|
||||
@Nullable Map<Long, Integer> pniRegistrationIds) {
|
||||
|
||||
@AssertTrue
|
||||
public boolean isSignatureValidOnEachSignedPreKey() {
|
||||
if (devicePniSignedPrekeys == null) {
|
||||
return true;
|
||||
}
|
||||
return devicePniSignedPrekeys.values().parallelStream()
|
||||
.allMatch(spk -> PreKeySignatureValidator.validatePreKeySignature(pniIdentityKey, spk));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
|
@ -34,4 +35,11 @@ public record PhoneNumberIdentityKeyDistributionRequest(
|
|||
@Valid
|
||||
@Schema(description="The new registration ID to use for the phone-number identity of each device")
|
||||
Map<Long, Integer> pniRegistrationIds) {
|
||||
|
||||
@AssertTrue
|
||||
public boolean isSignatureValidOnEachSignedPreKey() {
|
||||
return devicePniSignedPrekeys.values().parallelStream()
|
||||
.allMatch(spk -> PreKeySignatureValidator.validatePreKeySignature(pniIdentityKey, spk));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import java.util.Base64;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
|
||||
public abstract class PreKeySignatureValidator {
|
||||
public static final boolean validatePreKeySignature(final String identityKeyB64, final SignedPreKey spk) {
|
||||
try {
|
||||
final byte[] identityKeyBytes = Base64.getDecoder().decode(identityKeyB64);
|
||||
final byte[] prekeyBytes = Base64.getDecoder().decode(spk.getPublicKey());
|
||||
final byte[] prekeySignatureBytes = Base64.getDecoder().decode(spk.getSignature());
|
||||
|
||||
final ECPublicKey identityKey = Curve.decodePoint(identityKeyBytes, 0);
|
||||
|
||||
return identityKey.verifySignature(prekeyBytes, prekeySignatureBytes);
|
||||
} catch (IllegalArgumentException | InvalidKeyException e) {
|
||||
Metrics.counter(name(PreKeySignatureValidator.class, "invalidPreKeySignature")).increment();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
|
@ -48,4 +49,8 @@ public class PreKeyState {
|
|||
return identityKey;
|
||||
}
|
||||
|
||||
@AssertTrue
|
||||
public boolean isSignatureValid() {
|
||||
return PreKeySignatureValidator.validatePreKeySignature(identityKey, signedPreKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.ws.rs.client.Entity;
|
||||
|
@ -65,6 +67,8 @@ import org.junit.jupiter.params.provider.CsvSource;
|
|||
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.signal.libsignal.usernames.BaseUsernameException;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
||||
|
@ -122,6 +126,7 @@ import org.whispersystems.textsecuregcm.storage.UsernameHashNotAvailableExceptio
|
|||
import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException;
|
||||
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.MockUtils;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
|
@ -1622,7 +1627,8 @@ class AccountControllerTest {
|
|||
void testChangePhoneNumberChangePrekeys() throws Exception {
|
||||
final String number = "+18005559876";
|
||||
final String code = "987654";
|
||||
final String pniIdentityKey = "changed-pni-identity-key";
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
||||
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Device device2 = mock(Device.class);
|
||||
|
@ -1648,7 +1654,7 @@ class AccountControllerTest {
|
|||
var deviceMessages = List.of(
|
||||
new IncomingMessage(1, 2, 2, "content2"),
|
||||
new IncomingMessage(1, 3, 3, "content3"));
|
||||
var deviceKeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey(), 3L, new SignedPreKey());
|
||||
var deviceKeys = List.of(1L, 2L, 3L).stream().collect(Collectors.toMap(Function.identity(), n -> KeysHelper.signedPreKey(n + 100, pniIdentityKeyPair)));
|
||||
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||
|
||||
|
@ -1674,7 +1680,8 @@ class AccountControllerTest {
|
|||
@Test
|
||||
void testChangePhoneNumberSameNumberChangePrekeys() throws Exception {
|
||||
final String code = "987654";
|
||||
final String pniIdentityKey = "changed-pni-identity-key";
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
||||
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Device device2 = mock(Device.class);
|
||||
|
@ -1700,7 +1707,7 @@ class AccountControllerTest {
|
|||
var deviceMessages = List.of(
|
||||
new IncomingMessage(1, 2, 2, "content2"),
|
||||
new IncomingMessage(1, 3, 3, "content3"));
|
||||
var deviceKeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey(), 3L, new SignedPreKey());
|
||||
var deviceKeys = List.of(1L, 2L, 3L).stream().collect(Collectors.toMap(Function.identity(), n -> KeysHelper.signedPreKey(n + 100, pniIdentityKeyPair)));
|
||||
|
||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import java.util.concurrent.CompletableFuture;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
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;
|
||||
|
@ -34,6 +36,7 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
|||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
|
||||
|
||||
class AccountsManagerChangeNumberIntegrationTest {
|
||||
|
||||
|
@ -144,7 +147,8 @@ class AccountsManagerChangeNumberIntegrationTest {
|
|||
final String originalNumber = "+18005551111";
|
||||
final String secondNumber = "+18005552222";
|
||||
final int rotatedPniRegistrationId = 17;
|
||||
final SignedPreKey rotatedSignedPreKey = new SignedPreKey(1, "test", "test");
|
||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||
final SignedPreKey rotatedSignedPreKey = KeysHelper.signedPreKey(1L, pniIdentityKeyPair);
|
||||
|
||||
final AccountAttributes accountAttributes = new AccountAttributes(true, rotatedPniRegistrationId + 1, "test", null, true, new Device.DeviceCapabilities());
|
||||
final Account account = accountsManager.create(originalNumber, "password", null, accountAttributes, new ArrayList<>());
|
||||
|
@ -153,7 +157,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
|||
final UUID originalUuid = account.getUuid();
|
||||
final UUID originalPni = account.getPhoneNumberIdentifier();
|
||||
|
||||
final String pniIdentityKey = "changed-pni-identity-key";
|
||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
||||
final Map<Long, SignedPreKey> preKeys = Map.of(Device.MASTER_ID, rotatedSignedPreKey);
|
||||
final Map<Long, Integer> registrationIds = Map.of(Device.MASTER_ID, rotatedPniRegistrationId);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
|||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -39,6 +40,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
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;
|
||||
|
@ -59,6 +62,7 @@ import org.whispersystems.textsecuregcm.storage.Device;
|
|||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class KeysControllerTest {
|
||||
|
@ -76,6 +80,12 @@ class KeysControllerTest {
|
|||
|
||||
private static final int SAMPLE_PNI_REGISTRATION_ID = 1717;
|
||||
|
||||
private final ECKeyPair IDENTITY_KEY_PAIR = Curve.generateKeyPair();
|
||||
private final String IDENTITY_KEY = KeysHelper.serializeIdentityKey(IDENTITY_KEY_PAIR);
|
||||
|
||||
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");
|
||||
|
@ -83,14 +93,14 @@ class KeysControllerTest {
|
|||
|
||||
private final PreKey SAMPLE_KEY_PNI = new PreKey(7777, "test7");
|
||||
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY = new SignedPreKey( 1111, "foofoo", "sig11" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY2 = new SignedPreKey( 2222, "foobar", "sig22" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY3 = new SignedPreKey( 3333, "barfoo", "sig33" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY = new SignedPreKey( 4444, "foofoopni", "sig44" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY2 = new SignedPreKey( 5555, "foobarpni", "sig55" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY3 = new SignedPreKey( 6666, "barfoopni", "sig66" );
|
||||
private final SignedPreKey VALID_DEVICE_SIGNED_KEY = new SignedPreKey(89898, "zoofarb", "sigvalid");
|
||||
private final SignedPreKey VALID_DEVICE_PNI_SIGNED_KEY = new SignedPreKey(7777, "zoofarber", "sigvalidest");
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY = KeysHelper.signedPreKey( 1111, IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY2 = KeysHelper.signedPreKey( 2222, IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY3 = KeysHelper.signedPreKey( 3333, IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY = KeysHelper.signedPreKey( 4444, PNI_IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY2 = KeysHelper.signedPreKey( 5555, PNI_IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey SAMPLE_SIGNED_PNI_KEY3 = KeysHelper.signedPreKey( 6666, PNI_IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey VALID_DEVICE_SIGNED_KEY = KeysHelper.signedPreKey(89898, IDENTITY_KEY_PAIR);
|
||||
private final SignedPreKey VALID_DEVICE_PNI_SIGNED_KEY = KeysHelper.signedPreKey(7777, PNI_IDENTITY_KEY_PAIR);
|
||||
|
||||
private final static Keys KEYS = mock(Keys.class );
|
||||
private final static AccountsManager accounts = mock(AccountsManager.class );
|
||||
|
@ -153,8 +163,8 @@ class KeysControllerTest {
|
|||
when(existsAccount.getDevice(22L)).thenReturn(Optional.empty());
|
||||
when(existsAccount.getDevices()).thenReturn(allDevices);
|
||||
when(existsAccount.isEnabled()).thenReturn(true);
|
||||
when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey");
|
||||
when(existsAccount.getPhoneNumberIdentityKey()).thenReturn("existspniidentitykey");
|
||||
when(existsAccount.getIdentityKey()).thenReturn(IDENTITY_KEY);
|
||||
when(existsAccount.getPhoneNumberIdentityKey()).thenReturn(PNI_IDENTITY_KEY);
|
||||
when(existsAccount.getNumber()).thenReturn(EXISTS_NUMBER);
|
||||
when(existsAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of("1337".getBytes()));
|
||||
|
||||
|
@ -234,7 +244,7 @@ class KeysControllerTest {
|
|||
|
||||
@Test
|
||||
void putSignedPreKeyV2() {
|
||||
SignedPreKey test = new SignedPreKey(9998, "fooozzz", "baaarzzz");
|
||||
SignedPreKey test = KeysHelper.signedPreKey(9998, IDENTITY_KEY_PAIR);
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v2/keys/signed")
|
||||
.request()
|
||||
|
@ -250,7 +260,7 @@ class KeysControllerTest {
|
|||
|
||||
@Test
|
||||
void putPhoneNumberIdentitySignedPreKeyV2() {
|
||||
final SignedPreKey replacementKey = new SignedPreKey(9998, "fooozzz", "baaarzzz");
|
||||
final SignedPreKey replacementKey = KeysHelper.signedPreKey(9998, PNI_IDENTITY_KEY_PAIR);
|
||||
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v2/keys/signed")
|
||||
|
@ -268,7 +278,7 @@ class KeysControllerTest {
|
|||
|
||||
@Test
|
||||
void disabledPutSignedPreKeyV2() {
|
||||
SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz");
|
||||
SignedPreKey test = KeysHelper.signedPreKey(9999, IDENTITY_KEY_PAIR);
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v2/keys/signed")
|
||||
.request()
|
||||
|
@ -514,8 +524,9 @@ class KeysControllerTest {
|
|||
@Test
|
||||
void putKeysTestV2() {
|
||||
final PreKey preKey = new PreKey(31337, "foobar");
|
||||
final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||
final SignedPreKey signedPreKey = KeysHelper.signedPreKey(31338, identityKeyPair);
|
||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
||||
|
||||
List<PreKey> preKeys = new LinkedList<PreKey>() {{
|
||||
add(preKey);
|
||||
|
@ -540,15 +551,16 @@ class KeysControllerTest {
|
|||
assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337);
|
||||
assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar");
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT).setIdentityKey(eq("barbar"));
|
||||
verify(AuthHelper.VALID_ACCOUNT).setIdentityKey(eq(identityKey));
|
||||
verify(AuthHelper.VALID_DEVICE).setSignedPreKey(eq(signedPreKey));
|
||||
verify(accounts).update(eq(AuthHelper.VALID_ACCOUNT), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void putKeysByPhoneNumberIdentifierTestV2() {
|
||||
final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||
final SignedPreKey signedPreKey = KeysHelper.signedPreKey(31338, identityKeyPair);
|
||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
||||
|
||||
List<PreKey> preKeys = List.of(new PreKey(31337, "foobar"));
|
||||
|
||||
|
@ -572,16 +584,32 @@ class KeysControllerTest {
|
|||
assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337);
|
||||
assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar");
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT).setPhoneNumberIdentityKey(eq("barbar"));
|
||||
verify(AuthHelper.VALID_ACCOUNT).setPhoneNumberIdentityKey(eq(identityKey));
|
||||
verify(AuthHelper.VALID_DEVICE).setPhoneNumberIdentitySignedPreKey(eq(signedPreKey));
|
||||
verify(accounts).update(eq(AuthHelper.VALID_ACCOUNT), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void putPrekeyWithInvalidSignature() {
|
||||
final SignedPreKey badSignedPreKey = new SignedPreKey(1L, "foo", "bar");
|
||||
PreKeyState preKeyState = new PreKeyState(IDENTITY_KEY, badSignedPreKey, List.of());
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v2/keys")
|
||||
.queryParam("identity", "aci")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(preKeyState, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(422);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledPutKeysTestV2() {
|
||||
final PreKey preKey = new PreKey(31337, "foobar");
|
||||
final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||
final SignedPreKey signedPreKey = KeysHelper.signedPreKey(31338, identityKeyPair);
|
||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
||||
|
||||
List<PreKey> preKeys = new LinkedList<PreKey>() {{
|
||||
add(preKey);
|
||||
|
@ -606,7 +634,7 @@ class KeysControllerTest {
|
|||
assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337);
|
||||
assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar");
|
||||
|
||||
verify(AuthHelper.DISABLED_ACCOUNT).setIdentityKey(eq("barbar"));
|
||||
verify(AuthHelper.DISABLED_ACCOUNT).setIdentityKey(eq(identityKey));
|
||||
verify(AuthHelper.DISABLED_DEVICE).setSignedPreKey(eq(signedPreKey));
|
||||
verify(accounts).update(eq(AuthHelper.DISABLED_ACCOUNT), any());
|
||||
}
|
||||
|
@ -614,12 +642,11 @@ class KeysControllerTest {
|
|||
@Test
|
||||
void putIdentityKeyNonPrimary() {
|
||||
final PreKey preKey = new PreKey(31337, "foobar");
|
||||
final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
final SignedPreKey signedPreKey = KeysHelper.signedPreKey(31338, IDENTITY_KEY_PAIR);
|
||||
|
||||
List<PreKey> preKeys = List.of(preKey);
|
||||
|
||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, preKeys);
|
||||
PreKeyState preKeyState = new PreKeyState(IDENTITY_KEY, signedPreKey, preKeys);
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import java.util.Base64;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
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 SignedPreKey signedPreKey(long id, final ECKeyPair signingKey) {
|
||||
final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize();
|
||||
final byte[] sig = signingKey.getPrivateKey().calculateSignature(pubKey);
|
||||
return new SignedPreKey(id, Base64.getEncoder().encodeToString(pubKey), Base64.getEncoder().encodeToString(sig));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue