Skip to content

Commit 2e442f2

Browse files
committed
Added support for storing encryption parameters in HTTP headers (Google API client)
1 parent 37746c8 commit 2e442f2

File tree

2 files changed

+146
-8
lines changed

2 files changed

+146
-8
lines changed

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

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.mastercard.developer.encryption.EncryptionException;
55
import com.mastercard.developer.encryption.FieldLevelEncryption;
66
import com.mastercard.developer.encryption.FieldLevelEncryptionConfig;
7+
import com.mastercard.developer.encryption.FieldLevelEncryptionParams;
78

89
import java.io.ByteArrayOutputStream;
910
import java.io.IOException;
@@ -41,10 +42,25 @@ public void intercept(HttpRequest request) throws IOException {
4142
content.writeTo(outputStream);
4243
String requestPayload = outputStream.toString(StandardCharsets.UTF_8.name());
4344

44-
// Encrypt fields
45-
String encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
45+
// Encrypt fields & update headers
46+
String encryptedPayload;
47+
HttpHeaders headers = request.getHeaders();
48+
if (config.useHttpHeaders()) {
49+
// Generate encryption params and add them as HTTP headers
50+
FieldLevelEncryptionParams params = FieldLevelEncryptionParams.generate(config);
51+
updateHeader(headers, config.getIvHeaderName(), params.getIvValue());
52+
updateHeader(headers, config.getEncryptedKeyHeaderName(), params.getEncryptedKeyValue());
53+
updateHeader(headers, config.getEncryptionCertificateFingerprintHeaderName(), params.getEncryptionCertificateFingerprintValue());
54+
updateHeader(headers, config.getEncryptionKeyFingerprintHeaderName(), params.getEncryptionKeyFingerprintValue());
55+
updateHeader(headers, config.getOaepPaddingDigestAlgorithmHeaderName(), params.getOaepPaddingDigestAlgorithmValue());
56+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config, params);
57+
} else {
58+
// Encryption params will be stored in the payload
59+
encryptedPayload = FieldLevelEncryption.encryptPayload(requestPayload, config);
60+
}
61+
4662
HttpContent encryptedContent = new ByteArrayContent("application/json; charset=" + StandardCharsets.UTF_8.name(), encryptedPayload.getBytes());
47-
request.getHeaders().setContentLength(encryptedContent.getLength());
63+
headers.setContentLength(encryptedContent.getLength());
4864
request.setContent(encryptedContent);
4965

5066
} catch (EncryptionException e) {
@@ -62,10 +78,29 @@ public void interceptResponse(HttpResponse response) throws IOException {
6278
return;
6379
}
6480

65-
// Decrypt fields
66-
String decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
81+
// Decrypt fields & update headers
82+
String decryptedPayload;
83+
HttpHeaders headers = response.getHeaders();
84+
if (config.useHttpHeaders()) {
85+
// Read encryption params from HTTP headers and delete headers
86+
String ivValue = headers.getFirstHeaderStringValue(config.getIvHeaderName());
87+
String oaepPaddingDigestAlgorithmValue = headers.getFirstHeaderStringValue(config.getOaepPaddingDigestAlgorithmHeaderName());
88+
String encryptedKeyValue = headers.getFirstHeaderStringValue(config.getEncryptedKeyHeaderName());
89+
removeHeader(headers, config.getIvHeaderName());
90+
removeHeader(headers, config.getEncryptedKeyHeaderName());
91+
removeHeader(headers, config.getOaepPaddingDigestAlgorithmHeaderName());
92+
removeHeader(headers, config.getEncryptionCertificateFingerprintHeaderName());
93+
removeHeader(headers, config.getEncryptionKeyFingerprintHeaderName());
94+
FieldLevelEncryptionParams params = new FieldLevelEncryptionParams(ivValue, encryptedKeyValue, oaepPaddingDigestAlgorithmValue,
95+
null, null, config);
96+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config, params);
97+
} else {
98+
// Encryption params are stored in the payload
99+
decryptedPayload = FieldLevelEncryption.decryptPayload(responsePayload, config);
100+
}
101+
67102
HttpContent decryptedContent = new ByteArrayContent("application/json; charset=" + StandardCharsets.UTF_8.name(), decryptedPayload.getBytes());
68-
response.getHeaders().setContentLength(decryptedContent.getLength());
103+
headers.setContentLength(decryptedContent.getLength());
69104

