Skip to content

Commit 17a8df6

Browse files
committed
Fix cert pin serialization
1 parent 5ea978e commit 17a8df6

4 files changed

Lines changed: 203 additions & 17 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.split.android.client.network;
2+
3+
import com.google.gson.TypeAdapter;
4+
import com.google.gson.stream.JsonReader;
5+
import com.google.gson.stream.JsonWriter;
6+
7+
import java.io.IOException;
8+
9+
/**
10+
* Custom Gson {@link TypeAdapter} for {@link CertificatePin} that uses
11+
* {@code "algo"} and {@code "pin"} as JSON keys instead of the raw field names.
12+
*/
13+
public class CertificatePinSerializer extends TypeAdapter<CertificatePin> {
14+
15+
@Override
16+
public void write(JsonWriter out, CertificatePin src) throws IOException {
17+
out.beginObject();
18+
out.name("algo").value(src.getAlgorithm());
19+
out.name("pin");
20+
out.beginArray();
21+
for (byte b : src.getPin()) {
22+
out.value(b);
23+
}
24+
out.endArray();
25+
out.endObject();
26+
}
27+
28+
@Override
29+
public CertificatePin read(JsonReader in) throws IOException {
30+
String algorithm = null;
31+
byte[] pin = null;
32+
33+
in.beginObject();
34+
while (in.hasNext()) {
35+
String name = in.nextName();
36+
switch (name) {
37+
case "algo":
38+
algorithm = in.nextString();
39+
break;
40+
case "pin":
41+
pin = readByteArray(in);
42+
break;
43+
default:
44+
in.skipValue();
45+
break;
46+
}
47+
}
48+
in.endObject();
49+
50+
return new CertificatePin(pin, algorithm);
51+
}
52+
53+
private static byte[] readByteArray(JsonReader in) throws IOException {
54+
java.util.List<Byte> bytes = new java.util.ArrayList<>();
55+
in.beginArray();
56+
while (in.hasNext()) {
57+
bytes.add((byte) in.nextInt());
58+
}
59+
in.endArray();
60+
61+
byte[] result = new byte[bytes.size()];
62+
for (int i = 0; i < bytes.size(); i++) {
63+
result[i] = bytes.get(i);
64+
}
65+
return result;
66+
}
67+
}
Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package io.split.android.client.network;
22

3-
import com.google.gson.annotations.SerializedName;
43
import com.google.gson.reflect.TypeToken;
54

65
import java.lang.reflect.Type;
7-
import java.util.HashSet;
86
import java.util.Map;
97
import java.util.Set;
108

