Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 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
@@ -1,6 +1,6 @@
{
"name": "aws-kms-ee",
"version": "1.0.0",
"version": "1.0.1",
"description": "AWS KMS Envelope Encryption",
"main": "./lib/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion src/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NodeHttpHandler } from '@smithy/node-http-handler';

import { debug } from './utils';

const cache = new memoryCache.Cache();
export const cache = new memoryCache.Cache();

class Connector {
constructor(
Expand Down
43 changes: 28 additions & 15 deletions src/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import Connector from './connector';
import { encryptValue, decryptValue, logError } from './utils';

export const encryptObject = (object, {
masterKeyAlias, fields, dataKeys, regions, AES,
masterKeyAlias, fields, dataKeys, regions, AES, multiRegion = false,
}) => new Connector(masterKeyAlias)
.generateDataKey()
.then(encryptDataKeyPerRegion({ masterKeyAlias, dataKeys, regions }))
.then(encryptDataKeyPerRegion({
masterKeyAlias, dataKeys, regions, multiRegion,
}))
.then(dataKey =>
({
encrypted: cloneDeepWith(object, (value, key) => {
Expand Down Expand Up @@ -41,26 +43,37 @@ export const decryptObject = (object, metadata) =>
metadata,
}));

const encryptDataKeyPerRegion = metadata => ({ Plaintext, CiphertextBlob }) =>
Promise.all(otherRegions(metadata)
.map(region => new Connector(metadata.masterKeyAlias, region)
.encryptDataKey(Plaintext)
.then(resp => ({
[region]: base64EncodeUint8Array(resp.CiphertextBlob),
}))
.catch((err) => {
logError(err, 1, region);
return {
[region]: undefined,
};
const encryptDataKeyPerRegion = metadata => ({ Plaintext, CiphertextBlob }) => {
const regions = otherRegions(metadata);
const dataKey = base64EncodeUint8Array(CiphertextBlob);

return Promise.all((
metadata.multiRegion ?
regions.map(region => ({
[region]: dataKey,
}))
:
regions
.map(region => new Connector(metadata.masterKeyAlias, region)
.encryptDataKey(Plaintext)
.then(resp => ({
[region]: base64EncodeUint8Array(resp.CiphertextBlob),
}))
.catch((err) => {
logError(err, 1, region);
return {
[region]: undefined,
};
}))
)
.concat({
[process.env.AWS_REGION]: base64EncodeUint8Array(CiphertextBlob),
[process.env.AWS_REGION]: dataKey,
}))
.then(dataKeys => ({
dataKeys: merge({}, ...dataKeys),
Plaintext,
}));
};

const decryptDataKey = metadata => new Connector(metadata.masterKeyAlias)
.decryptDataKey(metadata.dataKeys[process.env.AWS_REGION])
Expand Down
32 changes: 31 additions & 1 deletion test/unit/functions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expect } from 'chai';
import * as sinon from 'sinon';

import { encryptObject, decryptObject } from '../../src/functions';
import Connector from '../../src/connector';
import Connector, { cache } from '../../src/connector';

import { MOCK_GEN_DK_RESPONSE, MOCK_DECRYPT_DK_RESPONSE, MOCK_ENCRYPT_DK_RESPONSE } from '../../src/fixtures';
import { DOMAIN_OBJECT } from '../fixtures';
Expand All @@ -15,6 +15,7 @@ describe('functions.js', () => {
});

afterEach(() => {
cache.clear();
sinon.restore();
});

Expand Down Expand Up @@ -146,6 +147,35 @@ describe('functions.js', () => {
.to.equal(MOCK_ENCRYPT_DK_RESPONSE.CiphertextBlob.toString('base64'));
});

it('should encrypt and decrypt for MultiRegion', async () => {
sinon.stub(Connector.prototype, 'generateDataKey')
.resolves(MOCK_GEN_DK_RESPONSE);
sinon.stub(Connector.prototype, 'decryptDataKey')
.resolves(MOCK_DECRYPT_DK_RESPONSE);

const encryptOutput = await encryptObject(
DOMAIN_OBJECT,
{
masterKeyAlias: 'alias/aws-kms-ee',
regions: ['us-east-1', 'us-west-2'],
fields: ['f1'],
multiRegion: true,
},
);

// console.log(JSON.stringify(encryptOutput, null, 2));
expect(encryptOutput.encrypted).to.not.deep.equal(DOMAIN_OBJECT);

const decryptOutput = await decryptObject(encryptOutput.encrypted, encryptOutput.metadata);
// console.log(JSON.stringify(decryptOutput, null, 2));
expect(decryptOutput.object).to.deep.equal(DOMAIN_OBJECT);
expect(decryptOutput.metadata.fields).to.exist;
expect(decryptOutput.metadata.dataKeys['us-east-1'])
.to.equal(MOCK_GEN_DK_RESPONSE.CiphertextBlob.toString('base64'));
expect(decryptOutput.metadata.dataKeys['us-west-2'])
.to.equal(MOCK_GEN_DK_RESPONSE.CiphertextBlob.toString('base64'));
});

it('should handle other region unable to encrypt data key', async () => {
sinon.stub(Connector.prototype, 'generateDataKey')
.resolves(MOCK_GEN_DK_RESPONSE);
Expand Down