70105
// The HttpResponse public interface prevent from updating the response payload:
71106
// "Do not read from the content stream unless you intend to throw an exception"
@@ -79,4 +114,21 @@ public void interceptResponse(HttpResponse response) throws IOException {
79114
throw new IOException("Failed to update response with decrypted payload!", e);
80115
}
81116
}
117+
118+
private static void removeHeader(HttpHeaders headers, String name) {
119+
if (name == null) {
120+
// Do nothing
121+
return;
122+
}
123+
headers.remove(name);
124+
}
125+
126+
private static void updateHeader(HttpHeaders headers, String name, String value) {
127+
if (name == null) {
128+
// Do nothing
129+
return;
130+
}
131+
headers.remove(name);
132+
headers.set(name, value);
133+
}
82134
}

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

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

2829
@RunWith(MockitoJUnitRunner.class)
@@ -57,7 +58,7 @@ public void testIntercept_ShouldEncryptRequestPayloadAndUpdateContentLengthHeade
5758
String encryptedPayload = encryptedPayloadStream.toString(StandardCharsets.UTF_8.name());
5859
Assert.assertFalse(encryptedPayload.contains("foo"));
5960
Assert.assertTrue(encryptedPayload.contains("encryptedFoo"));
60-
Assert.assertEquals(encryptedPayload.length(), httpHeaders.getContentLength().intValue());
61+
assertEquals(encryptedPayload.length(), httpHeaders.getContentLength().intValue());
6162
}
6263

6364
@Test
@@ -117,6 +118,7 @@ public void testInterceptResponse_ShouldDecryptResponsePayloadAndUpdateContentLe
117118
.build();
118119
HttpResponse response = mock(HttpResponse.class);
119120
HttpHeaders httpHeaders = new HttpHeaders();
121+
httpHeaders.setContentLength(100l);
120122
when(response.parseAsString()).thenReturn(encryptedPayload);
121123
when(response.getHeaders()).thenReturn(httpHeaders);
122124

@@ -130,7 +132,7 @@ public void testInterceptResponse_ShouldDecryptResponsePayloadAndUpdateContentLe
130132
InputStream payloadInputStream = (InputStream) contentField.get(response);
131133
String payload = IOUtils.toString(payloadInputStream, StandardCharsets.UTF_8);
132134
assertPayloadEquals("{\"data\":\"string\"}", payload);
133-
Assert.assertEquals(payload.length(), httpHeaders.getContentLength().intValue());
135+
assertEquals(payload.length(), httpHeaders.getContentLength().intValue());
134136
}
135137

