Skip to content
This repository was archived by the owner on Feb 28, 2024. It is now read-only.

Commit 899d555

Browse files
Adding ability to encrypt/decrypt array bodies
1 parent d59cfaa commit 899d555

File tree

4 files changed

+124
-44
lines changed

4 files changed

+124
-44
lines changed

src/Developer/Encryption/FieldLevelEncryption.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static function encryptPayload($payload, $config, $params = null) {
3333

3434
// Perform encryption (if needed)
3535
foreach ($config->getEncryptionPaths() as $jsonPathIn => $jsonPathOut) {
36-
self::encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params);
36+
$payloadJsonObject = self::encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params);
3737
}
3838

3939
// Return the updated payload
@@ -64,7 +64,7 @@ public static function decryptPayload($payload, $config, $params = null) {
6464

6565
// Perform decryption (if needed)
6666
foreach ($config->getDecryptionPaths() as $jsonPathIn => $jsonPathOut) {
67-
self::decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params);
67+
$payloadJsonObject = self::decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params);
6868
}
6969

7070
// Return the updated payload
@@ -91,7 +91,7 @@ private static function encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jso
9191
$inJsonObject = self::readJsonElement($payloadJsonObject, $jsonPathIn);
9292
if (is_null($inJsonObject)) {
9393
// Nothing to encrypt
94-
return;
94+
return $payloadJsonObject;
9595
}
9696

9797
if (empty($params)) {
@@ -108,10 +108,7 @@ private static function encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jso
108108
if ('$' !== $jsonPathIn) {
109109
JsonPath::delete($payloadJsonObject, $jsonPathIn);
110110
} else {
111-
// Delete keys one by one
112-
foreach ($inJsonObject as $key => $value) {
113-
unset($inJsonObject->$key);
114-
}
111+
$payloadJsonObject = json_decode('{}');
115112
}
116113

117114
// Add encrypted data and encryption fields at the given JSON path
@@ -132,6 +129,7 @@ private static function encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jso
132129
if (!empty($config->getOaepPaddingDigestAlgorithmFieldName())) {
133130
$outJsonObject->{$config->getOaepPaddingDigestAlgorithmFieldName()} = $params->getOaepPaddingDigestAlgorithmValue();
134131
}
132+
return $payloadJsonObject;
135133
}
136134

