Skip to content

Commit

Permalink
Merge pull request #39 from Mastercard/feature/graceful-error-handling
Browse files Browse the repository at this point in the history
Handle encryption/decryption exceptions gracefully
  • Loading branch information
ech0s7r authored Aug 30, 2022
2 parents e9d7c15 + 0a4317b commit 38670a2
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 27 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"dependencies": {
"insomnia-url": "^3.3.0",
"mastercard-client-encryption": "^1.4.0",
"mastercard-client-encryption": "^1.6.0",
"mastercard-oauth1-signer": "^1.1.6",
"node-forge": "^1.3.0"
},
Expand Down
54 changes: 32 additions & 22 deletions src/encryption/client-encryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,31 @@ module.exports.request = async (context) => {
) {
const headers = mcContext.requestHeader();

const fle = utils.cryptoService(mcContext.encryptionConfig);
const cryptoService = utils.cryptoService(mcContext.encryptionConfig);

if (utils.isJWE(mcContext.encryptionConfig)) {
// Convert cert
fle.crypto.encryptionCertificate = utils.pkcs8to1(mcContext.encryptionConfig.encryptionCertificate);
cryptoService.crypto.encryptionCertificate = utils.pkcs8to1(mcContext.encryptionConfig.encryptionCertificate);
}

const encrypted = fle.encrypt(
mcContext.url,
headers,
JSON.parse(body.text)
);
try {
const encrypted = cryptoService.encrypt(
mcContext.url,
headers,
JSON.parse(body.text)
);

// override header
for (const [name, value] of Object.entries(encrypted.header)) {
context.request.setHeader(name, value);
}
// override header
for (const [name, value] of Object.entries(encrypted.header)) {
context.request.setHeader(name, value);
}

// replace body
context.request.setBodyText(JSON.stringify(encrypted.body));
// replace body
context.request.setBodyText(JSON.stringify(encrypted.body));
} catch (e) {
// eslint-disable-next-line no-console
console.log("Error occurred encrypting the request", e);
}
}
};

Expand All @@ -48,16 +53,21 @@ module.exports.response = async (context) => {
mcContext.isJsonResponse() &&
mcContext.encryptionConfig
) {
const fle = utils.cryptoService(mcContext.encryptionConfig);
const cryptoService = utils.cryptoService(mcContext.encryptionConfig);

const response = JSON.parse(fs.readFileSync(body.path));
const decryptedBody = fle.decrypt({
body: response,
header: mcContext.responseHeader(),
request: {
url: mcContext.url
}
});
context.response.setBody(JSON.stringify(decryptedBody));
try {
const decryptedBody = cryptoService.decrypt({
body: response,
header: mcContext.responseHeader(),
request: {
url: mcContext.url
}
});
context.response.setBody(JSON.stringify(decryptedBody));
} catch (e) {
// eslint-disable-next-line no-console
console.log("Error occurred decrypting the response", e);
}
}
};
3 changes: 2 additions & 1 deletion test/__res__/config-jwe.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"encryptedValueFieldName": "encryptedData",
"publicKeyFingerprintType": "certificate",
"encryptionCertificate": "./test/__res__/test_certificate.cert",
"privateKey": "./test/__res__/test_key.der"
"privateKey": "./test/__res__/test_key.der",
"dataEncoding": "base64"
}
}
}
38 changes: 38 additions & 0 deletions test/__res__/config-root-element.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"host": "https://sandbox.api.mastercard.com",
"mastercard": {
"consumerKey": "H5MR_EL6fmGG_jzvC6T7Wu43IZ-0plTTTfalNHft2482b23a!eab46a89cc2b46ebb31aeec610f52eeb0000000000000000",
"keyAlias": "mykeyalias",
"keystoreP12Path": "./test/__res__/test_key_container.p12",
"keystorePassword": "Password1",
"encryptionConfig": {
"paths": [
{
"path": "/resource",
"toEncrypt": [
{
"element": "$",
"obj": "$"
}
],
"toDecrypt": [
{
"element": "$",
"obj": "$"
}
]
}
],
"oaepPaddingDigestAlgorithm": "SHA-512",
"ivFieldName": "iv",
"encryptedKeyFieldName": "encryptedKey",
"encryptedValueFieldName": "encryptedData",
"oaepHashingAlgorithmFieldName": "oaepHashingAlgorithm",
"publicKeyFingerprintFieldName": "publicKeyFingerprint",
"publicKeyFingerprintType": "certificate",
"dataEncoding": "hex",
"encryptionCertificate": "./test/__res__/test_certificate.cert",
"privateKey": "./test/__res__/test_key.der"
}
}
}
13 changes: 13 additions & 0 deletions test/__res__/mock-response-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Errors": [
{
"Error": {
"Source": "Gateway",
"ReasonCode": "DECLINED",
"Description": "Unauthorized - Access Not Granted",
"Recoverable": false,
"Details": null
}
}
]
}
32 changes: 32 additions & 0 deletions test/encryption/client-encryption.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ describe('Encryption', () => {
body: body
});

const contextWithRootElem = helper.context({
url: 'https://api.mastercard.com/service/api/resource',
body: body,
config: require('../__res__/config-root-element.json').mastercard
});

const mockResponse = (_path) => {
const readFileSync = fs.readFileSync;
const fn = (path, options) => {
Expand Down Expand Up @@ -251,4 +257,30 @@ describe('Encryption', () => {

assert(setBody.notCalled);
});

it('should not override response if decryption fails', async () => {
mockResponse('./test/__res__/mock-response-error.json');
sinon.spy(contextWithRootElem.response, 'setBody');
sinon.mock(contextWithRootElem.response).expects('getHeader').returns('application/json');
sinon.mock(contextWithRootElem.response).expects('getBodyStream').returns({ path: 'mocked-response-path' });

await plugin.responseHooks[0](contextWithRootElem); // decrypt

sinon.assert.notCalled(contextWithRootElem.response.setBody);

fs.readFileSync.restore();
});

it('should not ovveride request if encryption fails', async () => {
let ctx = helper.context({ body: "invalid json" });
const setHeader = sinon.spy(ctx.request, 'setHeader');
const setBodyText = sinon.spy(ctx.request, 'setBodyText');

await plugin.requestHooks[0](ctx); // encrypt

assert(setHeader.notCalled);
assert(setBodyText.notCalled);
});


});

0 comments on commit 38670a2

Please sign in to comment.