Skip to content

Commit 2fe7b15

Browse files
committed
Added support for storing encryption parameters in HTTP headers (OkHttp and Okhttp2)
1 parent c002b43 commit 2fe7b15

9 files changed

+340
-37
lines changed

src/main/java/com/mastercard/developer/encryption/FieldLevelEncryption.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ public static String decryptPayload(String payload, FieldLevelEncryptionConfig c
130130
private static void encryptPayloadPath(DocumentContext payloadContext, String jsonPathIn, String jsonPathOut,
131131
FieldLevelEncryptionConfig config, FieldLevelEncryptionParams params) throws GeneralSecurityException, EncryptionException {
132132

133-
JsonProvider jsonProvider = jsonPathConfig.jsonProvider();
134133
Object inJsonElement = readJsonElement(payloadContext, jsonPathIn);
135134
if (inJsonElement == null) {
136135
// Nothing to encrypt
@@ -158,7 +157,7 @@ private static void encryptPayloadPath(DocumentContext payloadContext, String js
158157
payloadContext.delete(jsonPathIn);
159158
} else {
160159
// Delete keys one by one
161-
Collection<String> propertyKeys = new ArrayList<>(jsonProvider.getPropertyKeys(inJsonElement));
160+
Collection<String> propertyKeys = new ArrayList<>(jsonEngine.getPropertyKeys(inJsonElement));
162161
for (String key : propertyKeys) {
163162
payloadContext.delete(jsonPathIn + "." + key);
164163
}

src/main/java/com/mastercard/developer/interceptors/FeignFieldLevelEncryptionDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public Object decode(Response response, Type type) throws IOException {
4444
// Decrypt fields & update headers
4545
String decryptedPayload;
4646
if (config.useHttpHeaders()) {
47-
// Read encryption params in HTTP headers and delete headers
47+
// Read encryption params from HTTP headers and delete headers
4848
String ivValue = readHeader(response, config.getIvHeaderName());
4949
response = removeHeader(response, config.getIvHeaderName());
5050
String oaepPaddingDigestAlgorithmValue = readHeader(response, config.getOaepPaddingDigestAlgorithmHeaderName());

src/main/java/com/mastercard/developer/interceptors/OkHttp2FieldLevelEncryptionInterceptor.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.mastercard.developer.encryption.EncryptionException;
44
import com.mastercard.developer.encryption.FieldLevelEncryption;
55
import com.mastercard.developer.encryption.FieldLevelEncryptionConfig;
6+
import com.mastercard.developer.encryption.FieldLevelEncryptionParams;
67
import com.squareup.okhttp.*;
78
import okio.Buffer;
89

@@ -45,10 +46,25 @@ private static Request handleRequest(Request request, FieldLevelEncryptionConfig
4546
requestPayload = buffer.readUtf8();
4647
}
4748

48-
// Encrypt fields
49-
String encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
49+
// Encrypt fields & update headers
50+
String encryptedPayload;
51+
Request.Builder requestBuilder = request.newBuilder();
52+
if (config.useHttpHeaders()) {
53+
// Generate encryption params and add them as HTTP headers
54+
FieldLevelEncryptionParams params = FieldLevelEncryptionParams.generate(config);
55+
updateHeader(requestBuilder, config.getIvHeaderName(), params.getIvValue());
56+
updateHeader(requestBuilder, config.getEncryptedKeyHeaderName(), params.getEncryptedKeyValue());
57+
updateHeader(requestBuilder, config.getEncryptionCertificateFingerprintHeaderName(), params.getEncryptionCertificateFingerprintValue());
58+
updateHeader(requestBuilder, config.getEncryptionKeyFingerprintHeaderName(), params.getEncryptionKeyFingerprintValue());
59+
updateHeader(requestBuilder, config.getOaepPaddingDigestAlgorithmHeaderName(), params.getOaepPaddingDigestAlgorithmValue());
60+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config, params);
61+
} else {
62+
// Encryption params will be stored in the payload
63+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
64+
}
65+
5066
RequestBody encryptedBody = RequestBody.create(requestBody.contentType(), encryptedPayload);
51-
return request.newBuilder()
67+
return requestBuilder
5268
.method(request.method(), encryptedBody)
5369
.header("Content-Length", String.valueOf(encryptedBody.contentLength()))
5470
.build();
@@ -74,10 +90,29 @@ private static Response handleResponse(Response response, FieldLevelEncryptionCo
7490
return response;
7591
}
7692

77-
// Decrypt fields
78-
String decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
93+
// Decrypt fields & update headers
94+
String decryptedPayload;
95+
Response.Builder responseBuilder = response.newBuilder();
96+
if (config.useHttpHeaders()) {
97+
// Read encryption params from HTTP headers and delete headers
98+
String ivValue = response.header(config.getIvHeaderName(), null);
99+
String oaepPaddingDigestAlgorithmValue = response.header(config.getOaepPaddingDigestAlgorithmHeaderName(), null);
100+
String encryptedKeyValue = response.header(config.getEncryptedKeyHeaderName(), null);
101+
removeHeader(responseBuilder, config.getIvHeaderName());
102+
removeHeader(responseBuilder, config.getEncryptedKeyHeaderName());
103+
removeHeader(responseBuilder, config.getOaepPaddingDigestAlgorithmHeaderName());
104+
removeHeader(responseBuilder, config.getEncryptionCertificateFingerprintHeaderName());
105+
removeHeader(responseBuilder, config.getEncryptionKeyFingerprintHeaderName());
106+
FieldLevelEncryptionParams params = new FieldLevelEncryptionParams(ivValue, encryptedKeyValue, oaepPaddingDigestAlgorithmValue,
107+
null, null, config);
108+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config, params);
109+
} else {
110+
// Encryption params are stored in the payload
111+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
112+
}
113+
79114
try (ResponseBody decryptedBody = ResponseBody.create(responseBody.contentType(), decryptedPayload)) {
80-
return response.newBuilder()
115+
return responseBuilder
81116
.body(decryptedBody)
82117
.header("Content-Length", String.valueOf(decryptedBody.contentLength()))
83118
.build();
@@ -86,4 +121,20 @@ private static Response handleResponse(Response response, FieldLevelEncryptionCo
86121
throw new IOException("Failed to intercept and decrypt response!", e);
87122
}
88123
}
124+
125+
private static void removeHeader(Response.Builder responseBuilder, String name) {
126+
if (name == null) {
127+
// Do nothing
128+
return;
129+
}
130+
responseBuilder.removeHeader(name);
131+
}
132+
133+
private static void updateHeader(Request.Builder requestBuilder, String name, String value) {
134+
if (name == null) {
135+
// Do nothing
136+
return;
137+
}
138+
requestBuilder.header(name, value);
139+
}
89140
}

src/main/java/com/mastercard/developer/interceptors/OkHttpFieldLevelEncryptionInterceptor.java

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.mastercard.developer.encryption.EncryptionException;
44
import com.mastercard.developer.encryption.FieldLevelEncryption;
55
import com.mastercard.developer.encryption.FieldLevelEncryptionConfig;
6+
import com.mastercard.developer.encryption.FieldLevelEncryptionParams;
67
import okhttp3.*;
78
import okio.Buffer;
89

@@ -45,10 +46,25 @@ private static Request handleRequest(Request request, FieldLevelEncryptionConfig
4546
requestPayload = buffer.readUtf8();
4647
}
4748

48-
// Encrypt fields
49-
String encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
49+
// Encrypt fields & update headers
50+
String encryptedPayload;
51+
Request.Builder requestBuilder = request.newBuilder();
52+
if (config.useHttpHeaders()) {
53+
// Generate encryption params and add them as HTTP headers
54+
FieldLevelEncryptionParams params = FieldLevelEncryptionParams.generate(config);
55+
updateHeader(requestBuilder, config.getIvHeaderName(), params.getIvValue());
56+
updateHeader(requestBuilder, config.getEncryptedKeyHeaderName(), params.getEncryptedKeyValue());
57+
updateHeader(requestBuilder, config.getEncryptionCertificateFingerprintHeaderName(), params.getEncryptionCertificateFingerprintValue());
58+
updateHeader(requestBuilder, config.getEncryptionKeyFingerprintHeaderName(), params.getEncryptionKeyFingerprintValue());
59+
updateHeader(requestBuilder, config.getOaepPaddingDigestAlgorithmHeaderName(), params.getOaepPaddingDigestAlgorithmValue());
60+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config, params);
61+
} else {
62+
// Encryption params will be stored in the payload
63+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
64+
}
65+
5066
RequestBody encryptedBody = RequestBody.create(requestBody.contentType(), encryptedPayload);
51-
return request.newBuilder()
67+
return requestBuilder
5268
.method(request.method(), encryptedBody)
5369
.header("Content-Length", String.valueOf(encryptedBody.contentLength()))
5470
.build();
@@ -74,10 +90,29 @@ private static Response handleResponse(Response response, FieldLevelEncryptionCo
7490
return response;
7591
}
7692

77-
// Decrypt fields
78-
String decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
93+
// Decrypt fields & update headers
94+
String decryptedPayload;
95+
Response.Builder responseBuilder = response.newBuilder();
96+
if (config.useHttpHeaders()) {
97+
// Read encryption params from HTTP headers and delete headers
98+
String ivValue = response.header(config.getIvHeaderName(), null);
99+
String oaepPaddingDigestAlgorithmValue = response.header(config.getOaepPaddingDigestAlgorithmHeaderName(), null);
100+
String encryptedKeyValue = response.header(config.getEncryptedKeyHeaderName(), null);
101+
removeHeader(responseBuilder, config.getIvHeaderName());
102+
removeHeader(responseBuilder, config.getEncryptedKeyHeaderName());
103+
removeHeader(responseBuilder, config.getOaepPaddingDigestAlgorithmHeaderName());
104+
removeHeader(responseBuilder, config.getEncryptionCertificateFingerprintHeaderName());
105+
removeHeader(responseBuilder, config.getEncryptionKeyFingerprintHeaderName());
106+
FieldLevelEncryptionParams params = new FieldLevelEncryptionParams(ivValue, encryptedKeyValue, oaepPaddingDigestAlgorithmValue,
107+
null, null, config);
108+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config, params);
109+
} else {
110+
// Encryption params are stored in the payload
111+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
112+
}
113+
79114
try (ResponseBody decryptedBody = ResponseBody.create(responseBody.contentType(), decryptedPayload)) {
80-
return response.newBuilder()
115+
return responseBuilder
81116
.body(decryptedBody)
82117
.header("Content-Length", String.valueOf(decryptedBody.contentLength()))
83118
.build();
@@ -86,4 +121,20 @@ private static Response handleResponse(Response response, FieldLevelEncryptionCo
86121
throw new IOException("Failed to intercept and decrypt response!", e);
87122
}
88123
}
124+
125+
private static void removeHeader(Response.Builder responseBuilder, String name) {
126+
if (name == null) {
127+
// Do nothing
128+
return;
129+
}
130+
responseBuilder.removeHeader(name);
131+
}
132+
133+
private static void updateHeader(Request.Builder requestBuilder, String name, String value) {
134+
if (name == null) {
135+
// Do nothing
136+
return;
137+
}
138+
requestBuilder.header(name, value);
139+
}
89140
}

