Skip to content

Commit 551a0b6

Browse files
authored
Merge pull request #54 from rfeelin/feature/adding-wildcard-paths
Adding support for wildcard encryption and decryption paths
2 parents cc55c66 + b0b6792 commit 551a0b6

File tree

8 files changed

+342
-19
lines changed

8 files changed

+342
-19
lines changed

README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ This library supports two types of encryption/decryption, both of which support
138138
+ [Performing JWE Decryption](#performing-jwe-decryption)
139139
+ [Encrypting Entire Payloads](#encrypting-entire-payloads-jwe)
140140
+ [Decrypting Entire Payloads](#decrypting-entire-payloads-jwe)
141+
+ [Encrypting Payloads with Wildcards](#encrypting-wildcard-payloads-jwe)
142+
+ [Decrypting Payloads with Wildcards](#decrypting-wildcard-payloads-jwe)
141143

142144
##### • Introduction <a name="jwe-introduction"></a>
143145

@@ -294,6 +296,70 @@ Output:
294296
}
295297
```
296298

299+
##### • Encrypting Payloads with Wildcards <a name="encrypting-wildcard-payloads-jwe"></a>
300+
301+
Wildcards can be encrypted using the "[*]" operator as part of encryption path:
302+
303+
```java
304+
JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
305+
.withEncryptionCertificate(encryptionCertificate)
306+
.withEncryptionPath("$.list[*]sensitiveField1", "$.list[*]encryptedField")
307+
//
308+
.build();
309+
```
310+
311+
Example:
312+
```java
313+
String payload = "{ \"list\": [ " +
314+
" { \"sensitiveField1\" : \"sensitiveValue1\"}, "+
315+
" { \"sensitiveField1\" : \"sensitiveValue2\"} " +
316+
"]}";
317+
String encryptedPayload = JweEncryption.encryptPayload(payload, config);
318+
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(encryptedPayload)));
319+
```
320+
321+
Output:
322+
```json
323+
{
324+
"list": [
325+
{"encryptedField": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"},
326+
{"encryptedField": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+asdvarvasdvfdvakmkmm"}
327+
]
328+
}
329+
```
330+
331+
##### • Decrypting Payloads with Wildcards <a name="decrypting-wildcard-payloads-jwe"></a>
332+
333+
Wildcards can be decrypted using the "[*]" operator as part of decryption path:
334+
335+
```java
336+
JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
337+
.withDecryptionKey(decryptionKey)
338+
.withDecryptionPath("$.list[*]encryptedField", "$.list[*]sensitiveField1")
339+
//
340+
.build();
341+
```
342+
343+
Example:
344+
```java
345+
String encryptedPayload = "{ \"list\": [ " +
346+
" { \"encryptedField\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw\"}, " +
347+
" { \"encryptedField\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+asdvarvasdvfdvakmkmm\"} " +
348+
" ]}";
349+
String payload = JweEncryption.decryptPayload(encryptedPayload, config);
350+
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(payload)));
351+
```
352+
353+
Output:
354+
```json
355+
{
356+
"list": [
357+
{"sensitiveField1": "sensitiveValue1"},
358+
{"sensitiveField2": "sensitiveValue2"}
359+
]
360+
}
361+
```
362+
297363
#### Mastercard Encryption and Decryption <a name="mastercard-encryption-and-decryption"></a>
298364

299365
+ [Introduction](#mastercard-introduction)
@@ -302,6 +368,8 @@ Output:
302368
+ [Performing Mastercard Decryption](#performing-mastercard-decryption)
303369
+ [Encrypting Entire Payloads](#encrypting-entire-mastercard-payloads)
304370
+ [Decrypting Entire Payloads](#decrypting-entire-mastercard-payloads)
371+
+ [Encrypting Payloads with Wildcards](#encrypting-wildcard-mastercard-payloads)
372+
+ [Decrypting Payloads with Wildcards](#decrypting-wildcard-mastercard-payloads)
305373
+ [Using HTTP Headers for Encryption Params](#using-http-headers-for-encryption-params)
306374

307375
##### • Introduction <a name="mastercard-introduction"></a>
@@ -469,6 +537,70 @@ Output:
469537
"sensitiveField1": "sensitiveValue1",
470538
"sensitiveField2": "sensitiveValue2"
471539
}
540+
541+
```
542+
##### • Encrypting Payloads with Wildcards <a name="encrypting-wildcard-mastercard-payloads"></a>
543+
544+
Wildcards can be encrypted using the "[*]" operator as part of encryption path:
545+
546+
```java
547+
FLEConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
548+
.withEncryptionCertificate(encryptionCertificate)
549+
.withEncryptionPath("$.list[*]sensitiveField1", "$.list[*]encryptedField")
550+
//
551+
.build();
552+
```
553+
554+
Example:
555+
```java
556+
String payload = "{ \"list\": [ " +
557+
" { \"sensitiveField1\" : \"sensitiveValue1\"}, "+
558+
" { \"sensitiveField1\" : \"sensitiveValue2\"} " +
559+
"]}";
560+
String encryptedPayload = FieldLevelEncryption.encryptPayload(payload, config);
561+
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(encryptedPayload)));
562+
```
563+
564+
Output:
565+
```json
566+
{
567+
"list": [
568+
{"encryptedField": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"},
569+
{"encryptedField": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+asdvarvasdvfdvakmkmm"}
570+
]
571+
}
572+
```
573+
574+
##### • Decrypting Payloads with Wildcards <a name="decrypting-wildcard-mastercard-payloads"></a>
575+
576+
Wildcards can be decrypted using the "[*]" operator as part of decryption path:
577+
578+
```java
579+
FLEConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
580+
.withDecryptionKey(decryptionKey)
581+
.withDecryptionPath("$.list[*]encryptedField", "$.list[*]sensitiveField1")
582+
//
583+
.build();
584+
```
585+
586+
Example:
587+
```java
588+
String encryptedPayload = "{ \"list\": [ " +
589+
" { \"encryptedField\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw\"}, " +
590+
" { \"encryptedField\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+asdvarvasdvfdvakmkmm\"} " +
591+
" ]}";
592+
String payload = FieldLevelEncryption.decryptPayload(encryptedPayload, config);
593+
System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(payload)));
594+
```
595+
596+
Output:
597+
```json
598+
{
599+
"list": [
600+
{"sensitiveField1": "sensitiveValue1"},
601+
{"sensitiveField2": "sensitiveValue2"}
602+
]
603+
}
472604
```
473605

474606
##### • Using HTTP Headers for Encryption Params <a name="using-http-headers-for-encryption-params"></a>

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,32 @@ static byte[] sha256digestBytes(byte[] bytes) throws NoSuchAlgorithmException {
4242

4343
void checkJsonPathParameterValues() {
4444
for (Map.Entry<String, String> entry : decryptionPaths.entrySet()) {
45-
if (!JsonPath.isPathDefinite(entry.getKey()) || !JsonPath.isPathDefinite(entry.getValue())) {
46-
throw new IllegalArgumentException("JSON paths for decryption must point to a single item!");
45+
if(entry.getKey().contains("[*]") || entry.getValue().contains("[*]")){
46+
if(!(entry.getKey().contains("[*]") && entry.getValue().contains("[*]"))){
47+
throw new IllegalArgumentException("JSON paths for decryption with wildcard must both contain a wildcard!");
48+
}
49+
if((entry.getKey().split("[*]", -1).length-1 > 1 || entry.getValue().split("[*]", -1).length-1 > 1)){
50+
throw new IllegalArgumentException("JSON paths for decryption with can only contain one wildcard!");
51+
}
52+
} else {
53+
if (!JsonPath.isPathDefinite(entry.getKey()) || !JsonPath.isPathDefinite(entry.getValue())) {
54+
throw new IllegalArgumentException("JSON paths for decryption must point to a single item!");
55+
}
4756
}
4857
}
4958

5059
for (Map.Entry<String, String> entry : encryptionPaths.entrySet()) {
51-
if (!JsonPath.isPathDefinite(entry.getKey()) || !JsonPath.isPathDefinite(entry.getValue())) {
52-
throw new IllegalArgumentException("JSON paths for encryption must point to a single item!");
60+
if(entry.getKey().contains("[*]") || entry.getValue().contains("[*]")){
61+
if(!(entry.getKey().contains("[*]") && entry.getValue().contains("[*]"))){
62+
throw new IllegalArgumentException("JSON paths for encryption with wildcard must both contain a wildcard!");
63+
}
64+
if((entry.getKey().split("[*]", -1).length-1 > 1 || entry.getValue().split("[*]", -1).length-1 > 1)){
65+
throw new IllegalArgumentException("JSON paths for encryption with can only contain one wildcard!");
66+
}
67+
} else {
68+
if (!JsonPath.isPathDefinite(entry.getKey()) || !JsonPath.isPathDefinite(entry.getValue())) {
69+
throw new IllegalArgumentException("JSON paths for encryption must point to a single item!");
70+
}
5371
}
5472
}
5573
}

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,17 @@ public static String encryptPayload(String payload, FieldLevelEncryptionConfig c
3838
for (Entry<String, String> entry : config.encryptionPaths.entrySet()) {
3939
String jsonPathIn = entry.getKey();
4040
String jsonPathOut = entry.getValue();
41-
payloadContext = encryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config, (FieldLevelEncryptionParams) params);
41+
if(!jsonPathIn.contains("[*]")){
42+
payloadContext = encryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config, (FieldLevelEncryptionParams) params);
43+
}else {
44+
String getFieldLength = jsonPathIn.split("\\[.*?\\]")[0].concat(".length()");
45+
Integer length = JsonPath.read(payload, getFieldLength);
46+
for (Integer i = 0; i < length; i++) {
47+
String newJsonPathIn = jsonPathIn.replace("*", i.toString());
48+
String newJsonPathOut = jsonPathOut.replace("*", i.toString());
49+
payloadContext = encryptPayloadPath(payloadContext, newJsonPathIn, newJsonPathOut, config, (FieldLevelEncryptionParams) params);
50+
}
51+
}
4252
}
4353

4454
// Return the updated payload
@@ -61,7 +71,17 @@ public static String decryptPayload(String payload, FieldLevelEncryptionConfig c
6171
for (Entry<String, String> entry : config.decryptionPaths.entrySet()) {
6272
String jsonPathIn = entry.getKey();
6373
String jsonPathOut = entry.getValue();
64-
payloadContext = decryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config, (FieldLevelEncryptionParams) params);
74+
if(!jsonPathIn.contains("[*]")){
75+
payloadContext = decryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config, (FieldLevelEncryptionParams) params);
76+
}else {
77+
String getFieldLength = jsonPathIn.split("\\[.*?\\]")[0].concat(".length()");
78+
Integer length = JsonPath.read(payload, getFieldLength);
79+
for (Integer i = 0; i < length; i++) {
80+
String newJsonPathIn = jsonPathIn.replace("*", i.toString());
81+
String newJsonPathOut = jsonPathOut.replace("*", i.toString());
82+
payloadContext = decryptPayloadPath(payloadContext, newJsonPathIn, newJsonPathOut, config, (FieldLevelEncryptionParams) params);
83+
}
84+
}
6585
}
6686

6787
// Return the updated payload

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,17 @@ public static String encryptPayload(String payload, JweConfig config) throws Enc
3030
for (Map.Entry<String, String> entry : config.getEncryptionPaths().entrySet()) {
3131
String jsonPathIn = entry.getKey();
3232
String jsonPathOut = entry.getValue();
33-
payloadContext = encryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config);
33+
if(!jsonPathIn.contains("[*]")){
34+
payloadContext = encryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config);
35+
}else {
36+
String getFieldLength = jsonPathIn.split("\\[.*?\\]")[0].concat(".length()");
37+
Integer length = JsonPath.read(payload, getFieldLength);
38+
for (Integer i = 0; i < length; i++) {
39+
String newJsonPathIn = jsonPathIn.replace("*", i.toString());
40+
String newJsonPathOut = jsonPathOut.replace("*", i.toString());
41+
payloadContext = encryptPayloadPath(payloadContext, newJsonPathIn, newJsonPathOut, config);
42+
}
43+
}
3444
}
3545

3646
// Return the updated payload
@@ -49,7 +59,17 @@ public static String decryptPayload(String payload, JweConfig config) throws Enc
4959
for (Map.Entry<String, String> entry : config.getDecryptionPaths().entrySet()) {
5060
String jsonPathIn = entry.getKey();
5161
String jsonPathOut = entry.getValue();
52-
payloadContext = decryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config);
62+
if(!jsonPathIn.contains("[*]")){
63+
payloadContext = decryptPayloadPath(payloadContext, jsonPathIn, jsonPathOut, config);
64+
}else {
65+
String getFieldLength = jsonPathIn.split("\\[.*?\\]")[0].concat(".length()");
66+
Integer length = JsonPath.read(payload, getFieldLength);
67+
for (Integer i = 0; i < length; i++) {
68+
String newJsonPathIn = jsonPathIn.replace("*", i.toString());
69+
String newJsonPathOut = jsonPathOut.replace("*", i.toString());
70+
payloadContext = decryptPayloadPath(payloadContext, newJsonPathIn, newJsonPathOut, config);
71+
}
72+
}
5373
}
5474

