diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapter.java index cd25cdb65..6f656fe75 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapter.java @@ -42,6 +42,10 @@ public ECPublicKey deserialize(final JsonParser parser, final DeserializationCon throw new JsonParseException(parser, "Could not parse EC public key as a base64-encoded value", e); } + if (ecPublicKeyBytes.length == 0) { + return null; + } + try { return new ECPublicKey(ecPublicKeyBytes); } catch (final InvalidKeyException e) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapter.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapter.java index bbbf7d62c..6af30454a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapter.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapter.java @@ -42,6 +42,10 @@ public KEMPublicKey deserialize(final JsonParser parser, final DeserializationCo throw new JsonParseException(parser, "Could not parse KEM public key as a base64-encoded value", e); } + if (kemPublicKeyBytes.length == 0) { + return null; + } + try { return new KEMPublicKey(kemPublicKeyBytes); } catch (final InvalidKeyException e) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapterTest.java new file mode 100644 index 000000000..868971f65 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/ECPublicKeyAdapterTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECPublicKey; + +import javax.annotation.Nullable; +import java.util.Base64; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ECPublicKeyAdapterTest { + + private static final ECPublicKey EC_PUBLIC_KEY = Curve.generateKeyPair().getPublicKey(); + + private record ECPublicKeyCarrier(@JsonSerialize(using = ECPublicKeyAdapter.Serializer.class) + @JsonDeserialize(using = ECPublicKeyAdapter.Deserializer.class) + ECPublicKey publicKey) { + } + + @ParameterizedTest + @MethodSource + void deserialize(final String json, @Nullable final ECPublicKey expectedPublicKey) throws JsonProcessingException { + final ECPublicKeyCarrier publicKeyCarrier = SystemMapper.jsonMapper().readValue(json, ECPublicKeyCarrier.class); + + assertEquals(expectedPublicKey, publicKeyCarrier.publicKey()); + } + + private static Stream deserialize() { + final String template = """ + { + "publicKey": %s + } + """; + + return Stream.of( + Arguments.of(String.format(template, "null"), null), + Arguments.of(String.format(template, "\"\""), null), + Arguments.of(String.format(template, "\"" + Base64.getEncoder().encodeToString(EC_PUBLIC_KEY.serialize()) + "\""), EC_PUBLIC_KEY) + ); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapterTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapterTest.java new file mode 100644 index 000000000..5a729ed2e --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/KEMPublicKeyAdapterTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.signal.libsignal.protocol.kem.KEMKeyPair; +import org.signal.libsignal.protocol.kem.KEMKeyType; +import org.signal.libsignal.protocol.kem.KEMPublicKey; +import javax.annotation.Nullable; +import java.util.Base64; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class KEMPublicKeyAdapterTest { + + private static final KEMPublicKey KEM_PUBLIC_KEY = KEMKeyPair.generate(KEMKeyType.KYBER_1024).getPublicKey(); + + private record KEMPublicKeyCarrier(@JsonSerialize(using = KEMPublicKeyAdapter.Serializer.class) + @JsonDeserialize(using = KEMPublicKeyAdapter.Deserializer.class) + KEMPublicKey publicKey) { + } + + @ParameterizedTest + @MethodSource + void deserialize(final String json, @Nullable final KEMPublicKey expectedPublicKey) throws JsonProcessingException { + final KEMPublicKeyCarrier publicKeyCarrier = SystemMapper.jsonMapper().readValue(json, KEMPublicKeyAdapterTest.KEMPublicKeyCarrier.class); + + assertEquals(expectedPublicKey, publicKeyCarrier.publicKey()); + } + + private static Stream deserialize() { + final String template = """ + { + "publicKey": %s + } + """; + + return Stream.of( + Arguments.of(String.format(template, "null"), null), + Arguments.of(String.format(template, "\"\""), null), + Arguments.of(String.format(template, "\"" + Base64.getEncoder().encodeToString(KEM_PUBLIC_KEY.serialize()) + "\""), + KEM_PUBLIC_KEY) + ); + } +}