src/main/java/com/mastercard/developer/json/JsonEngine.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.jayway.jsonpath.JsonPath;
44
import com.jayway.jsonpath.spi.json.JsonProvider;
55

6+
import java.util.Collection;
7+
import java.util.Collections;
68
import java.util.regex.Matcher;
79
import java.util.regex.Pattern;
810

@@ -95,7 +97,15 @@ public boolean isJsonObject(Object jsonElement) {
9597
public boolean isNullOrEmptyJson(Object jsonElement) {
9698
return jsonElement == null
9799
|| isNullOrEmpty(toJsonString(jsonElement))
98-
|| 0 == jsonElement.getClass().getFields().length;
100+
|| "{}".equals(toJsonString(jsonElement))
101+
|| Object.class.equals(jsonElement.getClass());
102+
}
103+
104+
public Collection<String> getPropertyKeys(Object jsonElement) {
105+
if (isNullOrEmptyJson(jsonElement)) {
106+
return Collections.emptyList();
107+
}
108+
return getJsonProvider().getPropertyKeys(jsonElement);
99109
}
100110

101111
/**

src/test/java/com/mastercard/developer/interceptor/FeignFieldLevelEncryptionDecoderTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static com.mastercard.developer.test.TestUtils.getTestFieldLevelEncryptionConfigBuilder;
2424
import static com.mastercard.developer.utils.FeignUtils.readHeader;
2525
import static org.hamcrest.core.Is.isA;
26+
import static org.junit.Assert.*;
2627
import static org.mockito.ArgumentMatchers.any;
2728
import static org.mockito.Mockito.*;
2829

@@ -70,7 +71,7 @@ public void testDecode_ShouldDecryptResponsePayloadAndUpdateContentLengthHeader(
7071
Response responseValue = responseCaptor.getValue();
7172
String payload = Util.toString(responseValue.body().asReader());
7273
assertPayloadEquals("{\"data\":\"string\"}", payload);
73-
Assert.assertEquals(String.valueOf(payload.length()), readHeader(responseValue, "Content-Length"));
74+
assertEquals(String.valueOf(payload.length()), readHeader(responseValue, "Content-Length"));
7475
}
7576

7677
@Test
@@ -188,12 +189,12 @@ public void testDecode_ShouldDecryptResponsePayloadAndRemoveEncryptionHttpHeader
188189
Response responseValue = responseCaptor.getValue();
189190
String payload = Util.toString(responseValue.body().asReader());
190191
assertPayloadEquals("{\"data\":\"string\"}", payload);
191-
Assert.assertEquals(String.valueOf(payload.length()), readHeader(responseValue, "Content-Length"));
192-
Assert.assertNull(readHeader(responseValue, "x-iv"));
193-
Assert.assertNull(readHeader(responseValue, "x-encrypted-key"));
194-
Assert.assertNull(readHeader(responseValue, "x-oaep-padding-digest-algorithm"));
195-
Assert.assertNull(readHeader(responseValue, "x-encryption-key-fingerprint"));
196-
Assert.assertNull(readHeader(responseValue, "x-encryption-certificate-fingerprint"));
192+
assertEquals(String.valueOf(payload.length()), readHeader(responseValue, "Content-Length"));
193+
assertNull(readHeader(responseValue, "x-iv"));
194+
assertNull(readHeader(responseValue, "x-encrypted-key"));
195+
assertNull(readHeader(responseValue, "x-oaep-padding-digest-algorithm"));
196+
assertNull(readHeader(responseValue, "x-encryption-key-fingerprint"));
197+
assertNull(readHeader(responseValue, "x-encryption-certificate-fingerprint"));
197198
}
198199

199200
private static Response.Body buildResponseBody(String payload) {

src/test/java/com/mastercard/developer/interceptor/FeignFieldLevelEncryptionEncoderTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static com.mastercard.developer.test.TestUtils.getTestFieldLevelEncryptionConfigBuilder;
1919
import static org.hamcrest.core.Is.isA;
20+
import static org.junit.Assert.*;
2021
import static org.mockito.Mockito.*;
2122

2223
public class FeignFieldLevelEncryptionEncoderTest {
@@ -48,8 +49,8 @@ public void testEncode_ShouldEncryptRequestPayloadAndUpdateContentLengthHeader()
4849
verify(request).body(encryptedPayloadCaptor.capture());
4950
verify(request).header(eq("Content-Length"), anyString());
5051
String encryptedPayload = encryptedPayloadCaptor.getValue();
51-
Assert.assertFalse(encryptedPayload.contains("foo"));
52-
Assert.assertTrue(encryptedPayload.contains("encryptedFoo"));
52+
assertFalse(encryptedPayload.contains("foo"));
53+
assertTrue(encryptedPayload.contains("encryptedFoo"));
5354
}
5455

5556
@Test
@@ -149,8 +150,8 @@ public void testEncode_ShouldEncryptRequestPayloadAndAddEncryptionHttpHeaders_Wh
149150
verify(request).body(encryptedPayloadCaptor.capture());
150151
verify(request).header(eq("Content-Length"), anyString());
151152
String encryptedPayload = encryptedPayloadCaptor.getValue();
152-
Assert.assertFalse(encryptedPayload.contains("foo"));
153-
Assert.assertTrue(encryptedPayload.contains("encryptedFoo"));
153+
assertFalse(encryptedPayload.contains("foo"));
154+
assertTrue(encryptedPayload.contains("encryptedFoo"));
154155
verify(request).header(eq("x-iv"), anyString());
155156
verify(request).header(eq("x-encrypted-key"), anyString());
156157
verify(request).header("x-oaep-padding-digest-algorithm", "SHA256");

0 commit comments

Comments
 (0)