diff --git a/integration-tests/src/main/java/org/signal/integration/Operations.java b/integration-tests/src/main/java/org/signal/integration/Operations.java index 399a061bb..e57eb8605 100644 --- a/integration-tests/src/main/java/org/signal/integration/Operations.java +++ b/integration-tests/src/main/java/org/signal/integration/Operations.java @@ -109,8 +109,8 @@ public final class Operations { registrationPassword, accountAttributes, true, - Optional.of(Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize())), - Optional.of(Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize())), + Optional.of(aciIdentityKeyPair.getPublicKey().serialize()), + Optional.of(pniIdentityKeyPair.getPublicKey().serialize()), Optional.of(generateSignedECPreKey(1, aciIdentityKeyPair)), Optional.of(generateSignedECPreKey(2, pniIdentityKeyPair)), Optional.of(generateSignedKEMPreKey(3, aciIdentityKeyPair)), diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java index 86a927e6e..a700b8a4c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/CertificateGenerator.java @@ -8,7 +8,6 @@ package org.whispersystems.textsecuregcm.auth; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import java.security.InvalidKeyException; -import java.util.Base64; import java.util.concurrent.TimeUnit; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPrivateKey; @@ -35,7 +34,7 @@ public class CertificateGenerator { SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder() .setSenderDevice(Math.toIntExact(device.getId())) .setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays)) - .setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey()))) + .setIdentityKey(ByteString.copyFrom(account.getIdentityKey())) .setSigner(serverCertificate) .setSenderUuid(account.getUuid().toString()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java index f687974ab..d73204202 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/CertificateController.java @@ -31,6 +31,8 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; + +import org.apache.commons.lang3.ArrayUtils; import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations; import org.signal.libsignal.zkgroup.calllinks.CallLinkAuthCredentialResponse; 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.entities.DeliveryCertificate; import org.whispersystems.textsecuregcm.entities.GroupCredentials; -import org.whispersystems.textsecuregcm.util.Util; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Path("/v1/certificate") @@ -74,7 +75,7 @@ public class CertificateController { @QueryParam("includeE164") @DefaultValue("true") boolean includeE164) throws InvalidKeyException { - if (Util.isEmpty(auth.getAccount().getIdentityKey())) { + if (ArrayUtils.isEmpty(auth.getAccount().getIdentityKey())) { throw new WebApplicationException(Response.Status.BAD_REQUEST); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java index 85ff99b14..b1fe45376 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeysController.java @@ -13,19 +13,14 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tags; import io.swagger.v3.oas.annotations.Parameter; 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.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; @@ -40,7 +35,8 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; 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.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.ChangesDeviceEnabledState; @@ -120,11 +116,11 @@ public class KeysController { updateAccount = true; } - final String oldIdentityKey = usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey(); - if (!preKeys.getIdentityKey().equals(oldIdentityKey)) { + final byte[] oldIdentityKey = usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey(); + if (!Arrays.equals(preKeys.getIdentityKey(), oldIdentityKey)) { updateAccount = true; - final boolean hasIdentityKey = StringUtils.isNotBlank(oldIdentityKey); + final boolean hasIdentityKey = ArrayUtils.isNotEmpty(oldIdentityKey); final Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent)) .and(HAS_IDENTITY_KEY_TAG_NAME, String.valueOf(hasIdentityKey)) .and(IDENTITY_TYPE_TAG_NAME, usePhoneNumberIdentity ? "pni" : "aci"); @@ -183,7 +179,7 @@ public class KeysController { @HeaderParam(HttpHeaders.USER_AGENT) String userAgent) throws RateLimitExceededException { - if (!auth.isPresent() && !accessKey.isPresent()) { + if (auth.isEmpty() && accessKey.isEmpty()) { 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()) { return Response.status(404).build(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java index 42b9b3422..f3cc67d2a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/ProfileController.java @@ -383,13 +383,8 @@ public class ProfileController { if (account.getIdentityKey() == null || account.getPhoneNumberIdentityKey() == null) { return; } - byte[] identityKeyBytes; - try { - identityKeyBytes = Base64.getDecoder().decode(usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() - : account.getIdentityKey()); - } catch (IllegalArgumentException ignored) { - return; - } + final byte[] identityKeyBytes = + usePhoneNumberIdentity ? account.getPhoneNumberIdentityKey() : account.getIdentityKey(); md.reset(); byte[] digest = md.digest(identityKeyBytes); byte[] fingerprint = Util.truncate(digest, 4); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/BaseProfileResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/BaseProfileResponse.java index 1d3a2719c..580211c83 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/BaseProfileResponse.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/BaseProfileResponse.java @@ -6,13 +6,19 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + import java.util.List; import java.util.UUID; public class BaseProfileResponse { @JsonProperty - private String identityKey; + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + private byte[] identityKey; @JsonProperty private String unidentifiedAccess; @@ -32,7 +38,7 @@ public class BaseProfileResponse { public BaseProfileResponse() { } - public BaseProfileResponse(final String identityKey, + public BaseProfileResponse(final byte[] identityKey, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final UserCapabilities capabilities, @@ -47,7 +53,7 @@ public class BaseProfileResponse { this.uuid = uuid; } - public String getIdentityKey() { + public byte[] getIdentityKey() { return identityKey; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangeNumberRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangeNumberRequest.java index 0593289bb..a744663c8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangeNumberRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangeNumberRequest.java @@ -15,6 +15,7 @@ import javax.annotation.Nullable; import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; @@ -36,7 +37,8 @@ public record ChangeNumberRequest( @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") - @NotBlank String pniIdentityKey, + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @NotEmpty byte[] pniIdentityKey, @Schema(description=""" A list of synchronization messages to send to companion devices to supply the private keys diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangePhoneNumberRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangePhoneNumberRequest.java index 9ccf3bb9d..821d7c731 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangePhoneNumberRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/ChangePhoneNumberRequest.java @@ -6,7 +6,10 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.swagger.v3.oas.annotations.media.Schema; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,7 +30,8 @@ public record ChangePhoneNumberRequest( @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") - @Nullable String pniIdentityKey, + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + @Nullable byte[] pniIdentityKey, @Schema(description=""" A list of synchronization messages to send to companion devices to supply the private keys diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PhoneNumberIdentityKeyDistributionRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PhoneNumberIdentityKeyDistributionRequest.java index 16438c68b..3e353d1c4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PhoneNumberIdentityKeyDistributionRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PhoneNumberIdentityKeyDistributionRequest.java @@ -10,17 +10,18 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.ArrayList; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import javax.validation.Valid; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; public record PhoneNumberIdentityKeyDistributionRequest( - @NotBlank + @NotEmpty + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) @Schema(description="the new identity key for this account's phone-number identity") - String pniIdentityKey, + byte[] pniIdentityKey, @NotNull @Valid diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponse.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponse.java index 0c983c244..47349b7a2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponse.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyResponse.java @@ -6,16 +6,19 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.annotations.VisibleForTesting; import io.swagger.v3.oas.annotations.media.Schema; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; import java.util.List; public class PreKeyResponse { @JsonProperty + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) @Schema(description="the public identity key for the requested identity") - private String identityKey; + private byte[] identityKey; @JsonProperty @Schema(description="information about each requested device") @@ -23,13 +26,13 @@ public class PreKeyResponse { public PreKeyResponse() {} - public PreKeyResponse(String identityKey, List devices) { + public PreKeyResponse(byte[] identityKey, List devices) { this.identityKey = identityKey; this.devices = devices; } @VisibleForTesting - public String getIdentityKey() { + public byte[] getIdentityKey() { return identityKey; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java index 8a1922b3d..fd7fb62b6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeySignatureValidator.java @@ -8,7 +8,6 @@ import static com.codahale.metrics.MetricRegistry.name; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Metrics; -import java.util.Base64; import java.util.Collection; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; @@ -18,9 +17,8 @@ public abstract class PreKeySignatureValidator { public static final Counter INVALID_SIGNATURE_COUNTER = Metrics.counter(name(PreKeySignatureValidator.class, "invalidPreKeySignature")); - public static boolean validatePreKeySignatures(final String identityKeyB64, final Collection spks) { + public static boolean validatePreKeySignatures(final byte[] identityKeyBytes, final Collection spks) { try { - final byte[] identityKeyBytes = Base64.getDecoder().decode(identityKeyB64); final ECPublicKey identityKey = Curve.decodePoint(identityKeyBytes, 0); final boolean success = spks.stream() diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java index 04287341e..6f8dd2a49 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/PreKeyState.java @@ -5,8 +5,11 @@ package org.whispersystems.textsecuregcm.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.annotations.VisibleForTesting; import io.swagger.v3.oas.annotations.media.Schema; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; + import java.util.ArrayList; import java.util.List; import javax.validation.Valid; @@ -48,23 +51,24 @@ public class PreKeyState { private SignedPreKey pqLastResortPreKey; @JsonProperty + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) @NotEmpty @NotNull @Schema(description="Required. " + "The public identity key for this identity (account or phone-number identity). " + "If this device is not the primary device for the account, " + "must match the existing stored identity key for this identity.") - private String identityKey; + private byte[] identityKey; public PreKeyState() {} @VisibleForTesting - public PreKeyState(String identityKey, SignedPreKey signedPreKey, List keys) { + public PreKeyState(byte[] identityKey, SignedPreKey signedPreKey, List keys) { this(identityKey, signedPreKey, keys, null, null); } @VisibleForTesting - public PreKeyState(String identityKey, SignedPreKey signedPreKey, List keys, List pqKeys, SignedPreKey pqLastResortKey) { + public PreKeyState(byte[] identityKey, SignedPreKey signedPreKey, List keys, List pqKeys, SignedPreKey pqLastResortKey) { this.identityKey = identityKey; this.signedPreKey = signedPreKey; this.preKeys = keys; @@ -88,7 +92,7 @@ public class PreKeyState { return pqLastResortPreKey; } - public String getIdentityKey() { + public byte[] getIdentityKey() { return identityKey; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/RegistrationRequest.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/RegistrationRequest.java index 3dddb6269..a7baa5c0f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/RegistrationRequest.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/entities/RegistrationRequest.java @@ -9,9 +9,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.annotations.VisibleForTesting; import io.swagger.v3.oas.annotations.media.Schema; import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; +import org.whispersystems.textsecuregcm.util.OptionalBase64ByteArrayDeserializer; import javax.validation.Valid; 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 atomic account creation must also be present. """) - Optional aciIdentityKey, + @JsonDeserialize(using = OptionalBase64ByteArrayDeserializer.class) + Optional aciIdentityKey, @Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = """ 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 atomic account creation must also be present. """) - Optional pniIdentityKey, + @JsonDeserialize(using = OptionalBase64ByteArrayDeserializer.class) + Optional pniIdentityKey, @JsonUnwrapped @JsonProperty(access = JsonProperty.Access.READ_ONLY) @@ -72,8 +76,8 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT @JsonProperty("recoveryPassword") byte[] recoveryPassword, @JsonProperty("accountAttributes") AccountAttributes accountAttributes, @JsonProperty("skipDeviceTransfer") boolean skipDeviceTransfer, - @JsonProperty("aciIdentityKey") Optional aciIdentityKey, - @JsonProperty("pniIdentityKey") Optional pniIdentityKey, + @JsonProperty("aciIdentityKey") Optional aciIdentityKey, + @JsonProperty("pniIdentityKey") Optional pniIdentityKey, @JsonProperty("aciSignedPreKey") Optional<@Valid SignedPreKey> aciSignedPreKey, @JsonProperty("pniSignedPreKey") Optional<@Valid SignedPreKey> pniSignedPreKey, @JsonProperty("aciPqLastResortPreKey") Optional<@Valid SignedPreKey> aciPqLastResortPreKey, @@ -97,7 +101,7 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT } @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private static boolean validatePreKeySignature(final Optional maybeIdentityKey, + private static boolean validatePreKeySignature(final Optional maybeIdentityKey, final Optional maybeSignedPreKey) { return maybeSignedPreKey.map(signedPreKey -> maybeIdentityKey diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 989da6a3e..d516d4360 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -24,6 +24,7 @@ import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities; +import org.whispersystems.textsecuregcm.util.ByteArrayAdapter; import org.whispersystems.textsecuregcm.util.ByteArrayBase64UrlAdapter; import org.whispersystems.textsecuregcm.util.Util; @@ -57,10 +58,14 @@ public class Account { private List devices = new ArrayList<>(); @JsonProperty - private String identityKey; + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + private byte[] identityKey; @JsonProperty("pniIdentityKey") - private String phoneNumberIdentityKey; + @JsonSerialize(using = ByteArrayAdapter.Serializing.class) + @JsonDeserialize(using = ByteArrayAdapter.Deserializing.class) + private byte[] phoneNumberIdentityKey; @JsonProperty("cpv") private String currentProfileVersion; @@ -282,23 +287,23 @@ public class Account { this.canonicallyDiscoverable = canonicallyDiscoverable; } - public void setIdentityKey(String identityKey) { + public void setIdentityKey(byte[] identityKey) { requireNotStale(); this.identityKey = identityKey; } - public String getIdentityKey() { + public byte[] getIdentityKey() { requireNotStale(); return identityKey; } - public String getPhoneNumberIdentityKey() { + public byte[] getPhoneNumberIdentityKey() { return phoneNumberIdentityKey; } - public void setPhoneNumberIdentityKey(final String phoneNumberIdentityKey) { + public void setPhoneNumberIdentityKey(final byte[] phoneNumberIdentityKey) { this.phoneNumberIdentityKey = phoneNumberIdentityKey; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 051e5b2c2..5c755306f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -28,7 +28,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; @@ -242,11 +241,12 @@ public class AccountsManager { } } - public Account changeNumber(final Account account, final String number, - @Nullable final String pniIdentityKey, - @Nullable final Map pniSignedPreKeys, - @Nullable final Map pniPqLastResortPreKeys, - @Nullable final Map pniRegistrationIds) throws InterruptedException, MismatchedDevicesException { + public Account changeNumber(final Account account, + final String number, + @Nullable final byte[] pniIdentityKey, + @Nullable final Map pniSignedPreKeys, + @Nullable final Map pniPqLastResortPreKeys, + @Nullable final Map pniRegistrationIds) throws InterruptedException, MismatchedDevicesException { final String originalNumber = account.getNumber(); final UUID originalPhoneNumberIdentifier = account.getPhoneNumberIdentifier(); @@ -308,7 +308,7 @@ public class AccountsManager { } public Account updatePniKeys(final Account account, - final String pniIdentityKey, + final byte[] pniIdentityKey, final Map pniSignedPreKeys, @Nullable final Map pniPqLastResortPreKeys, final Map pniRegistrationIds) throws MismatchedDevicesException { @@ -327,7 +327,7 @@ public class AccountsManager { } private boolean setPniKeys(final Account account, - @Nullable final String pniIdentityKey, + @Nullable final byte[] pniIdentityKey, @Nullable final Map pniSignedPreKeys, @Nullable final Map 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"); } - boolean changed = !pniIdentityKey.equals(account.getPhoneNumberIdentityKey()); + boolean changed = !Arrays.equals(pniIdentityKey, account.getPhoneNumberIdentityKey()); for (Device device : account.getDevices()) { if (!device.isEnabled()) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManager.java index 3015bdc2f..9de38496d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManager.java @@ -5,7 +5,6 @@ package org.whispersystems.textsecuregcm.storage; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; import com.google.protobuf.ByteString; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; @@ -40,7 +39,7 @@ public class ChangeNumberManager { } public Account changeNumber(final Account account, final String number, - @Nullable final String pniIdentityKey, + @Nullable final byte[] pniIdentityKey, @Nullable final Map deviceSignedPreKeys, @Nullable final Map devicePqLastResortPreKeys, @Nullable final List deviceMessages, @@ -80,7 +79,7 @@ public class ChangeNumberManager { } public Account updatePniKeys(final Account account, - final String pniIdentityKey, + final byte[] pniIdentityKey, final Map deviceSignedPreKeys, @Nullable final Map devicePqLastResortPreKeys, final List deviceMessages, diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayAdapter.java index 201966723..0942486c0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayAdapter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ByteArrayAdapter.java @@ -31,4 +31,3 @@ public class ByteArrayAdapter { } } } - diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/OptionalBase64ByteArrayDeserializer.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/OptionalBase64ByteArrayDeserializer.java new file mode 100644 index 000000000..677dd7af3 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/OptionalBase64ByteArrayDeserializer.java @@ -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> { + + @Override + public Optional deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException { + return Optional.of(Base64.getDecoder().decode(jsonParser.getValueAsString())); + } + + @Override + public Optional getNullValue(DeserializationContext ctxt) { + return Optional.empty(); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java index 7bb8a4c19..f5ea93e34 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/CertificateGeneratorTest.java @@ -21,8 +21,8 @@ import org.whispersystems.textsecuregcm.storage.Device; class CertificateGeneratorTest { 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 IDENTITY_KEY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"; + private static final String SIGNING_KEY = "ABOxG29xrfq4E7IrW11Eg7+HBbtba9iiS0500YoBjn4="; + private static final byte[] IDENTITY_KEY = Base64.getDecoder().decode("BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo"); @Test void testCreateFor() throws IOException, InvalidKeyException, org.signal.libsignal.protocol.InvalidKeyException { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index a36476f7f..f21b1493f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -334,9 +334,9 @@ class AccountControllerTest { }); when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer((Answer) invocation -> { - final Account account = invocation.getArgument(0, Account.class); - final String number = invocation.getArgument(1, String.class); - final String pniIdentityKey = invocation.getArgument(2, String.class); + final Account account = invocation.getArgument(0); + final String number = invocation.getArgument(1); + final byte[] pniIdentityKey = invocation.getArgument(2); final UUID uuid = account.getUuid(); 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) invocation -> { - final Account account = invocation.getArgument(0, Account.class); - final String pniIdentityKey = invocation.getArgument(1, String.class); + final Account account = invocation.getArgument(0); + final byte[] pniIdentityKey = invocation.getArgument(1); final String number = account.getNumber(); final UUID uuid = account.getUuid(); @@ -1641,7 +1641,7 @@ class AccountControllerTest { final String number = "+18005559876"; final String code = "987654"; 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); Device device2 = mock(Device.class); @@ -1695,7 +1695,7 @@ class AccountControllerTest { void testChangePhoneNumberSameNumberChangePrekeys() throws Exception { final String code = "987654"; 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); Device device2 = mock(Device.class); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java index 3e84f2db6..fb1a9917d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2Test.java @@ -138,9 +138,9 @@ class AccountControllerV2Test { when(changeNumberManager.changeNumber(any(), any(), any(), any(), any(), any(), any())).thenAnswer( (Answer) invocation -> { - final Account account = invocation.getArgument(0, Account.class); - final String number = invocation.getArgument(1, String.class); - final String pniIdentityKey = invocation.getArgument(2, String.class); + final Account account = invocation.getArgument(0); + final String number = invocation.getArgument(1); + final byte[] pniIdentityKey = invocation.getArgument(2); final UUID uuid = account.getUuid(); final List devices = account.getDevices(); @@ -180,7 +180,7 @@ class AccountControllerV2Test { .header(HttpHeaders.AUTHORIZATION, AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .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.emptyMap(), null, Collections.emptyMap()), MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class); @@ -203,7 +203,7 @@ class AccountControllerV2Test { AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .put(Entity.entity( new ChangeNumberRequest(encodeSessionId("session"), null, AuthHelper.VALID_NUMBER, null, - "pni-identity-key", + "pni-identity-key".getBytes(StandardCharsets.UTF_8), Collections.emptyList(), Collections.emptyMap(), null, Collections.emptyMap()), MediaType.APPLICATION_JSON_TYPE), AccountIdentityResponse.class); @@ -462,8 +462,8 @@ class AccountControllerV2Test { void setUp() throws Exception { when(changeNumberManager.updatePniKeys(any(), any(), any(), any(), any(), any())).thenAnswer( (Answer) invocation -> { - final Account account = invocation.getArgument(0, Account.class); - final String pniIdentityKey = invocation.getArgument(1, String.class); + final Account account = invocation.getArgument(0); + final byte[] pniIdentityKey = invocation.getArgument(1); final UUID uuid = account.getUuid(); final UUID pni = account.getPhoneNumberIdentifier(); @@ -498,7 +498,7 @@ class AccountControllerV2Test { AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .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_NUMBER, accountIdentityResponse.number()); @@ -554,15 +554,15 @@ class AccountControllerV2Test { * Valid request JSON for a {@link org.whispersystems.textsecuregcm.entities.PhoneNumberIdentityKeyDistributionRequest} */ private static String requestJson() { - return """ + return String.format(""" { - "pniIdentityKey": "pni-identity-key", + "pniIdentityKey": "%s", "deviceMessages": [], "devicePniSignedPrekeys": {}, "devicePniSignedPqPrekeys": {}, "pniRegistrationIds": {} } - """; + """, Base64.getEncoder().encodeToString("pni-identity-key".getBytes(StandardCharsets.UTF_8))); } /** @@ -798,8 +798,8 @@ class AccountControllerV2Test { account.setUnrestrictedUnidentifiedAccess(unrestrictedUnidentifiedAccess); account.setDiscoverableByPhoneNumber(discoverableByPhoneNumber); account.setBadges(Clock.systemUTC(), new ArrayList<>(badges)); - account.setIdentityKey(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - account.setPhoneNumberIdentityKey(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + account.setIdentityKey(aciIdentityKeyPair.getPublicKey().serialize()); + account.setPhoneNumberIdentityKey(pniIdentityKeyPair.getPublicKey().serialize()); assert !devices.isEmpty(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java index a815e57fb..1d5dc5ed1 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ProfileControllerTest.java @@ -124,10 +124,10 @@ class ProfileControllerTest { private static final ServerZkProfileOperations zkProfileOperations = mock(ServerZkProfileOperations.class); private static final byte[] UNIDENTIFIED_ACCESS_KEY = "test-uak".getBytes(StandardCharsets.UTF_8); - private static final String ACCOUNT_IDENTITY_KEY = "barz"; - private static final String ACCOUNT_PHONE_NUMBER_IDENTITY_KEY = "bazz"; - private static final String ACCOUNT_TWO_IDENTITY_KEY = "bar"; - private static final String ACCOUNT_TWO_PHONE_NUMBER_IDENTITY_KEY = "baz"; + private static final byte[] ACCOUNT_IDENTITY_KEY = "barz".getBytes(StandardCharsets.UTF_8); + private static final byte[] ACCOUNT_PHONE_NUMBER_IDENTITY_KEY = "bazz".getBytes(StandardCharsets.UTF_8); + private static final byte[] ACCOUNT_TWO_IDENTITY_KEY = "bar".getBytes(StandardCharsets.UTF_8); + 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 byte[] USERNAME_HASH = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH); @SuppressWarnings("unchecked") @@ -1153,13 +1153,13 @@ class ProfileControllerTest { try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request() .post(Entity.json(new BatchIdentityCheckRequest(List.of( new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, - convertStringToFingerprint(ACCOUNT_IDENTITY_KEY)), + convertKeyToFingerprint(ACCOUNT_IDENTITY_KEY)), 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, - convertStringToFingerprint(ACCOUNT_TWO_IDENTITY_KEY)), + convertKeyToFingerprint(ACCOUNT_TWO_IDENTITY_KEY)), 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.getStatus()).isEqualTo(200); @@ -1170,11 +1170,11 @@ class ProfileControllerTest { final Condition isAnExpectedUuid = new Condition<>(element -> { 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())) { - 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())) { - return Arrays.equals(Base64.getDecoder().decode(ACCOUNT_TWO_IDENTITY_KEY), element.identityKey()); + return Arrays.equals(ACCOUNT_TWO_IDENTITY_KEY, element.identityKey()); } else { return false; } @@ -1182,12 +1182,14 @@ class ProfileControllerTest { try (final Response response = resources.getJerseyTest().target("/v1/profile/identity_check/batch").request() .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, - convertStringToFingerprint("another1")), + convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))), new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_UUID_TWO, - convertStringToFingerprint("another2")), - new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertStringToFingerprint("456")) + convertKeyToFingerprint("another2".getBytes(StandardCharsets.UTF_8))), + new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, + convertKeyToFingerprint("456".getBytes(StandardCharsets.UTF_8))) ))))) { assertThat(response).isNotNull(); assertThat(response.getStatus()).isEqualTo(200); @@ -1200,13 +1202,13 @@ class ProfileControllerTest { } final List largeElementList = new ArrayList<>(List.of( - new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, convertStringToFingerprint("else1234")), - new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO, convertStringToFingerprint("another1")), - new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertStringToFingerprint("456")))); + new BatchIdentityCheckRequest.Element(AuthHelper.VALID_UUID, null, convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8))), + new BatchIdentityCheckRequest.Element(null, AuthHelper.VALID_PNI_TWO, convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))), + new BatchIdentityCheckRequest.Element(AuthHelper.INVALID_UUID, null, convertKeyToFingerprint("456".getBytes(StandardCharsets.UTF_8))))); for (int i = 0; i < 900; i++) { 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() @@ -1226,9 +1228,9 @@ class ProfileControllerTest { final Condition isAnExpectedUuid = new Condition<>(element -> { 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())) { - 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 { return false; } @@ -1243,9 +1245,9 @@ class ProfileControllerTest { { "aci": "%s", "fingerprint": "%s" } ] } - """, AuthHelper.VALID_UUID, Base64.getEncoder().encodeToString(convertStringToFingerprint("else1234")), - AuthHelper.VALID_PNI_TWO, Base64.getEncoder().encodeToString(convertStringToFingerprint("another1")), - AuthHelper.INVALID_UUID, Base64.getEncoder().encodeToString(convertStringToFingerprint("456"))); + """, AuthHelper.VALID_UUID, Base64.getEncoder().encodeToString(convertKeyToFingerprint("else1234".getBytes(StandardCharsets.UTF_8))), + AuthHelper.VALID_PNI_TWO, Base64.getEncoder().encodeToString(convertKeyToFingerprint("another1".getBytes(StandardCharsets.UTF_8))), + 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() .post(Entity.entity(json, "application/json"))) { @@ -1311,13 +1313,13 @@ class ProfileControllerTest { ] } """, 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 { - 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) { throw new AssertionError(e); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java index 8c13bf112..0bc6885aa 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/RegistrationControllerTest.java @@ -368,8 +368,8 @@ class RegistrationControllerTest { } static Stream atomicAccountCreationConflictingChannel() { - final Optional aciIdentityKey; - final Optional pniIdentityKey; + final Optional aciIdentityKey; + final Optional pniIdentityKey; final Optional aciSignedPreKey; final Optional pniSignedPreKey; final Optional aciPqLastResortPreKey; @@ -378,8 +378,8 @@ class RegistrationControllerTest { final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair(); final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair(); - aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize()); + pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize()); aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair)); pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair)); aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); @@ -457,8 +457,8 @@ class RegistrationControllerTest { } static Stream atomicAccountCreationPartialSignedPreKeys() { - final Optional aciIdentityKey; - final Optional pniIdentityKey; + final Optional aciIdentityKey; + final Optional pniIdentityKey; final Optional aciSignedPreKey; final Optional pniSignedPreKey; final Optional aciPqLastResortPreKey; @@ -467,8 +467,8 @@ class RegistrationControllerTest { final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair(); final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair(); - aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize()); + pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize()); aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair)); pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair)); aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); @@ -570,8 +570,8 @@ class RegistrationControllerTest { @MethodSource @SuppressWarnings("OptionalUsedAsFieldOrParameterType") void atomicAccountCreationSuccess(final RegistrationRequest registrationRequest, - final String expectedAciIdentityKey, - final String expectedPniIdentityKey, + final byte[] expectedAciIdentityKey, + final byte[] expectedPniIdentityKey, final SignedPreKey expectedAciSignedPreKey, final SignedPreKey expectedPniSignedPreKey, final SignedPreKey expectedAciPqLastResortPreKey, @@ -636,8 +636,8 @@ class RegistrationControllerTest { } private static Stream atomicAccountCreationSuccess() { - final Optional aciIdentityKey; - final Optional pniIdentityKey; + final Optional aciIdentityKey; + final Optional pniIdentityKey; final Optional aciSignedPreKey; final Optional pniSignedPreKey; final Optional aciPqLastResortPreKey; @@ -646,8 +646,8 @@ class RegistrationControllerTest { final ECKeyPair aciIdentityKeyPair = Curve.generateKeyPair(); final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair(); - aciIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - pniIdentityKey = Optional.of(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + aciIdentityKey = Optional.of(aciIdentityKeyPair.getPublicKey().serialize()); + pniIdentityKey = Optional.of(pniIdentityKeyPair.getPublicKey().serialize()); aciSignedPreKey = Optional.of(KeysHelper.signedECPreKey(1, aciIdentityKeyPair)); pniSignedPreKey = Optional.of(KeysHelper.signedECPreKey(2, pniIdentityKeyPair)); aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java index b99e06d03..cde69cb13 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java @@ -5,9 +5,7 @@ package org.whispersystems.textsecuregcm.storage; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -157,7 +155,7 @@ class AccountsManagerChangeNumberIntegrationTest { final UUID originalUuid = account.getUuid(); final UUID originalPni = account.getPhoneNumberIdentifier(); - final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair); + final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize(); final Map preKeys = Map.of(Device.MASTER_ID, rotatedSignedPreKey); final Map registrationIds = Map.of(Device.MASTER_ID, rotatedPniRegistrationId); @@ -174,7 +172,7 @@ class AccountsManagerChangeNumberIntegrationTest { assertEquals(Optional.empty(), deletedAccounts.findUuid(originalNumber)); assertEquals(Optional.empty(), deletedAccounts.findUuid(secondNumber)); - assertEquals(pniIdentityKey, updatedAccount.getPhoneNumberIdentityKey()); + assertArrayEquals(pniIdentityKey, updatedAccount.getPhoneNumberIdentityKey()); assertEquals(OptionalInt.of(rotatedPniRegistrationId), updatedAccount.getMasterDevice().orElseThrow().getPhoneNumberIdentityRegistrationId()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java index 7531a4fb5..4ebac9dd5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java @@ -20,11 +20,11 @@ import static org.mockito.Mockito.when; import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Instant; import java.util.ArrayList; import java.util.Optional; -import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -41,7 +41,6 @@ import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.entities.AccountAttributes; -import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; @@ -143,7 +142,7 @@ class AccountsManagerConcurrentModificationIntegrationTest { final boolean discoverableByPhoneNumber = false; final String currentProfileVersion = "cpv"; - final String identityKey = "ikey"; + final byte[] identityKey = "ikey".getBytes(StandardCharsets.UTF_8); final byte[] unidentifiedAccessKey = new byte[]{1}; final String pin = "1234"; final String registrationLock = "reglock"; @@ -185,12 +184,12 @@ class AccountsManagerConcurrentModificationIntegrationTest { 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, () -> assertEquals(discoverableByPhoneNumber, account.isDiscoverableByPhoneNumber()), () -> assertEquals(currentProfileVersion, account.getCurrentProfileVersion().orElseThrow()), - () -> assertEquals(identityKey, account.getIdentityKey()), + () -> assertArrayEquals(identityKey, account.getIdentityKey()), () -> assertArrayEquals(unidentifiedAccessKey, account.getUnidentifiedAccessKey().orElseThrow()), () -> assertTrue(account.getRegistrationLock().verify(clientRegistrationLock)), () -> assertEquals(unrestrictedUnidentifiedAccess, account.isUnrestrictedUnidentifiedAccess()) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java index a3c7645a7..60c4b6414 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java @@ -5,11 +5,7 @@ package org.whispersystems.textsecuregcm.storage; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -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.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; 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.cluster.api.sync.RedisAdvancedClusterCommands; + +import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; @@ -470,10 +468,10 @@ class AccountsManagerTest { .doAnswer(ACCOUNT_UPDATE_ANSWER) .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("identity-key", account.getIdentityKey()); + assertArrayEquals("identity-key".getBytes(StandardCharsets.UTF_8), account.getIdentityKey()); verify(accounts, times(1)).getByAccountIdentifier(uuid); 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]); assertThrows(IllegalArgumentException.class, () -> 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"); verify(accounts, never()).update(any()); @@ -739,7 +737,7 @@ class AccountsManagerTest { final List 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 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()); @@ -782,19 +780,19 @@ class AccountsManagerTest { UUID oldPni = account.getPhoneNumberIdentifier(); Map 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 assertEquals(oldUuid, updatedAccount.getUuid()); assertEquals(number, updatedAccount.getNumber()); assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier()); - assertEquals(null, updatedAccount.getIdentityKey()); + assertNull(updatedAccount.getIdentityKey()); assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey))); assertEquals(Map.of(1L, 101, 2L, 102), updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId))); // PNI stuff should - assertEquals("new-pni-identity-key", updatedAccount.getPhoneNumberIdentityKey()); + assertArrayEquals("new-pni-identity-key".getBytes(StandardCharsets.UTF_8), updatedAccount.getPhoneNumberIdentityKey()); assertEquals(newSignedKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey))); assertEquals(newRegistrationIds, @@ -829,19 +827,19 @@ class AccountsManagerTest { Map oldSignedPreKeys = account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey)); 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 assertEquals(oldUuid, updatedAccount.getUuid()); assertEquals(number, updatedAccount.getNumber()); assertEquals(oldPni, updatedAccount.getPhoneNumberIdentifier()); - assertEquals(null, updatedAccount.getIdentityKey()); + assertNull(updatedAccount.getIdentityKey()); assertEquals(oldSignedPreKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getSignedPreKey))); assertEquals(Map.of(1L, 101, 2L, 102), updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getRegistrationId))); // PNI keys should - assertEquals("new-pni-identity-key", updatedAccount.getPhoneNumberIdentityKey()); + assertArrayEquals("new-pni-identity-key".getBytes(StandardCharsets.UTF_8), updatedAccount.getPhoneNumberIdentityKey()); assertEquals(newSignedKeys, updatedAccount.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::getPhoneNumberIdentitySignedPreKey))); assertEquals(newRegistrationIds, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManagerTest.java index 531425092..35dd0377f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManagerTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; @@ -105,7 +106,7 @@ public class ChangeNumberManagerTest { Account account = mock(Account.class); when(account.getNumber()).thenReturn("+18005551234"); 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()); 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.getDevices()).thenReturn(List.of(d2)); - final String pniIdentityKey = "pni-identity-key"; + final byte[] pniIdentityKey = "pni-identity-key".getBytes(); final Map prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey()); final Map registrationIds = Map.of(1L, 17, 2L, 19); @@ -174,7 +175,7 @@ public class ChangeNumberManagerTest { when(account.getDevice(2L)).thenReturn(Optional.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 prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey()); final Map pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey()); final Map registrationIds = Map.of(1L, 17, 2L, 19); @@ -216,7 +217,7 @@ public class ChangeNumberManagerTest { when(account.getDevice(2L)).thenReturn(Optional.of(d2)); when(account.getDevices()).thenReturn(List.of(d2)); - final String pniIdentityKey = "pni-identity-key"; + final byte[] pniIdentityKey = "pni-identity-key".getBytes(); final Map prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey()); final Map pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey()); final Map registrationIds = Map.of(1L, 17, 2L, 19); @@ -256,7 +257,7 @@ public class ChangeNumberManagerTest { when(account.getDevice(2L)).thenReturn(Optional.of(d2)); when(account.getDevices()).thenReturn(List.of(d2)); - final String pniIdentityKey = "pni-identity-key"; + final byte[] pniIdentityKey = "pni-identity-key".getBytes(); final Map prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey()); final Map registrationIds = Map.of(1L, 17, 2L, 19); @@ -295,7 +296,7 @@ public class ChangeNumberManagerTest { when(account.getDevice(2L)).thenReturn(Optional.of(d2)); when(account.getDevices()).thenReturn(List.of(d2)); - final String pniIdentityKey = "pni-identity-key"; + final byte[] pniIdentityKey = "pni-identity-key".getBytes(); final Map prekeys = Map.of(1L, new SignedPreKey(), 2L, new SignedPreKey()); final Map pqPrekeys = Map.of(3L, new SignedPreKey(), 4L, new SignedPreKey()); final Map registrationIds = Map.of(1L, 17, 2L, 19); @@ -346,7 +347,7 @@ public class ChangeNumberManagerTest { final Map registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89); 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 @@ -376,7 +377,7 @@ public class ChangeNumberManagerTest { final Map registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89); 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 @@ -405,6 +406,6 @@ public class ChangeNumberManagerTest { final Map registrationIds = Map.of(1L, 17, 2L, 47, 3L, 89); 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)); } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java index b80edf19e..d9f9892ee 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/CertificateControllerTest.java @@ -113,8 +113,7 @@ class CertificateControllerTest { assertEquals(certificate.getSenderDevice(), 1L); assertTrue(certificate.hasSenderUuid()); assertEquals(AuthHelper.VALID_UUID.toString(), certificate.getSenderUuid()); - assertArrayEquals(certificate.getIdentityKey().toByteArray(), - Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY)); + assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY); } @Test @@ -142,8 +141,7 @@ class CertificateControllerTest { assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER); assertEquals(certificate.getSenderDevice(), 1L); assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString()); - assertArrayEquals(certificate.getIdentityKey().toByteArray(), - Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY)); + assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY); } @Test @@ -172,8 +170,7 @@ class CertificateControllerTest { assertTrue(StringUtils.isBlank(certificate.getSender())); assertEquals(certificate.getSenderDevice(), 1L); assertEquals(certificate.getSenderUuid(), AuthHelper.VALID_UUID.toString()); - assertArrayEquals(certificate.getIdentityKey().toByteArray(), - Base64.getDecoder().decode(AuthHelper.VALID_IDENTITY)); + assertArrayEquals(certificate.getIdentityKey().toByteArray(), AuthHelper.VALID_IDENTITY); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 0e3b0ff3d..f74fa10ff 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -278,8 +278,8 @@ class DeviceControllerTest { aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair)); - when(account.getIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - when(account.getPhoneNumberIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + when(account.getIdentityKey()).thenReturn(aciIdentityKeyPair.getPublicKey().serialize()); + when(account.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKeyPair.getPublicKey().serialize()); final LinkDeviceRequest request = new LinkDeviceRequest("5678901", new AccountAttributes(fetchesMessages, 1234, null, null, true, null), @@ -364,8 +364,8 @@ class DeviceControllerTest { aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair)); - when(account.getIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(aciIdentityKeyPair)); - when(account.getPhoneNumberIdentityKey()).thenReturn(KeysHelper.serializeIdentityKey(pniIdentityKeyPair)); + when(account.getIdentityKey()).thenReturn(aciIdentityKeyPair.getPublicKey().serialize()); + when(account.getPhoneNumberIdentityKey()).thenReturn(pniIdentityKeyPair.getPublicKey().serialize()); final LinkDeviceRequest request = new LinkDeviceRequest("5678901", new AccountAttributes(fetchesMessages, 1234, null, null, true, null), @@ -393,8 +393,8 @@ class DeviceControllerTest { @ParameterizedTest @MethodSource @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - void linkDeviceAtomicMissingProperty(final String aciIdentityKey, - final String pniIdentityKey, + void linkDeviceAtomicMissingProperty(final byte[] aciIdentityKey, + final byte[] pniIdentityKey, final Optional aciSignedPreKey, final Optional pniSignedPreKey, final Optional aciPqLastResortPreKey, @@ -440,8 +440,8 @@ class DeviceControllerTest { final Optional aciPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair)); final Optional pniPqLastResortPreKey = Optional.of(KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair)); - final String aciIdentityKey = KeysHelper.serializeIdentityKey(aciIdentityKeyPair); - final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair); + final byte[] aciIdentityKey = aciIdentityKeyPair.getPublicKey().serialize(); + final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize(); return Stream.of( Arguments.of(aciIdentityKey, pniIdentityKey, Optional.empty(), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey), @@ -453,8 +453,8 @@ class DeviceControllerTest { @ParameterizedTest @MethodSource - void linkDeviceAtomicInvalidSignature(final String aciIdentityKey, - final String pniIdentityKey, + void linkDeviceAtomicInvalidSignature(final byte[] aciIdentityKey, + final byte[] pniIdentityKey, final SignedPreKey aciSignedPreKey, final SignedPreKey pniSignedPreKey, final SignedPreKey aciPqLastResortPreKey, @@ -500,8 +500,8 @@ class DeviceControllerTest { final SignedPreKey aciPqLastResortPreKey = KeysHelper.signedKEMPreKey(3, aciIdentityKeyPair); final SignedPreKey pniPqLastResortPreKey = KeysHelper.signedKEMPreKey(4, pniIdentityKeyPair); - final String aciIdentityKey = KeysHelper.serializeIdentityKey(aciIdentityKeyPair); - final String pniIdentityKey = KeysHelper.serializeIdentityKey(pniIdentityKeyPair); + final byte[] aciIdentityKey = aciIdentityKeyPair.getPublicKey().serialize(); + final byte[] pniIdentityKey = pniIdentityKeyPair.getPublicKey().serialize(); return Stream.of( Arguments.of(aciIdentityKey, pniIdentityKey, signedPreKeyWithBadSignature(aciSignedPreKey), pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey), diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java index a1c71668c..73be92205 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/KeysControllerTest.java @@ -80,10 +80,10 @@ class KeysControllerTest { private static final int SAMPLE_PNI_REGISTRATION_ID = 1717; private final ECKeyPair IDENTITY_KEY_PAIR = Curve.generateKeyPair(); - private final String IDENTITY_KEY = KeysHelper.serializeIdentityKey(IDENTITY_KEY_PAIR); + private final byte[] IDENTITY_KEY = IDENTITY_KEY_PAIR.getPublicKey().serialize(); 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_KEY2 = KeysHelper.ecPreKey(5667); @@ -658,7 +658,7 @@ class KeysControllerTest { final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); 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)); @@ -688,7 +688,7 @@ class KeysControllerTest { final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, 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); @@ -718,7 +718,7 @@ class KeysControllerTest { final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); 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)); @@ -749,7 +749,7 @@ class KeysControllerTest { final SignedPreKey signedPreKey = KeysHelper.signedECPreKey(31338, identityKeyPair); final SignedPreKey pqPreKey = KeysHelper.signedECPreKey(31339, 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); @@ -795,7 +795,7 @@ class KeysControllerTest { final PreKey preKey = KeysHelper.ecPreKey(31337); final ECKeyPair identityKeyPair = Curve.generateKeyPair(); 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)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java index 5f174daf4..1c421c960 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/AuthHelper.java @@ -63,7 +63,7 @@ public class AuthHelper { public static final UUID UNDISCOVERABLE_UUID = UUID.randomUUID(); 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 Account VALID_ACCOUNT = mock(Account.class ); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java index 224e8962d..bd9450be9 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/KeysHelper.java @@ -5,7 +5,6 @@ package org.whispersystems.textsecuregcm.tests.util; -import java.util.Base64; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyPair; @@ -14,14 +13,11 @@ import org.whispersystems.textsecuregcm.entities.PreKey; import org.whispersystems.textsecuregcm.entities.SignedPreKey; public final class KeysHelper { - public static String serializeIdentityKey(ECKeyPair keyPair) { - return Base64.getEncoder().encodeToString(keyPair.getPublicKey().serialize()); - } public static PreKey ecPreKey(final long id) { return new PreKey(id, Curve.generateKeyPair().getPublicKey().serialize()); } - + public static SignedPreKey signedECPreKey(long id, final ECKeyPair identityKeyPair) { final byte[] pubKey = Curve.generateKeyPair().getPublicKey().serialize(); final byte[] sig = identityKeyPair.getPrivateKey().calculateSignature(pubKey);