5575
// Return the updated payload

src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionConfigBuilderTest.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,51 @@ public void testBuild_ShouldComputeCertificateAndKeyFingerprints_WhenFingerprint
9797
}
9898

9999
@Test
100-
public void testBuild_ShouldThrowIllegalArgumentException_WhenNotDefiniteDecryptionPath() throws Exception {
100+
public void testBuild_ShouldBuild_WhenHavingWildcardPaths() throws Exception {
101+
FieldLevelEncryptionConfig config = FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
102+
.withEncryptionPath("$.encryptedPayloads[*]", "$.payload[*]")
103+
.withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
104+
.withEncryptionCertificateFingerprint("97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B")
105+
.withEncryptionKeyFingerprint("F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810")
106+
.withEncryptionCertificateFingerprintFieldName("publicCertificateFingerprint")
107+
.withEncryptionCertificateFingerprintHeaderName("x-public-certificate-fingerprint")
108+
.withEncryptionKeyFingerprintFieldName("publicKeyFingerprint")
109+
.withEncryptionKeyFingerprintHeaderName("x-public-key-fingerprint")
110+
.withDecryptionPath("$.encryptedPayloads[*]", "$.payload[*]")
111+
.withDecryptionKey(TestUtils.getTestDecryptionKey())
112+
.withOaepPaddingDigestAlgorithm("SHA-512")
113+
.withOaepPaddingDigestAlgorithmFieldName("oaepPaddingDigestAlgorithm")
114+
.withOaepPaddingDigestAlgorithmHeaderName("x-oaep-padding-digest-algorithm")
115+
.withEncryptedValueFieldName("encryptedValue")
116+
.withEncryptedKeyFieldName("encryptedKey")
117+
.withEncryptedKeyHeaderName("x-encrypted-key")
118+
.withIvFieldName("iv")
119+
.withIvHeaderName("x-iv")
120+
.withFieldValueEncoding(HEX)
121+
.build();
122+
Assert.assertNotNull(config);
123+
}
124+
125+
@Test
126+
public void testBuild_ShouldThrowIllegalArgumentException_WhenNotHavingWildcardOnBothDecryptionPaths() throws Exception {
101127
expectedException.expect(IllegalArgumentException.class);
102-
expectedException.expectMessage("JSON paths for decryption must point to a single item!");
128+
expectedException.expectMessage("JSON paths for decryption with wildcard must both contain a wildcard!");
103129
FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
104130
.withDecryptionPath("$.encryptedPayloads[*]", "$.payload")
105131
.withDecryptionKey(TestUtils.getTestDecryptionKey())
106132
.build();
107133
}
108134

135+
@Test
136+
public void testBuild_ShouldThrowIllegalArgumentException_WhenMultipleWildcardsOnDecryptionPaths() throws Exception {
137+
expectedException.expect(IllegalArgumentException.class);
138+
expectedException.expectMessage("JSON paths for decryption with can only contain one wildcard!");
139+
FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
140+
.withDecryptionPath("$.encryptedPayloads[*]field1[*]subField", "$.payload[*]field1[*]encryptedSubField")
141+
.withDecryptionKey(TestUtils.getTestDecryptionKey())
142+
.build();
143+
}
144+
109145
@Test
110146
public void testBuild_ShouldThrowIllegalArgumentException_WhenMissingDecryptionKey() throws Exception {
111147
expectedException.expect(IllegalArgumentException.class);
@@ -121,11 +157,21 @@ public void testBuild_ShouldThrowIllegalArgumentException_WhenMissingDecryptionK
121157
}
122158

123159
@Test
124-
public void testBuild_ShouldThrowIllegalArgumentException_WhenNotDefiniteEncryptionPath() throws Exception {
160+
public void testBuild_ShouldThrowIllegalArgumentException_WhenNotHavingWildcardOnBothEncryptionPaths() throws Exception {
161+
expectedException.expect(IllegalArgumentException.class);
162+
expectedException.expectMessage("JSON paths for encryption with wildcard must both contain a wildcard!");
163+
FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
164+
.withEncryptionPath("$.encryptedPayloads[*]", "$.payload")
165+
.withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
166+
.build();
167+
}
168+
169+
@Test
170+
public void testBuild_ShouldThrowIllegalArgumentException_WhenMultipleWildcardsOnEncryptionPaths() throws Exception {
125171
expectedException.expect(IllegalArgumentException.class);
126-
expectedException.expectMessage("JSON paths for encryption must point to a single item!");
172+
expectedException.expectMessage("JSON paths for encryption with can only contain one wildcard!");
127173
FieldLevelEncryptionConfigBuilder.aFieldLevelEncryptionConfig()
128-
.withEncryptionPath("$.payloads[*]", "$.encryptedPayload")
174+
.withEncryptionPath("$.encryptedPayloads[*]field1[*]subField", "$.payload[*]field1[*]encryptedSubField")
129175
.withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
130176
.build();
131177
}

src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionWithDefaultJsonEngineTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static com.mastercard.developer.utils.EncodingUtils.base64Decode;
2525
import static com.mastercard.developer.utils.EncryptionUtils.loadDecryptionKey;
2626
import static org.hamcrest.core.Is.isA;
27+
import static org.junit.Assert.assertNotNull;
2728
import static org.junit.jupiter.api.Assertions.*;
2829

2930
public class FieldLevelEncryptionWithDefaultJsonEngineTest {
@@ -127,6 +128,34 @@ public void testEncryptPayload_Nominal() throws Exception {
127128
assertDecryptedPayloadEquals("{\"data\":{\"field1\":\"value1\",\"field2\":\"value2\"}}", encryptedPayload, config);
128129
}
129130

131+
@Test
132+
public void testEncryptPayload_ShouldEncryptWithWildcard() throws Exception {
133+
134+
// GIVEN
135+
String payload = "{ \"fields\": [" +
136+
" {" +
137+
" \"field1\": \"AAAA\"," +
138+
" \"field2\": \"asdf\"" +
139+
" }," +
140+
" {" +
141+
" \"field1\": \"BBBB\"," +
142+
" \"field2\": \"zxcv\"" +
143+
" }" +
144+
"]}";
145+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
146+
.withEncryptionPath("$.fields[*]field1", "$.fields[*]encryptedData")
147+
.withDecryptionPath("$.fields[*]encryptedData", "$.fields[*]field1")
148+
.withOaepPaddingDigestAlgorithm("SHA-256")
149+
.build();
150+
151+
// WHEN
152+
String encryptedPayload = FieldLevelEncryption.encryptPayload(payload, config);
153+
154+
// THEN
155+
assertDecryptedPayloadEquals("{\"fields\":[{\"field2\":\"asdf\",\"field1\":\"AAAA\"},{\"field2\":\"zxcv\",\"field1\":\"BBBB\"}]}", encryptedPayload, config);
156+
}
157+
158+
130159
@Test
131160
public void testEncryptPayload_ShouldSupportBase64FieldValueEncoding() throws Exception {
132161

0 commit comments

Comments
 (0)