137135
/**
@@ -147,14 +145,14 @@ private static function decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jso
147145
$inJsonObject = self::readJsonObject($payloadJsonObject, $jsonPathIn);
148146
if (is_null($inJsonObject)) {
149147
// Nothing to decrypt
150-
return;
148+
return $payloadJsonObject;
151149
}
152150

153151
// Read and remove encrypted data and encryption fields at the given JSON path
154152
$encryptedValueJsonElement = self::readAndDeleteJsonKey($inJsonObject, $config->getEncryptedValueFieldName());
155153
if (empty($encryptedValueJsonElement)) {
156154
// Nothing to decrypt
157-
return;
155+
return $payloadJsonObject;
158156
}
159157

160158
if (!$config->useHttpPayloads() && empty($params)) {
@@ -176,16 +174,17 @@ private static function decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jso
176174
$encryptedValueBytes = EncodingUtils::decodeValue($encryptedValueJsonElement, $config->getFieldValueEncoding());
177175
$decryptedValueBytes = self::decryptBytes($params->getSecretKeyBytes(), $params->getIvBytes(), $encryptedValueBytes);
178176

179-
// Add decrypted data at the given JSON path
177+
// Add decrypted data at the given JSON path
180178
$decryptedValue = self::sanitizeJson($decryptedValueBytes);
181179
$outJsonObject = self::checkOrCreateOutObject($payloadJsonObject, $jsonPathOut);
182-
self::addDecryptedDataToPayload($payloadJsonObject, $jsonPathOut, $outJsonObject, $decryptedValue);
180+
$payloadJsonObject = self::addDecryptedDataToPayload($payloadJsonObject, $jsonPathOut, $outJsonObject, $decryptedValue);
183181

184182
// Remove the input if now empty
185183
$inJsonElement = self::readJsonElement($payloadJsonObject, $jsonPathIn);
186184
if (empty((array)$inJsonElement) && '$' !== $jsonPathIn) {
187185
JsonPath::delete($payloadJsonObject, $jsonPathIn);
188186
}
187+
return $payloadJsonObject;
189188
}
190189

191190
/**
@@ -200,19 +199,25 @@ private static function addDecryptedDataToPayload($payloadJsonObject, $jsonPathO
200199
// 'json_decode' returns null for strings
201200
$decryptedValueJsonElement = $decryptedValue;
202201
}
202+
203+
if ('$' === $jsonPathOut && is_array($decryptedValueJsonElement)) {
204+
return $decryptedValueJsonElement;
205+
}
206+
203207
if (!is_object($decryptedValueJsonElement)) {
204208
// Array or primitive: overwrite
205209
$parentPath = JsonPath::getParentPath($jsonPathOut);
206210
$elementKey = JsonPath::getElementKey($jsonPathOut);
207211
$parentObject = JsonPath::find($payloadJsonObject, $parentPath);
208212
$parentObject->$elementKey = $decryptedValueJsonElement;
209-
return;
213+
return $payloadJsonObject;
210214
}
211215

212216
// Object: merge
213217
foreach ($decryptedValueJsonElement as $key => $value) {
214218
$outJsonObject->$key = $value;
215219
}
220+
return $payloadJsonObject;
216221
}
217222

218223
/**

src/Developer/Json/JsonPath.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ static function normalizePath($path) {
5353
* @throws \InvalidArgumentException
5454
*/
5555
static function find($jsonObject, $path) {
56-
if (!is_object($jsonObject)) {
57-
throw new \InvalidArgumentException('An object was expected!');
56+
if (!is_object($jsonObject) && !is_array($jsonObject)) {
57+
throw new \InvalidArgumentException('An object/array was expected!');
5858
}
5959

6060
$normalizedPath = self::normalizePath($path);

tests/Developer/Encryption/FieldLevelEncryptionTest.php

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -328,22 +328,6 @@ public function testEncryptPayload_ShouldThrowInvalidArgumentException_WhenOutPa
328328
FieldLevelEncryption::encryptPayload($payload, $config);
329329
}
330330

331-
public function testEncryptPayload_ShouldThrowInvalidArgumentException_WhenPayloadIsNotAnObject() {
332-
333-
// GIVEN
334-
$payload = '[]';
335-
$config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()
336-
->withEncryptionPath('', '')
337-
->build();
338-
339-
// THEN
340-
$this->expectException(InvalidArgumentException::class);
341-
$this->expectExceptionMessage('An object was expected!');
342-
343-
// WHEN
344-
FieldLevelEncryption::encryptPayload($payload, $config);
345-
}
346-
347331
public function testEncryptPayload_ShouldThrowInvalidArgumentException_WhenOutPathIsPathToJsonPrimitive() {
348332

349333
// GIVEN
@@ -700,6 +684,67 @@ public function testEncryptPayload_ShouldNotAddEncryptionParamsToPayload_WhenFie
700684
$this->assertEquals(1, count((array)$encryptedData)); // 'encryptedValue' only
701685
}
702686

687+
public function testEncryptPayload_ShouldEncryptRootArrays() {
688+
// GIVEN
689+
$payload = '[
690+
{},
691+
{}
692+
]';
693+
$config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()
694+
->withEncryptionPath('$', '$')
695+
->withOaepPaddingDigestAlgorithm('SHA-256')
696+
->build();
697+
698+
// WHEN
699+
$encryptedPayload = FieldLevelEncryption::encryptPayload($payload, $config);
700+
701+
// THEN
702+
$encryptedPayloadObject = json_decode($encryptedPayload);
703+
$this->assertNotEmpty($encryptedPayloadObject);
704+
$encryptedValue = $encryptedPayloadObject->encryptedValue;
705+
$this->assertNotEmpty($encryptedValue);
706+
$this->assertEquals(6, count((array)$encryptedPayloadObject));
707+
}
708+
709+
public function testEncryptPayload_ShouldEncryptComplexRootArrays() {
710+
// GIVEN
711+
$payload = '[
712+
{
713+
"data": {
714+
"field1": "value1",
715+
"field2": [
716+
"arrayValue1",
717+
"arrayValue2"
718+
]
719+
}
720+
},
721+
{
722+
"data": {
723+
"field3": "value2",
724+
"field4": [
725+
"arrayValue3",
726+
"arrayValue4"
727+
]
728+
}
729+
}
730+
]';
731+
$config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()
732+
->withEncryptionPath('$', '$')
733+
->withOaepPaddingDigestAlgorithm('SHA-256')
734+
->build();
735+
736+
// WHEN
737+
$encryptedPayload = FieldLevelEncryption::encryptPayload($payload, $config);
738+
739+
// THEN
740+
error_log($encryptedPayload);
741+
$encryptedPayloadObject = json_decode($encryptedPayload);
742+
$this->assertNotEmpty($encryptedPayloadObject);
743+
$encryptedValue = $encryptedPayloadObject->encryptedValue;
744+
$this->assertNotEmpty($encryptedValue);
745+
$this->assertEquals(6, count((array)$encryptedPayloadObject));
746+
}
747+
703748
public function testDecryptPayload_Nominal() {
704749

705750
// GIVEN
@@ -1301,4 +1346,48 @@ public function testDecryptPayload_ShouldThrowInvalidArgumentException_WhenEncry
13011346
// WHEN
13021347
FieldLevelEncryption::decryptPayload($encryptedPayload, $config, null);
13031348
}
1349+
1350+
public function testDecryptPayload_ShouldDecryptRootArrays() {
1351+
1352+
// GIVEN
1353+
$encryptedPayload = '{
1354+
"encryptedValue":"cf3762b1fa599e372eefe3e89ad81820",
1355+
"iv":"63e25a4515f0a06dd6b4838e8ea77abe",
1356+
"encryptedKey":"d3338322318e7801e652818e59174a694d347849bce30a032a7de609365789f1fd5f08f518ce1a4deede9387b855cd4920a486a1a37b5c5e4d766ffbe85223fc3676bb5f4476b8edfd90fee06486da05b9a5c43f299867e152cbbed4f464e9c0ce1fbc5f6f5e9792d7ecfc990c08fbd241a9c2e60f31387c99b9e3e5fffa9e43277aa5b9bb47b7d0db8715b8b04831dae05362d257c72427792c311a9d1ec983dc43eefee8dac62439fe2a1e1dc84fc51f784bec58b820e3135a9e324e2a8c1d9ed8cb458cf467dbed6b5328f7ea071fcc4a8d3ccc649e560b65a61915e285676da275035722f6c31e6a6d1fc5ed1d659aeeb0ed5d7eb725066c35b236dbb99d",
1357+
"encryptionCertificateFingerprint":"80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279",
1358+
"encryptionKeyFingerprint":"761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79",
1359+
"oaepHashingAlgorithm":"SHA256"
1360+
}';
1361+
$config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()
1362+
->withDecryptionPath('$', '$')
1363+
->build();
1364+
1365+
// WHEN
1366+
$payload = FieldLevelEncryption::decryptPayload($encryptedPayload, $config);
1367+
1368+
// THEN
1369+
$this->assertJsonStringEqualsJsonString('[{},{}]', $payload);
1370+
}
1371+
1372+
public function testDecryptPayload_ShouldDecryptComplexRootArrays() {
1373+
1374+
// GIVEN
1375+
$encryptedPayload = '{
1376+
"encryptedValue":"ee0973e4be3cf9080e6f8aab8f2d5c36fec3ddee2a99532921ef4698f1e5e06e60b7862013c0a860f5ad6708a965881124d7d5ba4e87e1b439f9f5a6fe40ab168ceaca98d44fbfe83a233486c2a05c47373d6a67080fa8bd94bd3ad1dcca6bad6dc72ad98a9077a29585317f938b35c6ba5c83943a5b3f957c9d240e488b99190197e9a4840242d99c7220832c3423bf",
1377+
"iv":"20a11d0f709c3a70c25ef950fa348cc8",
1378+
"encryptedKey":"5179ec91b5961ce568691c6194c1a0fea3d3b976af797dc2db6497ca7b9775d97954d8161dfec9e7ed86b8b61d140ebed2e185ce68e24e42baebad46cdc84bc568b184d07592acc10dad5bbb95abfdc0f5ebfdbb304301abccab28419fbff55325d7aa06c34a3b006773f4de5d947177da13a46131a65ceebcb14a6f86d73bf860e3a60fca7b52e1966a3762c8b17fbb10b067f443c2fb88f38a35834da61b8821592c47477215eb59fa176aff78f1ca392eda36743d9ebd20613c0d7fb71301c06f90f5439a3c24e1401388771392867adaa36181a8502b3584b4ee73d2363257c81f29f37a76e6e91167628ac763e2c4f940027cda2d3b770fcfd9a4cc1a0f",
1379+
"encryptionCertificateFingerprint":"80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279",
1380+
"encryptionKeyFingerprint":"761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79",
1381+
"oaepHashingAlgorithm":"SHA256"
1382+
}';
1383+
$config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()
1384+
->withDecryptionPath('$', '$')
1385+
->build();
1386+
1387+
// WHEN
1388+
$payload = FieldLevelEncryption::decryptPayload($encryptedPayload, $config);
1389+
1390+
// THEN
1391+
$this->assertJsonStringEqualsJsonString('[{"data":{"field1":"value1","field2":["arrayValue1","arrayValue2"]}},{"data":{"field3":"value2","field4":["arrayValue3","arrayValue4"]}}]', $payload);
1392+
}
13041393
}

tests/Developer/Json/JsonPathTest.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,20 +210,6 @@ public function testDelete_ShouldDoNothing_WhenElementDoesNotExist() {
210210
$this->assertEquals(json_decode($json), $jsonObject);
211211
}
212212

213-
public function testFind_ShouldThrowInvalidArgumentException_WhenArrayPassedAsArgument() {
214-
215-
// GIVEN
216-
$json = '{}';
217-
$jsonArray = json_decode($json, true);
218-
219-
// THEN
220-
$this->expectException(\InvalidArgumentException::class);
221-
$this->expectExceptionMessage('An object was expected!');
222-
223-
// WHEN
224-
JsonPath::find($jsonArray, '$');
225-
}
226-
227213
public function testGetParentPath_ShouldThrowInvalidArgumentException_WhenJsonPathEmpty() {
228214

229215
// GIVEN

0 commit comments

Comments
 (0)