Skip to content

Commit 74db2fb

Browse files
committed
PR feedback
1 parent fd37bb4 commit 74db2fb

14 files changed

+761
-315
lines changed

README.md

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Prerequisites](#prerequisites)
1919
- [Adding the Library to Your Project](#adding-the-libraries-to-your-project)
2020
- [Performing Field Level Encryption and Decryption](#performing-field-level-encryption-and-decryption)
21+
- [Performing JWE Encryption and Decryption](#performing-jwe-encryption-and-decryption)
2122
- [Integrating with OpenAPI Generator API Client Libraries](#integrating-with-openapi-generator-api-client-libraries)
2223

2324
## Overview <a name="overview"></a>
@@ -27,6 +28,7 @@ NodeJS library for Mastercard API compliant payload encryption/decryption.
2728
### Compatibility <a name="compatibility"></a>
2829

2930
- NodeJS 6.12.3+
31+
- NodeJS 13.14.0+ (JWE features)
3032

3133
There shouldn't be any Node compatibility issues with this package, but it's a good idea to keep your Node versions up-to-date. It is recommended that you use one of the LTS Node.js releases, or one of the more general recent releases. A Node version manager such as `nvm` (*Mac* and *Linux*) or `nvm-windows` is a good way to stay on top of this.
3234

@@ -186,7 +188,6 @@ Call `FieldLevelEncryption.decrypt()` with an (encrypted) `response` object with
186188
Example using the configuration [above](#configuring-the-field-level-encryption):
187189

188190
```js
189-
190191
const response = {};
191192
response.request = { url: "/resource1" };
192193
response.body =
@@ -221,6 +222,232 @@ Output:
221222
}
222223
}
223224
```
225+
### Performing JWE Encryption and Decryption <a name="performing-jwe-encryption-and-decryption"></a>
226+
#### JWE Encryption and Decryption <a name="jwe-encryption-and-decryption"></a>
227+
228+
+ [Introduction](#jwe-introduction)
229+
+ [Configuring the JWE Encryption](#configuring-the-jwe-encryption)
230+
+ [Performing JWE Encryption](#performing-jwe-encryption)
231+
+ [Performing JWE Decryption](#performing-jwe-decryption)
232+
+ [Encrypting Entire Payloads](#encrypting-entire-payloads-jwe)
233+
+ [Decrypting Entire Payloads](#decrypting-entire-payloads-jwe)
234+
235+
##### • Introduction <a name="jwe-introduction"></a>
236+
237+
This library uses [JWE compact serialization](https://datatracker.ietf.org/doc/html/rfc7516#section-7.1) for the encryption of sensitive data.
238+
The core methods responsible for payload encryption and decryption are `encryptData` and `decryptData` in the `JweEncryption` class.
239+
240+
* `encryptPayload` usage:
241+
```js
242+
const jwe = new clientEncryption.JweEncryption(config);
243+
//
244+
let encryptedRequestPayload = jwe.encrypt(endpoint, header, body);
245+
```
246+
247+
* `decryptPayload` usage:
248+
```js
249+
const jwe = new clientEncryption.JweEncryption(config);
250+
//
251+
let responsePayload = jwe.decrypt(encryptedResponsePayload);
252+
```
253+
254+
##### • Configuring the JWE Encryption <a name="configuring-the-jwe-encryption"></a>
255+
`JweEncryption` needs a config object to instruct how to decrypt/decrypt the payloads. Example:
256+
257+
```js
258+
const config = {
259+
paths: [
260+
{
261+
path: "/resource1",
262+
toEncrypt: [
263+
{
264+
/* path to element to be encrypted in request json body */
265+
element: "path.to.foo",
266+
/* path to object where to store encryption fields in request json body */
267+
obj: "path.to.encryptedFoo"
268+
}],
269+
toDecrypt: [
270+
{
271+
/* path to element where to store decrypted fields in response object */
272+
element: "path.to.encryptedFoo",
273+
/* path to object with encryption fields */
274+
obj: "path.to.foo"
275+
}
276+
]
277+
}
278+
],
279+
mode:'JWE',
280+
encryptedValueFieldName: 'encryptedData',
281+
publicKeyFingerprintType: 'certificate',
282+
encryptionCertificate: "./path/to/public.cert",
283+
privateKey: "./path/to/your/private.key"
284+
};
285+
```
286+
Mode must be set to JWE to use JWE encryption
287+
288+
##### • Performing JWE Encryption <a name="performing-jwe-encryption"></a>
289+
290+
Call `JweEncryption.encrypt()` with a JSON request payload, and optional `header` object.
291+
292+
Example using the configuration [above](#configuring-the-field-level-encryption):
293+
294+
```js
295+
const payload =
296+
{
297+
"path": {
298+
"to": {
299+
"foo": {
300+
"sensitive": "this is a secret!",
301+
"sensitive2": "this is a super-secret!"
302+
}
303+
}
304+
}
305+
};
306+
const jwe = new (require('mastercard-client-encryption')).JweEncryption(config);
307+
//
308+
let responsePayload = jwe.encrypt("/resource1", header, payload);
309+
```
310+
311+
Output:
312+
```json
313+
{
314+
"path": {
315+
"to": {
316+
"encryptedFoo": {
317+
"encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"
318+
}
319+
}
320+
}
321+
}
322+
```
323+
324+
##### • Performing JWE Decryption <a name="performing-jwe-decryption"></a>
325+
326+
Call `JweEncryption.decrypt()` with an (encrypted) `response` object with the following fields:
327+
328+
Example using the configuration [above](#configuring-the-jwe-encryption):
329+
```js
330+
const response = {};
331+
response.request = { url: "/resource1" };
332+
response.body =
333+
"{" +
334+
" \"path\": {" +
335+
" \"to\": {" +
336+
" \"encryptedFoo\": {" +
337+
" \"encryptedValue\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw\"" +
338+
" }" +
339+
" }" +
340+
" }" +
341+
"}";
342+
const jwe = new (require('mastercard-client-encryption')).JweEncryption(config);
343+
let responsePayload = jwe.decrypt(response);
344+
```
345+
346+
Output:
347+
```json
348+
{
349+
"path": {
350+
"to": {
351+
"foo": {
352+
"sensitiveField1": "sensitiveValue1",
353+
"sensitiveField2": "sensitiveValue2"
354+
}
355+
}
356+
}
357+
}
358+
```
359+
360+
##### • Encrypting Entire Payloads <a name="encrypting-entire-payloads-jwe"></a>
361+
362+
Entire payloads can be encrypted using the "$" operator as encryption path:
363+
364+
```js
365+
const config = {
366+
paths: [
367+
{
368+
path: "/resource1",
369+
toEncrypt: [
370+
{
371+
/* path to element to be encrypted in request json body */
372+
element: "$",
373+
/* path to object where to store encryption fields in request json body */
374+
obj: "$"
375+
}],
376+
toDecrypt: [
377+
]
378+
}
379+
],
380+
mode:'JWE',
381+
encryptedValueFieldName: 'encryptedData',
382+
publicKeyFingerprintType: 'certificate',
383+
encryptionCertificate: "./path/to/public.cert",
384+
privateKey: "./path/to/your/private.key"
385+
};
386+
```
387+
388+
Example:
389+
```js
390+
const payload = "{" +
391+
" \"sensitiveField1\": \"sensitiveValue1\"," +
392+
" \"sensitiveField2\": \"sensitiveValue2\"" +
393+
"}";
394+
const jwe = new (require('mastercard-client-encryption')).JweEncryption(config);
395+
//
396+
let responsePayload = jwe.encrypt("/resource1", header, payload);
397+
```
398+
399+
Output:
400+
```json
401+
{
402+
"encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"
403+
}
404+
```
405+
406+
##### • Decrypting Entire Payloads <a name="decrypting-entire-payloads-jwe"></a>
407+
408+
Entire payloads can be decrypted using the "$" operator as decryption path:
409+
410+
```js
411+
const config = {
412+
paths: [
413+
{
414+
path: "/resource1",
415+
toEncrypt: [
416+
417+
],
418+
toDecrypt: [
419+
{
420+
/* path to element where to store decrypted fields in response object */
421+
element: "$",
422+
/* path to object with encryption fields */
423+
obj: "$"
424+
}],
425+
}
426+
],
427+
mode:'JWE',
428+
encryptedValueFieldName: 'encryptedData',
429+
publicKeyFingerprintType: 'certificate',
430+
encryptionCertificate: "./path/to/public.cert",
431+
privateKey: "./path/to/your/private.key"
432+
};
433+
```
434+
435+
Example:
436+
```js
437+
const encryptedPayload = "{" +
438+
" \"encryptedValue\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw\"" +
439+
"}";
440+
const jwe = new (require('mastercard-client-encryption')).JweEncryption(config);
441+
let responsePayload = jwe.decrypt(encryptedPayload);
442+
```
443+
444+
Output:
445+
```json
446+
{
447+
"sensitiveField1": "sensitiveValue1",
448+
"sensitiveField2": "sensitiveValue2"
449+
}
450+
```
224451

225452
### Integrating with OpenAPI Generator API Client Libraries <a name="integrating-with-openapi-generator-api-client-libraries"></a>
226453

@@ -277,4 +504,4 @@ To use it:
277504

278505
/* use response/data object here */
279506
});
280-
```
507+
```
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
const forge = require('node-forge');
22
const utils = require('../utils/utils');
3+
const c = require("../utils/constants");
34

45
/**
5-
* @class Legacy Crypto
6+
* @class Field Level Crypto
67
*
7-
* Constructor to create an instance of `LegacyCrypto`: provide encrypt/decrypt methods
8+
* Constructor to create an instance of `FieldLevelCrypto`: provide encrypt/decrypt methods
89
*
910
* @param config encryption/decryption service configuration
1011
* @constructor
1112
*/
12-
function LegacyCrypto(config) {
13+
function FieldLevelCrypto(config) {
1314

1415
isValidConfig.call(this, config);
1516

@@ -54,7 +55,7 @@ function LegacyCrypto(config) {
5455

5556
// Encrypt payload
5657
cipher.start({iv: enc.iv});
57-
cipher.update(forge.util.createBuffer(data, 'utf8'));
58+
cipher.update(forge.util.createBuffer(data, c.UTF8));
5859
cipher.finish();
5960
const encrypted = cipher.output.getBytes();
6061

@@ -91,7 +92,7 @@ function LegacyCrypto(config) {
9192

9293
// Decrypt payload
9394
decipher.start({iv: utils.stringToBytes(iv, this.encoding)});
94-
decipher.update(forge.util.createBuffer(utils.stringToBytes(encryptedData, this.encoding), 'binary'));
95+
decipher.update(forge.util.createBuffer(utils.stringToBytes(encryptedData, this.encoding), c.BINARY));
9596
decipher.finish();
9697

9798
try {
@@ -206,7 +207,7 @@ function isValidConfig(config) {
206207
if (config["paths"].length === 0) {
207208
throw Error("Config not valid: paths should be not empty.");
208209
}
209-
if (config["dataEncoding"] !== "hex" && config["dataEncoding"] !== "base64") {
210+
if (config["dataEncoding"] !== c.HEX && config["dataEncoding"] !== c.BASE64) {
210211
throw Error("Config not valid: dataEncoding should be 'hex' or 'base64'");
211212
}
212213
validateFingerprint(config, contains);
@@ -237,4 +238,4 @@ function validateRootMapping(config) {
237238
});
238239
}
239240

240-
module.exports = LegacyCrypto;
241+
module.exports = FieldLevelCrypto;

0 commit comments

Comments
 (0)