Represent identity keys as byte arrays
This commit is contained in:
parent
796863341d
commit
d832eaa759
|
@ -109,8 +109,8 @@ public final class Operations {
|
||||||
registrationPassword,
|
registrationPassword,
|
||||||
accountAttributes,
|
accountAttributes,
|
||||||
true,
|
true,
|
||||||
Optional.of(Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize())),
|
Optional.of(aciIdentityKeyPair.getPublicKey().serialize()),
|
||||||
Optional.of(Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize())),
|
Optional.of(pniIdentityKeyPair.getPublicKey().serialize()),
|
||||||
Optional.of(generateSignedECPreKey(1, aciIdentityKeyPair)),
|
Optional.of(generateSignedECPreKey(1, aciIdentityKeyPair)),
|
||||||
Optional.of(generateSignedECPreKey(2, pniIdentityKeyPair)),
|
Optional.of(generateSignedECPreKey(2, pniIdentityKeyPair)),
|
||||||
Optional.of(generateSignedKEMPreKey(3, aciIdentityKeyPair)),
|
Optional.of(generateSignedKEMPreKey(3, aciIdentityKeyPair)),
|
||||||
|
|
|
@ -8,7 +8,6 @@ package org.whispersystems.textsecuregcm.auth;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.signal.libsignal.protocol.ecc.Curve;
|
import org.signal.libsignal.protocol.ecc.Curve;
|
||||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
|
||||||
|
@ -35,7 +34,7 @@ public class CertificateGenerator {
|
||||||
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
|
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
|
||||||
.setSenderDevice(Math.toIntExact(device.getId()))
|
.setSenderDevice(Math.toIntExact(device.getId()))
|
||||||
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
|
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
|
||||||
.setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey())))
|
.setIdentityKey(ByteString.copyFrom(account.getIdentityKey()))
|
||||||
.setSigner(serverCertificate)
|
.setSigner(serverCertificate)
|
||||||
.setSenderUuid(account.getUuid().toString());
|
.setSenderUuid(account.getUuid().toString());
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
|
||||||
import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse;
|
import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse;
|
||||||
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
|
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
|
||||||
|
@ -38,7 +40,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||||
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
|
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
|
||||||
import org.whispersystems.textsecuregcm.entities.DeliveryCertificate;
|
import org.whispersystems.textsecuregcm.entities.DeliveryCertificate;
|
||||||
import org.whispersystems.textsecuregcm.entities.GroupCredentials;
|
import org.whispersystems.textsecuregcm.entities.GroupCredentials;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
|
||||||
|
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
@Path("/v1/certificate")
|
@Path("/v1/certificate")
|
||||||
|
@ -74,7 +75,7 @@ public class CertificateController {
|
||||||
@QueryParam("includeE164") @DefaultValue("true") boolean includeE164)
|
@QueryParam("includeE164") @DefaultValue("true") boolean includeE164)
|
||||||
throws InvalidKeyException {
|
throws InvalidKeyException {
|
||||||
|
|
||||||
if (Util.isEmpty(auth.getAccount().getIdentityKey())) {
|
if (ArrayUtils.isEmpty(auth.getAccount().getIdentityKey())) {
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,19 +13,14 @@ import io.micrometer.core.instrument.Metrics;
|
||||||
import io.micrometer.core.instrument.Tags;
|
import io.micrometer.core.instrument.Tags;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -40,7 +35,8 @@ import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
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.ChangesDeviceEnabledState;
|
||||||
|
@ -120,11 +116,11 @@ public class KeysController {
|
||||||
updateAccount = true;
|
updateAccount = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String oldIdentityKey = usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey();
|
final byte[] oldIdentityKey = usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey();
|
||||||
if (!preKeys.getIdentityKey().equals(oldIdentityKey)) {
|
if (!Arrays.equals(preKeys.getIdentityKey(), oldIdentityKey)) {
|
||||||
updateAccount = true;
|
updateAccount = true;
|
||||||
|
|
||||||
final boolean hasIdentityKey = StringUtils.isNotBlank(oldIdentityKey);
|
final boolean hasIdentityKey = ArrayUtils.isNotEmpty(oldIdentityKey);
|
||||||
final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent))
|
final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent))
|
||||||
.and(HAS_IDENTITY_KEY_TAG_NAME, String.valueOf(hasIdentityKey))
|
.and(HAS_IDENTITY_KEY_TAG_NAME, String.valueOf(hasIdentityKey))
|
||||||
.and(IDENTITY_TYPE_TAG_NAME, usePhoneNumberIdentity ? "pni" : "aci");
|
.and(IDENTITY_TYPE_TAG_NAME, usePhoneNumberIdentity ? "pni" : "aci");
|
||||||
|
@ -183,7 +179,7 @@ public class KeysController {
|
||||||
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent)
|
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent)
|
||||||
throws RateLimitExceededException {
|
throws RateLimitExceededException {
|
||||||
|
|
||||||
if (!auth.isPresent() && !accessKey.isPresent()) {
|
if (auth.isEmpty() && accessKey.isEmpty()) {
|
||||||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +221,7 @@ public class KeysController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String identityKey = usePhoneNumberIdentity ? target.getPhoneNumberIdentityKey() : target.getIdentityKey();
|
final byte[] identityKey = usePhoneNumberIdentity ? target.getPhoneNumberIdentityKey() : target.getIdentityKey();
|
||||||
|
|
||||||
if (responseItems.isEmpty()) {
|
if (responseItems.isEmpty()) {
|
||||||
return Response.status(404).build();
|
return Response.status(404).build();
|
||||||
|
|
|
@ -383,13 +383,8 @@ public class ProfileController {
|
||||||
if (account.getIdentityKey() == null || account.getPhoneNumberIdentityKey() == null) {
|
if (account.getIdentityKey() == null || account.getPhoneNumberIdentityKey() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] identityKeyBytes;
|
final byte[] identityKeyBytes =
|
||||||
try {
|
usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey();
|
||||||
identityKeyBytes = Base64.getDecoder().decode(usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey()
|
|
||||||
: account.getIdentityKey());
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
md.reset();
|
md.reset();
|
||||||
byte[] digest = md.digest(identityKeyBytes);
|
byte[] digest = md.digest(identityKeyBytes);
|
||||||
byte[] fingerprint = Util.truncate(digest, 4);
|
byte[] fingerprint = Util.truncate(digest, 4);
|
||||||
|
|
|
@ -6,13 +6,19 @@
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
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 java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class BaseProfileResponse {
|
public class BaseProfileResponse {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String identityKey;
|
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||||
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
|
private byte[] identityKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String unidentifiedAccess;
|
private String unidentifiedAccess;
|
||||||
|
@ -32,7 +38,7 @@ public class BaseProfileResponse {
|
||||||
public BaseProfileResponse() {
|
public BaseProfileResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseProfileResponse(final String identityKey,
|
public BaseProfileResponse(final byte[] identityKey,
|
||||||
final String unidentifiedAccess,
|
final String unidentifiedAccess,
|
||||||
final boolean unrestrictedUnidentifiedAccess,
|
final boolean unrestrictedUnidentifiedAccess,
|
||||||
final UserCapabilities capabilities,
|
final UserCapabilities capabilities,
|
||||||
|
@ -47,7 +53,7 @@ public class BaseProfileResponse {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentityKey() {
|
public byte[] getIdentityKey() {
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import javax.annotation.Nullable;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.AssertTrue;
|
import javax.validation.constraints.AssertTrue;
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
|
@ -36,7 +37,8 @@ public record ChangeNumberRequest(
|
||||||
@JsonProperty("reglock") @Nullable String registrationLock,
|
@JsonProperty("reglock") @Nullable String registrationLock,
|
||||||
|
|
||||||
@Schema(description="the new public identity key to use for the phone-number identity associated with the new phone number")
|
@Schema(description="the new public identity key to use for the phone-number identity associated with the new phone number")
|
||||||
@NotBlank String pniIdentityKey,
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
|
@NotEmpty byte[] pniIdentityKey,
|
||||||
|
|
||||||
@Schema(description="""
|
@Schema(description="""
|
||||||
A list of synchronization messages to send to companion devices to supply the private keys
|
A list of synchronization messages to send to companion devices to supply the private keys
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -27,7 +30,8 @@ public record ChangePhoneNumberRequest(
|
||||||
@JsonProperty("reglock") @Nullable String registrationLock,
|
@JsonProperty("reglock") @Nullable String registrationLock,
|
||||||
|
|
||||||
@Schema(description="the new public identity key to use for the phone-number identity associated with the new phone number")
|
@Schema(description="the new public identity key to use for the phone-number identity associated with the new phone number")
|
||||||
@Nullable String pniIdentityKey,
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
|
@Nullable byte[] pniIdentityKey,
|
||||||
|
|
||||||
@Schema(description="""
|
@Schema(description="""
|
||||||
A list of synchronization messages to send to companion devices to supply the private keys
|
A list of synchronization messages to send to companion devices to supply the private keys
|
||||||
|
|
|
@ -10,17 +10,18 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.AssertTrue;
|
import javax.validation.constraints.AssertTrue;
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
public record PhoneNumberIdentityKeyDistributionRequest(
|
public record PhoneNumberIdentityKeyDistributionRequest(
|
||||||
@NotBlank
|
@NotEmpty
|
||||||
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
@Schema(description="the new identity key for this account's phone-number identity")
|
@Schema(description="the new identity key for this account's phone-number identity")
|
||||||
String pniIdentityKey,
|
byte[] pniIdentityKey,
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Valid
|
@Valid
|
||||||
|
|
|
@ -6,16 +6,19 @@ package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PreKeyResponse {
|
public class PreKeyResponse {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||||
@Schema(description="the public identity key for the requested identity")
|
@Schema(description="the public identity key for the requested identity")
|
||||||
private String identityKey;
|
private byte[] identityKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
@Schema(description="information about each requested device")
|
@Schema(description="information about each requested device")
|
||||||
|
@ -23,13 +26,13 @@ public class PreKeyResponse {
|
||||||
|
|
||||||
public PreKeyResponse() {}
|
public PreKeyResponse() {}
|
||||||
|
|
||||||
public PreKeyResponse(String identityKey, List<PreKeyResponseItem> devices) {
|
public PreKeyResponse(byte[] identityKey, List<PreKeyResponseItem> devices) {
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
this.devices = devices;
|
this.devices = devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public String getIdentityKey() {
|
public byte[] getIdentityKey() {
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import static com.codahale.metrics.MetricRegistry.name;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Counter;
|
import io.micrometer.core.instrument.Counter;
|
||||||
import io.micrometer.core.instrument.Metrics;
|
import io.micrometer.core.instrument.Metrics;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
import org.signal.libsignal.protocol.ecc.Curve;
|
import org.signal.libsignal.protocol.ecc.Curve;
|
||||||
|
@ -18,9 +17,8 @@ public abstract class PreKeySignatureValidator {
|
||||||
public static final Counter INVALID_SIGNATURE_COUNTER =
|
public static final Counter INVALID_SIGNATURE_COUNTER =
|
||||||
Metrics.counter(name(PreKeySignatureValidator.class, "invalidPreKeySignature"));
|
Metrics.counter(name(PreKeySignatureValidator.class, "invalidPreKeySignature"));
|
||||||
|
|
||||||
public static boolean validatePreKeySignatures(final String identityKeyB64, final Collection<SignedPreKey> spks) {
|
public static boolean validatePreKeySignatures(final byte[] identityKeyBytes, final Collection<SignedPreKey> spks) {
|
||||||
try {
|
try {
|
||||||
final byte[] identityKeyBytes = Base64.getDecoder().decode(identityKeyB64);
|
|
||||||
final ECPublicKey identityKey = Curve.decodePoint(identityKeyBytes, 0);
|
final ECPublicKey identityKey = Curve.decodePoint(identityKeyBytes, 0);
|
||||||
|
|
||||||
final boolean success = spks.stream()
|
final boolean success = spks.stream()
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
@ -48,23 +51,24 @@ public class PreKeyState {
|
||||||
private SignedPreKey pqLastResortPreKey;
|
private SignedPreKey pqLastResortPreKey;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
@NotNull
|
@NotNull
|
||||||
@Schema(description="Required. " +
|
@Schema(description="Required. " +
|
||||||
"The public identity key for this identity (account or phone-number identity). " +
|
"The public identity key for this identity (account or phone-number identity). " +
|
||||||
"If this device is not the primary device for the account, " +
|
"If this device is not the primary device for the account, " +
|
||||||
"must match the existing stored identity key for this identity.")
|
"must match the existing stored identity key for this identity.")
|
||||||
private String identityKey;
|
private byte[] identityKey;
|
||||||
|
|
||||||
public PreKeyState() {}
|
public PreKeyState() {}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public PreKeyState(String identityKey, SignedPreKey signedPreKey, List<PreKey> keys) {
|
public PreKeyState(byte[] identityKey, SignedPreKey signedPreKey, List<PreKey> keys) {
|
||||||
this(identityKey, signedPreKey, keys, null, null);
|
this(identityKey, signedPreKey, keys, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public PreKeyState(String identityKey, SignedPreKey signedPreKey, List<PreKey> keys, List<SignedPreKey> pqKeys, SignedPreKey pqLastResortKey) {
|
public PreKeyState(byte[] identityKey, SignedPreKey signedPreKey, List<PreKey> keys, List<SignedPreKey> pqKeys, SignedPreKey pqLastResortKey) {
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
this.signedPreKey = signedPreKey;
|
this.signedPreKey = signedPreKey;
|
||||||
this.preKeys = keys;
|
this.preKeys = keys;
|
||||||
|
@ -88,7 +92,7 @@ public class PreKeyState {
|
||||||
return pqLastResortPreKey;
|
return pqLastResortPreKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentityKey() {
|
public byte[] getIdentityKey() {
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,11 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
import org.whispersystems.textsecuregcm.util.OptionalBase64ByteArrayDeserializer;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.AssertTrue;
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
@ -53,14 +55,16 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
provided, an account will be created "atomically," and all other properties needed for
|
provided, an account will be created "atomically," and all other properties needed for
|
||||||
atomic account creation must also be present.
|
atomic account creation must also be present.
|
||||||
""")
|
""")
|
||||||
Optional<String> aciIdentityKey,
|
@JsonDeserialize(using = OptionalBase64ByteArrayDeserializer.class)
|
||||||
|
Optional<byte[]> aciIdentityKey,
|
||||||
|
|
||||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """
|
||||||
The PNI-associated identity key for the account, encoded as a base64 string. If
|
The PNI-associated identity key for the account, encoded as a base64 string. If
|
||||||
provided, an account will be created "atomically," and all other properties needed for
|
provided, an account will be created "atomically," and all other properties needed for
|
||||||
atomic account creation must also be present.
|
atomic account creation must also be present.
|
||||||
""")
|
""")
|
||||||
Optional<String> pniIdentityKey,
|
@JsonDeserialize(using = OptionalBase64ByteArrayDeserializer.class)
|
||||||
|
Optional<byte[]> pniIdentityKey,
|
||||||
|
|
||||||
@JsonUnwrapped
|
@JsonUnwrapped
|
||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
|
@ -72,8 +76,8 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
@JsonProperty("recoveryPassword") byte[] recoveryPassword,
|
@JsonProperty("recoveryPassword") byte[] recoveryPassword,
|
||||||
@JsonProperty("accountAttributes") AccountAttributes accountAttributes,
|
@JsonProperty("accountAttributes") AccountAttributes accountAttributes,
|
||||||
@JsonProperty("skipDeviceTransfer") boolean skipDeviceTransfer,
|
@JsonProperty("skipDeviceTransfer") boolean skipDeviceTransfer,
|
||||||
@JsonProperty("aciIdentityKey") Optional<String> aciIdentityKey,
|
@JsonProperty("aciIdentityKey") Optional<byte[]> aciIdentityKey,
|
||||||
@JsonProperty("pniIdentityKey") Optional<String> pniIdentityKey,
|
@JsonProperty("pniIdentityKey") Optional<byte[]> pniIdentityKey,
|
||||||
@JsonProperty("aciSignedPreKey") Optional<@Valid SignedPreKey> aciSignedPreKey,
|
@JsonProperty("aciSignedPreKey") Optional<@Valid SignedPreKey> aciSignedPreKey,
|
||||||
@JsonProperty("pniSignedPreKey") Optional<@Valid SignedPreKey> pniSignedPreKey,
|
@JsonProperty("pniSignedPreKey") Optional<@Valid SignedPreKey> pniSignedPreKey,
|
||||||
@JsonProperty("aciPqLastResortPreKey") Optional<@Valid SignedPreKey> aciPqLastResortPreKey,
|
@JsonProperty("aciPqLastResortPreKey") Optional<@Valid SignedPreKey> aciPqLastResortPreKey,
|
||||||
|
@ -97,7 +101,7 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
private static boolean validatePreKeySignature(final Optional<String> maybeIdentityKey,
|
private static boolean validatePreKeySignature(final Optional<byte[]> maybeIdentityKey,
|
||||||
final Optional<SignedPreKey> maybeSignedPreKey) {
|
final Optional<SignedPreKey> maybeSignedPreKey) {
|
||||||
|
|
||||||
return maybeSignedPreKey.map(signedPreKey -> maybeIdentityKey
|
return maybeSignedPreKey.map(signedPreKey -> maybeIdentityKey
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||||
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
|
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
|
||||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||||
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
@ -57,10 +58,14 @@ public class Account {
|
||||||
private List<Device> devices = new ArrayList<>();
|
private List<Device> devices = new ArrayList<>();
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String identityKey;
|
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||||
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
|
private byte[] identityKey;
|
||||||
|
|
||||||
@JsonProperty("pniIdentityKey")
|
@JsonProperty("pniIdentityKey")
|
||||||
private String phoneNumberIdentityKey;
|
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||||
|
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||||
|
private byte[] phoneNumberIdentityKey;
|
||||||
|
|
||||||
@JsonProperty("cpv")
|
@JsonProperty("cpv")
|
||||||
private String currentProfileVersion;
|
private String currentProfileVersion;
|
||||||
|
@ -282,23 +287,23 @@ public class Account {
|
||||||
this.canonicallyDiscoverable = canonicallyDiscoverable;
|
this.canonicallyDiscoverable = canonicallyDiscoverable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdentityKey(String identityKey) {
|
public void setIdentityKey(byte[] identityKey) {
|
||||||
requireNotStale();
|
requireNotStale();
|
||||||
|
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentityKey() {
|
public byte[] getIdentityKey() {
|
||||||
requireNotStale();
|
requireNotStale();
|
||||||
|
|
||||||
return identityKey;
|
return identityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhoneNumberIdentityKey() {
|
public byte[] getPhoneNumberIdentityKey() {
|
||||||
return phoneNumberIdentityKey;
|
return phoneNumberIdentityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPhoneNumberIdentityKey(final String phoneNumberIdentityKey) {
|
public void setPhoneNumberIdentityKey(final byte[] phoneNumberIdentityKey) {
|
||||||
this.phoneNumberIdentityKey = phoneNumberIdentityKey;
|
this.phoneNumberIdentityKey = phoneNumberIdentityKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -242,11 +241,12 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account changeNumber(final Account account, final String number,
|
public Account changeNumber(final Account account,
|
||||||
@Nullable final String pniIdentityKey,
|
final String number,
|
||||||
@Nullable final Map<Long, SignedPreKey> pniSignedPreKeys,
|
@Nullable final byte[] pniIdentityKey,
|
||||||
@Nullable final Map<Long, SignedPreKey> pniPqLastResortPreKeys,
|
@Nullable final Map<Long, SignedPreKey> pniSignedPreKeys,
|
||||||
@Nullable final Map<Long, Integer> pniRegistrationIds) throws InterruptedException, MismatchedDevicesException {
|
@Nullable final Map<Long, SignedPreKey> pniPqLastResortPreKeys,
|
||||||
|
@Nullable final Map<Long, Integer> pniRegistrationIds) throws InterruptedException, MismatchedDevicesException {
|
||||||
|
|
||||||
final String originalNumber = account.getNumber();
|
final String originalNumber = account.getNumber();
|
||||||
final UUID originalPhoneNumberIdentifier = account.getPhoneNumberIdentifier();
|
final UUID originalPhoneNumberIdentifier = account.getPhoneNumberIdentifier();
|
||||||
|
@ -308,7 +308,7 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account updatePniKeys(final Account account,
|
public Account updatePniKeys(final Account account,
|
||||||
final String pniIdentityKey,
|
final byte[] pniIdentityKey,
|
||||||
final Map<Long, SignedPreKey> pniSignedPreKeys,
|
final Map<Long, SignedPreKey> pniSignedPreKeys,
|
||||||
@Nullable final Map<Long, SignedPreKey> pniPqLastResortPreKeys,
|
@Nullable final Map<Long, SignedPreKey> pniPqLastResortPreKeys,
|
||||||
final Map<Long, Integer> pniRegistrationIds) throws MismatchedDevicesException {
|
final Map<Long, Integer> pniRegistrationIds) throws MismatchedDevicesException {
|
||||||
|
@ -327,7 +327,7 @@ public class AccountsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setPniKeys(final Account account,
|
private boolean setPniKeys(final Account account,
|
||||||
@Nullable final String pniIdentityKey,
|
@Nullable final byte[] pniIdentityKey,
|
||||||
@Nullable final Map<Long, SignedPreKey> pniSignedPreKeys,
|
@Nullable final Map<Long, SignedPreKey> pniSignedPreKeys,
|
||||||
@Nullable final Map<Long, Integer> pniRegistrationIds) {
|
@Nullable final Map<Long, Integer> pniRegistrationIds) {
|
||||||
if (ObjectUtils.allNull(pniIdentityKey, pniSignedPreKeys, pniRegistrationIds)) {
|
if (ObjectUtils.allNull(pniIdentityKey, pniSignedPreKeys, pniRegistrationIds)) {
|
||||||
|
@ -336,7 +336,7 @@ public class AccountsManager {
|
||||||
throw new IllegalArgumentException("PNI identity key, signed pre-keys, and registration IDs must be all null or all non-null");
|
throw new IllegalArgumentException("PNI identity key, signed pre-keys, and registration IDs must be all null or all non-null");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean changed = !pniIdentityKey.equals(account.getPhoneNumberIdentityKey());
|
boolean changed = !Arrays.equals(pniIdentityKey, account.getPhoneNumberIdentityKey());
|
||||||
|
|
||||||
for (Device device : account.getDevices()) {
|
for (Device device : account.getDevices()) {
|
||||||
if (!device.isEnabled()) {
|
if (!device.isEnabled()) {
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -40,7 +39,7 @@ public class ChangeNumberManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account changeNumber(final Account account, final String number,
|
public Account changeNumber(final Account account, final String number,
|
||||||
@Nullable final String pniIdentityKey,
|
@Nullable final byte[] pniIdentityKey,
|
||||||
@Nullable final Map<Long, SignedPreKey> deviceSignedPreKeys,
|
@Nullable final Map<Long, SignedPreKey> deviceSignedPreKeys,
|
||||||
@Nullable final Map<Long, SignedPreKey> devicePqLastResortPreKeys,
|
@Nullable final Map<Long, SignedPreKey> devicePqLastResortPreKeys,
|
||||||
@Nullable final List<IncomingMessage> deviceMessages,
|
@Nullable final List<IncomingMessage> deviceMessages,
|
||||||
|
@ -80,7 +79,7 @@ public class ChangeNumberManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account updatePniKeys(final Account account,
|
public Account updatePniKeys(final Account account,
|
||||||
final String pniIdentityKey,
|
final byte[] pniIdentityKey,
|
||||||
final Map<Long, SignedPreKey> deviceSignedPreKeys,
|
final Map<Long, SignedPreKey> deviceSignedPreKeys,
|
||||||
@Nullable final Map<Long, SignedPreKey> devicePqLastResortPreKeys,
|
@Nullable final Map<Long, SignedPreKey> devicePqLastResortPreKeys,
|
||||||
final List<IncomingMessage> deviceMessages,
|
final List<IncomingMessage> deviceMessages,
|
||||||
|
|
|
@ -31,4 +31,3 @@ public class ByteArrayAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.whispersystems.textsecuregcm.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class OptionalBase64ByteArrayDeserializer extends JsonDeserializer<Optional<byte[]>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<byte[]> deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
|
||||||
|
return Optional.of(Base64.getDecoder().decode(jsonParser.getValueAsString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<byte[]> getNullValue(DeserializationContext ctxt) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,8 +21,8 @@ import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
class CertificateGeneratorTest {
|
class CertificateGeneratorTest {
|
||||||
|
|
||||||
private static final String SIGNING_CERTIFICATE = "CiUIDBIhBbTz4h1My+tt+vw+TVscgUe/DeHS0W02tPWAWbTO2xc3EkD+go4bJnU0AcnFfbOLKoiBfCzouZtDYMOVi69rE7r4U9cXREEqOkUmU2WJBjykAxWPCcSTmVTYHDw7hkSp/puG";
|
private static final String SIGNING_CERTIFICATE = "CiUIDBIhBbTz4h1My+tt+vw+TVscgUe/DeHS0W02tPWAWbTO2xc3EkD+go4bJnU0AcnFfbOLKoiBfCzouZtDYMOVi69rE7r4U9cXREEqOkUmU2WJBjykAxWPCcSTmVTYHDw7hkSp/puG";
|
||||||
private static final String SIGNING_KEY = "ABOxG29xrfq4E7IrW11Eg7+HBbtba9iiS0500YoBjn4=";
|
private static final String SIGNING_KEY = "ABOxG29xrfq4E7IrW11Eg7+HBbtba9iiS0500YoBjn4=";
|
||||||
private static final String IDENTITY_KEY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo";
|
private static final byte[] IDENTITY_KEY = Base64.getDecoder().decode("BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCreateFor() throws IOException, InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException {
|
void testCreateFor() throws IOException, InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException {
|
||||||
|
|
|
@ -334,9 +334,9 @@ class AccountControllerTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
|
when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
|
||||||
final Account account = invocation.getArgument(0, Account.class);
|
final Account account = invocation.getArgument(0);
|
||||||
final String number = invocation.getArgument(1, String.class);
|
final String number = invocation.getArgument(1);
|
||||||
final String pniIdentityKey = invocation.getArgument(2, String.class);
|
final byte[] pniIdentityKey = invocation.getArgument(2);
|
||||||
|
|
||||||
final UUID uuid = account.getUuid();
|
final UUID uuid = account.getUuid();
|
||||||
final UUID pni = number.equals(account.getNumber()) ? account.getPhoneNumberIdentifier() : UUID.randomUUID();
|
final UUID pni = number.equals(account.getNumber()) ? account.getPhoneNumberIdentifier() : UUID.randomUUID();
|
||||||
|
@ -358,8 +358,8 @@ class AccountControllerTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
|
when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any())).thenAnswer((Answer<Account>) invocation -> {
|
||||||
final Account account = invocation.getArgument(0, Account.class);
|
final Account account = invocation.getArgument(0);
|
||||||
final String pniIdentityKey = invocation.getArgument(1, String.class);
|
final byte[] pniIdentityKey = invocation.getArgument(1);
|
||||||
|
|
||||||
final String number = account.getNumber();
|
final String number = account.getNumber();
|
||||||
final UUID uuid = account.getUuid();
|
final UUID uuid = account.getUuid();
|
||||||
|
@ -1641,7 +1641,7 @@ class AccountControllerTest {
|
||||||
final String number = "+18005559876";
|
final String number = "+18005559876";
|
||||||
final String code = "987654";
|
final String code = "987654";
|
||||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize();
|
||||||
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
Device device2 = mock(Device.class);
|
Device device2 = mock(Device.class);
|
||||||
|
@ -1695,7 +1695,7 @@ class AccountControllerTest {
|
||||||
void testChangePhoneNumberSameNumberChangePrekeys() throws Exception {
|
void testChangePhoneNumberSameNumberChangePrekeys() throws Exception {
|
||||||
final String code = "987654";
|
final String code = "987654";
|
||||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize();
|
||||||
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
final byte[] sessionId = "session-id".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
Device device2 = mock(Device.class);
|
Device device2 = mock(Device.class);
|
||||||
|
|
|
@ -138,9 +138,9 @@ class AccountControllerV2Test {
|
||||||
|
|
||||||
when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer(
|
when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer(
|
||||||
(Answer<Account>) invocation -> {
|
(Answer<Account>) invocation -> {
|
||||||
final Account account = invocation.getArgument(0, Account.class);
|
final Account account = invocation.getArgument(0);
|
||||||
final String number = invocation.getArgument(1, String.class);
|
final String number = invocation.getArgument(1);
|
||||||
final String pniIdentityKey = invocation.getArgument(2, String.class);
|
final byte[] pniIdentityKey = invocation.getArgument(2);
|
||||||
|
|
||||||
final UUID uuid = account.getUuid();
|
final UUID uuid = account.getUuid();
|
||||||
final List<Device> devices = account.getDevices();
|
final List<Device> devices = account.getDevices();
|
||||||
|
@ -180,7 +180,7 @@ class AccountControllerV2Test {
|
||||||
.header(HttpHeaders.AUTHORIZATION,
|
.header(HttpHeaders.AUTHORIZATION,
|
||||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.entity(
|
.put(Entity.entity(
|
||||||
new ChangeNumberRequest(encodeSessionId("session"), null, NEW_NUMBER, "123", "123",
|
new ChangeNumberRequest(encodeSessionId("session"), null, NEW_NUMBER, "123", "123".getBytes(StandardCharsets.UTF_8),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyMap(), null, Collections.emptyMap()),
|
Collections.emptyMap(), null, Collections.emptyMap()),
|
||||||
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
||||||
|
@ -203,7 +203,7 @@ class AccountControllerV2Test {
|
||||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.entity(
|
.put(Entity.entity(
|
||||||
new ChangeNumberRequest(encodeSessionId("session"), null, AuthHelper.VALID_NUMBER, null,
|
new ChangeNumberRequest(encodeSessionId("session"), null, AuthHelper.VALID_NUMBER, null,
|
||||||
"pni-identity-key",
|
"pni-identity-key".getBytes(StandardCharsets.UTF_8),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyMap(), null, Collections.emptyMap()),
|
Collections.emptyMap(), null, Collections.emptyMap()),
|
||||||
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class);
|
||||||
|
@ -462,8 +462,8 @@ class AccountControllerV2Test {
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any())).thenAnswer(
|
when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any())).thenAnswer(
|
||||||
(Answer<Account>) invocation -> {
|
(Answer<Account>) invocation -> {
|
||||||
final Account account = invocation.getArgument(0, Account.class);
|
final Account account = invocation.getArgument(0);
|
||||||
final String pniIdentityKey = invocation.getArgument(1, String.class);
|
final byte[] pniIdentityKey = invocation.getArgument(1);
|
||||||
|
|
||||||
final UUID uuid = account.getUuid();
|
final UUID uuid = account.getUuid();
|
||||||
final UUID pni = account.getPhoneNumberIdentifier();
|
final UUID pni = account.getPhoneNumberIdentifier();
|
||||||
|
@ -498,7 +498,7 @@ class AccountControllerV2Test {
|
||||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
.put(Entity.json(requestJson()), AccountIdentityResponse.class);
|
.put(Entity.json(requestJson()), AccountIdentityResponse.class);
|
||||||
|
|
||||||
verify(changeNumberManager).updatePniKeys(eq(AuthHelper.VALID_ACCOUNT), eq("pni-identity-key"), any(), any(), any(), any());
|
verify(changeNumberManager).updatePniKeys(eq(AuthHelper.VALID_ACCOUNT), eq("pni-identity-key".getBytes(StandardCharsets.UTF_8)), any(), any(), any(), any());
|
||||||
|
|
||||||
assertEquals(AuthHelper.VALID_UUID, accountIdentityResponse.uuid());
|
assertEquals(AuthHelper.VALID_UUID, accountIdentityResponse.uuid());
|
||||||
assertEquals(AuthHelper.VALID_NUMBER, accountIdentityResponse.number());
|
assertEquals(AuthHelper.VALID_NUMBER, accountIdentityResponse.number());
|
||||||
|
@ -554,15 +554,15 @@ class AccountControllerV2Test {
|
||||||
* Valid request JSON for a {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}
|
* Valid request JSON for a {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest}
|
||||||
*/
|
*/
|
||||||
private static String requestJson() {
|
private static String requestJson() {
|
||||||
return """
|
return String.format("""
|
||||||
{
|
{
|
||||||
"pniIdentityKey": "pni-identity-key",
|
"pniIdentityKey": "%s",
|
||||||
"deviceMessages": [],
|
"deviceMessages": [],
|
||||||
"devicePniSignedPrekeys": {},
|
"devicePniSignedPrekeys": {},
|
||||||
"devicePniSignedPqPrekeys": {},
|
"devicePniSignedPqPrekeys": {},
|
||||||
"pniRegistrationIds": {}
|
"pniRegistrationIds": {}
|
||||||
}
|
}
|
||||||
""";
|
""", Base64.getEncoder().encodeToString("pni-identity-key".getBytes(StandardCharsets.UTF_8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -798,8 +798,8 @@ class AccountControllerV2Test {
|
||||||
account.setUnrestrictedUnidentifiedAccess(unrestrictedUnidentifiedAccess);
|
account.setUnrestrictedUnidentifiedAccess(unrestrictedUnidentifiedAccess);
|
||||||
account.setDiscoverableByPhoneNumber(discoverableByPhoneNumber);
|
account.setDiscoverableByPhoneNumber(discoverableByPhoneNumber);
|
||||||
account.setBadges(Clock.systemUTC(), new ArrayList<>(badges));
|
account.setBadges(Clock.systemUTC(), new ArrayList<>(badges));
|
||||||
account.setIdentityKey(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
account.setIdentityKey(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
account.setPhoneNumberIdentityKey(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
account.setPhoneNumberIdentityKey(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
|
|
||||||
assert !devices.isEmpty();
|
assert !devices.isEmpty();
|
||||||
|
|
||||||
|
|
|
@ -124,10 +124,10 @@ class ProfileControllerTest {
|
||||||
private static final ServerZkProfileOperations zkProfileOperations = mock(ServerZkProfileOperations.class);
|
private static final ServerZkProfileOperations zkProfileOperations = mock(ServerZkProfileOperations.class);
|
||||||
|
|
||||||
private static final byte[] UNIDENTIFIED_ACCESS_KEY = "test-uak".getBytes(StandardCharsets.UTF_8);
|
private static final byte[] UNIDENTIFIED_ACCESS_KEY = "test-uak".getBytes(StandardCharsets.UTF_8);
|
||||||
private static final String ACCOUNT_IDENTITY_KEY = "barz";
|
private static final byte[] ACCOUNT_IDENTITY_KEY = "barz".getBytes(StandardCharsets.UTF_8);
|
||||||
private static final String ACCOUNT_PHONE_NUMBER_IDENTITY_KEY = "bazz";
|
private static final byte[] ACCOUNT_PHONE_NUMBER_IDENTITY_KEY = "bazz".getBytes(StandardCharsets.UTF_8);
|
||||||
private static final String ACCOUNT_TWO_IDENTITY_KEY = "bar";
|
private static final byte[] ACCOUNT_TWO_IDENTITY_KEY = "bar".getBytes(StandardCharsets.UTF_8);
|
||||||
private static final String ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY = "baz";
|
private static final byte[] ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY = "baz".getBytes(StandardCharsets.UTF_8);
|
||||||
private static final String BASE_64_URL_USERNAME_HASH = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
private static final String BASE_64_URL_USERNAME_HASH = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
|
||||||
private static final byte[] USERNAME_HASH = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH);
|
private static final byte[] USERNAME_HASH = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -1153,13 +1153,13 @@ class ProfileControllerTest {
|
||||||
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
||||||
.post(Entity.json(new BatchIdentityCheckRequest(List.of(
|
.post(Entity.json(new BatchIdentityCheckRequest(List.of(
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null,
|
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null,
|
||||||
convertStringToFingerprint(ACCOUNT_IDENTITY_KEY)),
|
convertKeyToFingerprint(ACCOUNT_IDENTITY_KEY)),
|
||||||
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO,
|
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO,
|
||||||
convertStringToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY)),
|
convertKeyToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY)),
|
||||||
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_UUID_TWO,
|
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_UUID_TWO,
|
||||||
convertStringToFingerprint(ACCOUNT_TWO_IDENTITY_KEY)),
|
convertKeyToFingerprint(ACCOUNT_TWO_IDENTITY_KEY)),
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null,
|
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null,
|
||||||
convertStringToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY))
|
convertKeyToFingerprint(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY))
|
||||||
))))) {
|
))))) {
|
||||||
assertThat(response).isNotNull();
|
assertThat(response).isNotNull();
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
|
@ -1170,11 +1170,11 @@ class ProfileControllerTest {
|
||||||
|
|
||||||
final Condition<BatchIdentityCheckResponse.Element> isAnExpectedUuid = new Condition<>(element -> {
|
final Condition<BatchIdentityCheckResponse.Element> isAnExpectedUuid = new Condition<>(element -> {
|
||||||
if (AuthHelper.VALID_UUID.equals(element.aci())) {
|
if (AuthHelper.VALID_UUID.equals(element.aci())) {
|
||||||
return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_IDENTITY_KEY), element.identityKey());
|
return Arrays.equals(ACCOUNT_IDENTITY_KEY, element.identityKey());
|
||||||
} else if (AuthHelper.VALID_PNI_TWO.equals(element.uuid())) {
|
} else if (AuthHelper.VALID_PNI_TWO.equals(element.uuid())) {
|
||||||
return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY), element.identityKey());
|
return Arrays.equals(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY, element.identityKey());
|
||||||
} else if (AuthHelper.VALID_UUID_TWO.equals(element.uuid())) {
|
} else if (AuthHelper.VALID_UUID_TWO.equals(element.uuid())) {
|
||||||
return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_TWO_IDENTITY_KEY), element.identityKey());
|
return Arrays.equals(ACCOUNT_TWO_IDENTITY_KEY, element.identityKey());
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1182,12 +1182,14 @@ class ProfileControllerTest {
|
||||||
|
|
||||||
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
||||||
.post(Entity.json(new BatchIdentityCheckRequest(List.of(
|
.post(Entity.json(new BatchIdentityCheckRequest(List.of(
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, convertStringToFingerprint("else1234")),
|
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null,
|
||||||
|
convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8))),
|
||||||
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO,
|
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO,
|
||||||
convertStringToFingerprint("another1")),
|
convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))),
|
||||||
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_UUID_TWO,
|
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_UUID_TWO,
|
||||||
convertStringToFingerprint("another2")),
|
convertKeyToFingerprint("another2".getBytes(StandardCharsets.UTF_8))),
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertStringToFingerprint("456"))
|
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null,
|
||||||
|
convertKeyToFingerprint("456".getBytes(StandardCharsets.UTF_8)))
|
||||||
))))) {
|
))))) {
|
||||||
assertThat(response).isNotNull();
|
assertThat(response).isNotNull();
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
|
@ -1200,13 +1202,13 @@ class ProfileControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<BatchIdentityCheckRequest.Element> largeElementList = new ArrayList<>(List.of(
|
final List<BatchIdentityCheckRequest.Element> largeElementList = new ArrayList<>(List.of(
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, convertStringToFingerprint("else1234")),
|
new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8))),
|
||||||
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO, convertStringToFingerprint("another1")),
|
new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO, convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))),
|
||||||
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertStringToFingerprint("456"))));
|
new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertKeyToFingerprint("456".getBytes(StandardCharsets.UTF_8)))));
|
||||||
|
|
||||||
for (int i = 0; i < 900; i++) {
|
for (int i = 0; i < 900; i++) {
|
||||||
largeElementList.add(
|
largeElementList.add(
|
||||||
new BatchIdentityCheckRequest.Element(UUID.randomUUID(), null, convertStringToFingerprint("abcd")));
|
new BatchIdentityCheckRequest.Element(UUID.randomUUID(), null, convertKeyToFingerprint("abcd".getBytes(StandardCharsets.UTF_8))));
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
||||||
|
@ -1226,9 +1228,9 @@ class ProfileControllerTest {
|
||||||
|
|
||||||
final Condition<BatchIdentityCheckResponse.Element> isAnExpectedUuid = new Condition<>(element -> {
|
final Condition<BatchIdentityCheckResponse.Element> isAnExpectedUuid = new Condition<>(element -> {
|
||||||
if (AuthHelper.VALID_UUID.equals(element.aci())) {
|
if (AuthHelper.VALID_UUID.equals(element.aci())) {
|
||||||
return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_IDENTITY_KEY), element.identityKey());
|
return Arrays.equals(ACCOUNT_IDENTITY_KEY, element.identityKey());
|
||||||
} else if (AuthHelper.VALID_PNI_TWO.equals(element.uuid())) {
|
} else if (AuthHelper.VALID_PNI_TWO.equals(element.uuid())) {
|
||||||
return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY), element.identityKey());
|
return Arrays.equals(ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY, element.identityKey());
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1243,9 +1245,9 @@ class ProfileControllerTest {
|
||||||
{ "aci": "%s", "fingerprint": "%s" }
|
{ "aci": "%s", "fingerprint": "%s" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
""", AuthHelper.VALID_UUID, Base64.getEncoder().encodeToString(convertStringToFingerprint("else1234")),
|
""", AuthHelper.VALID_UUID, Base64.getEncoder().encodeToString(convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8))),
|
||||||
AuthHelper.VALID_PNI_TWO, Base64.getEncoder().encodeToString(convertStringToFingerprint("another1")),
|
AuthHelper.VALID_PNI_TWO, Base64.getEncoder().encodeToString(convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))),
|
||||||
AuthHelper.INVALID_UUID, Base64.getEncoder().encodeToString(convertStringToFingerprint("456")));
|
AuthHelper.INVALID_UUID, Base64.getEncoder().encodeToString(convertKeyToFingerprint("456".getBytes(StandardCharsets.UTF_8))));
|
||||||
|
|
||||||
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request()
|
||||||
.post(Entity.entity(json, "application/json"))) {
|
.post(Entity.entity(json, "application/json"))) {
|
||||||
|
@ -1311,13 +1313,13 @@ class ProfileControllerTest {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
""", AuthHelper.VALID_UUID, AuthHelper.VALID_PNI,
|
""", AuthHelper.VALID_UUID, AuthHelper.VALID_PNI,
|
||||||
Base64.getEncoder().encodeToString(convertStringToFingerprint("else1234"))))
|
Base64.getEncoder().encodeToString(convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8)))))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] convertStringToFingerprint(String base64) {
|
private static byte[] convertKeyToFingerprint(byte[] key) {
|
||||||
try {
|
try {
|
||||||
return Util.truncate(MessageDigest.getInstance("SHA-256").digest(Base64.getDecoder().decode(base64)), 4);
|
return Util.truncate(MessageDigest.getInstance("SHA-256").digest(key), 4);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,8 +368,8 @@ class RegistrationControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stream<Arguments> atomicAccountCreationConflictingChannel() {
|
static Stream<Arguments> atomicAccountCreationConflictingChannel() {
|
||||||
final Optional<String> aciIdentityKey;
|
final Optional<byte[]> aciIdentityKey;
|
||||||
final Optional<String> pniIdentityKey;
|
final Optional<byte[]> pniIdentityKey;
|
||||||
final Optional<SignedPreKey> aciSignedPreKey;
|
final Optional<SignedPreKey> aciSignedPreKey;
|
||||||
final Optional<SignedPreKey> pniSignedPreKey;
|
final Optional<SignedPreKey> pniSignedPreKey;
|
||||||
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
||||||
|
@ -378,8 +378,8 @@ class RegistrationControllerTest {
|
||||||
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
||||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||||
|
|
||||||
aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
||||||
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
||||||
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
|
@ -457,8 +457,8 @@ class RegistrationControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Stream<Arguments> atomicAccountCreationPartialSignedPreKeys() {
|
static Stream<Arguments> atomicAccountCreationPartialSignedPreKeys() {
|
||||||
final Optional<String> aciIdentityKey;
|
final Optional<byte[]> aciIdentityKey;
|
||||||
final Optional<String> pniIdentityKey;
|
final Optional<byte[]> pniIdentityKey;
|
||||||
final Optional<SignedPreKey> aciSignedPreKey;
|
final Optional<SignedPreKey> aciSignedPreKey;
|
||||||
final Optional<SignedPreKey> pniSignedPreKey;
|
final Optional<SignedPreKey> pniSignedPreKey;
|
||||||
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
||||||
|
@ -467,8 +467,8 @@ class RegistrationControllerTest {
|
||||||
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
||||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||||
|
|
||||||
aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
||||||
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
||||||
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
|
@ -570,8 +570,8 @@ class RegistrationControllerTest {
|
||||||
@MethodSource
|
@MethodSource
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
void atomicAccountCreationSuccess(final RegistrationRequest registrationRequest,
|
void atomicAccountCreationSuccess(final RegistrationRequest registrationRequest,
|
||||||
final String expectedAciIdentityKey,
|
final byte[] expectedAciIdentityKey,
|
||||||
final String expectedPniIdentityKey,
|
final byte[] expectedPniIdentityKey,
|
||||||
final SignedPreKey expectedAciSignedPreKey,
|
final SignedPreKey expectedAciSignedPreKey,
|
||||||
final SignedPreKey expectedPniSignedPreKey,
|
final SignedPreKey expectedPniSignedPreKey,
|
||||||
final SignedPreKey expectedAciPqLastResortPreKey,
|
final SignedPreKey expectedAciPqLastResortPreKey,
|
||||||
|
@ -636,8 +636,8 @@ class RegistrationControllerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> atomicAccountCreationSuccess() {
|
private static Stream<Arguments> atomicAccountCreationSuccess() {
|
||||||
final Optional<String> aciIdentityKey;
|
final Optional<byte[]> aciIdentityKey;
|
||||||
final Optional<String> pniIdentityKey;
|
final Optional<byte[]> pniIdentityKey;
|
||||||
final Optional<SignedPreKey> aciSignedPreKey;
|
final Optional<SignedPreKey> aciSignedPreKey;
|
||||||
final Optional<SignedPreKey> pniSignedPreKey;
|
final Optional<SignedPreKey> pniSignedPreKey;
|
||||||
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
final Optional<SignedPreKey> aciPqLastResortPreKey;
|
||||||
|
@ -646,8 +646,8 @@ class RegistrationControllerTest {
|
||||||
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair();
|
||||||
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
|
||||||
|
|
||||||
aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair));
|
||||||
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair));
|
||||||
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -157,7 +155,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||||
final UUID originalUuid = account.getUuid();
|
final UUID originalUuid = account.getUuid();
|
||||||
final UUID originalPni = account.getPhoneNumberIdentifier();
|
final UUID originalPni = account.getPhoneNumberIdentifier();
|
||||||
|
|
||||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize();
|
||||||
final Map<Long, SignedPreKey> preKeys = Map.of(Device.MASTER_ID, rotatedSignedPreKey);
|
final Map<Long, SignedPreKey> preKeys = Map.of(Device.MASTER_ID, rotatedSignedPreKey);
|
||||||
final Map<Long, Integer> registrationIds = Map.of(Device.MASTER_ID, rotatedPniRegistrationId);
|
final Map<Long, Integer> registrationIds = Map.of(Device.MASTER_ID, rotatedPniRegistrationId);
|
||||||
|
|
||||||
|
@ -174,7 +172,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(originalNumber));
|
assertEquals(Optional.empty(), deletedAccounts.findUuid(originalNumber));
|
||||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(secondNumber));
|
assertEquals(Optional.empty(), deletedAccounts.findUuid(secondNumber));
|
||||||
|
|
||||||
assertEquals(pniIdentityKey, updatedAccount.getPhoneNumberIdentityKey());
|
assertArrayEquals(pniIdentityKey, updatedAccount.getPhoneNumberIdentityKey());
|
||||||
|
|
||||||
assertEquals(OptionalInt.of(rotatedPniRegistrationId),
|
assertEquals(OptionalInt.of(rotatedPniRegistrationId),
|
||||||
updatedAccount.getMasterDevice().orElseThrow().getPhoneNumberIdentityRegistrationId());
|
updatedAccount.getMasterDevice().orElseThrow().getPhoneNumberIdentityRegistrationId());
|
||||||
|
|
|
@ -20,11 +20,11 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
|
||||||
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;
|
||||||
|
@ -41,7 +41,6 @@ import org.mockito.stubbing.Answer;
|
||||||
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
|
||||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
|
||||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||||
|
@ -143,7 +142,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||||
|
|
||||||
final boolean discoverableByPhoneNumber = false;
|
final boolean discoverableByPhoneNumber = false;
|
||||||
final String currentProfileVersion = "cpv";
|
final String currentProfileVersion = "cpv";
|
||||||
final String identityKey = "ikey";
|
final byte[] identityKey = "ikey".getBytes(StandardCharsets.UTF_8);
|
||||||
final byte[] unidentifiedAccessKey = new byte[]{1};
|
final byte[] unidentifiedAccessKey = new byte[]{1};
|
||||||
final String pin = "1234";
|
final String pin = "1234";
|
||||||
final String registrationLock = "reglock";
|
final String registrationLock = "reglock";
|
||||||
|
@ -185,12 +184,12 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||||
return JsonHelpers.fromJson(redisSetArgumentCapture.getValue(), Account.class);
|
return JsonHelpers.fromJson(redisSetArgumentCapture.getValue(), Account.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyAccount(final String name, final Account account, final boolean discoverableByPhoneNumber, final String currentProfileVersion, final String identityKey, final byte[] unidentifiedAccessKey, final String pin, final String clientRegistrationLock, final boolean unrestrictedUnidentifiedAccess, final long lastSeen) {
|
private void verifyAccount(final String name, final Account account, final boolean discoverableByPhoneNumber, final String currentProfileVersion, final byte[] identityKey, final byte[] unidentifiedAccessKey, final String pin, final String clientRegistrationLock, final boolean unrestrictedUnidentifiedAccess, final long lastSeen) {
|
||||||
|
|
||||||
assertAll(name,
|
assertAll(name,
|
||||||
() -> assertEquals(discoverableByPhoneNumber, account.isDiscoverableByPhoneNumber()),
|
() -> assertEquals(discoverableByPhoneNumber, account.isDiscoverableByPhoneNumber()),
|
||||||
() -> assertEquals(currentProfileVersion, account.getCurrentProfileVersion().orElseThrow()),
|
() -> assertEquals(currentProfileVersion, account.getCurrentProfileVersion().orElseThrow()),
|
||||||
() -> assertEquals(identityKey, account.getIdentityKey()),
|
() -> assertArrayEquals(identityKey, account.getIdentityKey()),
|
||||||
() -> assertArrayEquals(unidentifiedAccessKey, account.getUnidentifiedAccessKey().orElseThrow()),
|
() -> assertArrayEquals(unidentifiedAccessKey, account.getUnidentifiedAccessKey().orElseThrow()),
|
||||||
() -> assertTrue(account.getRegistrationLock().verify(clientRegistrationLock)),
|
() -> assertTrue(account.getRegistrationLock().verify(clientRegistrationLock)),
|
||||||
() -> assertEquals(unrestrictedUnidentifiedAccess, account.isUnrestrictedUnidentifiedAccess())
|
() -> assertEquals(unrestrictedUnidentifiedAccess, account.isUnrestrictedUnidentifiedAccess())
|
||||||
|
|
|
@ -5,11 +5,7 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
@ -28,6 +24,8 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import io.lettuce.core.RedisException;
|
import io.lettuce.core.RedisException;
|
||||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -470,10 +468,10 @@ class AccountsManagerTest {
|
||||||
.doAnswer(ACCOUNT_UPDATE_ANSWER)
|
.doAnswer(ACCOUNT_UPDATE_ANSWER)
|
||||||
.when(accounts).update(any());
|
.when(accounts).update(any());
|
||||||
|
|
||||||
account = accountsManager.update(account, a -> a.setIdentityKey("identity-key"));
|
account = accountsManager.update(account, a -> a.setIdentityKey("identity-key".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
|
||||||
assertEquals(1, account.getVersion());
|
assertEquals(1, account.getVersion());
|
||||||
assertEquals("identity-key", account.getIdentityKey());
|
assertArrayEquals("identity-key".getBytes(StandardCharsets.UTF_8), account.getIdentityKey());
|
||||||
|
|
||||||
verify(accounts, times(1)).getByAccountIdentifier(uuid);
|
verify(accounts, times(1)).getByAccountIdentifier(uuid);
|
||||||
verify(accounts, times(2)).update(any());
|
verify(accounts, times(2)).update(any());
|
||||||
|
@ -674,7 +672,7 @@ class AccountsManagerTest {
|
||||||
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
Account account = AccountsHelper.generateTestAccount(number, UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||||
assertThrows(IllegalArgumentException.class,
|
assertThrows(IllegalArgumentException.class,
|
||||||
() -> accountsManager.changeNumber(
|
() -> accountsManager.changeNumber(
|
||||||
account, number, "new-identity-key", Map.of(1L, new SignedPreKey()), null, Map.of(1L, 101)),
|
account, number, "new-identity-key".getBytes(StandardCharsets.UTF_8), Map.of(1L, new SignedPreKey()), null, Map.of(1L, 101)),
|
||||||
"AccountsManager should not allow use of changeNumber with new PNI keys but without changing number");
|
"AccountsManager should not allow use of changeNumber with new PNI keys but without changing number");
|
||||||
|
|
||||||
verify(accounts, never()).update(any());
|
verify(accounts, never()).update(any());
|
||||||
|
@ -739,7 +737,7 @@ class AccountsManagerTest {
|
||||||
final List<Device> devices = List.of(DevicesHelper.createDevice(1L, 0L, 101), DevicesHelper.createDevice(2L, 0L, 102));
|
final List<Device> devices = List.of(DevicesHelper.createDevice(1L, 0L, 101), DevicesHelper.createDevice(2L, 0L, 102));
|
||||||
final Account account = AccountsHelper.generateTestAccount(originalNumber, uuid, originalPni, devices, new byte[16]);
|
final Account account = AccountsHelper.generateTestAccount(originalNumber, uuid, originalPni, devices, new byte[16]);
|
||||||
final Account updatedAccount = accountsManager.changeNumber(
|
final Account updatedAccount = accountsManager.changeNumber(
|
||||||
account, targetNumber, "new-pni-identity-key", newSignedKeys, newSignedPqKeys, newRegistrationIds);
|
account, targetNumber, "new-pni-identity-key".getBytes(StandardCharsets.UTF_8), newSignedKeys, newSignedPqKeys, newRegistrationIds);
|
||||||
|
|
||||||
assertEquals(targetNumber, updatedAccount.getNumber());
|
assertEquals(targetNumber, updatedAccount.getNumber());
|
||||||
|
|
||||||
|
@ -782,19 +780,19 @@ class AccountsManagerTest {
|
||||||
UUID oldPni = account.getPhoneNumberIdentifier();
|
UUID oldPni = account.getPhoneNumberIdentifier();
|
||||||
Map<Long, SignedPreKey> oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey));
|
Map<Long, SignedPreKey> oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey));
|
||||||
|
|
||||||
final Account updatedAccount = accountsManager.updatePniKeys(account, "new-pni-identity-key", newSignedKeys, null, newRegistrationIds);
|
final Account updatedAccount = accountsManager.updatePniKeys(account, "new-pni-identity-key".getBytes(StandardCharsets.UTF_8), newSignedKeys, null, newRegistrationIds);
|
||||||
|
|
||||||
// non-PNI stuff should not change
|
// non-PNI stuff should not change
|
||||||
assertEquals(oldUuid, updatedAccount.getUuid());
|
assertEquals(oldUuid, updatedAccount.getUuid());
|
||||||
assertEquals(number, updatedAccount.getNumber());
|
assertEquals(number, updatedAccount.getNumber());
|
||||||
assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier());
|
assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier());
|
||||||
assertEquals(null, updatedAccount.getIdentityKey());
|
assertNull(updatedAccount.getIdentityKey());
|
||||||
assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)));
|
assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)));
|
||||||
assertEquals(Map.of(1L, 101, 2L, 102),
|
assertEquals(Map.of(1L, 101, 2L, 102),
|
||||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId)));
|
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId)));
|
||||||
|
|
||||||
// PNI stuff should
|
// PNI stuff should
|
||||||
assertEquals("new-pni-identity-key", updatedAccount.getPhoneNumberIdentityKey());
|
assertArrayEquals("new-pni-identity-key".getBytes(StandardCharsets.UTF_8), updatedAccount.getPhoneNumberIdentityKey());
|
||||||
assertEquals(newSignedKeys,
|
assertEquals(newSignedKeys,
|
||||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey)));
|
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey)));
|
||||||
assertEquals(newRegistrationIds,
|
assertEquals(newRegistrationIds,
|
||||||
|
@ -829,19 +827,19 @@ class AccountsManagerTest {
|
||||||
Map<Long, SignedPreKey> oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey));
|
Map<Long, SignedPreKey> oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey));
|
||||||
|
|
||||||
final Account updatedAccount =
|
final Account updatedAccount =
|
||||||
accountsManager.updatePniKeys(account, "new-pni-identity-key", newSignedKeys, newSignedPqKeys, newRegistrationIds);
|
accountsManager.updatePniKeys(account, "new-pni-identity-key".getBytes(StandardCharsets.UTF_8), newSignedKeys, newSignedPqKeys, newRegistrationIds);
|
||||||
|
|
||||||
// non-PNI-keys stuff should not change
|
// non-PNI-keys stuff should not change
|
||||||
assertEquals(oldUuid, updatedAccount.getUuid());
|
assertEquals(oldUuid, updatedAccount.getUuid());
|
||||||
assertEquals(number, updatedAccount.getNumber());
|
assertEquals(number, updatedAccount.getNumber());
|
||||||
assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier());
|
assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier());
|
||||||
assertEquals(null, updatedAccount.getIdentityKey());
|
assertNull(updatedAccount.getIdentityKey());
|
||||||
assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)));
|
assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)));
|
||||||
assertEquals(Map.of(1L, 101, 2L, 102),
|
assertEquals(Map.of(1L, 101, 2L, 102),
|
||||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId)));
|
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId)));
|
||||||
|
|
||||||
// PNI keys should
|
// PNI keys should
|
||||||
assertEquals("new-pni-identity-key", updatedAccount.getPhoneNumberIdentityKey());
|
assertArrayEquals("new-pni-identity-key".getBytes(StandardCharsets.UTF_8), updatedAccount.getPhoneNumberIdentityKey());
|
||||||
assertEquals(newSignedKeys,
|
assertEquals(newSignedKeys,
|
||||||
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey)));
|
updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey)));
|
||||||
assertEquals(newRegistrationIds,
|
assertEquals(newRegistrationIds,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -105,7 +106,7 @@ public class ChangeNumberManagerTest {
|
||||||
Account account = mock(Account.class);
|
Account account = mock(Account.class);
|
||||||
when(account.getNumber()).thenReturn("+18005551234");
|
when(account.getNumber()).thenReturn("+18005551234");
|
||||||
var prekeys = Map.of(1L, new SignedPreKey());
|
var prekeys = Map.of(1L, new SignedPreKey());
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
changeNumberManager.changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyList(), Collections.emptyMap());
|
changeNumberManager.changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyList(), Collections.emptyMap());
|
||||||
verify(accountsManager).changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyMap());
|
verify(accountsManager).changeNumber(account, "+18025551234", pniIdentityKey, prekeys, null, Collections.emptyMap());
|
||||||
|
@ -131,7 +132,7 @@ public class ChangeNumberManagerTest {
|
||||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||||
when(account.getDevices()).thenReturn(List.of(d2));
|
when(account.getDevices()).thenReturn(List.of(d2));
|
||||||
|
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes();
|
||||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||||
|
|
||||||
|
@ -174,7 +175,7 @@ public class ChangeNumberManagerTest {
|
||||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||||
when(account.getDevices()).thenReturn(List.of(d2));
|
when(account.getDevices()).thenReturn(List.of(d2));
|
||||||
|
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes(StandardCharsets.UTF_8);
|
||||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||||
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||||
|
@ -216,7 +217,7 @@ public class ChangeNumberManagerTest {
|
||||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||||
when(account.getDevices()).thenReturn(List.of(d2));
|
when(account.getDevices()).thenReturn(List.of(d2));
|
||||||
|
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes();
|
||||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||||
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||||
|
@ -256,7 +257,7 @@ public class ChangeNumberManagerTest {
|
||||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||||
when(account.getDevices()).thenReturn(List.of(d2));
|
when(account.getDevices()).thenReturn(List.of(d2));
|
||||||
|
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes();
|
||||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||||
|
|
||||||
|
@ -295,7 +296,7 @@ public class ChangeNumberManagerTest {
|
||||||
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
when(account.getDevice(2L)).thenReturn(Optional.of(d2));
|
||||||
when(account.getDevices()).thenReturn(List.of(d2));
|
when(account.getDevices()).thenReturn(List.of(d2));
|
||||||
|
|
||||||
final String pniIdentityKey = "pni-identity-key";
|
final byte[] pniIdentityKey = "pni-identity-key".getBytes();
|
||||||
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
final Map<Long, SignedPreKey> prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey());
|
||||||
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
final Map<Long, SignedPreKey> pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey());
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
|
||||||
|
@ -346,7 +347,7 @@ public class ChangeNumberManagerTest {
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||||
|
|
||||||
assertThrows(StaleDevicesException.class,
|
assertThrows(StaleDevicesException.class,
|
||||||
() -> changeNumberManager.changeNumber(account, "+18005559876", "pni-identity-key", preKeys, null, messages, registrationIds));
|
() -> changeNumberManager.changeNumber(account, "+18005559876", "pni-identity-key".getBytes(StandardCharsets.UTF_8), preKeys, null, messages, registrationIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -376,7 +377,7 @@ public class ChangeNumberManagerTest {
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||||
|
|
||||||
assertThrows(StaleDevicesException.class,
|
assertThrows(StaleDevicesException.class,
|
||||||
() -> changeNumberManager.updatePniKeys(account, "pni-identity-key", preKeys, null, messages, registrationIds));
|
() -> changeNumberManager.updatePniKeys(account, "pni-identity-key".getBytes(StandardCharsets.UTF_8), preKeys, null, messages, registrationIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -405,6 +406,6 @@ public class ChangeNumberManagerTest {
|
||||||
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class,
|
assertThrows(IllegalArgumentException.class,
|
||||||
() -> changeNumberManager.changeNumber(account, "+18005559876", "pni-identity-key", null, null, messages, registrationIds));
|
() -> changeNumberManager.changeNumber(account, "+18005559876", "pni-identity-key".getBytes(StandardCharsets.UTF_8), null, null, messages, registrationIds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,8 +113,7 @@ class CertificateControllerTest {
|
||||||
assertEquals(certificate.getSenderDevice(), 1L);
|
assertEquals(certificate.getSenderDevice(), 1L);
|
||||||
assertTrue(certificate.hasSenderUuid());
|
assertTrue(certificate.hasSenderUuid());
|
||||||
assertEquals(AuthHelper.VALID_UUID.toString(), certificate.getSenderUuid());
|
assertEquals(AuthHelper.VALID_UUID.toString(), certificate.getSenderUuid());
|
||||||
assertArrayEquals(certificate.getIdentityKey().toByteArray(),
|
assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY);
|
||||||
Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -142,8 +141,7 @@ class CertificateControllerTest {
|
||||||
assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER);
|
assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER);
|
||||||
assertEquals(certificate.getSenderDevice(), 1L);
|
assertEquals(certificate.getSenderDevice(), 1L);
|
||||||
assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString());
|
assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString());
|
||||||
assertArrayEquals(certificate.getIdentityKey().toByteArray(),
|
assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY);
|
||||||
Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -172,8 +170,7 @@ class CertificateControllerTest {
|
||||||
assertTrue(StringUtils.isBlank(certificate.getSender()));
|
assertTrue(StringUtils.isBlank(certificate.getSender()));
|
||||||
assertEquals(certificate.getSenderDevice(), 1L);
|
assertEquals(certificate.getSenderDevice(), 1L);
|
||||||
assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString());
|
assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString());
|
||||||
assertArrayEquals(certificate.getIdentityKey().toByteArray(),
|
assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY);
|
||||||
Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -278,8 +278,8 @@ class DeviceControllerTest {
|
||||||
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
||||||
|
|
||||||
when(account.getIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
when(account.getIdentityKey()).thenReturn(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
when(account.getPhoneNumberIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
when(account.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
|
|
||||||
final LinkDeviceRequest request = new LinkDeviceRequest("5678901",
|
final LinkDeviceRequest request = new LinkDeviceRequest("5678901",
|
||||||
new AccountAttributes(fetchesMessages, 1234, null, null, true, null),
|
new AccountAttributes(fetchesMessages, 1234, null, null, true, null),
|
||||||
|
@ -364,8 +364,8 @@ class DeviceControllerTest {
|
||||||
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
||||||
|
|
||||||
when(account.getIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(aciIdentityKeyPair));
|
when(account.getIdentityKey()).thenReturn(aciIdentityKeyPair.getPublicKey().serialize());
|
||||||
when(account.getPhoneNumberIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(pniIdentityKeyPair));
|
when(account.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKeyPair.getPublicKey().serialize());
|
||||||
|
|
||||||
final LinkDeviceRequest request = new LinkDeviceRequest("5678901",
|
final LinkDeviceRequest request = new LinkDeviceRequest("5678901",
|
||||||
new AccountAttributes(fetchesMessages, 1234, null, null, true, null),
|
new AccountAttributes(fetchesMessages, 1234, null, null, true, null),
|
||||||
|
@ -393,8 +393,8 @@ class DeviceControllerTest {
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource
|
@MethodSource
|
||||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
void linkDeviceAtomicMissingProperty(final String aciIdentityKey,
|
void linkDeviceAtomicMissingProperty(final byte[] aciIdentityKey,
|
||||||
final String pniIdentityKey,
|
final byte[] pniIdentityKey,
|
||||||
final Optional<SignedPreKey> aciSignedPreKey,
|
final Optional<SignedPreKey> aciSignedPreKey,
|
||||||
final Optional<SignedPreKey> pniSignedPreKey,
|
final Optional<SignedPreKey> pniSignedPreKey,
|
||||||
final Optional<SignedPreKey> aciPqLastResortPreKey,
|
final Optional<SignedPreKey> aciPqLastResortPreKey,
|
||||||
|
@ -440,8 +440,8 @@ class DeviceControllerTest {
|
||||||
final Optional<SignedPreKey> aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
final Optional<SignedPreKey> aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair));
|
||||||
final Optional<SignedPreKey> pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
final Optional<SignedPreKey> pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair));
|
||||||
|
|
||||||
final String aciIdentityKey = KeysHelper.serializeIdentityKey(aciIdentityKeyPair);
|
final byte[] aciIdentityKey = aciIdentityKeyPair.getPublicKey().serialize();
|
||||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(aciIdentityKey, pniIdentityKey, Optional.empty(), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey),
|
Arguments.of(aciIdentityKey, pniIdentityKey, Optional.empty(), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey),
|
||||||
|
@ -453,8 +453,8 @@ class DeviceControllerTest {
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource
|
@MethodSource
|
||||||
void linkDeviceAtomicInvalidSignature(final String aciIdentityKey,
|
void linkDeviceAtomicInvalidSignature(final byte[] aciIdentityKey,
|
||||||
final String pniIdentityKey,
|
final byte[] pniIdentityKey,
|
||||||
final SignedPreKey aciSignedPreKey,
|
final SignedPreKey aciSignedPreKey,
|
||||||
final SignedPreKey pniSignedPreKey,
|
final SignedPreKey pniSignedPreKey,
|
||||||
final SignedPreKey aciPqLastResortPreKey,
|
final SignedPreKey aciPqLastResortPreKey,
|
||||||
|
@ -500,8 +500,8 @@ class DeviceControllerTest {
|
||||||
final SignedPreKey aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
final SignedPreKey aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair);
|
||||||
final SignedPreKey pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
final SignedPreKey pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair);
|
||||||
|
|
||||||
final String aciIdentityKey = KeysHelper.serializeIdentityKey(aciIdentityKeyPair);
|
final byte[] aciIdentityKey = aciIdentityKeyPair.getPublicKey().serialize();
|
||||||
final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair);
|
final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(aciIdentityKey, pniIdentityKey, signedPreKeyWithBadSignature(aciSignedPreKey), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey),
|
Arguments.of(aciIdentityKey, pniIdentityKey, signedPreKeyWithBadSignature(aciSignedPreKey), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey),
|
||||||
|
|
|
@ -80,10 +80,10 @@ class KeysControllerTest {
|
||||||
private static final int SAMPLE_PNI_REGISTRATION_ID = 1717;
|
private static final int SAMPLE_PNI_REGISTRATION_ID = 1717;
|
||||||
|
|
||||||
private final ECKeyPair IDENTITY_KEY_PAIR = Curve.generateKeyPair();
|
private final ECKeyPair IDENTITY_KEY_PAIR = Curve.generateKeyPair();
|
||||||
private final String IDENTITY_KEY = KeysHelper.serializeIdentityKey(IDENTITY_KEY_PAIR);
|
private final byte[] IDENTITY_KEY = IDENTITY_KEY_PAIR.getPublicKey().serialize();
|
||||||
|
|
||||||
private final ECKeyPair PNI_IDENTITY_KEY_PAIR = Curve.generateKeyPair();
|
private final ECKeyPair PNI_IDENTITY_KEY_PAIR = Curve.generateKeyPair();
|
||||||
private final String PNI_IDENTITY_KEY = KeysHelper.serializeIdentityKey(PNI_IDENTITY_KEY_PAIR);
|
private final byte[] PNI_IDENTITY_KEY = PNI_IDENTITY_KEY_PAIR.getPublicKey().serialize();
|
||||||
|
|
||||||
private final PreKey SAMPLE_KEY = KeysHelper.ecPreKey(1234);
|
private final PreKey SAMPLE_KEY = KeysHelper.ecPreKey(1234);
|
||||||
private final PreKey SAMPLE_KEY2 = KeysHelper.ecPreKey(5667);
|
private final PreKey SAMPLE_KEY2 = KeysHelper.ecPreKey(5667);
|
||||||
|
@ -658,7 +658,7 @@ class KeysControllerTest {
|
||||||
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
||||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
final byte[] identityKey = identityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
||||||
|
|
||||||
|
@ -688,7 +688,7 @@ class KeysControllerTest {
|
||||||
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
||||||
final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair);
|
final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair);
|
||||||
final SignedPreKey pqLastResortPreKey = KeysHelper.signedECPreKey(31340, identityKeyPair);
|
final SignedPreKey pqLastResortPreKey = KeysHelper.signedECPreKey(31340, identityKeyPair);
|
||||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
final byte[] identityKey = identityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey);
|
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey);
|
||||||
|
|
||||||
|
@ -718,7 +718,7 @@ class KeysControllerTest {
|
||||||
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
||||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
final byte[] identityKey = identityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
||||||
|
|
||||||
|
@ -749,7 +749,7 @@ class KeysControllerTest {
|
||||||
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
||||||
final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair);
|
final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, identityKeyPair);
|
||||||
final SignedPreKey pqLastResortPreKey = KeysHelper.signedECPreKey(31340, identityKeyPair);
|
final SignedPreKey pqLastResortPreKey = KeysHelper.signedECPreKey(31340, identityKeyPair);
|
||||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
final byte[] identityKey = identityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey);
|
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey), List.of(pqPreKey), pqLastResortPreKey);
|
||||||
|
|
||||||
|
@ -795,7 +795,7 @@ class KeysControllerTest {
|
||||||
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
final PreKey preKey = KeysHelper.ecPreKey(31337);
|
||||||
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
final ECKeyPair identityKeyPair = Curve.generateKeyPair();
|
||||||
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair);
|
||||||
final String identityKey = KeysHelper.serializeIdentityKey(identityKeyPair);
|
final byte[] identityKey = identityKeyPair.getPublicKey().serialize();
|
||||||
|
|
||||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, List.of(preKey));
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class AuthHelper {
|
||||||
public static final UUID UNDISCOVERABLE_UUID = UUID.randomUUID();
|
public static final UUID UNDISCOVERABLE_UUID = UUID.randomUUID();
|
||||||
public static final String UNDISCOVERABLE_PASSWORD = "IT'S A SECRET TO EVERYBODY.";
|
public static final String UNDISCOVERABLE_PASSWORD = "IT'S A SECRET TO EVERYBODY.";
|
||||||
|
|
||||||
public static final String VALID_IDENTITY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo";
|
public static final byte[] VALID_IDENTITY = Base64.getDecoder().decode("BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo");
|
||||||
|
|
||||||
public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class);
|
public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class);
|
||||||
public static Account VALID_ACCOUNT = mock(Account.class );
|
public static Account VALID_ACCOUNT = mock(Account.class );
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.tests.util;
|
package org.whispersystems.textsecuregcm.tests.util;
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
import org.signal.libsignal.protocol.ecc.Curve;
|
import org.signal.libsignal.protocol.ecc.Curve;
|
||||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||||
import org.signal.libsignal.protocol.kem.KEMKeyPair;
|
import org.signal.libsignal.protocol.kem.KEMKeyPair;
|
||||||
|
@ -14,14 +13,11 @@ import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||||
|
|
||||||
public final class KeysHelper {
|
public final class KeysHelper {
|
||||||
public static String serializeIdentityKey(ECKeyPair keyPair) {
|
|
||||||
return Base64.getEncoder().encodeToString(keyPair.getPublicKey().serialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PreKey ecPreKey(final long id) {
|
public static PreKey ecPreKey(final long id) {
|
||||||
return new PreKey(id, Curve.generateKeyPair().getPublicKey().serialize());
|
return new PreKey(id, Curve.generateKeyPair().getPublicKey().serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignedPreKey signedECPreKey(long id, final ECKeyPair identityKeyPair) {
|
public static SignedPreKey signedECPreKey(long id, final ECKeyPair identityKeyPair) {
|
||||||
final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize();
|
final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize();
|
||||||
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey);
|
final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey);
|
||||||
|
|
Loading…
Reference in New Issue