@@ -15,18 +13,14 @@ public class CertificatePinningConfigurationProvider {
1513

1614
public static CertificatePinningConfiguration getCertificatePinningConfiguration(String pinsJson) {
1715
try {
18-
Type type = new TypeToken<Map<String, Set<CertificatePinDto>>>() {
16+
Type type = new TypeToken<Map<String, Set<CertificatePin>>>() {
1917
}.getType();
20-
Map<String, Set<CertificatePinDto>> certificatePins = Json.fromJson(pinsJson, type);
18+
Map<String, Set<CertificatePin>> certificatePins = Json.fromJson(pinsJson, type);
2119

2220
if (certificatePins != null && !certificatePins.isEmpty()) {
2321
CertificatePinningConfiguration.Builder builder = CertificatePinningConfiguration.builder();
24-
for (Map.Entry<String, Set<CertificatePinDto>> entry : certificatePins.entrySet()) {
25-
Set<CertificatePin> pins = new HashSet<>();
26-
for (CertificatePinDto dto : entry.getValue()) {
27-
pins.add(new CertificatePin(dto.pin, dto.algorithm));
28-
}
29-
builder.addPins(entry.getKey(), pins);
22+
for (Map.Entry<String, Set<CertificatePin>> entry : certificatePins.entrySet()) {
23+
builder.addPins(entry.getKey(), entry.getValue());
3024
}
3125

3226
return builder
@@ -38,11 +32,4 @@ public static CertificatePinningConfiguration getCertificatePinningConfiguration
3832

3933
return null;
4034
}
41-
42-
private static class CertificatePinDto {
43-
@SerializedName("pin")
44-
byte[] pin;
45-
@SerializedName("algo")
46-
String algorithm;
47-
}
4835
}

main/src/main/java/io/split/android/client/utils/Json.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.util.Set;
1616

1717
import io.split.android.client.dtos.KeyImpression;
18+
import io.split.android.client.network.CertificatePin;
19+
import io.split.android.client.network.CertificatePinSerializer;
1820
import io.split.android.client.service.impressions.KeyImpressionSerializer;
1921
import io.split.android.client.utils.serializer.DoubleSerializer;
2022

@@ -24,6 +26,7 @@ public class Json {
2426
.serializeNulls()
2527
.registerTypeAdapter(Double.class, new DoubleSerializer())
2628
.registerTypeAdapter(KeyImpression.class, new KeyImpressionSerializer())
29+
.registerTypeAdapter(CertificatePin.class, new CertificatePinSerializer())
2730
.create();
2831
private static volatile Gson mNonNullJson;
2932

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package io.split.android.client.network;
2+
3+
import static org.junit.Assert.assertArrayEquals;
4+
import static org.junit.Assert.assertEquals;
5+
import static org.junit.Assert.assertNotNull;
6+
import static org.junit.Assert.assertNull;
7+
8+
import com.google.gson.Gson;
9+
import com.google.gson.GsonBuilder;
10+
import com.google.gson.reflect.TypeToken;
11+
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
15+
import java.lang.reflect.Type;
16+
import java.util.Map;
17+
import java.util.Set;
18+
19+
public class CertificatePinSerializerTest {
20+
21+
private Gson mGson;
22+
23+
@Before
24+
public void setUp() {
25+
mGson = new GsonBuilder()
26+
.registerTypeAdapter(CertificatePin.class, new CertificatePinSerializer())
27+
.create();
28+
}
29+
30+
@Test
31+
public void serializeSinglePin() {
32+
CertificatePin pin = new CertificatePin(new byte[]{1, 2, 3}, "sha256");
33+
34+
String json = mGson.toJson(pin);
35+
36+
assertEquals("{\"algo\":\"sha256\",\"pin\":[1,2,3]}", json);
37+
}
38+
39+
@Test
40+
public void serializeNegativeByteValues() {
41+
CertificatePin pin = new CertificatePin(new byte[]{-80, 50, -99, -126, 11}, "sha256");
42+
43+
String json = mGson.toJson(pin);
44+
45+
assertEquals("{\"algo\":\"sha256\",\"pin\":[-80,50,-99,-126,11]}", json);
46+
}
47+
48+
@Test
49+
public void deserializeSinglePin() {
50+
String json = "{\"algo\":\"sha1\",\"pin\":[-116,-73,-94,-80,55]}";
51+
52+
CertificatePin pin = mGson.fromJson(json, CertificatePin.class);
53+
54+
assertNotNull(pin);
55+
assertEquals("sha1", pin.getAlgorithm());
56+
assertArrayEquals(new byte[]{-116, -73, -94, -80, 55}, pin.getPin());
57+
}
58+
59+
@Test
60+
public void roundTripPreservesData() {
61+
CertificatePin original = new CertificatePin(new byte[]{-116, -123, 30, -25}, "sha256");
62+
63+
String json = mGson.toJson(original);
64+
CertificatePin deserialized = mGson.fromJson(json, CertificatePin.class);
65+
66+
assertNotNull(deserialized);
67+
assertEquals(original.getAlgorithm(), deserialized.getAlgorithm());
68+
assertArrayEquals(original.getPin(), deserialized.getPin());
69+
}
70+
71+
@Test
72+
public void roundTripMapOfSets() {
73+
String expectedJson = "{\"events.split.io\":[{\"algo\":\"sha256\",\"pin\":[-80,50,-99,-126,11]},{\"algo\":\"sha1\",\"pin\":[-116,-73,-94,-80,55]}],\"sdk.split.io\":[{\"algo\":\"sha256\",\"pin\":[-116,-123,30,-25]}]}";
74+
75+
Type type = new TypeToken<Map<String, Set<CertificatePin>>>() {
76+
}.getType();
77+
Map<String, Set<CertificatePin>> deserialized = mGson.fromJson(expectedJson, type);
78+
79+
assertNotNull(deserialized);
80+
assertEquals(2, deserialized.size());
81+
assertEquals(2, deserialized.get("events.split.io").size());
82+
assertEquals(1, deserialized.get("sdk.split.io").size());
83+
84+
// Re-serialize and deserialize
85+
String reserialized = mGson.toJson(deserialized, type);
86+
Map<String, Set<CertificatePin>> roundTripped = mGson.fromJson(reserialized, type);
87+
88+
assertNotNull(roundTripped);
89+
assertEquals(deserialized.size(), roundTripped.size());
90+
for (Map.Entry<String, Set<CertificatePin>> entry : deserialized.entrySet()) {
91+
Set<CertificatePin> originalPins = entry.getValue();
92+
Set<CertificatePin> roundTrippedPins = roundTripped.get(entry.getKey());
93+
assertNotNull(roundTrippedPins);
94+
assertEquals(originalPins.size(), roundTrippedPins.size());
95+
assertEquals(originalPins, roundTrippedPins);
96+
}
97+
}
98+
99+
@Test
100+
public void deserializeWithUnknownFieldsIsIgnored() {
101+
String json = "{\"algo\":\"sha256\",\"pin\":[1,2],\"extra\":\"ignored\"}";
102+
103+
CertificatePin pin = mGson.fromJson(json, CertificatePin.class);
104+
105+
assertNotNull(pin);
106+
assertEquals("sha256", pin.getAlgorithm());
107+
assertArrayEquals(new byte[]{1, 2}, pin.getPin());
108+
}
109+
110+
@Test
111+
public void deserializeMissingFieldsResultsInNulls() {
112+
String json = "{}";
113+
114+
CertificatePin pin = mGson.fromJson(json, CertificatePin.class);
115+
116+
assertNotNull(pin);
117+
assertNull(pin.getAlgorithm());
118+
assertNull(pin.getPin());
119+
}
120+
121+
@Test
122+
public void serializeEmptyPinArray() {
123+
CertificatePin pin = new CertificatePin(new byte[]{}, "sha256");
124+
125+
String json = mGson.toJson(pin);
126+
127+
assertEquals("{\"algo\":\"sha256\",\"pin\":[]}", json);
128+
}
129+
}

0 commit comments

Comments
 (0)