136138
@Test
@@ -176,4 +178,88 @@ public void testInterceptResponse_ShouldThrowIOException_WhenDecryptionFails() t
176178
HttpExecuteFieldLevelEncryptionInterceptor instanceUnderTest = new HttpExecuteFieldLevelEncryptionInterceptor(config);
177179
instanceUnderTest.interceptResponse(response);
178180
}
181+
182+
@Test
183+
public void testIntercept_ShouldEncryptRequestPayloadAndAddEncryptionHttpHeaders_WhenRequestedInConfig() throws Exception {
184+
185+
// GIVEN
186+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
187+
.withEncryptionPath("$.foo", "$.encryptedFoo")
188+
.withIvHeaderName("x-iv")
189+
.withEncryptedKeyHeaderName("x-encrypted-key")
190+
.withOaepPaddingDigestAlgorithmHeaderName("x-oaep-padding-digest-algorithm")
191+
.withEncryptionCertificateFingerprintHeaderName("x-encryption-certificate-fingerprint")
192+
.withEncryptionKeyFingerprintHeaderName("x-encryption-key-fingerprint")
193+
.build();
194+
HttpRequest request = mock(HttpRequest.class);
195+
HttpHeaders httpHeaders = new HttpHeaders();
196+
when(request.getContent()).thenReturn(new ByteArrayContent(JSON_TYPE, "{\"foo\":\"bar\"}".getBytes()));
197+
when(request.getHeaders()).thenReturn(httpHeaders);
198+
199+
// WHEN
200+
HttpExecuteFieldLevelEncryptionInterceptor instanceUnderTest = new HttpExecuteFieldLevelEncryptionInterceptor(config);
201+
instanceUnderTest.intercept(request);
202+
203+
// THEN
204+
ArgumentCaptor<HttpContent> contentCaptor = ArgumentCaptor.forClass(HttpContent.class);
205+
verify(request).setContent(contentCaptor.capture());
206+
ByteArrayOutputStream encryptedPayloadStream = new ByteArrayOutputStream();
207+
contentCaptor.getValue().writeTo(encryptedPayloadStream);
208+
String encryptedPayload = encryptedPayloadStream.toString(StandardCharsets.UTF_8.name());
209+
Assert.assertFalse(encryptedPayload.contains("foo"));
210+
Assert.assertTrue(encryptedPayload.contains("encryptedFoo"));
211+
assertEquals(encryptedPayload.length(), httpHeaders.getContentLength().intValue());
212+
assertNotNull(httpHeaders.get("x-iv"));
213+
assertNotNull(httpHeaders.get("x-encrypted-key"));
214+
assertEquals("SHA256", httpHeaders.get("x-oaep-padding-digest-algorithm"));
215+
assertEquals("80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279", httpHeaders.get("x-encryption-certificate-fingerprint"));
216+
assertEquals("761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79", httpHeaders.get("x-encryption-key-fingerprint"));
217+
}
218+
219+
@Test
220+
public void testInterceptResponse_ShouldDecryptResponsePayloadAndRemoveEncryptionHttpHeaders_WhenRequestedInConfig() throws Exception {
221+
222+
// GIVEN
223+
String encryptedPayload = "{" +
224+
" \"encryptedData\": {" +
225+
" \"encryptedValue\": \"21d754bdb4567d35d58720c9f8364075\"" +
226+
" }" +
227+
"}";
228+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
229+
.withDecryptionPath("$.encryptedData", "$.data")
230+
.withIvHeaderName("x-iv")
231+
.withEncryptedKeyHeaderName("x-encrypted-key")
232+
.withOaepPaddingDigestAlgorithmHeaderName("x-oaep-padding-digest-algorithm")
233+
.withEncryptionCertificateFingerprintHeaderName("x-encryption-certificate-fingerprint")
234+
.withEncryptionKeyFingerprintHeaderName("x-encryption-key-fingerprint")
235+
.build();
236+
237+
HttpResponse response = mock(HttpResponse.class);
238+
HttpHeaders httpHeaders = new HttpHeaders();
239+
httpHeaders.set("x-iv", "a32059c51607d0d02e823faecda5fb15");
240+
httpHeaders.set("x-encrypted-key", "a31cfe7a7981b72428c013270619554c1d645c04b9d51c7eaf996f55749ef62fd7c7f8d334f95913be41ae38c46d192670fd1acb84ebb85a00cd997f1a9a3f782229c7bf5f0fdf49fe404452d7ed4fd41fbb95b787d25893fbf3d2c75673cecc8799bbe3dd7eb4fe6d3f744b377572cdf8aba1617194e10475b6cd6a8dd4fb8264f8f51534d8f7ac7c10b4ce9c44d15066724b03a0ab0edd512f9e6521fdb5841cd6964e457d6b4a0e45ba4aac4e77d6bbe383d6147e751fa88bc26278bb9690f9ee84b17123b887be2dcef0873f4f9f2c895d90e23456fafb01b99885e31f01a3188f0ad47edf22999cc1d0ddaf49e1407375117b5d66f1f185f2b57078d255");
241+
httpHeaders.set("x-oaep-padding-digest-algorithm", "SHA256");
242+
httpHeaders.set("x-encryption-key-fingerprint", "761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79");
243+
httpHeaders.set("x-encryption-certificate-fingerprint", "80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279");
244+
httpHeaders.setContentLength(100l);
245+
when(response.parseAsString()).thenReturn(encryptedPayload);
246+
when(response.getHeaders()).thenReturn(httpHeaders);
247+
248+
// WHEN
249+
HttpExecuteFieldLevelEncryptionInterceptor instanceUnderTest = new HttpExecuteFieldLevelEncryptionInterceptor(config);
250+
instanceUnderTest.interceptResponse(response);
251+
252+
// THEN
253+
Field contentField = response.getClass().getDeclaredField("content");
254+
contentField.setAccessible(true);
255+
InputStream payloadInputStream = (InputStream) contentField.get(response);
256+
String payload = IOUtils.toString(payloadInputStream, StandardCharsets.UTF_8);
257+
assertPayloadEquals("{\"data\":\"string\"}", payload);
258+
assertEquals(payload.length(), httpHeaders.getContentLength().intValue());
259+
assertNull(response.getHeaders().get("x-iv"));
260+
assertNull(response.getHeaders().get("x-encrypted-key"));
261+
assertNull(response.getHeaders().get("x-oaep-padding-digest-algorithm"));
262+
assertNull(response.getHeaders().get("x-encryption-key-fingerprint"));
263+
assertNull(response.getHeaders().get("x-encryption-certificate-fingerprint"));
264+
}
179265
}

0 commit comments

Comments
 (0)