Skip to content

Commit

Permalink
fix(jwe): honour "pathToEncryptedData" property in jwe encryption
Browse files Browse the repository at this point in the history
"pathToEncryptedData" property was not being honoured in jwe encryption in some cases. This commit
fixes this.
  • Loading branch information
joseph-neeraj committed Nov 15, 2023
1 parent 11ab596 commit 3b9db82
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 56 deletions.
31 changes: 27 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"main": "src/index.js",
"scripts": {
"check": "npm test && npm run lint && npm run minify",
"test": "jest",
"minify": "mkdirp dist && browserify src/index.js | uglifyjs -o dist/mastercard-postman-encryption-lib.min.js",
"lint": "eslint '**/*.js' || (echo \"Run 'npm run lint:fix' to fix most errors\" && exit 1)",
Expand All @@ -20,7 +21,7 @@
"license": "ISC",
"dependencies": {
"js-sha256": "^0.10.1",
"mastercard-client-encryption": "^1.9.0",
"mastercard-client-encryption": "^1.10.0",
"node-jose": "^2.2.0"
},
"devDependencies": {
Expand Down
55 changes: 15 additions & 40 deletions src/jwe.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const jose = require('node-jose');
const { validateEnv } = require('./util');
const { EncryptionUtils } = require('mastercard-client-encryption');

function jweEncryption(pm) {
validateEnv(['pathToRawData', 'pathToEncryptedData', 'publicKeyFingerprint', 'encryptionCert'], pm.environment);
Expand All @@ -8,34 +9,14 @@ function jweEncryption(pm) {
const reqBody = JSON.parse(pm.request.body.raw);
const pathToRawData = pm.environment.get('pathToRawData');
const pathToEncryptedData = pm.environment.get('pathToEncryptedData');
const encryptedProperty = pm.environment.get('encryptedProperty') ?? 'encryptedData';
const encryptedValueFieldName = pm.environment.get('encryptedValueFieldName') ?? 'encryptedData';
const publicKeyFingerprint = pm.environment.get('publicKeyFingerprint');
const encryptionCertificate = pm.environment.get('encryptionCert');

// Get element in payload to encrypt
let tmpIn = reqBody;
let prevIn = null;

const paths = pathToRawData.split('.');
paths.forEach((e) => {
if (pathToRawData !== '$' && !Object.prototype.hasOwnProperty.call(tmpIn, e)) {
tmpIn[e] = {};
}
prevIn = tmpIn;
tmpIn = tmpIn[e];
});
const elem = pathToRawData.split('.').pop();
const target = pathToRawData !== '$' ? prevIn[elem] : reqBody;

// Get output path of encrypted payload
let outPath = reqBody;
const pathsOut = pathToEncryptedData.split('.');
pathsOut.forEach((e) => {
if (pathToEncryptedData !== '$' && !Object.prototype.hasOwnProperty.call(outPath, e)) {
outPath[e] = {};
}
outPath = outPath[e];
});
const encryptionTarget = EncryptionUtils.elemFromPath(pathToRawData, reqBody);
if (!encryptionTarget || !encryptionTarget.node) {
return resolve(reqBody);
}

const keystore = jose.JWK.createKeyStore();
return (
Expand All @@ -44,7 +25,7 @@ function jweEncryption(pm) {

// Encrypt payload and attach to request body
.then((publicKey) => {
const buffer = Buffer.from(JSON.stringify(target));
const buffer = Buffer.from(JSON.stringify(encryptionTarget.node));
return jose.JWE.createEncrypt(
{
format: 'compact',
Expand All @@ -57,20 +38,14 @@ function jweEncryption(pm) {
.final();
})
.then((encrypted) => {
if (pathToEncryptedData !== '$') {
outPath[encryptedProperty] = encrypted;
} else {
if (pathToRawData === '$') {
const properties = Object.keys(reqBody);
properties.forEach((e) => {
delete reqBody[e];
});
}
reqBody[encryptedProperty] = encrypted;
}
delete prevIn[elem];

resolve(reqBody);
// mirror what the mastercard encryption lib does
const encryptedReqBody = EncryptionUtils.addEncryptedDataToBody(
{ [encryptedValueFieldName]: encrypted },
{ element: pathToRawData, obj: pathToEncryptedData },
encryptedValueFieldName,
reqBody,
);
resolve(encryptedReqBody);
})
);
});
Expand Down
42 changes: 31 additions & 11 deletions test/jwe.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
const { jweEncryption } = require('../src/jwe');
const fs = require('fs');
const path = require('path');
const EncryptionUtils = require('mastercard-client-encryption').EncryptionUtils;

describe(`Tests for ${jweEncryption.name}()`, () => {
// the postman object
const pm = {};
const encryptionCert = fs.readFileSync(path.resolve(__dirname, './res/encryption_cert_pubic_key.pem'));
beforeEach(() => {
jest.restoreAllMocks();

const environment = {
pathToRawData: '$',
pathToEncryptedData: '$',
encryptedProperty: 'encryptedData',
encryptedValueFieldName: 'encryptedData',
encryptionCert,
publicKeyFingerprint: 'abcdef',
};
Expand All @@ -19,6 +22,23 @@ describe(`Tests for ${jweEncryption.name}()`, () => {
pm.request = { method: 'post', body: {} };
});

test(`Returns unencrypted object if finding the element to be encrypted fails`, async () => {
pm.environment.set('pathToRawData', '$');
pm.environment.set('pathToEncryptedData', '$');

const requestBody = {
a: 'b',
c: 'd',
};
pm.request.body.raw = JSON.stringify(requestBody);

jest.spyOn(EncryptionUtils, 'elemFromPath').mockReturnValue(null);

const encryptionResult = await jweEncryption(pm);

expect(encryptionResult).toEqual(requestBody);
});

test('Encrypts a request object when the encryption path is the root of the request object', async () => {
pm.environment.set('pathToRawData', '$');
pm.environment.set('pathToEncryptedData', '$');
Expand All @@ -28,20 +48,20 @@ describe(`Tests for ${jweEncryption.name}()`, () => {
c: 'd',
});

const mockUpdateFn = jest.fn();
pm.request.body.update = mockUpdateFn;

const expectedBodyFormat = {
encryptedData: 'the encrypted request body',
};

const actualEncryptedBody = await jweEncryption(pm);

expect(Object.keys(actualEncryptedBody)).toEqual(Object.keys(expectedBodyFormat));
expect(actualEncryptedBody.encryptedData).not.toBe(undefined);
expect(actualEncryptedBody.encryptedData).not.toBe(null);
});

test('Encrypts a request object when the encryption path is nested in the request object', async () => {
pm.request.body.raw = JSON.stringify({
irrelevantProperty: 'this should be preserved',
path: {
to: {
foo: {
Expand All @@ -55,19 +75,19 @@ describe(`Tests for ${jweEncryption.name}()`, () => {
pm.environment.set('pathToRawData', 'path.to.foo');
pm.environment.set('pathToEncryptedData', 'path.to.encryptedFoo');

const mockUpdateFn = jest.fn();
pm.request.body.update = mockUpdateFn;

const expectedBodyFormat = {
encryptedData: 'the encrypted request body',
irrelevantProperty: 'this should be preserved',
path: {
to: {
encryptedFoo: 'the encrypted request body',
},
},
};

const actualEncryptedBody = await jweEncryption(pm);

expect(actualEncryptedBody.path.to.encryptedFoo).not.toBe(undefined);
expect(actualEncryptedBody.path.to.encryptedFoo).not.toBe(null);
expect(Object.keys(actualEncryptedBody.path.to.encryptedFoo).sort()).toEqual(
Object.keys(expectedBodyFormat).sort(),
);
expect(Object.keys(actualEncryptedBody).sort()).toEqual(Object.keys(expectedBodyFormat).sort());
});
});

0 comments on commit 3b9db82

Please sign in to comment.